Software 

Tools 


Dr.  Dobb’s  Journal 

VOLU  M  E 


Software 

Tools 


M&T  BOOKS 


A  Publication  of  M&T  Publishing,  Redwood  City,  CA 


Acknowledgments 


We  have  always  used  this  space  to  thank  our  authors,  programmers  all,  who 
contribute  their  time,  knowledge,  and  source  code  for  the  edification  of  other 
programmers.  They  deserve  all  our  gratitude:  to  broadcast  hard-earned  knowledge 
like  seeds  on  the  wind  is  extraordinary  generosity.  We  of  Dr.  Dobb's  staff  are 
pleased  to  be  the  breeze  in  the  metaphor. 

Bound  Volume  Staff 

Production  Coordinator: 

Ann  Roskey 

Production: 

Joe  Sikoryak 

Cover  Design: 

Michael  Hollister 

Index: 

Rhoda  Simmons 


COPYRIGHT  ©  1986  by  M&T  Publishing  Inc.  All  rights  reserved.  No 
part  of  this  book  may  be  reprinted,  or  reproduced,  or  utilized  in  any  form  or  by 
any  electronic,  mechanical,  or  other  means,  now  known  or  hereafter  invented, 
including  photocopying  and  recording,  or  in  any  information  storage  and 
retrieval  system,  without  permission  in  writing  from  the  Publisher. 


Printed  in  the  United  States  of  America 


About  M&T 
Publishing 
and 

Dr.  Dobb’s  Journal 


M&T  Publishing  was  founded  in  1981  as  the  subsidiary  of  Markt  &  Technik  A.G., 
the  leading  West  German  publisher  of  computer  magazines,  books,  and  software.  In 
addition  to  its  active  role  in  the  international  microcomputer  marketplace,  M&T 
also  publishes  two  monthly  magazines.  Business  Software  was  aquired  in  1983, 
and  in  1 984  M&T  acquired  the  rights  to  publish  Dr.  Dobb's  Journal  from  People's 
Computer  Company. 

Since  its  first  issue  in  1976,  Dr.  Dobb's  Journal  has  maintained  a  unique  voice 
among  microcomputer  publications.  The  first  issue  was  published  to  provide 
short-term  distribution  of  the  newly  written  Tiny  BASIC  language.  Reader 
response  was  so  strong  that  it  went  immediately  into  monthly  production.  DDJ 
has  led  the  way  since  then  by  focusing  on  important  advances  in 
microcomputers,  printing  public -domain  software,  and  fostering  vital  reader 
interaction  on  technical  matters. 

Today,  Dr.  Dobb's  Journal  remains  the  prime  source  of  information  and 
software  tools  for  advanced  programmers.  Its  early  issues  are  still  in  demand, 
and  no  part  of  the  editorial  content  of  DDJ  has  ever  gone  out  of  print.  Volumes 
One  through  Ten  of  this  series  contain  the  entire  editorial  content  of  DDJ 's  first 
ten  years  and  are  available  in  bound  volumes  through  M&T  Publishing. 

Through  Dr.  Dobb's  Journal  and  its  other  software  publications,  M&T 
Publishing  is  providing  microcomputer  professionals  and  enthusiasts  with  the 
most  advanced  and  useful  software  information  available. 


Editor’s  Preface 


It  was  the  year  of  living  dangerously. 

This  book  is  a  collection  of  the  complete  editorial  content  of  Dr.  Dobb's  Journal  of 
Software  Tools  for  one  year  -  its  tenth  year,  1985.  It  was  an  exciting  year,  and  a  year 
in  which  the  magazine  pulled  the  beards  of  some  giants. 

Noticing  that  Apple  had  not  put  enough  memory  in  the  Macintosh,  we  showed  the 
world  how  to  add  more.  While  we  were  at  it,  we  also  beat  Apple  to  the  punch  with  a 
hard-disk  SCSI  interface  for  the  Mac. 

We  dared  to  point  out  in  detail  how  a  much-praised  implementation  of  Pascal  deviated 
from  the  language  standard.  We  challenged  the  Unix  establishment  with  plans  for  a  free 
Unix.  We  asked  hard  questions  about  privacy  and  control  in  the  Information  Age. 

But  mostly  we  published  tools  for  programmers.  That's  what  Dr.  Dobb's  Journal  of 
Software  Tools  has  been  doing  since  the  personal  computer  revolution  began  in  1975. 
In  this  volume  you'll  find  exhaustive  reviews  of  programmers'  editors  and  C 
compilers.  You'll  find  thousands  of  lines  of  useful  source  code  in  C,  Modula-2,  Pascal, 
Forth,  assembly  language,  and  Prolog.  And  you'll  read  professional  programmers' 
shoptalk  about  the  most  important  issues  in  programming. 


Michael  Swaine,  editor-in-chief 
Dr.  Dobb's  Journal  of  Software  Tools 


Contents 


VOLUME  10,  NUMBER  99,  1985 


4  Editorial 

5  Letters 

8  Dr.  Dobb  s  Clinic  D.  E.  CORTESI 
1 1  Fatten  Your  Mac  TOM  LAFLEUR  and  SUSAN  RAAB 
17  QuickDraw  Meets  ImageWriter  THOM  MAYER 
24  Archiving  Files  with  CP/M-80  and  CP/M-86  IAN  E.  ASHDOWN 

42  MBOOT  and  MODEM7  for  the  C-64's  CP/M  WALT  PlOTROWSKI 

57  Unstructured  Forth  Programming  RICHARD  WILTON 
59  Software  Reviews 

APL*PLUS/PC  STEFAN  H.  UNGER 
Tools  Financial  &  Statistical  STEFAN  H.  UNGER 
VSI — Virtual  Screen  Interface  RONALD  G.  PARSONS 
Fancy  Font  System,  The  DAVID  D.  CLARK 
73  Book  Reviews 

75  16-Bit  Software  Toolbox  RAY  DUNCAN 

85  The  Software  Designer  MICHAEL  SWAINE 

87  Of  Interest  R.  P.  SUTHERLAND 


VOLUME  10,  NUMBER  100,  1985 


92  Editorial 

93  Letters 

98  Dr.  Dobb’s  Clinic  D.  E.  CORTESI 
103  C/Unix  Programmer's  Notebook  A.  SKJELLUM 

1 06  Festschrift  for  Doctor  Dobb  SUZANNE  RODRIGUEZ,  TOM  PITTMAN,  BOB  ALBRECHT 

110  Fire  in  the  Valley  PAUL  FREIBERGER  and  MICHAEL  SWAINE 

1 1 7  Tiny  BASIC  for  the  68000  GORDON  BRANDLY 

121  An  Enhanced  ADFGVX  Cipher  System  C.  E.  BURTON 

140  More  dBASE  Tips  and  Techniques  GENE  HEAD 

143  Software  Reviews 

Modula-2/86  MICHAEL  SCHMIDT 

155  The  Software  Designer  MICHAEL  SWAINE 

1 56  CP/M  Exchange  ROBERT  BLUM 

164  Book  Reviews 

166  Of  Interest  R.  P.  SUTHERLAND 


VOLUME  10,  NUMBER  101,  1985 


1 72  Editorial 

173  Letters 

174  C  Chest  ALLEN  HOLUB 

187  Realizable  Fantasies  RICHARD  STALLMAN 

191  Programming  in  Logic  JOHN  MALPAS 

196  A  Tour  of  Prolog  D.  E.  CORTESI 

210  Tax  Advisor  DEAN  SCHLOBOHM 

234  16-Bit  Software  Toolbox  RAY  DUNCAN 

241  CP/M  Exchange  ROBERT  BLUM 

247  Software  Reviews 

SAVVY  PC  STEVE  KING 
250  Of  Interest  R.  P.  SUTHERLAND 


VOLUME  10,  NUMBER  102,  1985 


256  Editorial 

257  Letters 

258  Realizable  Fantasies  MICHAEL  SWAINE  and  BOB  ALBRECHT 

260  Dr.  Dobb's  Clinic  D.  E.  CORTESI 

264  MONTY  Plays  Scrabble  TERRY  A.  WARD 

269  Shortcut  to  SCISTAR:  For  Prowriter  Users  AMER  W.  NELSON 

273  fx80char:  A  Character  Editor  for  Epson  FX-80  Printers  DAVID  D.  CLARK 

293  A  Magic  Mushroom  for  the  Epson  Alice  GARY  B.  PALMER 

305  Let's  Mouse  Around  VINCENT  and  RONALD  G.  PARSONS 

310  Software  Reviews 

Turbo  Toolbox  KARL  R.  KACHIGAN 
SPSS/PC  STEVEN  ANTHONY  SOLA 
314  The  Software  Designer  MICHAEL  SWAINE 
316  C  Chest  ALLEN  HOLUB 

323  16-Bit  Software  Toolbox  RAY  DUNCAN 

328  CP/M  Exchange  ROBERT  BLUM 

333  Of  Interest  R.  P.  SUTHERLAND 


VOLUME  10,  NUMBER  103,  1985 


340  Editorial 

341  Letters 

345  Dr.  Dobb’s  Clinic  D.  E.  CORTESI 

350  Realizable  Fantasies  D.  E.  CORTESI 

353  C  Chest  ALLEN  HOLUB 

360  Using  Decision  Variables  in  Graphics  Primitives  THOM  HOGAN 
367  Solid  Shape  Drawing  on  the  Commodore  64  RICHARD  RYLANDER 
389  A  Compiler  Written  in  Prolog  G.  A.  EDGAR 
399  Software  Reviews 

Toolworks  C/80  D.  C.  SHOEMAKER 
Disk  Maker  I  JIM  KRONMAN 
AMPRO  Little  Board  RICHARD  CONN 
BetterBASIC  MATTHEW  TRASK 
413  16-Bit  Software  Toolbox  RAY  DUNCAN 


VOLUME  10,  NUMBER  104,  1985 


422  Editorial 

423  Letters 

428  Dr.  Dobb's  Clinic  D.  E.  CORTES1 

436  C  Chest  ALLEN  HOLUB 

446  Unix  Exchange  AXEL  SCHREINER 

452  Information  Age  Issues  DEAN  GENGLE 

456  Modems:  2400  Bit/Sec  and  Beyond  DALE  WALSH 

458  C  UART  Controller  DON  GAY 

463  Christensen  Protocols  in  C  DONALD  KRANTZ 

479  Software  Reviews 

Envoy  DAVID  W.  CARROLL 
481  16-Bit  Software  Toolbox  RAY  DUNCAN 

489  CP/M  Exchange  ROBERT  BLUM 

495  Of  Interest  ALEX  RAGEN 


VOLUME  10,  NUMBER  105,  1985 


502  Editorial 

503  Letters 

505  Dr.  Dobb's  Clinic  D.  E.  CORTESI 

510  C  Chest  ALLEN  HOLUB 

524  Build  a  Custom  PC  or  Clone  JIM  KRONMAN 

527  The  Ultimate  Parallel  Print  Spooler  DON  RINDSBERG 

534  Designing  a  Real-Time  Clock  for  the  S- 100  Bus  ALAN  D.  WILCOX 

559  Realizable  Fantasies  MICHAEL  SWAINE  and  BOB  ALBRECHT 

560  16-Bit  Software  Toolbox  RAY  DUNCAN 

572  Computer  Calisthenics  MICHAEL  WIESENBERG 

578  Of  Interest  MICHAEL  SWAINE 


VOLUME  10,  NUMBER  106,  1985 


586  Editorial 

587  Letters 

590  Dr.  Dobb’s  Clinic  D.  E.  CORTESI 

594  C  Compilers  for  MS-DOS  C  SPECIAL  INTEREST  GROUP  and  RICHARD  RELPH 

609  A  Peephole  Optimizer  for  Assembly  Language  Source  Code  DAVID  L.  FOX 

623  Small-C  Update  J.  E.  HENDRIX 

629  Asynchronous  Protocols  DAVID  W.  CARROLL 

633  C  Chest  ALLEN  HOLUB 

648  16-Bit  Software  Toolbox  RAY  DUNCAN 

652  CP/M  Exchange  ROBERT  BLUM 


VOLUME  10,  NUMBER  107,  1985 


658  Editorial 

659  Letters 

660  Dr.  Dobb's  Clinic  D.  E.  CORTES1 

664  C  Chest  ALLEN  HOLUB 

671  Unix  Exchange  AXEL  SCHREINER 

678  The  Software  Designer  MICHAEL  SWAINE 

680  Parallel  Pattern  Matching  and  Fgrep  IAN  ASHDOWN 

698  Bose-Nelson  Sort  JOE  CELKO 

704  Two  TEX  Implementations  for  the  IBM  PC  RICHARD  FURUTA  and  PIERRE  A.  MACKAY 

711  Mac  Toolbox  JOHN  BASS 

721  CP/M  Exchange  ROBERT  BLUM 

723  16-Bit  Software  Toolbox  RAY  DUNCAN 

730  Of  Interest  ALEX  RAGEN 


VOLUME  10,  NUMBER  108,  1985 


736  Editorial 

737  Letters 

738  Dr.  Dobb's  Clinic  D.  E.  CORTESI 

743  C  Chest  ALLEN  HOLUB 

759  A  Threaded-Code  Microprocessor  Bursts  Forth  LEO  BRODIE 

765  Design  of  a  Forth  Target  Compiler  H.  ROBINSON,  P.  MORSE  II,  S.  BOWHILL 

785  Software  Reviews 

Neon  BRUCE  HORN 
MacFORTH  ALAN  CLUTE 
791  Mac  Toolbox  JOHN  BASS 

794  CP/M  Exchange  ROBERT  BLUM 


VOLUME  10,  NUMBER  109,  1985 


804  Editorial 

805  Letters 

808  Dr.  Dobb's  Clinic  D.  E.  CORTESI 

816  Modula-2  versus  Pascal  for  Microcomputers:  An  Update  DAVID  W.  CARROLL 
822  Bit  Manipulation  in  Modula-2  BRIAN  R.  ANDERSON 
827  The  Software  Designer  PAUL  HECKEL 
833  Software  Reviews 

Programming  Editors  MARK  U.  EDWARDS 
849  16-Bit  Software  Toolbox  RAY  DUNCAN 

855  CP/M  Exchange  ROBERT  BLUM 

868  Of  Interest  ALEX  RAGEN 


VOLUME  10,  NUMBER  110,  1985 


874  Editorial 

875  Letters 

877  Dr.  Dobb's  Clinic  D.  E.  CORTESI 

879  C  Chest  ALLEN  HOLUB 

889  Windowing  Operating  Environments  MICAHEL  SWAINE 

893  BANKSWAP  ALBERT  S.  WOODHULL 

904  Adding  a  COPY  Command  to  ProDOS  SHAWN  DAY 

911  CP/M-68K  Conditional  Batch  Processing  ROGER  E.  DONA1S 

9 1 8  ProDOS  and  the  RAM  Disk  ALFRED  STEELE 

925  16-Bit  Software  Toolbox  RAY  DUNCAN 

929  Mac  Toolbox  JOHN  BASS 

931  CP/M  Exchange  W.  E.  WILSON 


938  Index 


I)r.  Dobb’s  Journal 


Editorial 

Editor-in-Chief  Michael  Swaine 
Managing  Editor  Randy  Sutherland 
Assistant  Editor  Frank  DeRose 
Contributing  Editors  Robert  Blum, 

Dave  Cortesi, 

Ray  Duncan. 

Anthony  Skjellum, 
Michael  Wiesenberg 
Copy  Editors  Polly  Koch;  Cindy  Martin 
Typesetter  Jean  Aring 
Editorial  Intern  Mark  Johnson 
Production 
Design  /  Production 

Director  Delta  Penna 
Art  Director  Shelley  Rae  Doeden 
Production  Assistant  Alida  Hinton 
Cover  William  Cone 
Advertising 

Advertising  Director  Stephen  Friedman 
Advertising  Sales  Walter  Andrzejewski, 
Shawn  Horst 
Beth  Dud  as 

Advertising  Coordinators  Alison  Milne, 

Lisa  Boudreau 

Circulation 
Circulation  and 

Promotions  Director  Beatrice  Blatteis 
Fulfillment  Manager  Stephanie  Barber 
Direct  Response 

Coordinator  Maureen  Snee 
Promotions  Coordinator  Jane  Sharninghouse 
Circulation  Assistant  Kathleen  Boyd 


M&T  Publishing,  Inc. 

Chairman  of  the  Board  Otmar  Weber 
Director  C.F.  von  Quadt 
President  Laird  Foshay 


Entire  contents  copyright  ©  1984  by  M&T  Publishing, 
Inc.  unless  otherwise  noted  on  specific  articles.  All 
rights  reserved. 

Dr.  Dobb’s  Journal  (USPS  307690)  is  published 
monthly  by  M&T  Publishing,  Inc.,  2464  Embarcadero 
Way,  Palo  Alto,  CA  94303,  (415)  424-0600.  Second 
class  postage  paid  at  Palo  Alto  and  at  additional  entry 
points. 

Address  correction  requested.  Postmaster:  Send  Form 
3579  to  Dr.  Dobb’s  Journal ,  2464  Embarcadero 
Way,  Palo  Alto,  CA  94303.  ISSN  0278-6508 

Subscription  Rates:  $25  per  year  within  the  United 
States,  $44  for  first  class  to  Canada  and  Mexico,  $62 
for  airmail  to  other  countries.  Payment  must  be  in  U.S. 
Dollars,  drawn  on  a  US.  Bank. 

Contributing  Subscribers:  Christine  Bell,  W.D. 
Rausch,  DeWitt  S.  Brown,  Burks  A.  Smith,  Robert  C. 
Luckey,  Transdata  Corp.,  Mark  Ketter,  Friden  Mail¬ 
ing  Equipment,  Frank  Lawyer,  Rodney  Black,  Kenneth 
Drexler,  Real  Paquin,  Ed  Matin,  John  Saylor  Jr.,  Ted 
A.  Reuss  III,  Info  World,  Stan  Veit,  Western  Material 
Control,  S.P.  Kennedy,  John  Hatch,  Richard  Jorgen¬ 
sen,  John  Boak,  Bill  Spees,  R.B.  Sutton.  Lifetime 
Subscribers:  Michael  S.  Zick,  F.  Kirk. 

Foreign  Distributors:  ASCII  Publishing,  Inc.  (Ja¬ 
pan),  Computer  Services  (Australia),  Computer  Store 
(New  Zealand),  Computercollectief  (Nederland),  Ho- 
mecomputer  Vertriebs  GMBH  (West  Germany),  In¬ 
ternational  Presse  (West  Germany),  La  Nacelle  Book¬ 
store  (France),  McGill’s  News  Agency  PTY  LTD 
(Australia),  Progresco  (France). 


People's  Computer  Company 

Dr.  Dobb’s  Journal  is  published  by  M&T  Publishing, 
Inc.  under  license  from  People’s  Computer  Company, 
2682  Bishop  Dr.,  Suite  107,  San  Ramon,  CA  94583,  a 
non-profit,  educational  corporation. 


January  1985 
Volume  10,  Issue  1 


Newcomers 

We  are  fortunate  to  have  found  a  part  time  technical  editor  in  Alex  Ragen,  a 
senior  systems  analyst  and  project  leader  at  Tandem  Computers,  Inc,  Alex’s 
extensive  programming  experience  and  writing  ability  help  us  to  ensure  the  accu¬ 
racy  and  readability  of  the  editorial  content  of  DDJ.  Another  newcomer  is  Frank 
DeRose,  assistant  editor.  Frank  is  an  experienced  writer  and  teacher.  He  was 
using  the  Unix  system  at  U.C.  Berkeley  to  compose  his  dissertation  before  we 
lured  him  away  from  academia  to  assist  the  Doctor. 

This  Month's  Cover 

William  Cone,  this  month’s  cover  artist,  used  a  Macintosh  drawing  with  under¬ 
painting  in  gouache  and  acrylic  to  depict  this  hungry  Mac. 

This  Month's  Referees 

Robert  Blum,  Contributing  Editor 

David  Cortesi,  Resident  Intern 

Thom  Hogan,  Editor-in-Chief,  Business  Software 

Mac  Addendum 

This  arrived  too  late  to  make  it  into  the  “Fat  Mac”  article.  If  you  have  the  newer 
128K  motherboard,  when  you  get  to  page  20  you  will  need  to  follow  these  in¬ 
structions  to  install  the  memory  select  logic. 

1)  Cut  and  remove  jumper  W1  (marked  128K  only)  located  at  G14  (see  photo 
on  this  page) 

2)  Install  and  solder  two  2.2K  V «  w  resistors  at  locations  R40  and  R41 

3)  Install  and  solder  a  47  ohm  lA  w  resistor  at  location  R42 

4)  Install  and  solder  a  .luf  capacitor  at  location  C51 

5)  Install  a  74AS253N  IC  into  the  socket  at  location  G13 

6)  Check  the  motherboard  for  any  solder  splashes,  or  broken  etch.  Clean  the 
board  with  alcohol  or  a  TG  degreaser  to  remove  any  flux  left  after  soldering 


4 

2 


Dr.  Dobb’s  Journal,  January  1985 


SOFTWARE  TOOLS  FOR  ADVANCED  PROGRAMMERS 


Dr.  Dobb's  Journal 


ARTICLES 


Fatten  Your  Mac 

by  Thomas  Lafleur  and  Susan  Raab 

18 

Step  by  step  instructions  to  increase  the  RAM  in  a 
Macintosh  to  512K;  diagrams,  photographs,  and  warnings 
are  included  (Reader  Ballot  No.  191) 

QuickDraw  Meets  ImageWriter 

by  Thom  Mayer 

26 

How  QuickDraw  stores  graphics  and  how  ImageWriter 
prints  graphics  (Reader  Ballot  No.  1 92) 

Archiving  Files  with  CP/M  80  and  CP/M  86 

by  Ian  E.  Ashdown 

36 

A  utility  that  determines  which  files  on  a  disk  have  been 
changed  and  automatically  backs  them  up  (Reader  Ballot 
No.  193) 

MBOOT  and  MODEM7  for  the  C-64's  CP/M 

by  Walt  Piotrowski 

62 

All  you  need  to  download  CP/M  software  (Reader  Ballot 

No.  194) 

Unstructured  Forth  Programming:  An 

Introduction 

by  Richard  Wilton 

86 

A  modest  proposal  (Reader  Ballot  No.  195) 

COLUMNS 

Dr.  Dobb's  Clinic 

by  D.E.  Cortesi 

12 

Review  of  A  C  Reference  Manual,  structured  search, 
ragged  patches,  Z80  assembly  riddle  (Reader  Ballot  No. 

190) 

1 6-Bit  Software  Toolbox 

by  Ray  Duncan 

108 

PCDOS  Version  3.0,  graphics  routines  for  IBM  PC  (Reader 
Ballot  No.  196) 

The  Software  Designer 

by  Michael  Swaine 

124 

Interview  with  Tom  Evslin  of  Solutions  Inc.;  designing  for 
the  Mac:  some  experiences  (Reader  Ballot  No.  197) 

DEPARTMENTS 

Editorial 

6 

Letters 

8 

Software  Reviews 

88 

STSC’s  APL  Interpreter,  VSI,  Fancy  Font 

Book  Reviews 

106 

Of  Interest 

126 

(Reader  Ballot  No.  1 98) 

Dr.  Dobb’s  Journal,  January  1985 


5 

3 


EDITORIAL 


I’d  been  looking  forward  to  this  conference  for  months  and  wasn’t  about  to  let 
the  power  failure  darken  or  the  rain  dampen  my  spirits.  From  the  cheerful 
arguments  going  on  around  me  as  we  stood  dripping  in  line  outside  the  dining 
hall,  I  gathered  that  others  shared  my  enthusiasm.  The  organizers  of  the  Hack¬ 
ers’  Conference  had  brought  a  hundred-odd  insanely  great  programmers  and  a 
few  writers  to  this  isolated  stretch  of  Marin  headlands  to  discuss  the  future  of  the 
hacker  ethic,  and  discuss  the  future  of  the  hacker  ethic  we  would;  the  weather 
was  irrelevant. 

The  first  session  dispelled  any  illusion  that  we  would  find  consensus  on  the 
meaning  of  the  hacker  ethic,  outside  of  its  having  something  to  do  with  program¬ 
ming  for  the  joy  of  it,  and  nothing  to  do  with  breaking  into  systems.  There  was  a 
wide  span  of  opinion  about  the  commercial  aspect  of  software  development,  from 
the  belief  that  all  software  should  be  free  to  a  what-the-traffic-will-bear  attitude. 
Bill  Atkinson,  admitting  that  he  wanted  his  QuickDraw  routines  for  the  Mac  to 
remain  proprietary  for  a  while,  looked  uneasy  when  Lee  Felsenstein  encouraged 
the  attendees  to  develop,  in  anarchic  concert,  a  reverse-engineered,  non-propri¬ 
etary  Hackers’  Mac.  Bob  Wallace,  whose  shareware  pay-if-you-like-it  plan  for 
PC  Write  was  keeping  food  on  his  table,  seemed  amused  by  the  debate.  But  there 
was  consensus  on  several  significant  points:  that  hacking  was  worthy  in  and  of 
itself,  that  the  attendees  ought  to  stay  in  communication  with  one  another  and 
that  it  was  important  to  pass  the  torch  to  the  next  generation  of  hackers.  That 
those  present  shared  something  worthy  of  passing  on  to  others.  At  the  end  of  the 
conference  I  walked  out  to  the  ocean  and  stood  on  the  rocks  above  the  surf, 
savoring  the  newfound  sense  of  rational  community,  as  though  I  had  been 
through  a  Woodstock  for  grownups. 

Three  days  later,  in  Las  Vegas,  cesspool  of  the  American  spirit,  I  stop  midaisle 
to  consult  my  Comdex  program,  but  a  heavy  diet  of  suites  has  thickened  my 
discriminative  faculties  until  all  the  booths  look  alike,  nor  does  the  program 
identify  the  players,  80  thousand  jaded  innocents  flown  in  from  the  undifferenti¬ 
ated  heartland  to  dance  the  shuck  and  jive  in  tight  suits,  unsensible  shoes,  flat 
borrowed  jargon.  There  are,  an  anonymous  passerby  pontificates  as  I  pore 
through  the  program  book,  some  Interesting  Products  here  this  year,  though 
Nothing  Revolutionary;  this  passing  summary  strikes  me  as  the  true  smug  theme 
of  this  year’s  Comdex.  Some  fashion  war  has  been  won,  it  seems:  the  pros,  the 
working  press,  the  veterans,  having  traded  in  last  year’s  cynicism  for  smartmoney 
conservatism,  even  the  quest  for  innovation  now  out  of  style,  pose  midaisle,  sniff¬ 
ing  out  the  Interesting  Products. 

One  area  still  open  to  innovation,  I  suggest  hopefully  that  evening  in  some 
smoky  Comdex  suite,  is  software  marketing;  I’m  thinking  of  shareware,  and  Bob 
Wallace,  who  seems  to  be  everywhere,  smiles.  The  next  day  I  meet  Ramon 
Zamora  midaisle  in  the  MGM  Grand  and  he  explains  that  his  new  company  is 
soliciting  grant  money  to  develop  shareware,  adding  that  “we  call  it  ‘public 
sector’  software.” 

In  another  suite  Lee  Felsenstein  elaborates  on  the  philosophy  behind  the 
Hackers’  Mac.  It  is  a  mistake  to  think  of  it  as  a  product,  he  explains;  it’s  a 
tendency;  an  appeal  to  the  hobbyist  willingness  to  try  things  out  for  no  reward 
but  the  chance  to  learn.  I  promise  to  mention  the  idea  in  Dr.  Dobb's. 

Back  in  the  office,  I  check  with  Randy  on  the  update  to  the  fatten-your-Mac 
article  and  the  status  of  our  in-house  public-domain  software  bulletin  board. 
Return  a  call  from  Bob  Albrecht,  who’s  excited  about  some  plan  to  lure  kids  into 
programming  in  C.  Read  in  Business  Week  that  Steve  Jobs  thinks  “[i]f  Apple 
falters,  innovation  will  cease.”  And  smile. 

Michael  Swaine 


6 

4 


Dr.  Dobb’s  Journal,  January  1985 


Spleen  Ventilations 

Dear  Dr.  Dobb's , 

One  very  annoying  thing  that  has  come 
up  recently  is  the  holy  war  involving 
computer  languages.  It  was  never 
more  apparent  than  in  your  October 
1984  issue  in  the  column  “The  Soft¬ 
ware  Designer.”  Typical  were  the 
statements  of  Philippe  Kahn,  who  was 
quoted  as  saying  “C  is  a  disease,”  and 
“When  I  see  people  writing  spread¬ 
sheets  in  C,  I  think  ‘They’re  out  of 
their  minds.’  It  was  designed  to  write 
operating  systems,”  and  “In  Europe  C 
is  considered  an  American  disease.” 

Really?  C  is  a  disease?  How?  Why 
does  the  fact  that  C’s  first  job  was  op¬ 
erating  systems  disqualify  it  from 
spreadsheets?  Forth  was  originally 
written  to  manipulate  radio  receivers. 
Does  this  mean  that  Dr.  Dobb’s  should 
have  rejected  the  article  that  demon¬ 
strated  using  Forth  for  the  Fast  Fouri¬ 
er  Transform?  All  of  Europe  hates  C? 
There  hasn’t  been  that  much  unanim¬ 
ity  since  WWI1. 

Now  it  may  be  true  that  C  has  some 
fatal  flaw  that  will  doom  it  to  die,  dino- 
saur-like,  in  some  tar  pit  of  abandoned 
operating  systems.  But  Kahn  certainly 
hasn’t  demonstrated  it,  and  he  has 
merely  made  himself  look  foolish  by 
making  statements  that  have  no  corre¬ 
lation  with  his  thesis.  And  the  sad 
thing  is  that  he  is  not  the  only  person 
doing  this. 

One  of  the  most  irresponsible  state¬ 
ments  I  have  ever  read  was  that  of 
Edsger  W.  Dijkstra,  in  his  “How  Do 
We  Tell  Truths  That  Might  Hurt?”  It 
read:  “It  is  practically  impossible  to 
teach  good  programming  to  students 
who  have  had  prior  exposure  to  BASIC: 
as  potential  programmers  they  are 
mentally  mutilated  beyond  hope  of 
regeneration.” 

Now,  this  is  patently  untrue — some 
of  the  best  programmers  I  know  start¬ 


ed  out  self-taught  in  BASIC  and  write 
very  clear,  structured  code.  What  I  as¬ 
sume  he  meant,  in  all  that  rhetoric, 
was  “BASIC  is  not  a  good  program¬ 
ming  language” — which  is  true  in  my 
opinion.  But  look  at  that  quote  again. 
I’ll  wager  that  there  were  hundreds  of 
mediocre  computer  science  teachers 
who  leapt  for  joy  when  they  read  that. 
BASIC  is  implemented  on  almost  every 
system,  which  means  that  it  is  hard  to 
avoid  coming  across  it  and  learning  it. 
Having  a  hard  time  teaching  that  stu¬ 
dent  certain  programming  practices? 
Well,  he  must  have  learned  some  BA¬ 
SIC — must  be  he’s  mentally  mutilated. 
Might  as  well  give  up  on  him;  he’ll  only 
flunk  anyway. 

If  all  we  get  are  diatribes  and  spleen 
ventilations,  what  chance  is  there  for 
the  future  of  program  language  de¬ 
sign?  When  a  computer  scientist  mere¬ 
ly  rants  instead  of  reasons,  how  seri¬ 
ously  can  one  take  claims  of  the 
designers  of  a  new  langauge? 

It  is  incredibly  annoying  reading 
such  nonsense  because  there  are  valid 
and  rational  reasons  for  not  using  a 
programming  language.  I  personally 
will  not  use  Forth,  because  I  do  not  like 
reverse  Polish  notation,  and  because  it 
is  too  close  to  assembly  language  for 
me  to  be  comfortable  with  it.  But  I  cer¬ 
tainly  will  not  condemn  those  who  use 
it,  and  if  they  write  good,  efficient  pro¬ 
grams  with  it,  more  power  to  them. 

Perhaps  DDJ  could  run  a  series  of 
interviews  with  software  designers, 
discussing  what  language  they  use  and 
emphasizing  the  positive.  It  might  let 
people  see  more  clearly  just  what  they 
might  be  missing. 

Thank  you  for  a  very  interesting 
magazine. 

John  M.  Gamble 

447 1  Eastwood  Dr. 

#18103 

Batavia,  OH  45103 


grep.c 

Dear  DDJ, 

Thanks  for  your  wonderful  public  do¬ 
main  contribution  of  grep.c  in  the  Oc¬ 
tober  issue.  It  has  become  a  well-worn 
tool  in  my  MSDOS  2.0  programming 
toolbox.  There  was  an  error  in  the  pro¬ 
gram  listing  that  you  might  warn  your 
readers  about  to  avoid  many  hours  of 
“bug”  hunting.  At  the  middle  of  page 
61  there  is  a  call  to  omatch  contained 
in  a  while  statement  passing  only  two 
parameters  when  three  are  required. 
The  line  should  be: 

while  (  *lin  &&  omatch(&lin, 
pat,  boln) ) 

Thanks  again  for  the  grep.c  utility. 
Sincerely, 

Michael  H.  Cox 

3659  Gas  Light  Curve  #5 

Montgomery,  AL  36116 

Dear  DDJ : 

It  was  with  interest  in  learning  more 
about  structures  and  pointers  in  C  that 
I  studied  Allen  Holub’s  article  on 
grep.c  in  the  October  1984  issue.  Per¬ 
haps  you  would  be  interested  in  know¬ 
ing  about  an  error  in  Listing  Two,  page 
64.  In  the  while  loop  of  the  dodash 
module  there  is  a  Boolean  test  on 
whether  dstart  —  dest  <  maxccl.  The 
last  statement  of  the  module  is  return 
(dest— dstart),.  Can  you  tell  that  one  of 
these  is  out  of  order?  It  turns  out  that 
if  the  former  expression  is  adjusted  to 
read  dest  -  dstart  <  maxccl,  then  do¬ 
dash  will  not  try  to  expand  a  character 
class  to  more  than  maxccl  characters. 

You  might  think  this  bug  is  innocu¬ 
ous,  but  with  my  particular  setup,  this 
turns  out  to  be  fatal  to  the  expansion  of 
sets,  like  [a-e],  to  character  classes, 
like  [abcde].  I  think  what’s  happening 
is  that  my  compiler  sees  the  pointers 
dstart  and  dest  as  unsigned,  so  that 


Dr.  Dobb's  Journal,  January  1985 


8 

5 


when  they  are  out  of  order  the  differ¬ 
ence  becomes  a  large  unsigned  value 
instead  of  a  small  signed  value.  This 
makes  the  while  stop  after  the  first 
pass!  For  the  record,  I  use  Cl  C86  on 
an  IBM  PC. 

Signed, 

Scott  D.  Thomas 
354  Colorado  Avenue 
Palo  Alto,  CA  94306 

Complex  Numbers 

Dear  DDJ : 

After  publication  of  my  article  “Sim¬ 
ple  Calculations  with  Complex  Num¬ 
bers”  in  the  October  1984  issue  of 
DDJ ,  Mr.  Alan  Tracht  of  Cleveland 
Heights,  OH,  noticed  problems  with 
the  routine  Polar.  For  certain  argu¬ 
ments,  it  would  return  incorrect  re¬ 
sults.  He  was  kind  enough  to  call  me 
with  suggestions  for  correcting  the  er¬ 
rors  and  also  told  me  about  a  less  cum¬ 
bersome  method  for  preventing  float¬ 
ing-point  underflow  and  overflow.  A 
section  of  the  revised  program  is  pre¬ 
sented  in  the  Listing  (page  10). 

The  CONSTANT  section  in  the  list¬ 
ing  is  a  slightly  revised  version  of  the 


constants  from  the  implementation 
section  of  Listing  One  in  the  original 
article.  The  new  version  of  Polar  is 
much  simpler  since  it  passes  on  most  of 
the  hard  work  to  a  new  FUNCTION 
called  ATan2.  This  function  is  a  Pascal 
counterpart  to  a  function  found  in 
most  Fortran  libraries.  It  takes  two  ar¬ 
guments,  the  first  a  dividend  and  the 
second  a  divisor,  and  returns  the  arc¬ 
tangent  of  the  quotient.  Because  of  the 
extra  information  inherent  in  the  two 
arguments,  the  ATan2  function  can 
correctly  handle  right  angles  and  de¬ 
termine  the  correct  quadrant  of  the  re¬ 
sult.  It  is  tailor  made  for  conversions  to 
polar  coordinates. 

During  testing  of  the  new  function,  I 
discovered  that  the  UCSD  version  of 
ATan  is  not  quite  as  “bullet-proof”  as  I 
had  thought.  It  was  necessary  to  add 
some  protection  for  that  function  as 
noted  in  the  listing.  Such  extra  protec¬ 
tion  was  not  needed  by  Turbo  Pascal  or 
any  of  the  C  or  Fortran  compilers  I 
used  to  check  the  new  ATan2 
algorithm. 

The  ATan2  function  calls  another 
new  function,  SafeDivide,  which  at¬ 
tempts  to  divide  its  first  argument  by 


the  second.  This  function  employs  the 
overflow  and  underflow  checking 
method  suggested  by  Mr.  Tracht.  It  is 
much  less  expensive  in  terms  of  execu¬ 
tion  time  than  the  original,  which 
made  calls  to  the  natural  logarithm 
function.  If  anyone  has  an  allergic  re¬ 
action  to  the  GOTOs,  they  can  be  re¬ 
placed  by  a  copy  of  the  division  state¬ 
ment  that  they  jump  to.  It  just  takes 
more  space  and  is  redundant. 

Finally,  I  noticed  a  typographical 
error  I  made  in  the  text  of  the  article. 
In  equation  6  on  page  31,  the  calcula¬ 
tion  of  the  amplitude  is  given  as  taking 
the  arctangent  of  the  real  coefficient 
divided  by  the  imaginary  coefficient. 
In  fact,  one  must  take  the  arctangent 
of  the  imaginary  coefficient  divided  by 
the  real  coefficient.  The  order  of  the 
dividend  and  divisor  in  the  original 
(and  new)  listing  is  correct,  however. 

I  hope  the  erroneous  Polar  routine 
did  not  greatly  inconvenience  anyone. 
I  would  also  like  to  thank  Mr.  Tracht 
for  his  helpful  suggestions. 

Sincerely, 

David  D.  Clark 
246  S.  Fraser  St.  #2 
State  College,  PA,  16801 


Letters  Listing  (Text  begins  on  page  8) 


CONST  MAX_REAL  =  9. 99999999999999999999E+37 ; 
PI  =  3.14159265358979323846; 

PI_0VER_2  «  1.57079632679489661923; 
BIG_SQRT  =  1.0E+19; 

CLOSEST  =  1.0E-19; 


{  pi  } 

{  pi/2.0  > 

{  Sqrt(MAX_REAL)  > 
{  Sqrt(MIN_REAL)  } 


FUNCTION  SafeDivideCx,  y  :  Real)  :  Real; 

{  divide  x  by  y  and  intercept  incipient  underflow  or  overflow  } 
LABEL  1; 


BEGIN  {  SafeDivide  } 

IF  Abs(y)  <1.0  THEN 

IF  Abs(x)  >  Ab s ( y ) *MAX_REAL 
SafeDivide  :=  MAX_REAL 
ELSE 

GOTO  1 

ELSE  IF  Abs(x)  <1.0  THEN 

IF  Abs(y)  >  Ab 8 ( x ) *MAX_REAL 
SafeDivide  :=  0.0 
ELSE 

GOTO  1 

ELSE 

1:  SafeDivide  :=  x/y 

END  {  of  SafeDivide  }; 


{  overflow  possible  } 

THEN 

{  catch  overflow  } 

{  underflow  possible  } 

THEN 

{  catch  underflow  } 


{  a  normal  calculation  ) 


10 

6 


Dr.  Dobb's  Journal,  January  1985 


FUNCTION  ATan2(x,  y  :  Real)  :  Real; 

{  take  arctangent  of  x/y  and  account  for  right  angles  and  proper  quadrant  } 

VAR  a  :  Real; 

BEGIN  {  ATan2  > 

IF  y  =  0.0  THEN  { 

IF  x  =  0.0  THEN  { 

a  :=  0.0 
ELSE 

a  :=  PI_0VER_2  { 

ELSE  BEGIN 

a  :=  Saf eDivide(x,  y) ;  { 

IF  a  >=  MAX_REAL  THEN  { 

a  :=  PI_OVER_2 
ELSE  BEGIN 

J.F  a  <>  0.0  THEN  BEGIN 

{  kludge  to  protect  UCSD  ATan  ) 
a  : =  Abs(a) ; 

IF  a  >  BIG_SQRT  THEN 
a  :=  PI_0VER_2 
ELSE  IF  a  <  CLOSEST  THEN 
a  :  =  0.  0 
ELSE 

a  : =  ATan(a) ; 

END; 

IF  y  <  0. 0  THEN  {  choose  upper  or  lower  half  of  plane  } 

a  :=  PI  -  a 

END 
END; 

IF  x  <  0.0  THEN  {  choose  left  or  right  half  of  plane  } 

a  :=  -a; 

ATan2  :=  a 
END  {  of  ATan 2  }; 

PROCEDURE  Polar{Arg:  Complex;  VAR  Modulus,  Amplitude:  Real}; 

BEGIN  {  Polar  } 

WITH  Arg  DO  BEGIN 

IF  Abs(Re)  <  CLOSEST  THEN 
Re  : =  0. 0 ; 

IF  Abs(Im)  <  CLOSEST  THEN 
Im  :  =  0.0; 

Modulus  :=  Sqrt(Sqr(Re)  +  Sqr(lm)); 

Amplitude  :=  ATan2(lm,  Re) 

END  {  of  WITH  Arg  } 

END  {  of  Polar  }; 


check  for  exceptional  cases  with  y  =  0  } 
if  both  arguments  are  zero,  return  zero  } 

otherwise  it's  a  right  angle  } 

try  the  divide,  preventing  unseemly  errors  } 
probably  tried  to  overflow  } 


End  listing 


Dr.  Dobb’s  Journal,  January  1985 


11 

7 


DR.  DOBB'S  CLINIC 


by  D.  E.  Cortesi 


The  compiled  and  analyzed  results  of 
your  throughput  measurements  aren’t 
quite  ready  for  this  January  column  as 
we  promised.  Next  month. 

We  C  A  Good  Book 

A  C  Reference  Manual,  by  Samuel  P. 
Harbison  and  Guy  L.  Steele,  Jr.  (Pren¬ 
tice-Hall,  1984;  $19.95)  was  published 
just  too  late  to  be  mentioned  in  the  C 
Resource  List  of  a  few  months  back. 
That’s  a  pity  because  it  is  the  best  C 
resource  we’ve  come  across  yet. 

The  book  is  just  what  its  title  implies: 
a  complete,  authoritative,  and  (so  far  as 
we  can  tell)  accurate  reference  to  the  C 
language.  It  is  beautifully  organized, 
with  many  small  topics  grouped  logical¬ 
ly  into  chapters.  Each  topic  ends  with  a 
list  in  small  type  of  all  the  other  topics 
that  are  related  to  it,  so  that  no  matter 
where  you  start  you  can  follow  the 
threads  of  a  concept  throughout  the 
book.  Differences  between  C  compilers 
are  covered  as  they  occur,  matters  of 
coding  style  and  dangerous  constructs 
are  discussed,  and  there  are  many  ex¬ 
amples,  mostly  illuminating. 

That  it’s  useful  and  accurate  is  a 
credit  to  its  authors,  but  that  the  book  is 
so  thorough  is  the  result  of  its  genesis; 
the  authors  say  it  “grew  out  of  our  ef¬ 
fort  to  write  a  family  of  C  compilers.” 
They  found  that,  “  In  spite  of  C’s  popu¬ 
larity  . . .  there  was  no  description  of  C 
precise  enough  to  guide  us  in  designing 
the  new  compilers  . . .  [and  none]  pre¬ 
cise  enough  for  our  programmer-cus¬ 
tomers,  who  would  be  using  compilers 
that  analyzed  C  programs  more  thor¬ 
oughly  than  was  the  custom.”  So  they 
compiled  this  one,  and  a  good  job  they 
made  of  it. 

Structured  Search 

Deep  in  the  code  of  the  DIFF  program 
(presented  as  a  separate  article  in  the 

12 


o 


August  DDJ),  we  posed  a  small  prob¬ 
lem  in  structured  programming.  At 
heart,  it  was  nothing  more  than  the  old 
“loop  with  two  exits,”  a  problem  that 
you’re  sure  to  meet  whenever  you  con¬ 
fine  yourself  to  the  fundamental  control 
shapes  permitted  by  structured  design. 

In  the  context  of  that  program,  the 
problem  went  like  this.  A  symbol  table 
named  ST  (an  array  indexed  from  0  to 
MaxSym-1)  is  being  treated  as  a  hash 
table  to  store  Lines.  Hash  (Line)  pro¬ 
duces  the  initial  probe  of  the  table  for 
any  Line.  Whenever  we  probe  an  entry 
of  the  table,  we  encounter  one  of  three 
results: 

(1)  If  ST[S].HashVal  is  negative,  then 
entry  S  is  free  and  the  present  Line 
may  be  installed  in  it. 

(2)  If  ST[S].HashVal  equals  Hash- 
(Line)  and  if  ST[S].LineVal  equals 
Line,  then  this  Line  has  already  been 
entered  and  S  is  its  index. 

(3)  Otherwise,  some  other  Line  is 
hashed  to  entry  S,  so  we  must  try  the 
next  entry  in  succession,  wrapping 
around  at  the  end  of  the  table. 

The  problem  fits  awkwardly  into 
conventional  program  structures  be¬ 
cause  the  loop  must  terminate  under  ei¬ 
ther  case  ( 1 )  or  case  (2),  but  if  it  termi¬ 
nates  under  case  ( 1 )  the  new  Line  must 
be  installed  in  ST[S].  In  the  original 
program  (which  was  hacked  together  in 
a  hurry),  this  was  all  done  with  Goto 
statements  in  an  efficient  but  inelegant 
way.  Several  readers  responded  with  re¬ 
written  functions,  and  the  variety  of 
their  solutions  is  interesting. 

Before  looking  at  them,  let’s  look  at 
a  side  issue.  We  stated  the  equality  test 
in  case  (2)  in  two  parts  for  perfor¬ 
mance  reasons.  It  takes  little  time  to 
compare  two  hash  signatures  (inte¬ 
gers).  Different  Lines,  however,  will 
occasionally  hash  to  the  same  value,  in 
which  case  (and  only  then)  the  slower 
test  of  comparing  two  variable-length 
strings  is  applied.  In  the  published  pro¬ 


gram,  this  was  handled  with  a  com¬ 
pound  IF  statement, 

if  (ST[S].HashVal  =  H) 

and  (ST[S].LineVal‘  =  Line) 
then  . . . 

Only  Paul  Sand  of  Dover,  NH,  no¬ 
ticed  that  such  a  statement  is  not  good 
Pascal.  “I  want  to  bring  to  your  atten¬ 
tion  a  portability  bug,”  he  wrote.  “The 
problem  is  that  standard  Pascal  does 
not  guarantee  short-circuit  evaluation 
of  Boolean  expressions.  For  example, 
in  good  old  Apple  Pascal,  both  tests 
will  always  be  done  no  matter  what  the 
outcome  of  the  first  test,  slowing 
things  down  considerably.  Other  ver¬ 
sions  of  Pascal  might  do  the  tests  in 
reverse  order.” 

Sand  is  dead  right.  C  promises  to 
short-circuit  the  second  (slower)  rela¬ 
tion  when  the  first  test  fails,  and  Ada 
offers  the  CAND  (conditional  and)  op¬ 
erator  for  explicit  control  of  the  se¬ 
quence  of  tests.  In  Pascal,  the  only  way 
to  get  the  desired  effect  is  to  write: 

if  ST[S].HashVal  =  H  then 
if  ST[S].LineVal“  =  Line  then 

When  you  do,  you  end  up  duplicating 
code  in  the  ELSE  leg  of  the  now  dou¬ 
bled  IF. 

Sand’s  version  of  the  search  code 
( Listing  One,  page  1 7 )  is  a  straightfor¬ 
ward  encoding  of  the  specifications 
given  above.  An  auxiliary  Boolean 
variable.  Done,  is  introduced  to  control 
the  loop.  Case  ( 1 )  is  handled  as  soon  as 
it  is  discovered  so  that,  on  termination, 
cases  (1)  and  (2)  have  been  made 
identical. 

A.  Salemma  (our  approximation 
from  a  handwritten  signature)  of 
Washington,  DC,  and  Wayne  Rivers 
of  Springfield,  VA,  sent  solutions 
much  like  Sand’s  except  that  they  used 
the  more  intuitive  “Repeat  .  .  .  Until 

Dr.  Dobb’s  Journal,  January  1985 


8 


Done”  structure  instead  of  “While 
Not  Done  Do.” 

It  isn’t  necessary  to  handle  all  three 
conditions  inside  the  loop.  Fred  Mar- 
chand  of  Bellingham,  WA,  rewrote  the 
program  in  C  for  his  own  uses,  but  his 
implementation  of  the  symbol-search 
function  translates  back  to  Pascal  as 
shown  in  Listing  Two  (page  17).  The 
loop  ends  when  either  case  (1 )  or  case 
(2)  is  discovered.  The  special  case  ( 1 ), 
entry  of  a  new  symbol,  is  handled  out¬ 


side  the  loop. 

Gary  Dale  of  Toronto,  Ontario,  took 
an  approach  which  is  similar  to  Mar- 
chand’s  but  he  made  better  use  of  Pas¬ 
cal’s  treatment  of  Boolean  data  (Listing 
Three,  page  17).  He  was  the  only  one  to 
use  a  Boolean  expression  to  set  the  val¬ 
ue  of  the  auxiliary  variable.  Dale  made 
a  number  of  other  changes  in  the  pro¬ 
gram,  including  implementing  his  own 
heap  storage  so  that  it  could  store  more 
lines. 


Ragged  Patches 

No  subject,  it  seems,  inspires  more 
imaginative,  not  to  say  panicky,  adven¬ 
tures  in  typography  than  the  program 
patch  presented  in  a  glossy  magazine. 
In  the  past  year,  various  PC-related 
magazines  have  carried  articles  on 
patching  WordStar  for  this  or  that 
purpose  as  well  as  articles  on  patching 
other  bits  of  MSDOS  or  PCDOS;  each 
has  used  a  different  way  of  showing  the 
necessary  Debug  commands. 

It’s  just  nerves,  we  think.  The  author 
knows  that  making  a  patch  is  a  risky 
business;  if  it  isn’t  done  just  right,  the 
results  will  be  unpredictable  (and,  fair¬ 
ly  or  not,  blamed  on  the  author).  So 
the  author  writes  down  exactly  what  is 
to  be  done,  making  up  typographic 
conventions  for  “what  the  computer 
will  show”  and  “what  the  reader 
should  enter”  on  the  fly. 

The  editors  also  know  that  patches 
are  risky  things  that  must  be  done  just 
right — and  that  they  haven’t  the  ex¬ 
pertise  to  tell  which  parts  of  the  au¬ 
thor’s  presentation  are  essential  and 
which  are  decoration.  So  they  change 
the  author’s  careful  instructions  as  lit¬ 
tle  as  possible;  this  usually  means  fit¬ 
ting  them  to  the  Procrustean  bed  of  a 
specific  column  width,  reducing  blanks 
and  commas  to  proportionally  spaced 
insignificance,  changing  the  ASCII 
apostrophe  to  an  inverted  comma,  and 
shrinking  fat  computer-printed  aster¬ 
isks  to  eentsy  specks  above  the  line. 

What’s  left  is  a  nearly  unreadable 
account  of  a  hypothetical  Debug  ses¬ 
sion.  Even  if  it  were  readable,  it 
wouldn’t  be  relevant.  Not  everyone 
uses  Debug;  other  debuggers,  and 
other  utilities  for  modifying  disk  files, 
are  available.  And  some  patches  can  be 
usefully  made  on  the  fly  by  another 
program.  The  problem  with  showing 
patches  in  terms  of  Debug  operations 
is  that  it  confuses  the  process  with  the 
desired  results.  All  that  is  relevant 
about  a  patch  is  the  address  of  the  tar¬ 
get  area,  what’s  in  that  area  now,  and 
what  to  change  it  to.  You  can  put  this 
data  neatly  in  a  three-column  table. 

The  Table  on  page  17  is  an  example. 
It  shows  a  patch  for  ZDOS, version  2.13 
(MSDOS  2  for  the  Zenith  100  series). 
The  patch  corrects  a  tendency  of  serial 
output  to  drop  characters.  It  appeared 
first  in  BUSS,  The  Independent  News- 


Dr.  Dobb's  Journal,  January  1985 


14 

9 


letter  of  Heath  Co.  Computers  as  a 
column  and  a  half  of  tortuous  instruc¬ 
tions.  The  table  is  clearer  and  less  likely 
to  lead  to  error.  It  informs  the  reader 
what  is  to  be  changed,  but  leaves  the 
how  of  the  change  unspecified. 

(Charles  Floto's  BUSS ,  incidentally, 
is  a  valuable  resource  for  anyone  who 
owns  Heath  hardware.  It  costs  $28  for  a 
year  of  20  gossip-  and  bargain-filled  is¬ 
sues;  call  (202)  544-0900  to  subscribe.) 

Wot  Duzzit  Dew? 

David  S.  Tilton  sent  us  the  following 
sequence  of  Z80  assembly  code.  It’s  an 
absolutely  astonishing  implementation 
of .  .  .  but  you  can  figure  it  out.  Unfor¬ 
tunately,  it  requires  such  narrowly  de¬ 
fined  conditions  that  it’s  probably  use¬ 
less,  despite  being  the  fastest  one  of  its 
kind.  What  does  it  do,  and  what  are  its 
narrow  requirements  for  utility? 


Loop: 

Might  there  be  a  way  to  use  this  gim¬ 

cp  (hi) 

mick  in  another  architecture  (e.g., 

ret  z 

8086,  68000)  to  get  the  same  result 

rl  L 

with  more  flexibility?  DD| 

djnz  Loop 

Id  L,0 

Reader  Ballot 

Vote  for  your  favorite  feature/article. 

ret 

Circle  Reader  Service  No.  1 90. 

Load  10. SYS  with  the  debug  command:  L  1000:0  0  A  20 


At  .  .  . 

Find  .  .  . 

Replace  .  .  . 

1 000: 1 E32 

MOVE  AH,AL 

MOV  BH,AL 

1 000: 1 E47 

POP  BX 

MOV  AL,AH 

MOV  AL,BH 
POP  BX 

1000:1E91 

MOV  AH,AL 

MOV  BH,AL 

1000:1EA6 

POP  BX 

MOV  AL,AH 

MOV  AL,BH 
POP  BX 

Table 

A  patch  to  the  BIOS  of  ZDOS  2.1 3  (not  PCDOS)  to  prevent  dropping 
bytes  in  serial  output.  Addresses  based  on  loading  10. SYS  from 
disk  under  Debug;  not  correct  for  patching  the  active  system. 


Dr.  Dobb's  Clinic  (Text  begins  on  page  12) 
Listing  One. 

Structured  hash-table  search  by  Paul  Sand 

h  :  =  hash (Line) ; 
s  :  =  h  mo  d  Max  Sym ; 
done  :=  FALSE; 

while  not  done  do 

if  (ST[s] .HashVal  <  0)  then  begin 
with  ST[s]  do  begin 
hashval  :=  h; 
new(LineVal) ; 

LineVal"  :=  Line 

end ; 

done  :=  TRUE 

end 

else 

if  ST[s ] .HashVal  =  h  then 

if  ST[s ] .LineVal'  =  Line  then 
done  :=  TRUE 

else 

s  :=  (s  +  1)  mod  MaxSym 

else 

s  :=  (s  +  1)  mod  MaxSym; 
store  :=  s;  {  result  } 

End  Listing  One 

Listing  Two. 

Structured  hash-table  search  by  Fred  Marchand 

h  :=  Hash (Line) ; 
s  : =  h  mod  MaxSym  ; 
found  :=  FALSE; 

while  (ST[s ] .HashVal  >  0)  and  (not  found)  do 
if  (ST[s ]. HashVal  =  h)  and 

17 

10 


(ST[s ] .LineVal"  =  Line)  then 
found  :=  T&UE 

else 

s  :=  (s+1)  mod  MaxSym; 

if  (ST[s]. HashVal  <  0)  then 
with  ST[s]  do  begin 
HashVal  :=  h; 
new(LineVal) ; 

LineVal'*  =  Line 

end; 

store  :=  s;  End  Listing  Two 

Listing  Three. 

Structured  hash-table  search  by  Gary  Dale 

h  :=  Hash (Line) ; 
s  : =  h  mod  MaxSym ; 

repeat 

with  ST[s]  do 

if  (HdshVal  =  h)  then 

found  :=  LineVal"  =  Line 

else 

found  :=  HashVal  <  0; 
if  not  found  then 

s  :=  (s+1)  mod  MaxSym 
until  found; 

if  (ST[s  ]  .HashVal  <  0)  then 
with  ST[s]  do  begin 
HashVal  : =  h ; 
new ( Line Val)  ; 

LineVal”  :=  Line 

end ; 

store  :  =  s  ;  End  Listings 

Dr.  Dobb’s  Journal.  January  1985 


The  modifications  described  in  the  article  by  Lafleur  and 
Raab  are  not  for  amateurs.  In  fact,  we  can’t  with  a  clear 
conscience  recommend  that  you  void  your  warranty  and  risk 
destroying  your  motherboard  to  save  a  few  dollars.  Al¬ 
though  we’ve  taken  steps  to  convince  ourselves  of  the  techni¬ 
cal  accuracy  of  the  article  (and  we  know  that  Tom  Lafleur 
has  used  this  procedure  on  several  Macs),  we  haven’t  yet 
had  the  nerve  to  fatten  a  Mac  ourselves.  Don't  take  this  on 
as  a  first  hardware  project.  Don’t  undertake  it  if  you  aren’t 
sure  of  the  risks  involved.  And  don’t  blame  us  if  anything 
goes  wrong.  We  are  providing  this  information  strictly  as  a 
service  to  those  who  know  how  to  use  it.  We  take  no  respon¬ 
sibility  for  fried  Macs.  If  you  have  the  newer  128  mother¬ 
board,  read  the  addendum  on  page  4. 

Are  you  tired  of  those  pesky  disk  writes  while  using 
MacPaint?  Does  the  performance  of  even  your  new 
hard  disk  drive  leave  you  longing  for  the  lightning 
response  of  a  RAM  disk?  Want  to  run  Lotus  1-2-3  and  can’t, 
or  are  you  developing  MAC  software  and  just  plain  running 
out  of  room? 

For  these  and  many  other  reasons,  Macintosh  owners  ev¬ 
erywhere  are  rushing  to  Apple  for  a  FAT  MAC  upgrade  that 
packs  a  full  512K  of  memory  into  the  system.  Most  will  (and 
should)  wait  for  Apple  to  add  the  necessary  chips  to  the  Mac¬ 
intosh  motherboard.  However,  if  you  are  one  of  the  adventur¬ 
ous  few  willing  to  sacrifice  your  Apple  warranty  and  wager 
the  life  of  your  Macintosh  against  your  soldering  skills,  here’s 
how  to  perform  the  upgrade  yourself  for  half  the  cost. 

You'll  Need.  .  . 

The  checklist  in  Table  I  (page  21 )  shows  the  parts  and  tools 
you’ll  need  to  complete  the  job.  To  minimize  your  MAC’s 
down  time,  make  sure  you  have  everything  on  the  list  before 
you  begin.  All  of  the  parts  can  be  found  at  your  local  elec¬ 
tronics  supply  house  or  by  looking  for  ads  in  your  favorite 
computer  magazine. 

Because  the  256K  RAM  chips  are  a  little  harder  to  find,  I 
have  listed  a  few  sources.  We  have  paid  $15  to  $45  each  for 
the  memory  chips:  they  are  not  cheap!  We’ve  tested  only 
NEC  memory  chips  in  this  conversion,  but  other  equivalent 
256K  memory  chips  should  work  as  well.  Once  you’ve  got 
the  parts  and  equipment  together,  you’re  ready  to  begin. 

The  Procedure 

You’ll  need  about  four  hours  of  quiet  concentration  to  com¬ 
plete  this  upgrade  yourself.  Before  you  begin,  read  through 
the  entire  procedure.  Then  disconnect  all  cables  from  the 
Macintosh,  including  the  keyboard,  mouse,  printer,  extra 
drive,  power,  and  anything  else  you  might  have  plugged  in. 
(1)  Remove  the  case  screws.  Use  the  Xcelite  XTD-10  Torx 
screwdriver  to  remove  all  five  of  the  case  screws  (see  Photo  1 
on  page  20).  There  are  two  near  the  bottom,  one  inside  the 
battery  case,  and  two  deep  inside  the  handle. 


Fatten  Your  Mac 

by  Tom  Lafleur  and  Susan  Raab 


Veteran  microcomputer  hardware  hack¬ 
ers  have  denounced  Apple  for  abandon¬ 
ing  the  principle  of  an  open  architecture 
with  the  Mac.  But  to  the  true  hacker ;  all 
architectures  are  open. 


Tom  Lafleur,  P.O.  Box  490,  Del  Mar,  CA  92014. 

Susan  Raab,  Digital  Research  International,  160  Central 
Avenue,  Pacific  Grove,  CA  93950. 

Dr.  Dobb’s  Journal,  January  1985 


18 

11 


(2)  Remove  the  case.  Turn  the  Macintosh  facedown  on  a 
table.  Gently  press  down  on  the  battery  compartment,  power 
connector,  and  I /O  connectors  while  you  lift  up  on  the  back 
of  the  case.  The  back  of  the  case  should  come  off  in  your 
hands,  leaving  the  faceplate  in  position  around  the  screen 
and  disk  drive  door.  If  the  case  is  stubborn,  insert  a  long 
metal  ruler  into  the  the  seam  between  the  faceplate  and  the 
back  of  the  case  (see  Photo  2,  page  20).  (  Do  not  use  a 
screwdriver  here.)  Use  the  ruler  to  gently  pry  the  seam  open 
as  you  pull  up  on  the  back  of  the  case. 

(3)  Remove  the  motherboard.  Find  and  disconnect  the  two 
cables  that  attach  the  display  board  and  disk  drive  to  the 
motherboard  (see  Photo  3,  page  20).  Ease  the  motherboard 
from  its  connections  and  remove  it  from  the  chassis.  Check 
the  revision  number  of  the  motherboard.  We’ve  successfully 
performed  this  upgrade  on  motherboards  that  have  the  revi¬ 
sion  numbers  630-0101  screened  on  top  and  820-0086C 
etched  on  the  back. 

(4)  Locate  and  remove  the  memory  chips.  You’ll  find  sixteen 
memory  chips  with  part  number  MT-  4264  at  IC  locations 
F5  through  FI 2  and  G5  through  G12  (see  Photo  4,  page  20) 
Remove  them  from  the  motherboard  as  follows: 

•  Using  a  small  pair  of  wire  cutters,  clip  all  the  pins  off 
each  chip  as  close  to  the  chip  as  you  can  (see  Photo  5, 
page  20).  Then  throw  away  all  128K  of  these  memory 
chips — they  aren’t  as  valuable  as  the  Macintosh 
motherboard! 

•  Using  a  low-temperature  soldering  iron  (700°),  remove 
all  the  memory  chip  pins  from  the  motherboard  (see  Pho¬ 
to  6,  page  21).  It’s  handy  to  use  a  Weller  WTCPN  solder¬ 
ing  station  because  it  has  a  magnetic  tip  that  can  pull  the 
pin  out  of  the  board  as  soon  as  the  pin  is  heated,  but  other 
soldering  irons  will  work  if  you  use  care.  This  is  a  repeti¬ 
tive  task  (256  pins!)  and  you’ll  soon  develop  a  rhythm.  To 
keep  the  rhythm  going,  skip  over  pins  8  and  16  from  each 
chip  on  the  first  pass.  Because  they’re  connected  to  the 
inner  power  and  ground  layers  of  the  motherboard,  they 


require  more  heat  and  time  to  remove.  If  you  remove 
them  on  a  second  pass,  they’ll  establish  their  own  rhythm. 

•  Clean  out  all  the  holes  with  a  solder  sucker  (see  Photo  7, 
page  21).  Again  skip  holes  8  and  16  on  your  first  pass  to 
keep  your  rhythm  going,  then  clean  them  out  on  a  second 
pass. 

•  Clean  the  motherboard  with  a  fine  brush.  Look  the 
board  over  for  any  solder  splashes  or  broken  etch. 

(5)  Insert  IC  sockets.  Carefully  solder  a  good  IC  socket  in 
each  memory  chip  location.  When  you’re  done,  clean  the 
back  side  of  the  motherboard  with  alcohol  or  a  TF  degreaser 
to  remove  all  of  the  flux  left  after  soldering.  Examine  the 
motherboard  again  for  short  or  broken  etch. 

(6)  Install  new  memory  chips.  Carefully  insert  the  sixteen 
41256  memory  chips  into  the  sockets  (see  Photo  8,  page  21). 

(7)  Test  motherboard.  Before  you  go  any  further,  check  your 
work  by  starting  up  the  system.  At  this  point,  it  should  oper¬ 
ate  as  a  normal  128K  Macintosh.  To  complete  the  test,  fol¬ 
low  these  steps: 

•  Insert  the  motherboard  back  into  the  Macintosh  chas¬ 
sis.  Connect  the  display  and  drive  cables  to  the  mother¬ 
board,  then  connect  the  power  cable. 

•  Start  up  the  system.  If  all  is  well,  you’ll  see  the  normal 
question-mark  disk  icon.  If  the  system’s  diagnostic  soft¬ 
ware  finds  a  problem,  it  will  display  a  sad  icon  (or  as 
much  of  one  as  it  can!).  If  the  system  does  not  start  nor- 


19 

12 


Dr.  Dobb’s  Journal,  January  1985 


Photo  1 


Photo  2 


Photo  3 


Photo  4 


Photo  5 


Dr.  Dobb  s  Journal.  January  1985 


mally,  check  the  trouble-shooting  section  at  the  end  of 
this  article.  Do  not  go  on  to  the  next  step  until  the  system 
boots  up  normally. 

(8)  Remove  the  motherboard.  Again  disconnect  the  display, 
disk,  and  power  cables  from  the  motherboard.  Ease  the  moth¬ 
erboard  from  its  connectors  and  remove  it  from  the  chassis. 

(9)  Assemble  a  new  memory  select  IC.  You  must  build  a 
memory  select  IC  that  lets  the  Macintosh  access  the  extra 
memory  you’ve  just  installed.  Create  the  new  IC  as  follows, 
using  Photos  9  and  10  (page  21)  and  Figures  One  and  Two 
(page  23)  for  reference. 

•  On  a  good  quality  16-pin  1C  socket,  bend  out  all  the 
pins  except  2,  7,  14,  and  16. 

•  Use  solder  and  some  small-gauge  wire  (30  awg)  to  con¬ 
nect  pins  1,  10,  11,  12,  and  13  to  pin  8. 

•  Solder  a  2.2K  1/4  watt  resistor  between  pins  1 5  and  16. 

•  Use  solder  and  some  small-gauge  wire  to  connect  pins  3 
and  4  to  pin  15. 

•  Insert  a  74F253  or  a  SN74AS253  dual  4-to-l  multi¬ 
plexer  into  this  modified  IC  socket. 

(10)  Install  the  new  memory  select  IC.  You  must  connect  the 
new  IC  assembly  to  the  68000  address  bus,  memory  select 
logic,  and  other  lines  on  the  motherboard  as  follows: 

•  Mount  your  new  IC  assembly  on  top  of  the  74F253  (or 
SN74AS253)  located  at  F3  on  the  motherboard.  Solder 
pins  2,  7,  14,  and  16  of  the  new  IC  assembly  to  the  same 
pins  on  the  74F253  at  F3. 

•  Focate  the  seven  IC  pads  at  E3,  next  to  pins  32  and  33 
of  the  68000  microprocessor  (see  below): 


33 

1 

0 

(  +  5  Volts) 

2 

0 

(  Dram  pin  1 ) 

3 

0 

(  A18) 

68000 

4 

0 

(  SEE  -  A) 

5 

0 

(  SEL  -  B) 

6 

0 

(  A17) 

7 

0 

(  GND) 

32 

•  On  the  back  side  of  the  motherboard,  cut  the  etch  be¬ 
tween  pins  1  and  2  at  location  E3. 

•  Solder  a  47-ohm  1  /4  watt  resistor  between  pin  7  of  the 
new  IC  assembly  and  pin  2  of  the  IC  pad  at  location  E3. 
(See  Figure  One.) 

•  Using  solder  and  small-gauge  wire,  connect  pin  5  of  the 
new  IC  assembly  to  pin  3  of  the  IC  pads  at  location  E3. 

•  Using  solder  and  small-gauge  wire,  connect  pin  6  of  the 
new  IC  assembly  to  pin  6  of  the  IC  pads  at  location  E3. 

•  Check  the  motherboard  for  any  solder  splashes,  missed 
wiring,  or  broken  etch.  Clean  the  board  with  alcohol  or  a 
TF  degreaser  to  remove  any  flux  left  after  soldering. 

(11)  Test  your  FAT  MAC!  Insert  the  upgraded  motherboard 
into  the  Macintosh  chassis.  Reconnect  the  disk,  display,  and 
power  cables.  Power  up  your  Macintosh  and  check  for  the 
normal  question-mark  disk  icon.  If  your  MAC  does  not  ap¬ 
pear  normal,  review  the  trouble-shooting  section  at  the  end 
of  this  article.  If  the  normal  question-mark  disk  icon  ap¬ 
pears,  insert  your  system  disk  and  open  the  disk  copy  pro¬ 
gram.  The  disk  copy  program  should  display  a  message  that 

20 

13 


337-0001 


says  it  only  works  with  a  standard  128K  MAC.  To  see  how 
much  memory  you  have,  start  up  BASIC  (if  you  have  it)  and 
enter  the  free  memory  command:  PRINT  FRE(O).  On  our 
system,  BASIC  reports  about  340K  of  memory.  You  may 
also  want  to  run  the  MAC  memory  diagnostics  as  outlined 
below  for  a  few  hours  to  check  for  any  long-term  problems. 


Photo  6 


Trouble  Shooting 

If  the  system  does  not  start  normally,  use  a  multimeter  to 
check  that  all  the  connections  you’ve  made  are  connected 
properly.  If  all  the  connections  check  out,  use  the  system 
diagnostic  program  in  the  Macintosh  ROM  to  determine  if 


Photo  7 


Parts: 

Qty.  Part  No.  Description  Vendor 

16  41256-200  256K  200-ns  NEC 

memory  chips 

1 7  1 6-pin  1C  sockets 

1  SN74AS253N  Dual  4-to-1  multiplexer  Tl, Motorola 


74F253N 


Fairchild 


Photo  8 


2.2K  1  / 4  watt  resistor 
(any  resistor  from  1 K 
to  4.7K  will  work) 
47-ohm  1/4  watt 
resistor 


Memory  chip  vendors: 

The  first  five  vendors  sell  their  products  retail  and  have  a 
small  or  no  minimum  order;  the  other  vendors  are  indus¬ 
trial  suppliers  and  may  have  a  minimum  order. 

Jameco  electronics  (4 1 5)  592-8097 

JDR  microdevices  (408)  995-5430 

Advance  computer  products  (800)  854-8230 

(714)  558-8822 

Jade  computer  products  (800)  42 1  -5500 

(800)262-1710 

DoKay  computer  products  (800)  538-8800 

(800)  848-8008 

NARA  (408)  748-9200 

TAKA  (415)952-9000 

Japan  electronics  (818)369-1833 

D-L-C  (213)938-2677 

Tools: 

Xcelite  XTD-10  Torx  screwdriver,  6-inch  shaft 
Long  metal  ruler 
Low-temperature  soldering  iron 
(Weller  WTCPN  recommended) 

Solder  sucker 
Small-gauge  wire  (30  awg) 

Multimeter  (for  problem  solving) 


Photo  9 


Photo  10 


Table  I. 

Supplies  Checklist 


Dr.  Dobb  s  Journal.  January  I9R5 


Chip 

Data  Bit 

Pin  2  to 

Pin  14  to 

Location 

Number 

68000  Pin 

Memory 
Buffer  Pin 

F5 

DO 

5 

E12-  2 

F6 

D1 

4 

4 

F7 

D2 

3 

6 

F8 

D3 

2 

8 

F9 

D4 

1 

17 

FIO 

D5 

64 

15 

F1 1 

D6 

63 

13 

F12 

D7 

62 

1 1 

G5 

D8 

61 

El  3  -  2 

G6 

D9 

60 

4 

G7 

DIO 

59 

6 

G8 

Dll 

58 

8 

G9 

D12 

57 

17 

GIO 

D13 

56 

15 

G11 

D14 

55 

13 

G12 

D15 

54 

1 1 

Table  II. 

Connections  for  Pins  2  and  1 4 


Class  Code 

Sub  Code 

1  =  ROM  test  failed 

Meaningless 

2  =  Memory  test  -  bus  subtest 

Identifies  bad  chips 

3  =  Memory  test  -  byte  write 

Identifies  bad  chips 

4  =  Memory  test  -  Mod3  test 

Identifies  bad  chips 

5  =  Memory  test  -  address 

Identifies  bad  chips 

uniqueness 

Table  III. 

Diagnostic  Codes 

Data  Bit 

Location 

Sub  Code  Bits 

0 

F5 

0001 

1 

F6 

0002 

2 

F7 

0004 

3 

F8 

0008 

4 

F9 

0010 

5 

FIO 

0020 

6 

F1 1 

0040 

7 

F12 

0080 

8 

G5 

0100 

9 

G6 

0200 

10 

G7 

0400 

1 1 

G8 

0800 

12 

G9 

1000 

13 

GIO 

2000 

14 

G11 

4000 

15 

G12 

8000 

Table  IV. 

Chip  Identification 

any  of  the  memory  chips  you’ve  inserted  are  bad. 

Checking  the  connections  is  another  repetitive  task:  you 
must  make  sure  that  all  of  the  256  new  connections  you’ve 
made  carry  signal  to  the  appropriate  destinations.  And  most 
of  the  connections  carry  signal  to  more  than  one  place! 

For  example,  a  signal  on  pin  0  on  one  memory  chip  should 
be  connected  to  pin  0  on  every  other  memory  chip.  A  signal  on 
pin  1  should  appear  on  pin  1  of  all  the  other  memory  chips. 
Check  for  this  continuity  on  all  pins  except  2,  14,  and  15. 

Pin  1 5  is  common  among  chips  in  the  same  row.  For  exam¬ 
ple,  pin  15  on  a  chip  in  row  F  should  be  connected  to  every 
other  pin  15  in  row  F  but  not  in  row  G.  Pin  15  on  a  chip  in 
row  G  should  be  connected  to  every  other  pin  in  row  G.  Pin  2 
on  each  of  the  sixteen  memory  chips  is  directly  connected  to 
one  of  the  sixteen  data  lines  of  the  68000  microprocessor.  Pin 
14  connects  the  memory  chips  to  the  memory  buffer  circuits 
at  locations  E 1 2  and  E 1 3. 

Table  II  (at  left)  shows  how  pins  2  and  14  should  be  con¬ 
nected.  Each  row  in  the  table  gives  information  about  one  of 
the  memory  chips.  The  first  column  lists  the  chip’s  location. 
The  second  column  lists  the  data  bit  of  the  68000.  The  third 
column  lists  the  pin  on  the  68000  to  which  pin  2  of  the  mem¬ 
ory  chip  should  be  connected.  The  fourth  column  lists  the  pin 
on  the  memory  buffers  to  which  pin  14  of  the  memory  chip 
should  be  attached. 

If  you  discover  that  one  of  these  connections  is  not  con¬ 
nected  properly,  find  and  correct  the  broken  etch,  or  add 
some  small-gauge  wire  until  the  connection  is  restored.  If  all 
the  connections  are  in  working  order  and  you’re  still  having 
trouble,  use  the  system  diagnostics  in  the  Macintosh  ROM  to 
identify  the  bad  memory  chips.  Look  at  all  signals  connected 
to  the  suspected  chip  for  a  bad  connection. 

Diagnostics 

Before  starting  the  diagnostics,  you  must  have  installed  the 
programmer’s  buttons  Interrupt  and  Reset  on  the  left  side  of 
your  Macintosh.  Hold  down  the  Interrupt  button  and  either 
press  the  Reset  button  or  power  on  your  Macintosh. 

A  sad  Macintosh  icon  appears  with  a  numeric  code  under 
it.  If  all  is  working  well,  the  code  will  be  OF  000D,  and  some 
small  bits  will  cycle  under  the  code  to  indicate  that  the  Mac¬ 
intosh  is  running  the  memory  diagnostic  program.  The  nu¬ 
meric  code  that  you  will  see  has  two  parts;  for  example,  OF  is 
the  class  code  and  000D  is  the  sub  code.  As  shown  in  Table 
III  (at  left),  the  class  code  tells  what  part  of  the  diagnostic 
program  found  an  error,  and  the  sub  code  tells  what  the  error 
was.  Each  of  the  sixteen  bits  in  the  sub  code  identifies  one  of 
the  sixteen  memory  chips.  Table  IV  (at  left)  maps  the  sub 
code  bits  to  their  respective  chip’s  location. 

If  the  diagnostics  discover  more  than  one  bad  chip,  the  sub 
code  displays  multiple  bits.  For  example,  if  bit  3  is  bad,  the 
diagnostics  display  sub  code  0008.  If  both  bit  3  and  bit  1 0  are 
bad,  the  diagnostics  display  sub  code  0408.  If  the  diagnostics 
identify  a  bad  chip,  you’ll  have  to  replace  it  with  a  good  one 
or  find  the  problem  on  the  board. 

After  all  the  diagnostics  have  passed,  the  program  dis¬ 
plays  an  exception  code  giving  the  current  state  of  your 
MAC.  You  should  normally  see  a  sub  code  of  000D,  NMI. 
The  others  are  listed  in  Table  V  (at  right)  for  your  informa¬ 
tion  only. 


Dr.  Dobb's  Journal.  January  1985 


22 

15 


Removing  the  Modification 

If  you  need  to  convert  your  Macintosh  back  into  a  standard 
128K  unit,  simply  remove  the  IC  assembly  you  added  at 
location  F3,  remove  the  sixteen  41256  memory  chips,  and 
replace  them  with  standard  200-ns  4 1 64  64K  memory  chips. 
You  must  also  connect  the  jumper  at  location  E3,  between 
pins  1  and  2. 

We’ve  completed  the  conversion  on  over  10  MACs  and 
have  had  no  problems  with  this  upgrade.  So  good  luck  with 
your  new  FAT  MAC. 


DDJ 

Reader  Ballot 

Vote  for  your  favorite  feature/article. 
Circle  Reader  Service  No.  191. 


READ.ME 

The  256K  dynamic  RAM  chips  that  turn  a  Mac  into  a  Fat 
Mac  are  very  sensitive  to  static  electricity.  Please  note  the 
following  advice  on  handling  256K  RAMs,  adapted  from  in¬ 
formation  from  John  Gilchrist  of  Microprocessors  Unlimit¬ 
ed  in  Beggs,  Oklahoma. 

To  damage  a  true  LSI  device  like  a  256K  RAM  chip,  you 
don’t  have  to  touch  it.  Being  close  to  it  with  a  high  potential 
voltage  on  your  body  will  do  the  job.  You  can,  for  example, 
generate  3000  volts  by  walking  across  a  carpet  in  leather 
shoes  or  by  peeling  off  a  foot  or  so  of  cellophane  tape,  and 
you  won’t  feel  a  thing  as  the  tiny  spark  jumps  to  the  IC, 
doing  its  hidden  damage. 

Following  these  steps  should  lessen  the  risk  to  the  chips 
from  static  electricity. 

1.  If  you  have  a  choice  of  workspace,  almost  any  floor 
covering  is  better  (for  the  present  purpose)  than  carpeting. 

2.  Take  off  your  shoes.  Don’t  take  this  as  a  personal  re¬ 
mark,  but  your  feet  sweat  enough  to  make  it  unlikely  that  a 
high  static  charge  can  build  up  when  you  are  standing  bare¬ 
foot  on  a  noncarpet  surface.  And  don’t  wear  any  nylon 
clothing. 

3.  Spread  out  a  large  sheet  of  aluminum  foil  (about  a 
three-foot  length)  and  work  on  that.  Wrap  a  corner  of  the 
foil  around  the  Mac  chassis  or  motherboard  and  poke  the 
computer’s  power  cord  through  the  foil.  Keep  your  body 
(e.g.,  one  elbow)  on  the  foil  throughout  the  process  of  pre¬ 
paring  the  Mac  to  receive  its  new  chips. 

4.  Don’t  handle  the  256K  chips  until,  or  any  more  than, 
you  have  to.  When  you  are  ready  for  them,  slide  the  chips  out 
of  their  factory  tube  onto  the  foil.  Keep  your  body  in  con¬ 
stant  contact  with  the  foil  as  you  install  the  chips. 

5.  When  you’ve  finished  the  installation  and  are  ready  to 
test  your  work,  be  sure  to  remove  the  wall  plug  from  the  foil 
before  plugging  it  into  the  wall.  Otherwise  you  could  fry 
more  than  a  chip. 


Class  Code  Sub  Code 

F=  Exception  0001  Bus  error 

0002  Address  error 
0003  Illegal  instruction 
0004  Zero  divide 
0005  Check  instruction 
0006  Traps  instruction 
0007  Privilege  violation 
0008  Trace 
0009  Line  1010 
000 A  Line  1111 
000B  Other  exception 
000C  Nothing 

000D  NMI  (normal  indication) 

Table  V. 

Exception  Codes 


23 

in 


Dr.  Dobb  s  Journal.  January  1985 


QuickDraw  Meets  Image  Writer 


Apple’s  new  crop  of  computers, 
the  Macintosh  and  the  Lisa, 
makes  extensive  use  of  bit¬ 
mapped  graphics.  Less  visible  than  the 
graphics  hardware,  but  just  as  neces¬ 
sary,  is  the  well  thought-out,  well  craft¬ 
ed,  integrated  collection  of  graphics 
software  primitives  called  QuickDraw. 
These  routines  make  it  a  simple  matter 
to  put  graphics  into  programs.  Because 
of  QuickDraw,  programmers  have  an 
easier  job,  users  get  better  software, 
and  Apple — who  paid  for  its  develop¬ 
ment  racks  up  sales.  Unfortunately, 
the  Lisa  QuickDraw  routines  lack  any 
support  for  the  Apple  Image  Writer  dot 
matrix  printer;  hence,  this  article.  The 
ImageWriter  exhibits  the  quality  fea¬ 
tures  we  have  begun  to  associate  with 
Apple  hardware  (e.g.,  well-crafted 
built-in  software  primitives  and  com¬ 
plete  concise  documentation).  I  would 
recommend  the  ImageWriter  to  anyone 
in  the  market  for  a  printer. 


look  at  the  routine  1MAGE_PRINT 
and  discuss  some  of  the  considerations 
that  influenced  its  design. 

QuickDraw  stores  an  image  in  a  con¬ 
tinuous  piece  of  memory  with  each  bit 
corresponding  to  one  pixel:  a  one  for 
black,  a  zero  for  white.  For  example, 
the  Lisa  screen,  which  measures  720  X 
364  dots,  requires  262,080  bits  or  32K 
bytes.  The  exact  relationship  between 
the  data  in  memory  and  the  pixels  on 
the  screen  depends  on  the  values  stored 
in  the  bitMap.  A  bitMap  is  a  data 
structure  specified  in  Pascal  as  follows: 


by  Thom  Mayer 


BitMap  =  RECORD 
baseAddr  :  QDPtr; 
row  Bytes  :  Integer; 
bounds  :  Rect; 

END; 

The  field  baseAddr  is  a  pointer  (memo¬ 
ry  address)  to  the  beginning  of  the 
block  of  memory  where  the  image  is 


Printing  sideways  with  a  Macintosh. 


Figures  I  and  2  (page  27)  were 
drawn  on  a  Lisa  by  QuickDraw  and 
were  printed  on  an  ImageWriter  dot 
matrix  printer  with  the  enclosed  rou¬ 
tine.  Listing  One  (page  31)  gives  a 
Pascal  unit  containing  the  function 
IMAGE_PRINT,  and  Listing  Two 
(page  35)  demonstrates  its  use.  In  this 
article  we  will  briefly  review  the  way 
QuickDraw  stores  a  graphics  image 
and  the  way  the  ImageWriter  prints 
graphics.  Then  we  will  take  a  cursory 


Thom  Mayer,  Tigre  Designs,  3006  La¬ 
fayette,  Austin,  TX  78722. 


stored.  We  should  imagine  this  memory 
grouped  by  lines,  each  line  containing 
the  number  of  bytes  specified  by  the 
field  rowBytes.  Two  dots  on  the  screen 
that  have  the  same  vertical  coordinate 
and  differ  in  the  horizontal  coordinate 
by  one  are  neighboring  bits  in  memory. 
Two  dots  on  a  QuickDraw  image  that 
have  the  same  horizontal  coordinate 
and  differ  in  the  vertical  coordinate  by 
one  are  separated  by  exactly  rowBytes 
bytes  in  memory  (Figure  3,  page  27). 

The  ImageWriter,  like  all  dot  matrix 
printers,  forms  characters  and  graphics 
by  printing  a  pattern  of  dots.  The  print¬ 
er  head  is  composed  of  a  vertical  array 
of  nine  dot-forming  devices  spaced 
1/72  inch  apart;  at  one  stroke,  the  Im- 


Dr.  Dobb’s  Journal,  January  1985 


26 

17 


ageWriter  produces  a  pattern  of  nine 
vertical  pixels.  Normal  characters  are 
composed  of  seven  such  patterns,  with  a 
blank  eighth  pattern  forming  the  space 
between  the  characters.  The  horizontal 
spacing  between  successive  patterns 
can  be  set  by  sending  the  printer  control 
codes:  A  spacing  of  1  /72  inch  yields 
nine  characters  per  inch,  a  spacing  of 
1/136  inch  yields  17  characters  per 
inch,  and  so  on  (Figure  4a,  page  28). 

When  the  ImageWriter  is  in  graph¬ 
ics  mode,  each  byte  received  produces 
an  eight-pixel  pattern  determined  by 
the  binary  representation  of  the  byte 
(recall  1  byte  =  8  bits).  The  top  pixel 
corresponds  to  the  least  significant  bit, 
and  the  bottom  pixel  corresponds  to 
the  most  significant  bit.  A  dot  is  print¬ 
ed  if  there  is  a  one  in  the  corresponding 
bit;  no  dot  is  printed  if  there  is  a  zero 
(Figure  4b,  page  28). 

Because  a  byte  in  QuickDraw  corre¬ 
sponds  to  eight  horizontal  pixels  on  the 
screen,  and  a  byte  to  the  ImageWriter 
prints  eight  pixels  aligned  vertically, 
the  image  will  come  out  sideways  on 
the  paper.  Each  printed  line  of  graph¬ 
ics  corresponds  to  a  vertical  strip  on 
the  screen,  eight  pixels  wide. 

The  pixels  on  the  Lisa  screen  are  not 
square:  they  are  1.5  times  as  high  as 
they  are  wide.  On  the  printer,  the  verti¬ 
cal  spacing  between  dots  is  fixed  by  the 
printer  head  to  1  /72  inch,  so  to  match 
the  Lisa  screen  aspect  ratio  we  need  a 
horizontal  dot  length  1  /48  of  an  inch, 
which  we  make  out  of  two  dots  on 
1  /96-inch  spacing.  At  this  spacing,  an 
image  the  size  of  the  Lisa  screen  nearly 
fills  an  8  X  10  sheet  of  paper. 

The  printer  algorithm  naturally 
breaks  into  several  steps,  shown  here  in 
the  simple  form  of  the  main  routine: 

function  image_print  ({parame¬ 
ters}  ) :  boolean; 
begin 

size_of_image; 
init_printer; 
init_buffer; 
for  line_number:=  1 
to  (row_width)  do 
begin 

buffer_a_Iine 

(line_number); 

spew_buffer; 

end; 

close_printer; 
image_print:  =  true; 


27 


■i  Q 


Dr.  Dobb  s  Journal,  January  1985 


end; 

This  algorithm  did  not  spring  out  of 
my  head  fully  formed  in  top  down 
style,  but  rather  1  developed  it  through 
successive  refinement  of  a  crude  proto¬ 
type.  At  some  point  in  the  develop¬ 
ment,  however,  you  must  translate 
your  work  into  the  structured  pro¬ 
gramming  form.  This  process  may  give 
you  insights  into  how  to  improve  your 
work,  and  it  will  certainly  lengthen  the 
lifespan  of  your  programs  by  simplify¬ 
ing  future  modifications.  Good  soft¬ 
ware  is  too  costly  to  be  disposable. 

Briefly  let’s  go  through  each  routine: 

( 1 )  SIZE_OF_IMAGE  looks  at  the  bit- 
Map  to  see  where  the  image  is  stored  in 
memory  (baseAddr),  how  it  is  orga¬ 
nized  (rowBytes),  and  what  size  image 
we  are  to  print  (bounds). 

(2)  INIT_PR1NTER  sends  the  printer 
the  initialization  codes.  To  change  from 
the  default  settings,  which  are  appro¬ 
priate  for  text,  to  the  proper  settings  for 
graphics  takes  1 6  bytes  of  data,  so  most 
of  this  first  block  is  NULs. 

(3)  The  FOR  loop  prints  one  line  of 
graphics  per  pass.  BUFFER_A_LINE 
gathers  the  bytes  needed  to  print  the 
line  and  stores  them  in  the  buffer. 
SPEW^BUFFER  sends  the  buffer  to 
the  printer  and  listens  for  confirmation 
from  the  printer.  At  the  beginning  and 
end  of  the  buffer  are  control  codes  that 
are  initialized  by  INIT_BUFFER. 

(4)  C LOS E_PR INTER  close  files, 
switches  the  printer  back  to  default 


settings,  and  form-feeds  the  paper.  If 
the  routine  gets  this  far,  it  has  detected 
no  problems  and  returns  the  value  true. 

There  we  have  it,  the  means  to  print 
a  QuickDraw  image  on  the  Image- 
Writer  dot  matrix  printer.  Now  we  will 
look  more  closely  at  the  program  de¬ 
sign,  so  have  your  listing  handy. 

IMAGE_PRINT  is  set  up  as  a  Bool¬ 
ean-valued  function  that  returns  false 
only  if  the  routine  couldn’t  print  the 
image.  The  usual  cause  for  failure  is 
the  printer  being  off,  which  is  detected 
when  the  blockwrite  routine  says  it  was 
unable  to  complete  the  transfer  of 
blocks.  All  failures  are  channeled 
through  the  procedure  NO—GOOD  : 

procedure  no_good; 
begin 

image_print:  =  false; 
exit(image_print); 
end; 

The  not  well-known  but  extremely 
handy  procedure  exit  satisfies  those 
programming  urges  that  in  other  envi¬ 
ronments  might  be  handled  by  a  GOTO 
statement.  It  is  the  calling  program’s 
responsibility  to  respond  appropriately 
to  failures.  The  following  code  frag¬ 
ment,  which  uses  the  system  call 
PAbortFlag,  gives  the  user  the  ability 
to  recover  or  abort: 

while  not  (  PAbortFlag  or 
IMAGE_PRINT 
(grafptr.printerPort)  ) 
begin 

writeln  (  ‘  fix  printer  or  press 


• 

• 

• 

• 

0 

1 

2 

3 

4 

5 

6 
7 

• 

1 

• 

• 

• 

• 

• 

• 

• 

• 

• 

• 

• 

• 

• 

• 

• 

Figure  4a. 

The  printer  head  produces  nine 
vertical  pixels  per  shot.  The 
standard  characters  are  con¬ 
structed  from  eight  such  verti¬ 
cal  patterns. 

Figure  4b. 

In  graphics  mode,  each  byte 
produces  a  vertical  line  of  eight 
pixels.  This  pattern  was  pro¬ 
duced  by  the  byte  01 101011 
binary  (i.e.,  107  decimal). 

abort  key  ’); 

writeln  ( 4  press  RETURN  to 
continue  ’); 

read  In  (  ); 

end; 

Filling  the  buffer  with  the  bytes 
needed  to  print  one  line  is  like  buying 
groceries  at  the  supermarket.  BUF- 
FER_LINE  loads  a  byte,  skips  row 
_bytes  bytes,  loads  a  byte,  and  so  on; 
clearly  we  need  random  access  to 
memory  by  the  byte.  This  calls  for  a 
packed  array  of  char.  But  we  don’t 
want  to  make  a  new  array:  we  want  to 
use  the  memory  that  QuickDraw  has 
already  set  up.  So  rather  than  declar¬ 
ing  an  array  variable,  we  declare  a 
pointer  variable,  CFI,  of  type  pointer  to 
packed  array  of  char.  Now  we  are  all 
set.  Assign  CH  the  value  baseAddr 
(i.e.,  the  beginning  of  the  image  mem¬ 
ory),  and  an  expression  like  CFT[n] 
gives  access  to  the  nth  byte  of  the  im¬ 
age  memory.  The  memory  has  not 
been  changed  only  the  way  the  pro¬ 
gram  views  it. 

Pascal  is  a  strongly  typed  language, 
and  most  compilers  check  the  type  of  a 
pointer  before  allowing  assignment. 
Just  as  you  can’t  mix  apples  with  or¬ 
anges,  you  can’t  mix  pointers  to  apples 
with  pointers  to  oranges.  Lisa  Pascal, 
however,  includes  a  wonderful  exten¬ 
sion,  the  unary  operator  @,  which  re¬ 
turns  the  pointer  to  the  operand  (just 
like  the  &  operator  in  C).  For  exam¬ 
ple,  if  Z  is  an  array,  then  @Z  is  a 
pointer  that  points  to  Z.  Simple,  yes  ? 
The  @  operator  has  another  important 
feature:  the  pointer  resulting  from  an 
@  operation,  like  the  nil  pointer,  is  as¬ 
signment-compatible  with  any  other 
type  pointer.  To  make  CH  point  to  the 
same  place  as  bitAddr,  use  the  assign¬ 
ment  CH:=  @bitAddr'.  I  call  this  de¬ 
typing  a  pointer.  It  is  like  a  CAST  oper¬ 
ation  in  C. 

Between  the  routine  IMAGE 
_PRINT  and  the  printer  sits  the  device 
driver  routine  that  actually  transfers 
the  bytes.  A  feature  of  the  Lisa  port 
driver  is  the  automatic  insertion  of  a 
line  feed  after  each  carriage  return,  an 
option  that  can  be  enabled  and  dis¬ 
abled  with  system  calls.  The  automatic 
line  feed  may  be  handy  if  you  have  a 
vintage  1960  printer,  but  in  a  graphics 
setting,  adding  the  byte  00001010  be¬ 
hind  every  occurrence  of  the  byte 


Dr.  Dobb's  Journal,  January  1985 


28 

19 


0001 101  is  a  bug.  To  track  down  this 
bug,  without  the  20-20  vision  of  hind¬ 
sight,  required  a  long  session  with  the 
debugger.  Among  other  capabilities, 
the  debugger  allows  you  to  halt  the 
program  and  to  go  in  and  look  at  the 
memory — even  change  it  if  you  wish — 
and  then  resume  operation  of  your  pro¬ 
gram.  My  search  went  as  follows. 

First  I  went  in  and  looked  at  the 
memory  where  the  screen  image  was 
stored  to  make  sure  that  it  looked  as  I 
had  expected.  Then  I  looked  at  the 
memory  corresponding  to  my  buffer  to 
make  sure  it  was  correct.  Upon  closely 
inspecting  the  buffer  for  the  line  that 
was  giving  me  trouble,  I  found  out  that 
the  bug  happened  only  when  the  byte 
0D  was  in  the  buffer.  With  the  debug¬ 
ger  I  could  change  the  occurrences  of 
0D  to  some  other  byte,  or  vice  versa, 
and  ascertain  that  0D  was  the  guilty 
party.  0D  is  the  code  for  carriage  re¬ 
turn,  and  it  was  instantly  clear  to  me 
that  somebody  was  adding  an  LF  after 
every  carriage  return.  But  who? 

I  spent  a  long  time  thinking  the 


printer  was  the  culprit  but  finally  con¬ 
vinced  myself  otherwise  and  uncovered 
the  automatic  line  feed  feature  of  the 
port  driver.  The  system  calls  necessary 
to  reset  this  appear  in  the  procedure 
AUTO—LF,  which  can  enable  or  dis¬ 
able  the  automatic  line  feed  on  either 
serial  port.  Unfortunately,  the  routine 
now  needs  to  know  which  port  the 
printer  is  connected  to.  I  was  tempted 
to  circumvent  this  need  by  temporarily 
resetting  both  port  drivers,  but  this 
would  be  unwise  on  a  multitasking  ma¬ 
chine  like  the  Lisa  because  some  back¬ 
ground  task  might  be  using  the  other 
port;  for  example,  a  modem  connected 
to  the  other  serial  port  might  be  in  use 
by  a  BBS  running  in  the  background. 

In  the  interest  of  simplicity,  this  ver¬ 
sion  of  IMAGE_PRINT  makes  no  at¬ 
tempt  at  treating  large  blank  spaces  in¬ 
telligently,  an  addition  that  can 
improve  speed  substantially.  As  pre¬ 
sented,  IMAGE_PR1NT  requires  188 
seconds  to  print  a  720  X  364  picture. 
Also  in  interest  of  simplicity,  the  width 
of  the  printed  image  is  rounded  up  to 


the  nearest  multiple  of  eight,  i.e.,  on  a 
byte  boundary.  For  those  desiring  sam¬ 
ple  QuickDraw  programs  and  a  ver¬ 
sion  of  IMAGE_PR!NT  that  does  not 
make  the  above  two  simplifications, 
mail  me  a  $25  check,  and  1  will  send 
you  a  3.5-inch  Lisa  format  disk  with 
two  versions  of  IMAGE_PR1NT,  other 
graphics  utilities,  and  complete  pro¬ 
grams  that  use  QuickDraw  and  the 
utilities.  Source  and  compiled  code  is 
included.  You  are  welcome  to  distrib¬ 
ute  the  code  for  noncommercial  pur¬ 
poses  as  long  as  you  do  not  remove  my 
copyright  notice.  Please  identify  any 
modifications  you  make,  and  I  encour¬ 
age  you  to  share  any  successes  you 
have.  We  are  the  neurons  of  our  cul¬ 
tural  brain. 


DDJ 


QuickDraw  Meets  ImageWriter  (Text  begins  on  page  26) 

Listing  One 

t 

t*R-> 

unit  GPHU;  (graphics  utilities  ) 

{  C0PYRI GUT  T. MAYER  1984  —  DISTRIBUTION  FOR  N0N-C0MERCIAL  PURPOSES  ALLOWED  > 

interface 
uses 
<$U-> 

<$U  QD/QuickDraw  }  QuickDraw, 

{♦U  QD/QDSupport  >  QDSupport, 

{$U  syscal 1 }  syscal 1 ; 

{«U+> 


•function  image_pr f n t ( tp :grafptr  ;pr i n terPor t : i n teger )  :  boolean; 
imp  1 emen  tat i on 
function  image_printj 

type  cars  =  packed  arrayCO . .max int]  of  char; 
var 

pr i n  ter :text;p:file; 

row_bytes ,by tes_to_pr int , h  e i gh  t , 1 i ne_n  umber , i ndex , i , buf f_top : i n  teger ; 
ch : ‘cars ; 

buff  s  packed  arrayCO  ..  10233  of  char; 

procedure  Au  toL i nef eedfwh i chPor t : i n teger  jenabl ed :bool  ean) ; 

{  whlchPort=0  ->  portA,  whichPort=l  ->  portB  ) 
var  Ecode : i n teger ; pa th : pathname ;Cparm:Dc type ; 
begin 

if  <whichport  mod  2=0)  then  path :='-RS232A'  else  path :=' -RS232B' ; 

CParm.dcVersion :=2;  CParm.dcCode :=1?{ 

if  enabled  then  CParm.dcDataCO] :=1  else  CParm.dcDataCO] :=0; 

DEVI CE_C0NTR0L< Ecode, pa th.Cparm) ; 

end ;  (Continued  on  next  page) 


31 


Dr.  Dobb  s  Journal,  January  1985 


QuickDraw  Meets  ImageWriter  (Listing  Continued,  text  begins  on  page  26) 
Listing  One 


procedure  no_good; 
begin 

im»ge_pr  i n t :=f a) se ; 
ex i t< image_pr i nt ) ; 

end; 

procedure  i  n  i  t _ p r inter ; 

war  i  :  integer ; 
beg  i  n 

(  -first  we  disable  auto  line  -feed  in  RS232  device  driver  } 

Au toL i neFeed<Pr i n terPor  t ,  f  al  se ) ; 

(  the  printer  file  is  to  be  accessed  via  blockwrite.  3 
reset(p,' -printer7); 

(  send  one  block,  mostly  nuls,  with  initialization  codes  :} 

(this  tells  printer  to  recognize  8th  bit  -  neccessahy  for  graphics  ) 
buff [0] :=chr<27) ; 
buffll) :=chr<90) ; 
buff [2] :=chr (0) ; 
buff C3] :=chr<50> : 

(make  sure  printer  adds  no  LF  after  CR  } 
buff C4] s=chr (27) ; 
buff C5] :=chr ( 90 ) ; 
buff I<S3 t=chr< 128) ; 
buff (73 :=chr <0) ; 

(  this  sets  horizontal  spacing  to  96  dots  per  Inch  3 
buff (83 :=chr(27) ; 
buff (93  tm'E' ; 

(  this  sets  vertical  line  feed  to  1/9  inch,  i.e.  height  of  8  dots  3 
buffi  103 :=chr<27) ; 
buff  1 1 1 3  :='T' ; 
buff 1123 !■' 1 ' ; 
buffi  133 :=,6' ; 

(this  sets  printer  head  motion  left  to  right  only,  yields  better  quality  output  3 
buff  1143 :=chr<27) ; 
buff  1153 :=')' ; 

for  ii=16  to  511  do  buf  f  I  i  3  i=*chr<0) ;  (most  of  block  is  NULs  3 

(  now  actually  send  the  initialization  block  3 

if  <bl ockwr i te(p ,buf f , 1 ) <31 )  then  no_good; 

end; 

procedure  init_buffer; 

var  i  !  i n  teger ; 
beg  i  n 

(  these  first  six  bytes  say  "here  comes  728  graphics  codes*  3 
buff  103 :=chr<27) ; 
buffi  1 3 :='G' ; 
buff  123 :='  0 ' ; 
buff  133 ; 
buff  143 :='2' ; 
buff  153 s='  8' ; 

for  i :=6  to  1023  do  buf f I i 3 :=chr (0) ;  (blank  if  not  otherwise  reset  3 
buf f 1 7343  :=chr( 1 3) ;buf f 1 7353  s=chr( 10) ;  (  CR  LF  ends  each  line) 

(Continued  on  page  34) 

32 

21 


buf f_top i=6 ; 

Dr.  Dobb’s  Journal,  January  1985 


QuickDraw  Meets  ImageWriter  (Listing  Continued,  text  begins  on  page  26) 

Listing  One 


end; 

procedure  enbuff  (a(char)  ; 
begin 

if  buf f_top»l 022  then  ex i t (enbuff ) ; 
buf  f  [buf  f_top)  :=a ;  buf  f  [  buff  _top  +  l  ]  :*a; 
buf f_top i=buf f_top+2; 

end; 

procedure  spew_buffer; 
const  nblocks=2; 
begin 

if  (bl  ockwr i te (p ,buff  ,nbl ocks) < )nbl ocks)  then  no_good; 
buf f_top :=6 ; 

•  nd; 

procedure  c 1 o»e_pr i nter ; 
war  pr i n ter ! tex t ; 
begin 

cl ose<p) ; 

rewr itefprinter, '-printer'); 
write(printer,chr<27)l'c')j 
write(printer,chr<12)); 
c 1 ose(pr inter); 

Au toL i neFeed<  Pr i n  terPor  t , true) ; 

end; 

procedure  buf f er_a_t i ne< 1 i ne_n umber (integer); 

var  i : i n teger ; 

begin 

i ndex :=row_by tes  -  line_number; 
for  i :=1  to  height  do  (buffer  a  line) 
beg  i  n 

enbuf f (ch* [ i ndex] ) ; 
i  ndex := i  ndex  +  row_by tes ; 
end; 

end ; 

procedure  si ze_of_image ; 
uar  width  :  integer ; 
begin 

wi th  tp' .portBi ts  do 
beg  i  n 

r  ow_by  tes:=rowbytes; 
height (“bounds .bot  tom-bounds . top  ; 
width  (=  bounds. right  -  bounds. left; 
ch (=3baseAddr  * ; 

end; 

by tes_to_pr i n t (=w i dth  div  8; 

if  width  mod  8  <>0  then  bytes_to _pr i n t (=by tes_to_pr i n t+ 1 ; 

end; 


(return  printer  to  default  set  up) 
{form  feed) 

{  enable  port  driver  autoLF  function  ) 


begin 

s i 7e_of_lmage ; 
i n i t_pr i nter ; 
i n i t_buf f er ; 

for  1 i ne_number (=1  to  (bytes_to_pr int)  do 
begin 

buf f er_a_l i ne ( 1 i ne_number) ; 
spew_buf f er ; 

end; 

cl ose_pr inter; 
image_pr i n  t :=true  ; 
end;  {  of  image_print  ) 
end.  {  of  implementation  ) 


End  Listing  One 


34 


Dr.  Dobb’s  Journal.  January  1985 


Listing  Two 


program  example;  <  this  sample  program  creates  ILLUSTRATION  1  } 

uses 

<*U  QD/QuickDraw  )  QuickDraw, 

<4U  QD/QDSupport  }  QDSupport, 

{*U  syscal 1 }  syscal 1 , 

<*U  gphu  )  gphu,  {  graphics  utilities  -  here  we  use  : 

i n i t_graph i cs 
bar_p 1 ot 
image_print  ) 

{$U  mathlib)  mathlib;  {  math  library  -  here  we  use  : 

square_wave 

famp  <  i.e.  Four i erAMPl i tudes  )  ) 

war 

wave  :  array!  1  ..  2561  oF  real; 
i , j : integer  ; 

begin 

i n i t_graph i c s ;  erase_screen | 

{  make  a  square  wave  and  graph  i t  :  > 
square_wave  Owave  ,256 ,16); 

bar_graph  <  256,  Swavetll,  10,40,  256,128,  0.0,  1.0,  true, False,  'wave  Form',"); 

{  Fourier  transForm  it  and  graph  that  :  } 

Famp ( 256 , Swave )  ; 

bar_graph  <  129,  Swave,  400,40,  200,128,  0.0,  1.0,  true , Fal se ,' Four i er  amplitude',"); 

{  print  the  whole  screen  > 

iF  not  image_print  <  thePort,  0  )  then  wr i te 1 n( ' turn  on  the  printer'); 

end.  End  Listings 


Dr.  Dobb's  Journal,  January  1985 


35 

23 


Archiving  Files  with  CP/M-80 
and  CP/M-86 


by  Ian  E.  Ashdown 


If  you  use  CP/M-80  versions  2.x  or  have  been  written  to  give  even  the  nov- 
CP/M-86,  you  know  the  problem  ice  C  programmer  a  clear  understand- 
well:  sitting  there  at  two  in  the  morn-  ing  of  what  is  going  on  each  step  of  the 
ing  trying  to  remember  which  files  you  way.  What  follows  is  a  general  descrip- 
worked  on  so  you  can  copy  them  to  a  tion  that  covers  the  salient  features  of 
backup  disk.  If  you  have  a  hard  disk  in  BU  from  a  user’s  viewpoint, 
your  system,  the  problem  can  be  an  The  CP/M-80  Version  2  Interface 
acute  pain.  Which  of  several  hundred  Guide's  description  of  BDOS  Service 

files  did  you  update  or  otherwise  modi-  30  (Set  File  Attributes)  states  that  the 

fy  during  your  marathon  program-  file  attribute  bit  t3-prime  is  “reserved 

ming  session?  for  future  system  expansion.”  Howev- 

The  ideal  solution  would  be  to  have  a  er,  if  you  use  a  disk  utility  to  set  13- 

utility  program  that  somehow  deter-  prime  to  true  in  the  file’s  directory  en- 

mines  which  files  have  been  changed  on  try,  you  will  find  that  the  BDOS  resets 
a  disk  and  automatically  copies  them  to  it  to  false  (zero)  whenever  the  directo- 
a  backup  disk.  This  procedure,  known  ry  entry  is  changed.  Since  this  means 
as  “incremental  backup,”  is  superior  by  that  the  file  has  been  opened,  written 

far  to  the  usual  methods  of  either  rely-  to,  and  closed  (or  else  renamed)  by  the 

ing  on  your  memory  (at  two  in  the  BDOS,  t3-prime  is  apparently  an  un¬ 
morning?)  or  copying  the  entire  disk.  documented  attribute  bit  that  indi- 

Although  Digital  Research’s  docu-  cates  when  a  file  has  been  updated, 
mentation  for  their  CP/M-80  and  CP/  This  behavior  is  not  an  unreliable  ar- 
M-86  operating  systems  gives  no  indi-  tifact  of  some  other  process;  DR  I  added 

cation  that  a  file  is  marked  in  any  man-  a  very  similar  feature,  called  the  “Ar- 

ner  when  it  is  written  to  or  renamed,  chive”  attribute,  to  its  multiuser  MP/M 
you  nevertheless  can  implement  an  in-  2  operating  system.  The  version  of  PIP- 
cremental  backup  utility  exactly  as  de-  .COM  supplied  with  MP/M  2  features 
scribed  above — the  ideal  solution.  BU  an  “A”  option,  which  causes  PIP  to 
is  one  example  of  such  an  implementa-  copy  only  those  files  that  have  their  Ar- 
tion.  chive  bits  set  false.  After  copying  each 


An  expedition  into  the  jungle  of  Undocumented 
Features  comes  home  with  an  orphan  attribute  bit. 


Theory  and  Practice  file,  PIP  sets  the  bit  true.  It  seems  logi- 

For  a  detailed  explanation  of  the  inner  cal,  then,  that  in  writing  CP/M-80  ver- 

workings  of  BU,  you  should  read  the  sions  2.x  and  CP/M-86,  DRI  intended 

comments  accompanying  the  source  to  rewrite  its  version  of  PIP  to  include 

code  in  the  Listing  (page  39).  These  an  “A”  option  but  for  whatever  reason 

_  never  got  around  to  doing  so.  This 

leaves  the  user  to  come  up  with  a  utility 
Ian  Ashdown,  by  Heart  Software,  2  -  that  takes  advantage  of  this  orphaned 
2016  West  First  Avenue,  Vancouver,  attribute. 

B.C.  V6J  IG8.  A  variety  of  such  utilities  are  avail- 


36 

24 


Dr.  Dobb’s  Journal,  January  1985 


able,  including  one  in  the  public  do¬ 
main  and  at  least  two  commercial 
products.  BU’s  advantage  is  that  it  is 
written  in  C,  thus  presenting  you  with 
the  opportunity  to  customize  it  to  your 
particular  needs.  The  source  code  has 
been  profusely  commented  for  precise¬ 
ly  this  reason. 

BU  accepts  command  lines  of  the 
following  form: 

BU  x[:afn]  y  [-AFHQSn] 
where  x  =  drive  name  of  disk  to  be 
backed  up, 

y  =  drive  name  of  backup  disk, 

and  the  optional  arguments  are: 

-A  All  files,  regardless  of  status 
-F  Fast  copy  (without  verification) 

-H  Hard  disk  (files  may  be  split) 

-Q  Query  each  file  before  backup 
-S  System  attribute  copied  to 
backup 

-n  Backup  USER  n  files  only 
(0-31) 

afn  Any  legal  CP/M  ambiguous 

fileref  (can  only  be  used  with  -n 
option) 

If  the  above  is  a  bit  confusing,  some 
examples  may  help  in  explaining  the 
various  options: 

BU  a  b  -  Copy  updated  files  in  all  user 
areas  from  drive  A:  to  drive  B:. 

BU  c  a  -f  -  Copy  updated  files  in  all 
user  areas  from  drive  C:  to  drive  A: 
without  verification  of  copied  files. 
BU  a:file.typ  m  -5  -  Copy  file  A:FILE- 
TYP  (user  area  5)  to  same  user 
area  on  drive  M:. 

BU  a:file*.t?  c  -Oq  -  Copy  any  files  in 
user  area  0  matching  ambiguous 
file  reference  A:FlLE*.t?  to  the 
same  user  area  on  drive  C:.  The 
operator  is  queried  before  each  file 
is  copied.  Answering  y  or  Y  for 
“Yes”  results  in  the  file  being  ar¬ 
chived;  anything  else  results  in  the 
file  being  skipped. 

BU  b  a  -ah  -  Copy  all  files  from  all 
user  areas  from  drive  B:  to  drive 
A:.  If  BU  runs  out  of  backup  disk 
space  while  copying  a  file,  the  file 
will  be  split  across  two  disks. 

BU  a  b  -a  -s  -  Copy  all  files  from  all 
user  areas  from  drive  A:  to  drive 
B:.  Those  files  with  the  System  at¬ 
tribute  set  are  copied  as  System 

Dr.  Dubb’s  Journal.  January  1985 


files  to  drive  B:.  (Note  that  the 
dash  options  can  be  separated.) 

A  fair  amount  of  the  code  involved  in 
BU  has  to  do  with  defensive  program¬ 
ming:  always  assume  that  the  user  will 
make  a  mistake.  The  command  line  is 
validated  as  thoroughly  as  possible. 
Any  errors  detected  are  displayed  with 
an  appropriate  message,  along  with  the 
previously  listed  explanation  of  what 
command  lines  are  valid. 

Once  in  operation,  assuming  no  op¬ 
tions  have  been  selected  or  ambiguous 
file  references  specified,  the  program 


will  scan  the  directory  of  the  disk  in 
drive  x,  note  which  files  have  been 
changed  since  the  last  time  BU  was  run 
on  that  disk,  then  copy  only  those  files 
to  the  disk  in  drive  y.  Existing  files  with 
the  same  fileref  and  user  number  on  the 
backup  disk  are  automatically  erased. 

Because  the  new  files  are  backup 
copies,  each  one  is  read  after  it  is  writ¬ 
ten  and  verified  character  by  character 
with  the  original  file.  All  available 
memory  is  used  to  buffer  the  data  for 
disk  read  and  write  so  that  BU  can  copy 
and  verify  as  quickly  as  possible.  Once 
the  new  file  has  been  fully  verified,  its 


38 

26 


file  attributes  are  set  to  “directory” 
(DIR)  and  “read-only”  (R/O)  to  en¬ 
sure  that  it  can  be  displayed  in  a  direc¬ 
tory  listing  of  the  backup  disk  and  that 
it  cannot  be  accidentally  erased. 

If  the  combined  size  of  the  files  to  be 
backed  up  exceeds  the  available  space 
on  the  backup  disk,  BU  will  take  one  of 
two  actions,  depending  on  whether  or 
not  the-H  option  has  been  selected.  In 
the  default  mode,  BU  will  stop  when  it 
runs  out  of  disk  space  and  erase  the 
current,  partially  written,  backup  file, 
It  will  then  ask  the  operator  to  insert  a 
fresh  disk  in  the  backup  drive.  When 
this  is  done,  BU  will  begin  to  copy  files 
to  the  new  disk. 

The  -H  option  is  intended  primarily 
for  use  with  hard  disks,  where  the  size 
of  the  files  may  exceed  the  capacity  of 
new  backup  disks.  When  BU  runs  out 
of  disk  space  with  this  option  active,  it 
will  close  the  current,  partially  written, 
backup  file,  set  its  attributes  to  DIR 
and  R/O,  then  ask  the  operator  to  in¬ 
sert  a  fresh  disk  in  the  backup  drive. 
When  this  is  done,  BU  will  open  a  se¬ 
quentially  numbered  fileref  (e.g., 
FILE.TYP  would  become  FILE-- 
01.TYP)  and  continue  writing  the  cur¬ 
rent  file  to  this  new  backup  file  from 
where  it  left  off.  The  file  in  effect  will 
be  split  across  two  or  more  backup 
disks,  with  no  wasted  disk  space. 

Reassembling  these  split  files  is 
quite  simple.  In  principle,  you  need 
only  open  the  first  file  for  write  access, 
use  lseek(  )  to  find  its  end,  open  the 
second  file  for  read  access,  then  ap¬ 
pend  it  to  the  first  file.  The  C  code  re¬ 
quired  to  do  this  is  left  to  the  reader  as 
an  exercise.  Alternatively,  you  can  al¬ 
ways  use  the  concatenation  feature  of 
DRI’s  P1P.COM  utility.  The  command 
line  would  be: 

PIP  rebuilt. fil  =  first. fil, second. fil 

The  disadvantage  of  this  approach  is 
that  all  three  files  must  be  on-line  at  the 
same  time,  which  in  a  two-drive  system 
means  shuffling  one  of  them  to  the  des¬ 
tination  disk  before  you  can  concate¬ 
nate  and  rebuild  your  original  file. 

What  BU  does  not  address  is  archi¬ 
val  file  maintenance  procedures.  If  BU 
is  used  with  a  dual  floppy  drive  system 
and  every  working  disk  has  its  own 
backup  copy,  there  is  no  problem.  At 
the  end  of  your  programming  session, 


simply  insert  the  backup  disk  in  the 
second  drive,  type  “BU  x  y,”  and  every 
changed  file  is  automatically  updated. 
The  backup  disk  becomes  a  duplicate 
of  the  working  disk  (although  you  do 
have  to  manually  erase  files  that  were 
deleted  from  the  working  disk). 

The  problem  arises  when  you  want 
to  maintain  backup  copies  of  hard  disk 
files.  Depending  on  whether  or  not  the 
-H  option  is  used,  files  will  be  on  dif¬ 
ferent  disks  and  possibly  split  across 
disks  after  you  run  BU.  When  the  files 
later  are  changed  again,  it  may  happen 
that  BU  will  archive  them  on  different 
disks  than  the  backup  copies.  BU  auto¬ 
matically  erases  existing  files  with  the 
same  fileref  and  user  number  on  the 
backup  disk  before  copying  a  file,  but 
it  can’t  do  that  when  the  file  is  on  an¬ 
other  disk.  This  leaves  the  responsibil¬ 
ity  of  deleting  (or  otherwise  archiving) 
obsolete  versions  of  files  to  the  user. 

It  is  a  very  simple  matter  to  extend 
BU  so  that  a  disk  identifier  file  is  added 
to  each  backup  disk.  This  would  be  es¬ 
pecially  useful  when  using  multiple 
floppies  to  archive  hard  disk  files;  the 
identifier  files  could  be  numbered  se¬ 
quentially.  However,  because  disk  nam¬ 
ing  and  storage  procedures  are  very 
much  a  matter  of  individual  taste,  this 
has  been  left  to  the  reader  to 
implement. 

Etymological  Nuisances 

BU  will  mishandle  random  access  files 
that  have  been  created  with  unwritten 
records  or  unallocated  blocks  or  ex¬ 
tents.  Because  it  uses  the  BDOS  se¬ 
quential  read  service  to  access  the  files, 
BU  will  stop  reading  random  access 
files  at  the  first  unallocated  block  or 
extent.  Happily,  very  few  programs  be¬ 
have  in  such  an  unfriendly  manner. 
(This  is  perhaps  because  most  file  copy 
utilities  will  balk  at  such  files  as  well.) 

Speaking  of  file  copy  utilities,  a  few 
are  available  that,  under  certain  cir¬ 
cumstances,  will  write  a  file  without 
resetting  its  Archive  attribute.  One  of 
these,  oddly  enough,  is  DRI’s  own  PIP- 
.COM.  If  a  file  on  a  disk  has  its  Archive 
and  Read-Only  attribute  bits  set  true, 
you  can  copy  another  file  with  the 
same  fileref  and  user  number  to  the 
disk  with  PIP  only  by  using  the  “W” 
option.  However,  the  BDOS  will  not  re¬ 
set  the  Archive  attribute  bit  after¬ 
wards,  so  BU  will  be  unable  to  recog- 

Dr.  Dobb’s  Journal,  January  1985 


nize  the  file  as  changed.  The  only 
solution  here  is  to  be  aware  of  the 
problem  and,  if  necessary,  to  perform  a 
manual  file  backup  immediately  after 
using  the  utility. 

BU  accepts  ambiguous  file  refer¬ 
ences  only  when  a  user  number  is  also 
specified.  In  one  sense,  this  is  an  aspect 
of  the  user  interface  design:  a  user  nor¬ 
mally  should  be  allowed  access  to  files 
in  one  user  area  only,  especially  when 
operations  using  ambiguous  filenames 
are  being  performed.  More  truthfully, 
BDOS  Services  17  (Search  for  First 
File)  and  18  (Search  for  Next  File) 
will  not  accept  file  references  for  all 
user  areas.  Either  they  search  for  all 
files  in  all  user  areas  (including  erased 
files),  or  they  search  a  particular  user 
area  only.  The  only  way  BU  could  find 
an  unambiguous  or  ambiguously  speci¬ 


fied  file  in  all  user  areas  would  be  to 
search  the  disk  directory  32  times! 

Suggested  Enhancements 

Those  of  you  with  the  ambition  and  the 
time  can  extend  BU  to  become  a  com¬ 
plete  file  archiving  utility.  If  your  sys¬ 
tem  has  a  hard  disk,  BU  could  main¬ 
tain  a  catalog  file  that  records  each 
fileref  and  version  number  (without 
erasing  previous  versions),  any  perti¬ 
nent  file  statistics  such  as  size  in  kilo¬ 
bytes  and  assigned  file  attributes,  the 
backup  disk  identification  number,  the 
time  and  date  the  backup  copy  was 
made,  and  the  operator  who  made  the 
backup.  Even  if  you  don’t  own  a  hard 
disk,  you  could  maintain  a  similar  sort 
of  file  on  each  backup  disk  for  record¬ 
keeping  purposes.  As  a  starting  point 
for  such  a  program,  you  might  consid¬ 


er  the  "Archive”  program  that  is  de¬ 
veloped  in  Kernighan  &  Plauger’s 
book  Software  Tools  (Addison- Wes¬ 
ley,  ISBN  1-201-03669-X).  The  pro¬ 
gram  is  presented  in  source  code  using 
RATFOR,  which  for  all  practical  pur¬ 
poses  is  a  variation  on  a  theme  of  the  C 
programming  language. 

So,  there  you  have  it:  a  fully  func¬ 
tional  incremental  backup  utility  for 
CP/M-80  and  CP/M-86.  Why  DR1  did 
not  include  an  Archive  option  with  PIP 
for  these  operating  systems  after  going 
to  the  trouble  of  designing  all  the  nec¬ 
essary  features  into  them  is  a  matter 
for  future  historians  of  computer  soft¬ 
ware  to  ponder.  In  the  meantime,  I 
hope  you  enjoy  using  BU.  ddj 

Reader  Ballot 

Vote  for  your  favorite  feature/article. 

Circle  Reader  Service  No.  1 93. 


Archiving  Files  Listing  (Text  begins  on  page  36) 


/*  BU.C  -  A  File  Backup  Utility  for  CF’/M-QO  8*.  CP/M-86 
* 


* 

# 

* 

* 

* 

* 

* 

* 

# 

* 

* 

* 

* 

* 

* 

* 

* 

* 

* 

* 

■* 

* 

* 

* 

* 

* 

* 

* 

* 

* 

* 

* 

* 

* 

* 


Copyright:  Ian  Ashdown 

byHeart  Software 
2  -  2016  West  First  Avenue 
Vancouver;  B.C.  V6J  1G8 
Canada 

Acknowledgment:  DeSrnet  C  code  and  suggestions  for  program 
improvement  courtesy  of  Dr*  Babb's  Journal 
Contributing  Editor  Anthony  Skjellum 

Version:  1*1  Written  for  Aztec  Cl  I  vl*06b  < CP/M-80) 

and  DeSrnet  C8Q  v2.2  (CP/M-86) 

Date:  December  31st;  1983  (Version  1.0) 

September  7th;  1984  (Version  1*1) 

BU  utilizes  the  undocumented  "archive"  file  attribute  feature 
of  CP/M-QO  Versions  2.x  and  CP/M-86  to  automatically  detect 
files  that  have  been  changed  since  the  disk  was  last  "backed 
up"  and  copy  them  (with  verification)  to  a  backup  disk.  This 
program  performs  the  same  action  as  the  "A"  option  of  PIP 
under  Digital  Research' s  MP/M  2;  for  which  the  Archive 
attribute  is  documented. 

Usage:  BU  xC:afn3  y  C-AFHQSnl 

where  x  =  drive  name  of  disk  to  be  backed  up 
y  =  drive  name  of  backup  disk 

and  the  optional  arguments  are: 

-A  All  files;  regardless  of  status 

-F  Fast  copy  (without  ver i f ica t ion ) 

-H  Hard  disk  (files  may  be  split) 

-Q  Query  each  file  before  backup 

-S  System  attribute  copied  to  backup 


(Continued  on  next  page) 


Dr.  Dobb's  Journal,  January  1985 


39 

27 


Archiving  Files  Listing  (Listing  Continued,  text  begins  on  page  36) 


*  -n  Backup  USER  ' n'  files  only  <0-31) 

*  afn  Any  legal  CP/M  ambiguous  fileref 

*  (can  only  be  used  with  -n  option) 

*/ 

“include  "stdio.h" 

“include  "ctype.h"  /■*  Contains  macro  for  "isdigitO"  */ 

/***  DEFINITIONS  ***/ 

“define  AZTEC  1  /*  Aztec  Cl  I  vl.06b  (CP/M-80)  */ 

»de fine  DESMET  0  /*  DeSmet  C88  v2.2  (pP/M-86)  */ 

“if  DESMET 

“define  movmem(src ;dest > I en>  _mov( len(src ,dest ) 

“endi f 

“define  ERROR  -1 

“define  DEL  -1  /*  Deleted  fileref  flag  */ 

“define  ALL  -1  /*  All  user  numbers  flag  */ 

“define  TRUE  -1 

“define  FALSE  0 

“define  SUCCESS  0 

“define  0_RD0NLV  0 

“define  USER-ERR  0  /*  Specified  fileref  must  only  be  used 

with  -number  command- line  option  */ 

“define  BAD-FREF  1  /*  Illegal  file  reference  */ 

“define  BAD_ARGS  2  /*  Illegal  command  line  */ 

“define  BAD-OPT  3  /*  Illegal  option  */ 

“define  BAD-USER  4  /*  Illegal  user  number  */ 

“define  BAD-DRV  5  /*  Illegal  drive  names  */ 

“define  SAME-DRV  6  /*  Same  drive  name  for  output  as  input  */ 

“define  OPN-ERR  8  /*  File  open  error  */ 

“define  READ-ERR  9  /*  File  read  error  */ 

“define  CLS-ERR  10  /*  File  close  error  */ 

“define  BAD-VFY  11  /*  File  verify  error  */ 

“define  DIR-IO  6  /*  BDOS  Direct  I/O  service  */ 

“define  RESET-DRV  13  /*  BDOS  Reset  All  Drives  service  */ 

“define  SEL-DRV  14  /*  BDOS  Select  Drive  service  */ 

“define  SRCH..F  17  /*  BDOS  Search  Next  service  */ 

“define  SRCH-N  18  /*  BDOS  Search  Next  service  */ 

“define  GET-DRV  25  /*  BDOS  Get  Default  Drive  service  */ 

“define  SET-DMA  26  /*  BDOS  Set  DMA  Address  service  */ 

“define  SET-ATT  30  /*  BDOS  Set  File  Attributes  service  */ 

“define  USER-CODE  32  /*  BDOS  Get/Set  User  Code  service  */ 

“define  MAX-USER  32  /*  32  user  codes  under  CP/M  (see  DR's 

documentation  for  BDOS  Service  32)  */ 

/***  GLOBAL  VARIABLES  ***/ 

char  ent.drv,  /*  Entry  drive  code  */ 
ent.user,  /*  Entry  user  code  */ 
cur.user;  /*  Current  user  code  */ 

/***  MAIN  BODY  OF  CODE  ***/ 

rna i n ( argc t argv ) 
int  argc$ 
char  *argvC3; 

< 

char  in_dsK>  /*  Drive  name  of  input  disk  */ 

out-dsk,  /*  Drive  name  of  output  (backup)  disk  */ 

in_drv>  /*  Drive  code  of  input  disk  */ 

out-drv;  /*  Drive  code  of  output  disk  */ 

seg_no/  /*  Segment  number  for  split  files  */ 

in_fileC153/  /*  Fileref  of  current  input  file  */ 

out_f  i  1  eC  153  ,  /*  Fileref  of  current  output  file  */  (Continued  on  page  42) 


40 


Dr.  Dobb's  Journal.  January  1985 


Archiving  Files  Listing  (Listing  Continued,  text  begins  on  page  36) 


c  ,  /*  Scratch  variable  */ 

*5/  /*  Scratch  string  pointer  */ 

*buffer,  /*  Pointer  to  directory  entry  returned  */ 

/*  by  "srch_f  i  le(  )  "  */ 

»srch_ filet);  ' 

*ma  I  I  oc  (  ) ; 

/*  File  control  blocks  of  current  input  and  output  files  */ 


static  char  in_fcbC333, 
out_fcb[33D ; 

/*  Structure  for  linked  list 

s  t  rue  t  f i 1 e_ref 

< 

char  nameC12D; 
struct  file_ref  *next; 
j  root  , 

*f ref _1 , 

*  f re  f _2; 


/*  (Automatically  initialized  */ 
/*  to  zero  by  compiler)  */ 


/*  File  reference  */ 

/*  Pointer  to  next  instance  */ 
/*  Start  of  linked  list  */ 

/*  Scratch  pointers  to  */ 

/*  1  inked  I ist  instances  */ 


of  f i 1  ere f s  */ 


/*  Initialized  file  control  block  for  "srch_ f i 1 e ( ) " .  This  FCB 
is  for  a  fully  ambiguous  fileref  that  causes  "srch_f i I e ( ) " 
to  return  all  directory  entries  for  the  current  default 
drive.  */ 


static  char  fcbC3 


{/  7  9  /  /  o  /  /  9  /  '9.  .9.  '9'  '9' 

'  ,0,0 ;0>? 


int  file_cnt  =  0,  /* 

dup_flag,  /* 

a  I  I _  f i 1 es ,  /* 

fast_copy,  /* 

hard_d i sk  ,  /* 

query,  /* 

system,  /* 

user.no ,  /* 

next-flag  =  FALSE?/* 


Count  of  file  to  be  backed  up  */ 
Duplicate  fileref  flag  */ 

All-files  flag  (cmd-line  option)  */ 
Fast  copy  flag  (cmd-line  option)  */ 
Hard  disk  flag  (cmd-line  option)  */ 
Query  flag  (cmd-line  option)  */ 
System  flag  (cmd-line  option)  */ 
User  number  (cmd-line  option)  */ 
Flag  to  indicate  to  "srch_f i I e ( ) " 
that  a  "search  next"  is  required  */ 


register  int  i , j; 


/*  Loop  indices  */ 


lonq  begin, 
end  5 


/*  Input  file  position  variables  */ 


/*  Display  program  header  */ 
print f("\nBU  Version  1.1"); 

printf<"  Copyright  1983,  1984"); 

print f("  byHeart  Sof tware\n\n" ) ; 


/*  Initialize  command- line  options  */ 


all-files  =  FALSE;  /* 

fast_copy  =  FALSE;  /* 

hard-disk  =  FALSE;  /* 

query  =  FALSE;  /* 

system  =  FALSE;  /* 

user— no  =  ALL;  /* 


/*  Parse  command  1 ine  for 
i f ( ar gc  <  3 ) 


Copy  only  non-archived  files  */ 

Copy  files  with  verification  */ 

Files  will  not  be  split  across  backup 
disks  if  remaining  capacity  of  backup 
disk  is  less  than  current  file  size  */ 
Backup  without  query  */ 

Assiqn  directory  attribute  to  all 
backup  files  */ 

Backup  files  in  all  user  areas  */ 
user-selected  options  (if  any)  */ 


(Continued  on  page  44) 


Dr.  Dobb’s  Journal.  January  1985 


42 

29 


Archiving  Files  Listing  (Listing  Continued,  text  begins  on  page  36) 


error ( BAD.ARGS /NULL ) ;  /*  Illegal  command  line  */ 

if(argc  >  3) 

{ 

i  =  3;  /*  Start  with  third  command-line  argument  */ 

whileti  <  argc) 

< 

i f  <*argv[ i 3  ! =  '  -  '  > 

error(BAD_OPT/argv£ i  ]  ) ?  /*  Missing  leading  '-'  #/ 

s  =  argvC i 3+1? 
whi le(#s) 

{ 

if (*s  ==  'A')  /*  ChecK  for  all-files  option  */ 

all-files  =  TRUE? 

else  if(*s  ==  'F')  /*  Check  for  fast  copy  option  */ 

fast_copy  =  TRUE? 

else  if(*s  ==  'H'>  /*  Check  for  hard  disk  option  */ 

hard_disk  =  TRUE? 

else  if(*s  ==  '  Q'  )  /*  Check  for  query  option  */ 

query  =  TRUE? 

else  if(*s  ==  'S')  /*  Check  for  system  option  */ 

system  =  TRUE? 

else  i f ( isdigi t <*s> )  /*  Check  for  user  number  option  */ 

i 

user.no  =  *s++  -  'O'? 
i f ( isdi gi t ( *s ) ) 

user.no  =  user.no  *  10  +  *s++  -  'O'? 
if (user.no  <  0  I!  user.no  >  31) 
error ( BAD.USER >argv[ i 3  >  ? 
cont inue? 

> 

e  1  se 

error ( BAB.OPT , argvC i 3 ) ? 
s++? 

•v 

Jt 

i++? 

> 

> 

/*  Validate  input  parameters  •*/ 

i f < * (argvtl 3+1 )  !=  ' \0' )  /*  Check  for  specified  fileref  */ 

{ 

if (user.no  ==  ALL)  /*  Can  only  use  with  specified  */ 

error (USER.ERR; NULL) ?  /*  user  number  <-n  option)  */ 

/*  Modify  "fcbCl"  to  incorporate  fileref  */ 

i f ( copy.f re f ( f cb , argvC 1 3 )  ==  ERROR) 
error ( BAD.FREF , argvC 13)? 

} 

if(*argv£13  <  'A'  II  *argvC13  >  'P'  I! 

*argvC23  <  'A'  II  *argvC23  >  'P'> 
error ( BAD.DRV ;NULL ) ?  /*  Illegal  drive  names  */ 

if(*argvC13  ==  *argvC23) 

error (SAME.BRV ,NULL) ?  /*  Drive  names  are  same  */ 

/*  Save  entry  drive  and  user  codes  */ 

ent.drv  =  bdos < CET.DRV ) ? 
ent.user  =  bdos ( USER-CODE /Oxf f ) ? 

/*  Calculate  input  and  output  drive  codes  */ 

in.drv  =  (in.dsk  =  *argvC13)  -  'A'  +  1? 
out.drv  =  (out.dsk  =  *argvC23)  -  'A'  +  1? 

/*  Set  default  drive  to  input  drive  */ 


44 

30 


Dr.  Dobb's  Journal,  January  1985 


bdos  <  SEL..DRV ,  in_drv- 1 ) ; 

/*  Set  user  code  to  "user.no"  if  -n  option  specified  */ 

if(user_no  !=  ALL) 

bdos ( USER-CODE  >user_no) ; 

/*  Read  first  12  bytes  of  updated  active  directory  entries  int 
linked  list  of  filerefs.  If  first  byte  of  entry  is  0xe5 , 
then  file  has  been  erased.  */ 

root. next  =  NULL;  /*  Initialize  linked  list  root  */ 

fref..l  =  Ikrootj  /*  Initialize  linked  list  pointer  */ 

whiletbuffer  =  srch_f i le( fcb;next_f lag) ) 

{ 

/*  Bit  7  of  third  filetype  byte  (t3')  in  directory  entry 
is  the  Archive  attribute  indicator.  The  BDOS  sets  this 
bit  to  zero  whenever  it  updates  a  directory  entry.  */ 

if  (buffer  COI  !=  0xe5  ?<8< 

(bufferCO]  ==  user.no  I!  user.no  ==  ALL)  <*-.8.. 

( ! ( buf f erCll 3  &  0x80)  i!  all -files)) 

r 

V 

fref_l->next  =  /*  Allocate  space  for  fileref  instance  */ 

(struct  file_ref  *  )  rna  1  1  oc  (  si  zeof  ( s  t  rue  t  file_ref)); 
fref_l  =  fref_l- >next ;  /*  Assign  space  to  instance  */ 

movmem  (  bu  f  f  er  >  f  ref  _1  -  >  name  >  12 ) ;  /*  Move  fileref  to  */ 

/*  I  inked  I ist  instance  */ 

fref_l->next  =  NULL;  /*  Indicate  current  end  of  list  */ 

> 

next-flag  =  TRUE;  /*  Only  first  call  to  " srch_f i 1 e ( ) "  */ 

/*  should  be  made  with  "next-flag"  */ 

>  /*  set  to  FALSE  */ 

/*  If  no  files  have  been  backed  up  ...  */ 

i f ( ( roo t . nex t )  /*  Null  "root. next"  indicates  no  files  have  */ 

C  /*  been  changed  */ 

print f( "NO  FILES  HAVE  BEEN  UPDATED"); 
i f (user-no  !=  ALL) 

print f("  in  user  area  Xd\n",user_no); 
e  1  se 

put char ( ' \n' ) ; 

resetO;  /*  Reset  user  and  drive  codes  to  entry  values  */ 
exi t (0) ; 

J 

/*  There  may  be  duplicate  filerefs  in  linked  list  due  to  some 
files  occupying  more  than  one  extent  on  the  disk.  These 
duplicates  must  be  marked  as  "deleted"  i^n  the  list. 
(Duplicate  filerefs  with  different  user  codes  are  valid.)  #/ 

/*  For  all  filerefs  ...  */ 

fref_l  =  8-.root ;  /*  Initialize  a  linked  list  pointer  */ 

wh i )e( fref_l->next ) 

C 

fref_ 1  =  fref_l- >next ;  /*  Root  instance  is  NULL  entry  */ 

dup_flag  =  FALSE;  /*  Reset  duplicate  fileref  flag  */ 

/*  For  all  preceding  filerefs  ...  */ 

fref_2  =  kroot;  /*  Initialize  another  linked  list  pointer  */ 
fref_2  =  f ref_2- >next ;  /*  Skip  root  instance  */ 

whi 1 e( fref_2- >next  !=  f ref _1- >next ) 

{ 

/*  Compare  filerefs  (ignore  deleted  filerefs  and  different 
user  codes)  */ 


( Continued  on  next  page) 


Dr.  Dobb's  Journal,  January  1985 


45 

31 


Archiving  Files  Listing  (Listing  Continued,  text  begins  on  page  36) 


i f < f ref_2- >nameC03  !=  DEL  &&  fref_i- > name [03  ==  f re f _2- >name£03 > 
i  f  <  !  strncmp  (  f  ref  _1-  >narne+l ,  fref  _2-  >name+l >  11 )  ) 

{ 

dup_flag  =  TRUE?  /*  Indicate  duplicate  fileref  */ 

break ; 

3 

fref_2  =  fref_2- >next ; 

> 

i f ( dup_  f I ag  ==  TRUE) 

f ref _1- >name£03  =  DEL;  /*  Delete  if  duplicate  fileref  */ 
e  I  se 

f  i  I  e_.cn  t ++ ;  /*  Increment  file  count  */ 


/*  Display  file  copy  header  */ 

print f ( "Number  of  files  to  be  copied:  %d\n\n" , f i 1 e_cnt ) ; 
print f ( "User:  Files  being  copied  to  Drive  Xc:\n\n"; 

out_dsk ) ; 

/*  Initialize  current  input  and  output  fileref  templates  */ 

in_fileC03  =  in_dsk; 
out_file£03  =  out-dsk; 
in_fileC13  =  out_fileC13  = 
in_file£103  =  out_fileClC»]  = 
in_fi)eC143  =  out_fi)e[143  =  ' \0' ; 

/*  Initialize  current  input  and  output  FCB  templates  */ 

in_fcbC03  =  in_drv; 
out_fcbC03  =  out_drv; 

/*  For  all  validated  filerefs  do  ...  */ 

for (cur.user  =  0;  cur_user  <  MAX-USER;  cur_user++) 

{ 

if (user.no  !=  ALL) 

if (cur.user  !=  user.no) 
cont inue; 

bdos ( USER-CODE ,cur_user ) ;  /*  Set  user  code  to  "cur.user"  */ 

fref-l  =  8<root;  /*  Initialize  linked  list  pointer  */ 

whi I e ( f ref_l- >next  > 

fref_l  =  f ref _i- >next ;  /*  Root  instance  is  NULL  entry  */ 

i  f  (  fref_l- >nameC03  ==  cur.user) 

{ 

/*  Update  the  current  input  and  output  FCB's  */ 

movmeml fref _1- >name+l > i n_ f cb+1 > 1 1 ) ; 
movmem ( f ref _1- >name+l ,out_fcb+l,ll); 

/*  Reset  the  Read-Only  and  System  attribute  bits  of  the 
FCB's  so  that  the  file  can  be  copied  and  displayed 
(unless  the  "system"  flag  is  TRUE)  */ 

out_fcbC93  &=  Ox7f;  /*  Read-Only  attribute  */ 

i f ( ! system ) 

out_fcbC103  8.=  Ox7f;  /*  System  attribute  */ 

/*  Set  the  Archive  attribute  bit  of  the  FCB's  to  indicate 
that  the  file  has  been  backed  up  */ 

in_fcbC113  1=  0x80; 
out_fcbC113  !=  0x80; 

(Continued  on  page  48) 


46 


Dr.  Dobb  s  Journal,  January  1985 


Archiving  Files  Listing  (Listing  Continued,  text  begins  on  page  36) 


/*  Move  the  fileref  from  the  FCB's  to  the  initialized 
input  and  output  fileref  templates  */ 

movmem( in_fcb+l > in_f i I e+2,8) ;  /*  Filename  move  */ 

movmem  ( ou  t  _f  cb+1  ,ou  t  _f  i  1  e+2  >8 ) ; 

movmemt in_fcb+9/in_f i le+11/3) i  /*  Filetype  move  */ 
movmem(out_fcb+9,out_f i le+11 >3) ; 

/*  Strip  high  order  bits  off  filerefs  to  form  proper 
ASCII  characters  */ 

for ( j  =2;  j  <=  13}  j++) 
t 

in_filetj3  &=  0x7f; 
out_filetj3  &  =  0x7f} 


/*  Display  the  filerefs  #/ 

print  ft"  X2d  its  -->  %s"  ,cur_us#r  ,  in_  f  i  I  e  ,ou  t  _f  i  I  e  > } 

/*  Query  operator  far  backup  if  indicated  by  "query" 
flag  */ 

i f ( query ) 

< 

printft"  O.K.  to  backup?  ")} 
i  f  (  ( c  =  in.chrO)  ==  'y'  il  c  ==  'Y') 
puts ( "Yes" ) } 
e  1  se 
( 

puts ( "No" ) } 

continue;  /*  Go  do  next  fileref  if  "No"  */ 

} 

> 

e  1  se 

pu  tchar ( ' \n' ) ; 

/*  Copy  file  from  the  input  disk  to  the  output  disk  */ 

i f (hard_disk )  /*  Split  file  across  backup  disks  i f  */ 

{  /*  necessary  #/ 

beqin  3  OL;  /*  Initialize  file  position  pointer  */ 
seg_no  3  0;  /*  and  split  file  segment  number  */ 

do 
i 

/*  Reset  the  Read-Only  attribute  of  the  output  file 
(if  it  exists)  so  that  the  input  file  can  be 
copied  to  it  */ 

bdos(SET_ATT  ^out-fcb) ; 

end  ■  copy_f i 1 e ( i n_f i I e ,ou t _f i 1 e , begi n )  ; 
i f (! fast_copy)  /*  Verify  file  unless  -F  selected  */ 
ver i fy_fi te( in_fi le,out_fi le^begin); 

/*  Set  the  Read-Only  attribute  of  the  output  file  */ 

out_fcbC93  ! =  0x80; 
bdos(SET_ATT,out_fcb) ; 

out_fcbC93  8<=  0x7f;  /*  «<•  and  reset  the  fcb  */ 

i f ( end  ! =  NULL ) 

< 

/*  File  has  been  partially  written  on  current 

backup  disk  -  new  disk  required  to  continue  */ 


Dr.  Dobb's  Journal,  January  1985 


48 

33 


new_di.sk  (out_f  i  I  e /hard -disk  ) ; 

/♦  Append  segment  number  to  filename  of  output 
fileref  (e.q.  -  BsFILE.TYP  will  become 
B:FILE--01. TYP)  */ 

seg_no++ ; 

for(j  =  2;  j  <=  7;  j++)  /*  Change  spaces  to  */ 

i f ( out_f i 1 et j3  a=  '  '  )  /*  ' -'  character  */ 
out-fileCjD  =  '  - '  ; 

out_fileC83  =  seg_no/10  +  'O';  /*  Append  segment  */ 
out_fileC9]  =  seg_no%10  +  'O';  /*  number  */ 

/♦  Display  filerefs  again  */ 

printft"  %2d  %s  -->  %s\n", 

cur _user  , i n_  file,out_file); 

> 

begin  =  end; 

> 

whi 1 e ( end  !=  NULL);  /*  Loop  until  file  is  written  */ 

> 

e  I  se 
{ 

/*  Reset  the  Read-Only  attribute  of  the  output  file 
(if  it  exists)  */ 

bdos ( SET- ATT ,ou t_f cb  )  ; 

i f ( copy- f i  1  e ( i n_ f i 1 e , out _ f i 1 e ,0L )  ) =  NULL) 

C 

/*  Disk  was  full  -  erase  partially  written  file,  back 
up  fileref  pointer  and  rewrite  file  to  new  disk  */ 

uni  ink ( ou t _f i I e ) ; 

new_disk ( ou t _f i 1 e ,hard_d i sk ) ; 

i--? 

cont inue; 

J 

i f (! fast_copy )  /*  Verify  file  unless  -F  selected  */ 

veri f y_f i I e ( in_f i 1 e ,ou t _f i I e ,0L> ; 

/*  Set  the  Read-Only  attribute  of  the  output  file  */ 

out_fcbC9I  1=  0x80; 
bdos ( SET- ATT , out_fcb); 

> 

/♦Set  the  Archive  attribute  of  the  input  file  to 

indicate  that  the  file  was  successfully  backed  up  •»/ 

bdos ( SET- ATT , i n_  f cb ) ; 

> 

> 

reset ();  /*  Reset  user  and  drive  codes  to  entry  values  */ 

> 

/***  FUNCTIONS  ♦**/ 

/*  Search  for  first  or  next  directory  entry  */ 
char  *srch_ f i I e ( fcb_ptr ,nex t_f lag) 

char  *fcb_ptr;  /*  Pointer  to  file  control  block  */ 

int  next-flag;  /*  Flag  to  indicate  "search  next”  */ 

{ 

static  char  sf_curC323,  /*  Current  directory  entry  buffer  */ 
sf_fcbC363;  /*  File  control  block  buffer  */ 


49 

34 


(Continued  on  next  page) 
Dr.  Dobb's  Journal.  January  1985 


Archiving  Files  Listing  (Listing  Continued,  text  begins  on  page  36) 


int  index ,  /*  Index  of  directory  entry  in  DMA  buffer  */ 

*ptr;  /*  Pointer  to  directory  entry  in  DMA  buffer  */ 

bdos  (  SET-OMA  >0x80 ) ;  /*  Set  DMA  address  to  8C>h  */ 

i f (  ! next  _  flag) 

{ 

movmem  (  f  cb_p  t  r  ,s  f  _f  cb ,  16 ) ;  /*  Initialize  FCB  buffer  */ 

if ((index  =  bdos(SRCH_F,sf_fcb> )  ==  Oxff)  /*  Find  first  */ 
return  NULL;  /*  Return  NULL  if  unsuccessful  */ 

e  1  se 

if ((index  =  bdos ( SRCH_N,NULL ) )  ==  Oxff)  /*  Find  next  */ 
return  NULL;  /*  Return  NULL  if  unsuccessful  */ 


/*  EiDOS  services  17  and  18  leave  four  consecutive  directory 

entries  of  32  bytes  each  in  the  128-byte  DMA  buffer  and  also 
returns  an  index  value  of  0;  1;  2  or  3  to  indicate  the 
correct  directory  entry  in  the  accumu)  a  tor.  The  "bdosO" 
function  returns  this  index  value.  */ 


ptr  =  0x80  +  index  *  32;  /*  Calculate  pointer  to  directory 

entry  */ 

movmem ( pt r , s f_cur ,32 ) ;  /*  Move  directory  entry  to  current 

directory  entry  buffer  */ 

return  sf_cur; 


/*  Copy  file  starting  at  "offset"  from  beginning  */ 


copy_f i le(in_ file, out_file, off set) 


char  *in_f i 1 e, 
*out_f i I e; 
1 ong  offset; 


/*  Input  fileref  */ 

/*  Output  fileref  */ 

/*  Input  file  position  offset  */ 


register  int  in_cnt> 
out.cnt; 

int  fd...  in> 
fd_out  t 

ful 1 _d i sk  =  FALSE; 


/*  Character  counts  for  unbuffered 

/*  Input  file  descriptor  */ 

/*  Output  file  descriptor  */ 

/*  Full  disk  flag  */ 


1/0  */ 


char  *buffer,  /*  Input  file  buffer  */ 

*buff_ptr>  /*  Pointer  to  current  position  in  "bufferCJ"  */ 
*ma 1 1 oc ( ) ; 


unsigned  buf_size  =  32768;  /*  Initial  memory  allocation  size  */ 

/*  "read!  )"  accepts  a  maximum  of  32768  bytes  at  a  time.  Allocate 
as  much  memory  as  possible  up  to  this  limit  for  the  input 
buffer,  using  128  byte  decrements.  */ 


do 

i  f  (  bu  f  fer  =  rna  I  I  oc  (  bu  f _s  i  ze  )  ) 
break ; 

whi 1 e ( buf_size  -=  128); 

/*  Open  input  file  for  unbuffered  Read-Only  access  *•/ 

if((fd_in  =  open( in_f i 1 e,0_RD0NLY ) )  ==  ERROR) 
error  ( 0F'N_ERR  ,  i  n_  f  i  1  e )  ; 

/*  Create  the  output  file  by  first  deleting  it  (if  it 
exists),  then  opening  it  for  unbuffered  Write-Only 
access.  */ 


( Continued  on  page  52) 


Dr.  Dobb’s  Journal,  January  1985 


50 

35 


Archiving  Files  Listing  (Listing  Continued,  text  begins  on  page  36) 


i  f  (  (  f  d_ou t  =  creat  (out_.fi  le, NULL)  )  ==  ERROR) 
error ( OPN_ERR ;Out_file); 

/*  Initialize  input  file  position  pointer  to  "offset"  */ 
1  seek (fd_in;offset;0); 

/*  Copy  input  file  to  output  file  by  bufferinq  data 
through  "bufferCD"  */ 


do 

< 

if((in_cnt  =  read < f d_i n ; bu f f er ;buf _s ize ) )  ==  ERROR) 
error ( READ-ERR  ;in_file); 

buff_ptr  =  buffer;  /*  Initialize  "bufferCD"  pointer  */ 
out.cnt  =  0;  /*  and  "out_cnt"  */  y 

do 
{ 

/*  Write  contents  of  "bufferC3"  to  output  file  in  128 
byte  records  until  either  the  buffer  is  written  or  a 
write  error  occurs.  Since  the  Oxla  (AZ)  character  CP/M 
uses  as  an  EOF  marker  is  a  valid  file  character  for  non- 
ASCII  files;  "read<>"  always  reads  the  last  128  byte 
record  of  a  file  under  CF'/M.  */ 


i f ( wr i te ( f d_ou t ;buf f _p t r ; 128 )  !=  128) 

{ 

/*  The  standard  implementation  of  "writeO"  does  not 
distinguish  between  a  full  disk  or  directory  and  a 
write  error  in  its  returned  error  code.  Thus;  it  is 
assumed  that  an  error  means  a  ful I  disk/directory.  */ 


fu) I -disk  =  TRUE; 
break ; 

> 

buff_ptr  +=  128;  /*  Increment  "bufferLI"  ptr  */ 

out.cnt  +=  128;  /*  Update  count  of  chars  written  */ 

> 

while(in_cnt  >  out_cnt);  /*  Until  end  of  "bufferC]"  */ 
offset  +=  out.cnt;  /*  Update  input  file  position  pointer 
if ( ful 1 -disk  ==  TRUE) 
break ; 


while(in_cnt  ==  buf_size>;  /*  Until  end 
free (buffer) ;  /*  Deallocate  buffer 

i f (c I ose( fd_in)  ==  ERROR)  /*  Close  the 

error ( CLS-ERR , i n_  f i 1 e ) ; 
if (close(fd-out)  ==  ERROR) 
error (CLS-ERR ;OUt _f i 1 e  > ; 


of  file  */ 
space  */ 
files  */ 


*/ 


/*  If  full  disk  return  new  offset  for  input  file;  else  */ 

/*  return  NULL  to  indicate  completion  of  file  copy  operation  */ 


return  (full -disk  ?  offset  s  NULL); 

> 


/*  Compare  portion  of  input  file  starting  at  "offset"  from  begin¬ 
ning  of  file  with  output  file  */ 


veri fy_f i let in_fi )e;Out_fi I e ; o  f  f  se  t ) 

•har  *in_file;  /*  Input  fileref  */ 

*out_file;  /*  Output  fileref  */ 

long  offset;  /*  Input  file  position 


offset  */ 


register  int  match..cnt; 


/*  Scratch  variable  */ 


52 

36 


Dr.  Dobb’s  Journal,  January  1985 


int  out..cnt,  /♦  Character  counts  for  unbuffered  I/O  ♦  / 
fd_in,  /♦  Input  file  descriptor  ♦  / 
fd._out;  /♦  Output  file  descriptor  ♦  / 

char  ♦buffer,  /♦  Dynami ca I  1 y-a 1  I  oca  ted  buffer  ♦  / 

♦in_ptr,  /♦  Input  file  buffer  pointer  */ 

♦out_ptr,  /♦  Output  file  buffer  pointer  */ 

♦ma I  I oc  O  5 

unsigned  buf_size  =  65280;  /♦  Initial  mempry  allocation  size  */ 

/♦  "read! )"  and  "write! )"  accept  a  maximum  of  32768  bytes  at  a 
time.  Allocate  as  much  memory  as  possible  up  to  this  limit 
for  both  the  input  and  output  buffers;  using  256  byte 
decrements  (128  bytes  for  each  buffer).  ♦  / 

do 

if (buffer  =  ma 1  I oc ( bu f _s i ze ) ) 
break ; 

whi 1 e ( bu f_size  -=  256); 

/♦  Divide  "buffer! D"  in  two  for  "in_ptr"  and  "out_ptr"  ♦  / 
buf_size  /=  2; 

if((fd_in  =  open ( i n_f i I e ,0_RD0NLY ) )  ==  ERROR)  /♦  Open  files  */ 
error (0PN_ERR;in_f i le) ; 

if((fd_out  =  open ( ou t _ f i I e ,0_RD0NLY ) )  ==  ERROR) 
error  ( OPN-ERR ;  ou  t  _  f  i  I  e ) ; 

I  seek  (  fd„in,of  fset  ;0) ;  /♦  Initialize  file  position  pointer  ♦  / 

/♦  Read  in  characters  from  both  files  and  compare  ♦  / 
do 

in..ptr  =  buffer;  /♦  Assign  buffer  pointers  ♦  / 
out_ptr  =  in_ptr  +  buf_size; 
i f ( read ( f d_in ; in_pt r ;buf -size >  =  =  ERROR) 
error (READ_ERR;in_f i  le) ; 

i  f  (  (out.cnt  =  read ( f d_ou t ;OU t_p t r ;bu f _s i ze ) )  ==  ERROR) 
error ( RE AD-ERR ; ou  t  _  f i  1  e ) ; 
match-cnt  =  out.cnt; 

whi 1 e ( ma t ch_cn t -- )  /*  Verify  character  */ 

i f < *in_ptr++  !=  *out_ptr++)  /*  by  character,  and  */ 

{  /*  delete  the  output  */ 

i f (c 1 ose< fd_out )  ==  ERROR)  /*  file  if  they  fail  */ 

error ( CLS_ERR,out_f i 1 e ) ;  /*  to  match  */ 

uni  ink (out-file); 
error ( BAD- VFY ,out_f i  I  e) ; 

} 

\ 

J 

whi I e ( ou t_ cn t  ==  buf_size>;  /*  Until  end  of  output  file  */ 
free(buf fer) ;  /*  Deallocate  buffer  space  */ 

i f (c lose( fd.in)  ==  ERROR)  /*  Close  the  files  -  verifi-  */ 

error (CLS-ERR, in_f i 1 e > ;  /*  cation  was  successful  */ 

i f (c lose! fd_out )  ==  ERROR) 
error ( CLS-ERR ,out_file); 


/*  Copy  fileref  to  file  control  block  */ 
copy_f ref ( fcb , fref  > 

char  *fcb,  /*  Pointer  to  file  control  block  *•/ 

♦fref;  /♦  Pointer  to  fileref  ♦/ 

char  c;  /*  Scratch  variable  */ 

int  i,  /*  Fileref  index  variable  ♦/ 

j,  /*  FCB  index  variable  */ 

k,  /*  Scratch  variable  */ 

done;  /*  Loop  break  flag  ♦/ 

(Continued  on  next  page) 


Dr.  Dobb's  Journal,  January  1985 


53 

37 


Archiving  Files  Listing  (Listing  Continued,  text  begins  on  page  36) 


i f ( f  re  f  C 1 ]  ! =  !!  frefC23  ==  '  \0'  ) 

return  ERROR;  /*  No  drive  code  separator  or  null  fileref  */ 

/*  Calculate  drive  code  from  drive  name  and  put  in  FCB  */ 

fcbCOD  =  f re f  C  03  -  ' A'  +1; 

/*  Process  remainder  of  fileref  */ 


done  =  FALSE; 

for(i  =  2>j  =  l;i  <=  9;i++,j++)  /*  Skip  drive  code  in  */ 

i  / *  f i 1  ere f  */ 

swi tch(c  =  f ref C i  3  ) 


case  ' . ' : 
i f ( i  ==  2) 

return  ERROR; 
fort  ;  j  <=>  8; 

f  c  b  C  j  D  =  '  '  ; 

done  =  TRUE; 
break ; 
case  ' * ' : 

fort  ;  j  <=  8; 

f cb  C  j  3  =  '?'; 
i++; 

done  =  TRUE; 
break ; 
case  ' \0' : 

fort  ;  j  <=•  11; 

fcbtj]  =  '  '; 

return  SUCCESS; 
case  ' t ' : 


/*  Filetype  separator  */ 

/*  Null  filename  */ 

j++ ) 

/*  Pad  filename  with  trailing  blanks  */ 


/*  Match  any  following  string  */ 

j++ ) 

/*  Pad  filename  with  trailing  */ 
/*  question  marks  */ 


/*  End  of  fileref  */ 

j++)  /*  Pad  FCB  with  trailing  spaces  */ 


/*  Illegal  filename  characters  */ 


case  ' ; ' : 
case  ' * ' » 


case  ' : 
case  ' C  '  t 
case  ' 3 ' s 
case  ' t 


case  ' < ' : 
case  ' > ' : 

return  ERROR; 
de  f au  1  t  s 

if  tc  >=  '  !  '  &6.  c  <=  ) 

fcbCjD  =  c;  /*  Copy  character  from  fileref  to  FCB  */ 
e  1  se 

return  ERROR;  /*  Nonprintable  character  or  '  '  */ 

> 

i f ( done ) 
break ; 


c  -  frefCiJ; 

ifttc  =  f  re  f  C  i  3  )  ==  ' \0')  /*  End  of  fileref  */ 

{ 

fort  ;  j  <  =  11;  j++)  /*  Pad  FCB  with  trailing  spaces  */ 

f  cbt  j  3  =  '  '; 
return  SUCCESS; 

> 

else  iftc  ==  '«')  /*  Filetype  separator  */ 

<  \ 

i  +  +  ; 

k  =  i  +  2;  /*  Set  limit  of  3  characters  */ 

fort  ;  i  <=  k;  i++>j++) 

< 

done  =  FALSE; 
switchtc  =  frefCil) 


54 

38 


(Continued  on  page  56) 
Dr.  Dobb  s  Journal,  January  1985 


Archiving  Files  Listing  (Listing  Continued,  text  begins  on  page  36) 


{ 

case  /*  Match  any  following  string  */ 

for (  ;  j  <=  11;  j++) 

fcblj]  =  '  ?'  ;  /*  Pad  filetype  with  trailing  */ 

return  SUCCESS;  /*  question  marks  */ 

case  ' \0' :  /*  End  of  fileref  */ 

for(  ;  j  <  =  11;  j++)  /*  Pad  FCB  with  trailing  */ 

fcbCj]  =  '  ';  /*  spaces  */ 

return  SUCCESS; 

case  ' . ' :  /*  Illegal  filetype  characters  */ 

case  ' ; ' : 

case  '  f '  : 

case  ' s ' : 

case  '  = '  : 

case  ' C ' : 

case  '  3 '  : 

case  '  : 

case  '  < ' : 

case  '  : 

return  ERROR; 
def aul t ; 

i  f  ( c  >  =  '  !  '  &S-.  c  <  =  '  ~ '  ) 

fcbCj]  =  c;  /*  Copy  character  from  fileref  */ 
else  /*  to  FCB  */ 

return  ERROR;  /*  Nonprintable  character  or  '  '  */ 

y 

> 

/*  Return  ERROR  if  filetype  too  long  */ 

return  (frefCi]  ==  ' \0'  ?  SUCCESS  :  ERROR); 

> 

e  1  se 

return  ERROR;  /*  Filename  too  long  */ 


/*  Error  report  */ 
error ( n , s ) 

int  n;  /*  Error  code  */ 

char  *s;  /*  Pointer  to  optional  string  */ 

{ 

swi t  ch ( n ) 

case  USER-ERR: 

pr int f ( " \007*»  ERROR  -  No  user  number  specified  **\n"); 
break ; 

case  BAD-FREF : 

pr i nt f ( " \007#*  ERROR  -  Illegal  file  reference:  %s  **\n">s); 
break ; 

case  BAD..ARGS: 

pr i n t f < “ \007#*  ERROR  -  Illegal  command  line  **\n" ) ; 
break ; 

case  BAD-OPT:  ^ 

pr i n t f ( " \007**  ERROR  -  Illegal  command  line  option:"); 
printf<"  *s  **\n",s); 
break ; 

case  BAD-USER: 

print f ( "\007**  ERROR  -  User  number  must  be  inside  range"); 
printft"  of  0  to  31  *#\n">; 
break ; 

case  BAD-DRV: 

pr in t f < " \007**  ERROR  -  Drive  names  must  be  inside  range"); 
printf<"  of  'A'  to  'P'  **\n" ) ; 

( Continued  on  page  58) 


Dr.  Dobb’s  Journal,  January  1985 


56 


Archiving  Files  Listing  (Listing  Continued,  text  begins  on  page  3(>) 


break ; 

case  SAMF-DRV : 

print f (" \007**  ERROR  -  Drive  names  cannot  be  equal  **\n"); 
break ; 

case  OPN-ERR: 

print f ( "\007\n**  ERROR  -  Cannot  open  file  %s  **\n",s); 
reset  < ) 5 
exi  t  < 0 ) ; 
case  READ-ERR: 

print  f  <  "\007\n**  ERROR  -  React  error  on  file  %s  **\n",s); 
reset (  ) ; 
exi t (0) ; 
case  CLS-ERR: 

print f < "\007\n**  ERROR  -  Cannot  close  file  %s  **\n",s); 
reset  < ) ; 
ex i t ( 0 ) ; 
case  BAD-VFY: 

print  f  ( "\007\n*»  ERROR  -  Failed  verify  on  file  %s  **\n" ,s); 
reset ( ) ; 
exi t (0) ; 

> 


pr  i  n t  f  (  " 

\nUsaqe:  BU  xC:afnD 

y  c 

-AFHQSnD \n\n" ) ; 

print  f ( " 

where  x  a  dr 

i  ve 

name  of  disk  to  be  backed  up\n"); 

pr int  f  < " 

y  =  drive 

name  of  backup  disk\n\n">; 

print  f ( " 

and  the  optional 

arguments  are: \n\n"l; 

pr i n  t  f ( “ 

-A 

All  files,  reqardlass  of 

pr int  f ( " 

status\n" ) ; 

pr i n  t  f  (  " 

-F 

Fast  copy  (without  "); 

print  f ( " 

ver i f ica  t ion ) \n" ) ; 

pr i n t  f ( " 

-H 

Hard  disk  (files  may  be" 

); 

print  f ( " 

sp 1 i t ) \n” ) ; 

print  f ( " 

-G 

Query  each  file  before") 

pr int  f ( " 

backup\n"  ) ; 

pr i n  t  f ( " 

-S 

System  attribute  copied 

to"); 

print  f  < ” 

backup\n" > ; 

pr in t  f ( ” 

-n 

Backup  USER  'n'  files  only"); 

pr int  f ( " 

(0-31 ) \n"  ) ; 

print  f ( " 

-afn 

Any  legal  CP/M  arnbi  guouos  "  ) ; 

pr int f  (  " 

f  i  I  eref \n" ) ; 

pr-  i  n  t  f  (  " 

(can  only  be  used  with  - 

n"  > ; 

pri nt  f ( " 

opt  ion ) \n" ) ; 

exi.  t  (0) ; 

> 

/*  Request 

a  new  backup  disk 

to  be  inserted  in  the  output  drive  */ 

new_disk ( name , hard-disk ) 
char  *name; 
int  hard_disk; 
i 

char  d; 


print f ( "\007\n  **  BACKUP  DISK  FULL  **\n\n"); 

i f ( hard_d i sk ) 

print f ( "WARNING:  -H  option  active  -  FILE  WILL  BE  SPLIT\n\n">: 
print f <" Insert  new  disk  into  drive  %c 
pr int f( "Type  ' C'  when  ready  or  'A'  to 


to  corn inue. \n' 
abor  t  ...  ”  )  ; 


, name  COD ) ; 


while(<d  =  in_chr(>)  != 


8,8..  d  !  = 


8..S,  d  !  = 


8<8,  d  !  =  '  A'  ) 


i  f (d  ==  'a'  I  !  d  ==  ' A'  ) 

< 

uni i nk ( name )  ; 
exi t (0) ; 

y 

e  1  se 


( Continued  on  page  60) 


58 


Dr.  Dobb’s  Journal.  January  1985 


Archiving  Files  Listing  (Listing  Continued,  text  begins  on  page  36) 


pr in t  f ( " \n\n" ) 5 

bdos ( RESET—DRV / NULL ) 5  /*  Reset  drives  */ 

> 

/*  Reset  user  and  drive  codes  to  entry  values  */ 

I  e-set  (  ) 

r 

\ 

bdos ( USER-CODE /en*_user) ; 

bdos ( SEL-DRV , en  t  _drv ) ; 


/*  Ge t  character  from  current  CP/’M  CON:  device  without  echo  */ 
in.-chr  (  ) 
int  c; 
do 

c  =  bdos ( DIR_IO /Oxf  f ) ; 
wh  i  1  e  <  !  c ) ; 
return  c; 

} 

/*  Additional  function  required  for  DeSrnet  C  version  of  BU86.C  *7 

tt if  DESMET 
bdos ( be  t  a , de I  t  a ) 
int  beta/ 
de I ta; 

{ 

return  _.os  <  be  t  a  /  de  I  t  a ) ; 

\ 

J 

«endi  f 

/**«  EXPLANATION  OF  AZTEC  Cl  I  STDIO.H  FUNCTIONS  ***/ 

/*  bdos(bc/de)  I  DeSrnet  equivalent  is  defined  under  FUNCTIONS  ! 

*  int  be /de; 

* 

*  Cal  Is  CP/M's  BDOS  with  8000  CPU  register  pair  BC  set  to  "be" 

*  and  register  pair  DE  set  to  "de".  The  value  returned  by  the 

*  8080  CPU  accumulator  is  the  return  value. 

* 

*  movrnem  (  sre /des  t  /  I  enqth )  !  DeSrnet  equivalent  is  defined  I 

*  char  *dest/  *srr»  I  under  DEFINITIONS  ! 

*  int  length; 

* 

*  Moves  data  from  "sre"  to  "dest".  The  number  of  bytes  is 

*  specified  by  the  parameter  "length". 

* 

*  s t rnemp ( s t rl /S t r2 /max )  !  Identical  for  DeSrnet  I 

*  char  *strl/*str2; 

*  int  max; 

* 

*  Compares  "strl"  to  "str2"  for  at  most  "max"  characters/  and 

*  returns  NULL  if  strings  are  equal/  -1  if  "strl”  is  less  than 

*  "str-2"/  and  +1  if  "strl"  is  qreater  than  "str2". 

*/ 

/*  End  of  BU.C  */ 


End  Listing 


Dr.  Dobb's  Journal,  January  1985 


60 

41 


MBOOT  and  MODEM7  for  the 
C-64's  CP/M 


by  Walt  Piotrowski 


If  you  have  CP/M  for  your  Commo¬ 
dore  64,  you  may  have  noticed  that 
the  ads  for  CP/M  software  never 
mention  that  the  program  is  available 
in  Commodore’s  disk  format.  If  you 
have  tried  to  buy  any  of  this  software, 
you  have  found  out  why  the  ads  don’t 
mention  it:  with  one  exception,  dis¬ 
kettes  for  the  C-64  are  not  available 
(the  only  exception  that  I  am  aware  of 
is  Turbo  Pascal  from  Borland 
International). 

One  company.  Laboratory  Micro¬ 
systems,  tried  quite  hard  to  help  me 
buy  their  product  but  simply  did  not 
have  access  to  any  equipment  that 
could  write  a  diskette  for  the  C-64. 
One  of  their  suggestions  was  to  provide 
the  software  on  an  IBM  PC-compatible 
diskette;  since  I  have  access  to  a  PC,  I 
could  transfer  the  program  to  the  C- 
64.  Unfortunately,  I  did  not  have  any 
software  for  my  C-64  that  could  do  a 
download  either. 


MBOOT  on  the  C-64 — Download 
Only 

MBOOT,  from  the  DDJ  CP/M  ex¬ 
change  column,  was  a  good  attempt  at  a 
generic  program  to  download  files  us¬ 
ing  Ward  Christensen’s  Xmodem  file 
transfer  protocol.  In  principle,  all  you 
have  to  do  is  take  the  listing,  change  a 
few  equates  to  match  your  modem,  type 
in  the  program,  and  assemble  it,  and 
you  are  ready  to  download  files.  If  you 
are  familiar  with  the  way  that  CP/M  is 
implemented  on  the  C-64  (see  the  refer¬ 
ences  at  the  end  of  the  article  if  you 
aren’t),  you  have  probably  guessed  that 
modifying  MBOOT  for  this  computer 
isn’t  quite  that  easy. 

Normal  CP/M  operation  on  the  C- 
64  does  not  require  access  to  the  RS- 
232  port,  so  there  is  no  code  in  the 
BIOS  to  handle  this  port.  Because  the 
modem  is  controlled  through  the  RS- 
232  port,  the  modification  for  MBOOT 
required  some  65 1 0  code  in  addition  to 


" This  article  is  about  the  MBOOT  download  program 
(DDJ,  July  1982)  and  the  modifications  that  were 
necessary  to  make  it  work  on  the  C-64." 


This  article  is  about  the  MBOOT 
download  program  {DDJ,  July  1982) 
and  the  modifications  that  were  neces¬ 
sary  to  make  it  work  on  the  C-64.  Once 
you  have  MBOOT  working,  you  can 
use  a  modem  to  download  public  do¬ 
main  software  from  RCPMs,  and,  as¬ 
suming  you  have  access  to  a  machine 
with  one  of  the  more  popular  formats, 
you  can  also  use  it  to  set  some  of  the 
commercial  software  that  you  need. 


Walt  Piotrowski,  State  University  of 
New  York,  Binghamton,  NY  13901. 


M  BOOT’S  8080  code.  This  6510  code 
makes  RS-232  I/O  requests  to  the 
Commodore  I/O  kernel.  The  problem 
is  made  a  little  more  complex  by  the 
fact  that  the  kernel  handles  the  RS- 
232  transfers  one  bit  at  a  time  by  ser¬ 
vicing  nonmaskable  interrupts  from  a 
6526  Interface  Adapter  chip.  Normal¬ 
ly,  the  kernel  accepts  an  RS-232  re¬ 
quest  and  sets  up  the  hardware  for  the 
transfer  but  returns  control  to  the  re¬ 
questor  before  the  actual  transfer  is 
complete.  The  kernel  then  services  the 
interrupts  as  they  come  along;  other 
6510  activities  proceed  in  parallel  with 


62 

42 


Dr.  Dobb's  Journal,  January  19S5 


the  RS-232  transfer. 

Under  CP/M,  however,  it  is  quite 
normal  to  shut  off  the  65 1 0  for  long  pe¬ 
riods  while  programs  run  in  the  Z80. 
But  the  hardware  does  not  allow  the 
Z80  to  receive  these  interrupts,  nor 
does  it  contain  any  provision  for  switch¬ 
ing  on  the  6510  if  an  interrupt  arrives. 
This  means  that,  unless  MBOOT  con¬ 
tains  some  provision  to  ensure  that  the 
6510  is  running  (almost)  continuously, 
RS-232  transfers  will  become  garbled. 
Making  sure  that  the  6510  is  running 
while  a  character  is  being  sent  is  not  a 
big  problem  since  transmission  is  total¬ 
ly  under  M  BOOT’S  control.  The  6510 
code  that  1  have  added  to  make  the 
transmission  request  to  the  I/O  kernel 
includes  a  loop  that  waits  in  the  6510 
until  the  kernel’s  RS-232  status  byte 
shows  that  the  character  has  been  com¬ 
pletely  transmitted.  Data  reception  is  a 
little  more  complex. 

Listing  One  (MBOOT64,  page  67)  is 
the  modification  of  M  BOOT’S  original 
8080  code.  If  you  were  to  compare 
MBOOT64  to  MBOOT,  you  would  find 
that  M BOOT’S  inline  I/O  instructions, 
which  originally  were  directed  to  the 
modem  port,  have  been  replaced  with 
calls  to  a  set  of  6510  interface  routines. 
These  routines  can  be  found  at  the  end 
of  Listing  One.  They  allow  the  pro¬ 
gram  to  open  the  RS-232  channel 
(OPM DLM ),  close  it  (CLMDLM ),  send 
a  character  to  the  modem 
(WRMDEM),  receive  a  character 
(RDMDEM),  receive  a  character  in  a 
time  out  loop  (RDMDEM2),  and  read  a 
keyboard  character  (KYBD).  The  in¬ 
terface  routines  do  not  actually  per¬ 
form  these  functions:  they  set  up  pa¬ 
rameters  in  memory  that  eventually 
cause  a  transfer  to  corresponding  6510 
routines  that  do  the  work  required. 
Listing  Two  (CPMMD65,  page  79) 
contains  the  65 1 0  counterparts  of  each 
interface  routine. 

The  actual  transfer  of  control  be¬ 
tween  the  Z80  and  the  6510  takes 
place  at  the  label  GO6510  in  the  inter¬ 
face  routine  section  of  MBOOT64.  The 
transfer  of  control  employs  a  user 
function  in  the  BIOS  that  Commodore 
has  provided  to  standardize  the  inter¬ 
face  between  CP/M  programs  and 
user-developed  6510  programs.  This 
user  function  and  the  standard  inter¬ 
face  are  described  in  the  Commodore 
64  CP/M  user’s  guide.  The  modifica¬ 


tions  to  MBOOT  make  use  of  request 
code  9  in  this  standard  interface,  and 
the  program  should  be  usable  even  if 
future  modifications  are  made  to  Com¬ 
modore’s  CP/M. 

MBOOT  (and  MBOOT64)  has  two 
modes  of  operation:  a  file  receive  mode 
and  a  dumb  terminal  mode.  In  the  file 
receive  mode,  the  XMODEM  protocol 
provides  a  strict  handshake  sequence 
between  the  computer  transmitting  the 
file  and  the  computer  receiving  the  file. 
Because  of  this  handshaking,  data  re¬ 
ception  in  the  file  receive  mode  is  not 
much  more  difficult  than  data  trans¬ 
mission:  MBOOT  (and  MBOOT64) 
sends  a  character  to  the  computer  that 
is  transmitting  the  file  then  enters  a 
wait  loop.  This  transmitted  character 
(ASCII  ACK  or  NAK)  is  a  signal  that 
the  receiving  computer  expects  one 
sector  of  data. 

In  the  original  MBOOT,  the  wait 
loop  contained  code  to  poll  the  modem 
and  a  timeout  counter  to  prevent  a  per¬ 
manent  hangup  if  the  data  never  ar¬ 
rived.  A  vestige  of  this  loop  is  at  the 
label  MWTI  in  MBOOT64.  At  MWTI 
(location  03E5),  you  will  find  a  call  to 
RDMDEM2,  which  activates  the  “read 
with  wait”  function  in  the  6510;  this 
read  with  wait  is  at  label  INPUT2  (line 
139)  in  CPMMD65. 

Immediately  after  a  byte  is  received, 
control  is  returned  from  the  6510  to 
the  Z80.  At  this  point,  the  65 10  is  shut 
off  while  the  Z80  takes  care  of  the  re¬ 
ceived  character.  However,  this  time 
period  is  very  short,  and  the  6510  is 
always  back  on  in  time  to  accurately 
receive  the  next  character. 

In  the  dumb  terminal  mode,  charac¬ 
ters  arrive  at  random  times,  making  it 
impossible  to  predict  the  exact  mo¬ 
ment  when  a  character  will  begin  to 
arrive.  The  modification  to  this  mode 
takes  advantage  of  the  fact  that  the 
terminal  code  is  a  very  short  loop  in 
which  a  modem  status  check  occurs  ev¬ 
ery  few  microseconds.  The  modifica¬ 
tion  simply  puts  this  status  check  func¬ 
tion  into  the  6510.  Because  the  6510 
will  now  be  turned  on  every  few  micro¬ 
seconds,  it  will  be  able  to  service  the 
RS-232  interrupt  either  as  soon  as  it 
arrives  or  very  shortly  thereafter. 

The  start  of  the  terminal  loop  is  at 
label  TERM  (location  019A)  in 
MBOOT64.  In  this  loop,  a  call  to 
RDMDEM,  one  of  the  6510  interface 


DeSmet 

C 

8086/8088 

Development  $1flO 
Package  IU9 


FULL  DEVELOPMENT  PACKAGE 

•  Full  K&R  C  Compiler 

•  Assembler,  Linker  &  Librarian 

■  Full-Screen  Editor 

•  Execution  Profiler 

■  Complete  STDIO  l>rary  (>120  Func) 

Automatic  DOS  LX/2  X  SUPPORT 

BOTH  8087  AND  S/W  FLOATING  POINT 

OVERLAYS 

OUTSTANDING  PERFORMANCE 

■  First  and  Second  in  AUG  '83  BYTE 
benchmarks 


SYMBOLIC  DEBUGGER  $50 

•  Examine  &  change  variables  by 
name  using  C  expressions 

•  Flip  between  debug  and  display 
screen 

■  Display  C  source  during  execution 

■  Set  multiple  breakpoints  by  function 
or  line  number 

DOS  LINK  SUPPORT  $35 


■  Uses  DOS  .OBJ  Format 

•  LINKs  with  DOS  ASM 

•  Uses  Lattice®  naming  conventions 


Check:  □  Dev.  Pkg  (109) 

□  Debugger  (50) 

□  DOS  Link  Supt.  (35) 

SHIP  TO:  _ _ 


WARE 

CORPORATION 

P.O.  BOX  C 
Sunnyvale,  CA  94087 
(408)  720-9696 

All  orders  shipped  UPS  surface  on  IBM  format  disks 
Shipping  included  m  price  California  residents  add 
sales  tax  Canada  shipping  add  S5.  elsewhere  add 
SI 5  Checks  mus!  be  on  US  Bank  and  in  US  Dollais 
Call  9  am  -  1  pm.  to  CHARGE  by  VISA/MC/AMEX. 


Dr.  Dobb's  Journal.  January  1985 


Circle  no.  1 8  on  reader  service  card. 


63 

4.n 


routines,  activates  the  modem  status 
check  function  in  the  6510.  This  func¬ 
tion  is  located  at  label  INPUT  (line 
1 10)  in  CPMMD65. 

Each  time  INPUT  is  entered,  it  ex¬ 
amines  the  kernel’s  status  byte  to  see  if 
the  kernel  has  begun  to  input  a  charac¬ 
ter.  If  it  has,  the  routine  waits  until  the 
character  has  been  completely  trans¬ 
ferred  (Listing  Two,  lines  126-131). 
INPUT  and  RDMDEM  also  return  the 
modem  character  to  the  caller  if  one  is 
ready.  The  wait  loop  in  INPUT,  plus  the 
one  in  the  character  transmit  routine 
(Listing  Two,  lines  102-104),  keeps 
the  6510  running  a  very  high  percen¬ 
tage  of  the  time,  and  full  duplex  opera¬ 
tion  at  300  baud  proceeds  smoothly. 
(More  information  on  data  rates  is  in¬ 
cluded  near  the  end  of  the  article.) 

There  is  an  additional  complicating 
factor  involving  the  terminal  mode.  As 
you  probably  know,  the  C-64’s  native 
character  set  is  not  ASCII.  When  Com¬ 
modore  implemented  CP/M,  it  chose 
to  generate  ASCII  with  a  new  keyboard 
scan  routine.  This  CP/M  keyboard 


scan  runs  partly  in  the  6510  and  partly 
in  the  Z80.  Unfortunately,  the  key  de¬ 
bounce  loop  is  located  in  the  Z80.  This 
means  that  whenever  a  key  is  pressed 
the  Z80  enters  this  delay  loop;  if  a  mo¬ 
dem  character  happens  to  come  along 
while  the  Z80  is  in  this  loop,  the  mo¬ 
dem  character  gets  lost  or  garbled. 

This  turns  out  to  be  a  significant 
problem  because  in  the  terminal  mode 
every  character  typed  at  the  C-64  is 
echoed  by  the  computer  at  the  other 
end  of  the  line.  At  normal  typing 
speeds,  this  debounce  delay  loop 
causes  almost  every  echoed  character 
to  be  garbled — it  becomes  impossible 
to  read  what  you  have  typed.  The  solu¬ 
tion  was  to  add  a  new  keyboard  routine 
in  the  6510  and  to  use  that  routine  in¬ 
stead  of  the  CP/M  keyboard  routine 
while  in  the  terminal  loop.  The  inter¬ 
face  between  MBOOT64  and  the  new 
keyboard  routine  is  at  label  KYBD  (lo¬ 
cation  04CC)  in  MBOOT64,  and  the 
corresponding  6510  routine  is  at  label 
KBDCHR  (line  154)  in  CPMMD65. 

The  keyboard  routine  that  I  imple¬ 


mented  is  a  very  simple  one:  it  uses  the 
normal  C-64  kernel  keyboard  routines 
(not  the  CP/M  routine)  and  simply 
translates  received  characters  from  the 
C-64  character  set  into  ASCII  through 
a  translation  table.  This  routine  is  ade¬ 
quate  for  the  terminal  mode,  at  least 
for  me,  but  you  will  notice  that  it  does 
not  allow  you  to  switch  to  an  all  upper 
case  keyboard  mode.  You  will  also  no¬ 
tice  that  the  characters  with  ASCII 
codes  32  (space)  and  below  have  an 
auto  repeat.  This  repeat  is  built  into 
the  native  C-64,  and  it  was  easier  to 
leave  it  in  than  to  try  to  undo  it. 

The  modification  to  MBOOT  in¬ 
cludes  one  further  change.  The  origi¬ 
nal  MBOOT  contained  code  to  buffer 
16  sectors  before  actually  doing  a  disk 
write.  1  tried  it  both  ways  and,  with 
1541  disk  drives,  found  no  speed  ad¬ 
vantage  in  doing  this  buffering.  Be¬ 
cause  the  program  is  easier  to  type  in 
without  this  code,  I  omitted  it.  (Inci¬ 
dentally,  the  comments  in  the 
M  BOOT64  listing  were  not  in  the  origi¬ 
nal  DDJ  column,  but  I  needed  them  to 
help  me  to  understand  the  program.  I 
believe  that  they  are  correct,  but  it 
isn’t  easy  to  debug  a  comment,  so  they 
may  contain  some  misinterpretations.) 

The  Figure  (at  left)  shows  where  the 
8080  and  6510  portions  of  the  modified 
MBOOT  reside  in  memory.  The  8080 
code  begins  at  the  beginning  of  the  TPA 
($100  in  Z80  space)  while  the  6510 
code  begins  at  location  $600.  You  will 
note  from  Listing  Two  that  the  6510 
code  is  actually  assembled  at  $  1 600  due 
to  the  $  1 000  offset  between  the  address 
spaces  of  the  two  processors.  The  data 
shared  between  the  two  parts  of  each 
function  resides  in  memory  locations 
$603  —  $605  in  Z80  space. 

The  figure  also  shows  the  location  of 
the  character  translation  table  that  is 
used  in  the  terminal  keyboard  routine 
and  the  new  location  of  the  kernel’s  RS- 
232  buffers.  These  RS-232  buffers 
originally  were  moved  from  their  nor¬ 
mal  location  so  that  a  48K  CP/M  could 
be  used.  Since  writing  the  program,  I 
have  discovered  that  the  48K  CP/M 
will  occasionally  send  a  garbage  char¬ 
acter  out  the  RS-232  port  while  doing 
disk  transfers.  Most  RCPMs  ignore 
these  characters,  but  they  cause  the 
XMODEM  program  on  a  few  RCPMs  to 
abort  while  doing  file  transfers.  I  have 
found  that  it  is  best  to  use  a  44K  CP/M 


64 

44 


Dr.  Dobb's  Journal.  January  1985 


when  using  MBOOT  or  other  modem 
programs,  even  though  they  will  work 
most  of  the  time  with  a  48K  CP/M. 

There  are  two  ways  to  add  the  6510 
code  to  MBOOT.  The  easiest,  not  that 
it  works,  is  simply  to  include  the  code 
at  the  end  of  the  8080  assembly  as  a  set 
of  hex  constants.  (Don’t  forget  that  it 
must  begin  at  location  $600.)  The  sec¬ 
ond  way,  if  you  have  a  6510  assembler 
that  runs  on  the  native  6510,  is  to  as¬ 
semble  the  program  and  load  the  ob¬ 
ject  code  into  memory  prior  to  doing  a 
CP/M  cold  start;  the  cold  start  leaves 
these  6510  program  locations  unaf¬ 
fected.  Once  CP/M  has  been  loaded, 
use  DDT  to  load  in  M BOOT’S  8080 
COM  file  and  save  the  combined  pro¬ 
gram  with  “SAVE  7  C64MBT.COM.” 

M0DEM4 — Upload  and  Download 

After  I  had  MBOOT  working,  I  began 
to  run  up  my  long  distance  phone  bill 
calling  RCPMs  and  looking  for  a  copy 
of  the  source  code  for  MODEM7.  Dur¬ 
ing  the  search,  I  found  many  RCPMs 
with  copies  of  the  object  code  but  none 
with  the  source  code.  Since  this  search 
was  getting  rather  expensive,  I  decided 
to  stop  and  go  to  work  on  a  copy  of 
MODEM4,  a  program  I  had  found  and 
downloaded  while  looking  for 
MODEM7. 

MODEM4  apparently  is  a  modem 
program  that  was  part  of  the  evolution 
toward  MODEM7.  You  can  use  it  as  a 
terminal  program  as  well  as  to  upload 
and  download  files  using  Christensen’s 
protocol.  I  modified  it  for  the  C-64  and 
used  it  to  put  copies  of  itself  on  several 
RCPMs  in  the  Northeast.  It’s  called 
MODEM464  and  a  short  .DOC  file  ac¬ 
companies  it. 

To  find  it,  look  for  M0DEM464.0BJ. 
Download  it  with  MBOOT,  rename  it 
MODEM464.COM,  and  run  it.  If  you 
run  it  by  typing  MODEM464  T,  you  will 
be  in  the  terminal  mode  and  the  pro¬ 
gram  will  act  the  same  as  MBOOT. 
When  you  are  ready  to  download  a  file 
(after  you  have  set  up  the  RCPM’s 
XMODEM),  exit  from  the  program  with 
CTL-E  and  type  “MODEM464  R  file¬ 
name. ext.”  This  will  reload  the  program 
from  disk  and  start  it  into  file  receive 
mode.  Once  the  file  has  been  trans¬ 
ferred,  the  program  goes  directly  back 
into  terminal  mode.  If  you  want  to  send 
a  file,  type  “MODEM464  S  filena¬ 
me. ext.”  The  program  will  send  the  file 


Dr.  Dobb's  Journal,  January  1985 


and  go  to  terminal  mode  when  complet¬ 
ed,  just  as  it  does  in  the  file  receive 
mode. 

MODEM7 — Upload,  Download, 
and  More 

A  short  time  after  I  finished  the  work 
on  MODEM4, 1  came  across  the  source 
code  for  M DM730,  a  version  of  MO¬ 
DEM?.  MODEM7  is  a  full-feature  mo¬ 
dem  program  with  a  terminal  mode,  a 
file  upload  and  download  capability,  a 
terminal  mode  capture  buffer  for 
ASCII  text,  and  a  terminal  mode  ASCII 
file  transmit  function.  The  ASCII  cap¬ 
ture  buffer  is  useful  for  saving  text, 
such  as  the  directory  of  an  RCPM,  that 
you  don’t  want  to  lose  but  can’t  transfer 
in  file  (XMODEM)  form.  The  ASCII 
file  transmit  function  is  useful  if  you 
have  a  long  message  that  you  want  to 
leave  on  a  bulletin  board  without  using 
the  time  (and  money)  to  compose  it 
while  on-line.  These  ASCII  file  features 
are  also  useful  if  you  wish  to  exchange 
text  files  with  someone  who  does  not 
have  a  program  with  the  XMODEM 
protocol.  MDM730  will  also  autodial 
with  several  different  kinds  of  modems, 
and  it  allows  you  to  echo  text  directly  to 
your  printer.  (The  modified  version  will 
autodial  only  with  a  Hayes  modem.) 

The  modified  program,  called 
MD730C64,  is  harder  to  learn  to  use 
than  either  MBOOT  or  MODEM4  be¬ 
cause  it  has  so  many  features.  To  get 
going,  you  must  download  three  files: 
the  executable  program  (MD730C64- 
.OBJ),  the  documentation  for  the  origi¬ 
nal  program  (MDM730.DOC),  and  the 
notes  that  explain  the  few  differences 
between  MDM730  and  MD730C64 
(MD730C64.DOC). 

Getting  MD730C64.OBJ  is  easy: 
download  it  using  MBOOT,  rename  it 
to  a  .COM  file,  and  you  are  ready  to 
run  it.  Getting  the  documentation, 
however,  is  a  bit  more  difficult.  Most 
RCPMs  store  long  text  files  in  a 
squeezed  format  to  conserve  disk  space 
and  to  shorten  the  download  time;  you 
can  recognize  squeezed  files  because 
they  have  a  Q  in  the  file  extension. 
Therefore,  you  may  find  MD730C64- 
DQC  instead  of  MD730.DOC.  If  you 
do,  you  must  also  download  a  program 
to  unsqueeze  the  file  on  your  own  sys¬ 
tem.  This  unsqueeze  program  has 
many  versions,  but  it  almost  always 
has  a  name  that  starts  with  USQ. 

66 


Getting  the  original  MDM730  pro¬ 
gram  documentation  file  may  be 
tougher  still.  On  most  RCPMs  you 
must  download  a  library  file  called 
MDM730.LBR.  A  library  file  is  a  col¬ 
lection  of  individual  files  relating  to 
the  same  program  -MDM730.DQC  is 
one  of  the  individual  files  in  MDM730- 
,LBR.  To  extract  the  file  that  you  want 
from  a  library,  you  need  a  library  utili¬ 
ty  program  called  LU  (and  its  docu¬ 
mentation  LU.DQC).  Once  you  have 
learned  to  use  LU,  you  can  extract 
MDM730.DQC  from  the  MDM730  li¬ 
brary  file.  This  library  also  contains  a 
program  that  lets  you  change  the  list  of 
RCP/M  phone  numbers  in  MDM730  to 
ones  that  you  call  more  frequently. 

Don’t  be  discouraged  by  the  com¬ 
plexity  of  this  whole  process.  Once  you 
start  to  find  your  way  around  RCPMs, 
you  will  find  that  most  of  the  really 
worthwhile  programs  are  in  libraries 
and  have  squeezed  documentation. 
Your  experiences  and  the  utility  pro¬ 
grams  that  you  have  downloaded  will 
pay  off  later. 

I  ran  into  some  interesting  problems 
when  modifying  MDM730.  The  biggest 
was  the  size  of  the  source  code.  The 
squeezed  file  that  1  downloaded  was 
1 05K,  which  will  fit  on  a  C-64  diskette. 
When  unsqueezed,  it  becomes  158K, 
which  will  not.  To  get  an  assembly  list¬ 
ing,  I  had  to  use  my  modified  MO- 


DEM4  to  transfer  the  squeezed  source 
code  and  a  copy  of  an  unsqueeze  pro¬ 
gram  to  a  Cromemco  Z80  system  at 
the  university  where  I  work.  The  disks 
on  this  system  hold  250K,  and  I  was 
able  to  unsqueeze  the  file  and  assemble 
it  there.  My  original  intent  was  to 
modify  MDM730  by  editing  and  reas¬ 
sembling  the  original  source  file,  but  I 
decided  that  editing  and  assembling  on 
one  machine  and  testing  on  another  30 
miles  away  would  be  a  nearly  impossi¬ 
ble  task.  Because  of  that,  I  chose  to 
make  the  modification  in  the  form  of  a 
patch  overlay. 

Caveats 

M  DM  730  is  a  very  complex  program; 
consequently,  so  is  MD730C64.  I  have 
tested  it  carefully,  but  you  may  uncover 
some  minor  problems.  Also,  because  of 
equipment  limitations,  I  have  not  been 
able  to  test  any  of  these  programs  at 
data  rates  higher  than  300  baud.  Be¬ 
cause  of  the  switch  that  takes  place  be¬ 
tween  processors  while  the  data  is  arriv¬ 
ing,  the  program  may  need  more  work  to 
run  successfully  at  higher  rates.  Please 
let  me  know  if  you  uncover  any  prob¬ 
lems  and  if  you  are  able  (or  unable)  to 
run  at  rates  higher  than  300  baud. 

A  note  about  modems;  When  I  began 
accessing  RCPMs,  I  used  the  VIC  mo¬ 
dem  that  I  had  been  using  for  Compu¬ 
Serve.  I  discovered  that,  while  it  works 


with  CompuServe  because  they  have  a 
local  phone  number,  on  the  typical  long 
distance  call  with  a  noisy  line  the  VIC 
modem  simply  doesn’t  work  well.  If  you 
plan  to  call  RCPMs,  you  will  need  a  mo¬ 
dem  with  better  immunity  to  noise. 

References 

1.  Commodore  Business  Machines. 
Commodore  64  CP/M  Operating 
System  User's  Guide.  Indianapolis: 
Howard  W.  Sams  and  Co.,  1983. 

2.  Head,  Gene.  “CP/M  Exchange.” 
Dr.  Dobb's  Journal ,  July  1982, 
pages  42  -  50. 

3.  Hoff,  Irvin  M.  “MDM730  (notes  on 
how  to  use).”  File  MDM730.DOC, 
1984. 

4.  Piotrowski,  Walt.  “CP/M  on  the 
Commodore  64.”  Dr.  Dobb's  Jour¬ 
nal,  June  1 984. 

RCPMs  with  MD730C64 

1.  Allentown  (PA)  RBBS/RCPM,  Bill 
Earnest,  (215)  398-3937. 

2.  Bearsville  (NY)  Town  SJBBS, 
Hank  Szyszka,  (914)  679-6559. 

3.  St.  Mary’s  College  (MD)  RCP/M, 
Jonathan  Crawford  and  Bob  Beas¬ 
ley,  (301)  863-7165. 

Thanks  to  the  SYSOPS  for  permission 
to  use  their  names  and  numbers.  DD| 

Reader  Ballot 

Vote  for  your  favorite  feature/article. 

Circle  Reader  Service  No.  1 94. 


MBOOT  and  MODEM7  (Text  begins  on  page  62) 

Listing  One 


B<MB00T64.  F'RN 

MBOOT 64  -  FILE  DOWNLOAD  PROGRAM 

ORIGINAL  MBOOT  BY  GENE  HEAD 
D.D.J.  OCTOBER »  1982 

MODIFIED  FOR  THE  C-64  BY 
WALT  PIOTROWSKI 


-E  TO  EXIT  TERM  MODE  TO  CP/M 


0000 

“ 

BASE 

EQU 

0 

0005 

= 

f 

EXITCHR 

EOU 

OSH 

»CTL 

0004 

= 

FILCHR 

i 

EQU 

04H 

5  CTL 

0600 

_ 

r 

MD65Z 

EQU 

600H 

rLOC 

1600 

= 

MD65 

EQU 

MD65Z+1000H 

0603 

= 

MDFUNC 

EQU 

MD65Z+3 

JMOD! 

0604 

= 

MDCHAR 

EQU 

MD65Z+4 

f  MODI 

67 

i (6510  SPACE) 


(Continued  on  next  page) 


Dr.  Dobb's  Journal.  January  1985 


MBOOT  and  MODEM7  (Listing  Continued,  text  begins  on  page  62) 

Listing  One 


0605 

= 

MDREC 

EQU 

MD65Z+5 

f MODEM  CHAR  RCVD  FLAG 

F900 

f 

BIOSFN 

EQU 

0F900H 

5BIOS65  FUNCTION  CODE 

F906 

a 

BIOSAD 

EQU 

0F906H 

t MODEM  ROUTINE  INDIRECT  ADDRESS 

CE00 

= 

0N6510 

EQU 

OCEOOH 

5  MEM  LOC  TO  TURN  6510  ON 

000A 

= 

t 

ERRLIM 

EQU 

10 

» NUMBER  OF  TRIES  FOR  ONE  BLOCK 

0001 

— 

f 

SOH 

EQU 

1 

JASCII  CONTROLS 

0004 

= 

EOT 

EQU 

4 

0006 

= 

ACK 

EQU 

6 

0015 

= 

NAK 

EQU 

15H 

0018 

= 

CAN 

EQU 

18H 

000  A 

= 

LF 

EQU 

10 

000D 

= 

CR 

EQU 

13 

0005 

f 

BDOS 

EQU 

BASE+5 

005C 

= 

FCB 

EQU 

BASE+5CH 

0100 

r 

ORG 

BASE+100H 

0100 

210000 

f 

LXI 

H  t  0 

f CLEAR  HL 

0103 

39 

DAD 

SP 

JMAKE  A  COPY  OF  SP 

0104 

222B05 

SHLD 

STCK 

t SAVE  FOR  EXIT 

0107 

312B05 

LXI 

SF' t  STCK 

i POINT  TO  LOCAL  STACK 

010A 

CDF903 

CALL 

INITADR 

i SET  UP  BIOS  CALLS 

oion 

CD3604 

CALL 

ILPRT 

0110 

432D363420 

DB 

' C-64  MBOOT  AS  OF  ' 

0121 

342F312F38 

DB 

'4/1/84 

fCRrLFrO 

012A 

3A5D00 

LDA 

FCB+1 

) LOOK  AT  FILE  NAME 

01 2D 

FE20 

CPI 

/  / 

r BLANK? 

012F 

C25501 

JNZ 

TERM1 

JNO  -  GO  PROCESS 

0132 

CD3604 

CALL 

ILPRT 

» WRITE  ERROR  MSG 

0135 

2B2B4E4F20 

DB 

' ++N0  FILE  NAME  SPEC IFIED++ ' , CR , LF , 0 

0152 

C37304 

JMP 

EXIT 

0155 

CD3604 

9 

TERM1 

CALL 

ILPRT 

? WRITE  FIRST  MSG 

0158 

0D0A544552 

DB 

CR  >  LF i ' 

TERMINAL  MODE ' »  CR  r  LF 

0169 

43544C2D4!: 

DB 

' CTL-E 

EXITS  TO  CP/M' 

017C 

ODOA 

DB 

CR » LF 

017E 

43544C2D44 

DB 

' CTL-D 

STARTS  FILE  XFER ' 

0194 

0D0A00 

DB 

CR  t LF i 0 

0197 

CD7E04 

CALL 

OPMDEM 

fOPEN  FOR  MODEM 

9 

r 

DUMB 

TERMINAL  LOOP 

019A 

CDCC04 

f 

TERM 

CALL 

KYBD 

f CHECK  FOR  KYBD  CHAR 

019D 

FEOO 

CPI 

0 

J  CHAR  RECEIVED? 

019F 

CAAFOl 

JZ 

TERML 

i GO  CHECK  FOR  MODEM  CHAR 

01A2 

FE05 

CPI 

EXITCHR 

fEXIT  TO  CF’M? 

01A4 

CA7304 

JZ 

EXIT 

01A7 

FE04 

CPI 

FILCHR 

f BEGIN  RECEIVING  FILE? 

01A9 

CABF01 

JZ 

RCUFIL 

01  AC 

CD9004 

CALL 

WRMDEM 

; OUTPUT  KYBD  CHAR  TO  MODEM 

01 AF 

CD9E04 

9 

TERhL 

CALL 

RDMDEM 

f INPUT  MODEM  CHAR 

01B2 

FEOO 

CPI 

0 

iO  -  NO  CHAR  RECVD 

01B4 

CA9A01 

JZ 

TERM 

i BACK  TO  MAIN  LOOP 

01B7 

E67F 

ANI 

7FH 

fREMOVE  PARITY 

01B9 

CD1204 

CALL 

TYPE 

i DISPLAY  RECVD  CHAR 

01BC 

C39A01 

JMP 

TERM 

{BACK  TO  MAIN  LOOP 

f 

9 

FILE 

RECEIVE 

01 BF 

CD1403 

9 

RCVFIL 

CALL 

ERASFIL 

} SEE  IF  FILE  EXISTS 

01C2 

CD5B03 

CALL 

MAKEFIL 

i OPENFILE 

( Continued  on  page  70) 


Dr.  Dobb’s  Journal.  January  1985 


68 

47 


MBOOT  and  MODEM7  (Listing  Continued,  text  begins  on  page  62) 
Listing  One 


01C5 

CD3604 

CALL 

ILPRT 

{ PRINT  MSG 

01C8 

46494C4520 

DB 

'FILE  OPEN,  READY  TO  RECEIOE',CR, 

LF ,  0 

01E6 

CD1802 

9 

RCULP 

CALL 

RCOSECT 

{ GO  RECEIOE  A  SECTOR 

01E9 

DAF801 

JC 

RCOEOT 

{ CARRY  SET-SECTOR  IN  OK 

01EC 

CDBA03 

CALL 

WRSECT 

{WRITE  TO  DISK 

01EF 

CD0C03 

CALL 

INCRSNO 

{ INCREMENT  SECTOR  # 

01F2 

CDCE02 

CALL 

SENDACK 

{ALL  OK  -  ACKNOWLEDGE 

01F5 

C3E601 

JMP 

RCOLP 

{ GET  NEXT  SECTOR 

01F8 

CDBA03 

9 

RCVEOT 

CALL 

WRSECT 

{EOT  RCOD-OUTPUT  ALL  TO  DISK 

01FB 

CDCE02 

CALL 

SENDACK 

{  SEND  ACKNOWLEDGE 

01FE 

CD9A03 

CALL 

CLOSFIL 

{ CLOSE  DISK  FILE 

0201 

CD4504 

CALL 

ERXIT 

{EXIT  (NO  ERROR) 

0204 

0D0A545241 

DB 

CR ,  LF,  ' 

TRANSFER  COMPLETE* ' 

0218 

AF 

9 

RCUSECT 

XRA 

A 

{ RECEIOE  A  SECTOR 

0219 

32EE04 

STA 

ERRCT 

} CLEAR  ERROR  CTR 

021C 

060A 

9 

RCORPT 

MOI 

B,  10 

j  LONG  DELAY 

021E 

CBE403 

CALL 

RECO 

{RECEIOE  ONE  CHARACTER 

0221 

DA3102 

JC 

RCOSERR 

{NO  CHAR  -  ERROR 

0224 

FE01 

CPI 

SOH 

{START  OF  HEADER? 

0226 

CA7802 

JZ 

RCOSOH 

{YES-CONTINUE  RECEIOING 

0229 

B7 

ORA 

A 

{CLEAR  CARRY  FOR  NEXT  TRY 

022A 

CA1C02 

JZ 

RCORPT 

{GO  TRY  AGAIN 

022D 

FE04 

CPI 

EOT 

{END  OF  TRANSMISSION? 

022F 

37 

STC 

{YES  -  CARRY=ALL  DONE 

0230 

C8 

RZ 

0231 

0601 

9 

RCOSERR 

MOI 

B,  1 

{REC  ERROR-IGNORE  REST  OF 

TRANSMISSION 

0233 

CDE403 

CALL 

RECO 

{GET  NEXT  CHAR 

0236 

D23102 

JNC 

RCOSERR 

{CHAR  RECOD-GET  ANOTHER 

0239 

3E15 

MOI 

A»NAK 

{NO  CHAR-DATA  ALL  IN 

023B 

CDD002 

CALL 

SEND 

{SEND  NAK  TO  INDICATE  REC 

ERROR 

023E 

3AEE04 

LDA 

ERRCT 

{GET  ERROR  CTR 

0241 

3C 

INR 

A 

{INCREMENT  IT 

0242 

32EE04 

STA 

ERRCT 

{PUT  IT  BACK 

0245 

FEOA 

CPI 

ERRLIM 

{TOO  MANY? 

0247 

C21C02 

JNZ 

RCORPT 

{NO-TRY  AGAIN 

024A 

CD9A03 

9 

RCOSABT 

CALL 

CLOSFIL 

024D 

CD4504 

CALL 

ERXIT 

0250 

2B2B554E41 

DB 

' ++UNABLE  TO  RECEIOE  BLOCK' 

0269 

0D0A2B2B41 

DB 

CR,LF, '++ABORTING++*' 

0278 

0601 

9 

RCOSOH 

MOI 

B,  1 

{SOH  RECEIOED 

027A 

CDE403 

CALL 

RECO 

{SECTOR  NUM  IS  NEXT 

027D 

DA3102 

JC 

RCOSERR 

{NO  CHAR  -  ERROR 

0280 

57 

MOO 

D ,  A 

{SECTOR  NUM  TO  D 

0281 

0601 

MOI 

B,  1 

{SHORT  WAIT 

0283 

CBE403 

CALL 

RECO 

{NEG  OF  SECTOR  NUM  IS  NEXT 

0286 

DA3102 

JC 

RCOSERR 

{NO  CHAR  -  ERROR 

0289 

2F 

CMA 

{MAKE  POSITIOE 

028A 

BA 

CMP 

D 

{SAME? 

028B 

CA9102 

JZ 

RCODATA 

{YES  -  GET  DATA 

028E 

C33102 

JMP 

RCOSERR 

{NOT  SAME  -  ERROR 

0291 

7A 

9 

RCODATA 

MOO 

A ,  D 

{SECTOR  NUM  TO  A 

0292 

32EC04 

STA 

RCOSNO 

{ SAOE  FOR  COMPARISON  WITH 

EXPECTED 

0295 

0E00 

MOI 

C,0 

{CLEAR  CHECKSUM 

0297 

218000 

LX  I 

H , BASE+80H  i BUFFER  ADDRESS 

029A 

0601 

9 

RCOCHR 

MOI 

B,  1 

{SHORT  WAIT 

029C 

CBE403 

CALL 

RECO 

{GET  A  CHARACTER 

(Continued  on  page  72) 

70  Dr.  Dobb’s  Journal.  January  1985 


48 


MBOOT  and  MODEM 7  (Listing  Continued,  text  begins  on  page  62) 

Listing  One 


029F 

DA3102 

JC 

RCOSERR 

f  NO  CHAR  -  ERROR 

02A2 

CD 1204 

CALL 

TYPE 

r*****SECURITY  BLANKET***** 

02A5 

77 

MOO 

Mr  A 

r  CHAR  TO  BUFFER 

02A6 

2C 

INR 

L 

r  POINT  TO  NEXT  BUFFER  SLOT 

02A7 

C29A02 

JNZ 

RCOCHR 

r 128  CHARS  RECEIOED? 

02AA 

51 

MOO 

DrC 

f CHECKSUM  TO  D 

02AB 

0601 

MOI 

Br  1 

r SHORT  WAIT 

02AD 

CDE403 

CALL 

RECO 

r GET  CHECKSUM 

02B0 

BA3102 

JC 

RCOSERR 

f  NO  CHAR  -  ERROR 

02B3 

BA 

CMP 

D 

r RECEIVED  =  COMPUTED? 

02B4 

C23102 

JNZ 

RCOSERR 

( NOT  SAME  -  ERROR 

02B7 

3AEC04 

LDA 

RCOSNO 

r  GET  RECEIOED  SECTOR  NUM 

02BA 

47 

MOO 

BrA 

rINTO  B  FOR  COMPARE 

02BB 

3AED04 

LDA 

SECTNO 

r GET  LAST  SECTOR  NUMBER 

02BE 

B8 

CMP 

B 

f  SAME? 

02BF 

CAC802 

JZ 

RECOACK 

r  REPEAT  -  IGNORE 

02C2 

3C 

INR 

A 

f INCREMENT  LAST 

02C3 

B8 

CMP 

B 

i  SAME  AS  RECEIOED? 

02C4 

C2D402 

JNZ 

ABORT 

f  NO  -  ERROR 

02C7 

C9 

RET 

r RETURN  TO  MAIN  LOOP 

02C8 

CDCE02 

f 

RECVACK 

CALL 

SENDACK 

r HANDSHAKE 

02CB 

C31802 

JMP 

RCOSECT 

f  GET  NEXT  SECTOR 

02CE 

3E06 

9 

SENDACK 

MOI 

Ar  ACK 

02D0 

CD9004 

9 

SEND 

CALL 

WRMDEM 

r  SEND  CHAR  TO  MODEM 

02D3 

C9 

RET 

02D4 

312B05 

9 

ABORT 

LX  I 

SPrSTCK 

rRESTORE  CP/M  STACK  POINTER 

02D7 

0601 

9 

ABORTL 

MOI 

El  r  1 

f  SHORT  WAIT 

02D9 

CDE403 

CALL 

RECO 

r GET  CHARACTER 

02BC 

D2D702 

JNC 

ABORTL 

5L00P  UNTIL  INPUT  STOPS 

02DF 

3E18 

MOI 

ArCAN 

f  CANCEL  CHARACTER 

02E1 

CDD002 

CALL 

SEND 

r  SEND  TO  MODEM 

02E4 

0601 

9 

ABORTW 

MOI 

El  r  1 

r  SHORT  WAIT 

02E6 

CDE403 

CALL 

RECO 

r  GET  CHARACTER 

02E9 

D2E402 

JNC 

ABORTW 

fLOOP  UNTIL  INPUT  STOPS 

02EC 

3E20 

MOI 

Ar'  ' 

r  GET  A  BLANK 

02EE 

CDD002 

CALL 

SEND 

r  SEND  TO  MODEM 

02F1 

CD4504 

CALL 

ERXIT 

r  LEAVE  (ERROR) 

02F  4 

4D424F4F54 

DB 

'MBOOT  PROGRAM  CANCELLED*' 

030C 

3AED04 

9 

INCRSNO 

LDA 

SECTNO 

f  GET  CALCULATED  SECTOR  NUM 

030F 

3C 

INR 

A 

r INCREMENT  IT 

0310 

32ED04 

STA 

SECTNO 

r PUT  IT  BACK 

0313 

C9 

RET 

0314 

115C00 

r 

ERASFIL 

LXI 

DrFCB 

r  FCB  ADDRESS 

0317 

0E1 1 

MOI 

Cr  17 

t SEARCH  FOR  FIRST  CODE 

0319 

CD0500 

CALL 

BDOS 

031C 

3C 

INR 

A 

5255  MEANS  NO  MATCH 

03  IB 

C8 

RZ 

r  NOT  FOUND  IS  OK 

031E 

CD3604 

CALL 

IL.PRT 

r PRINT  MSG 

0321 

2B2B46494C 

DB 

'++FILE 

EXISTS r  TYPE  Y  TO  ERASE: 'rO 

0341 

CD2A04 

CALL 

KEYIN 

r  GET  KEYBOARD  CHAR 

0344 

F5 

PUSH 

PSU 

rSAUE  A  COPY 

0345 

CD1204 

CALL 

TYPE 

rECHO  IT 

0348 

CD0B04 

CALL 

CRLF 

r CAR  RET  LINE  FEED 

034B 

FI 

POP 

PSW 

f  GET  CHAR  BACK 

034C 

E65F 

ANI 

5FH 

r REMOVE  PARITY  AND  CASE 

034E 

FE59 

CPI 

'Y' 

rY  =  ERASE  IT 

(Continued  on  page  74) 


Dr.  Dobb's  Journal,  January  1985 


72 


MBOO  T  and  MODEM7  (Listing  Continued,  text  begins  on  page  62) 
Listing  One 


0350 

C24B04 

JNZ 

MXIT 

f NOT  Y  -  EXIT  PROGRAM 

0353 

1 1 5C00 

LXI 

D  t  FCB 

f  FCB  ABBRESS 

0356 

0E13 

MVI 

C>  19 

iBELETE  FILE  COBE 

0358 

CD0500 

CALL 

BBOS 

035B 

9 

115C00  MAKEFIL 

LXI 

D » FCB 

f  FCB  ABBRESS 

035E 

0E16 

MVI 

C  f  22 

f MAKE  FILE  COBE 

0360 

CB0500 

CALL 

BBOS 

0363 

3C 

INR 

A 

f A=255  IF  ERROR 

0364 

CO 

RNZ 

f RETURN  -  OK 

0365 

CD4504 

CALL 

ERXIT 

{LEAVE  (ERROR) 

0368 

2B2B455252 

DB 

' ++ERR0R 

-  CAN ' ' T  MAKE  FILE'rCR,LF 

0383 

44495245 

43 

BB 

'BIRECTORY  MUST  BE  FULL$ ' 

039A 

115C00 

f 

CLOSFIL 

LXI 

!•  i  FCB 

f  FCB  ABBRESS 

039B 

0E10 

MVI 

Cr  16 

f FILE  CLOSE  COBE 

039F 

CB0500 

CALL 

BBOS 

03A2 

3C 

INR 

A 

f  A=255  IF  ERROR 

03A3 

CO 

RNZ 

;not  zero  is  ok 

03A4 

CB4504 

CALL 

ERXIT 

i LEAVE  (ERROR) 

03A7 

2B2B43414E 

BB 

s  '  ++CAN ' ' 

T  CLOSE  FILE$ ' 

03BA 

s 

r 

WRSECT 

EQU 

t 

t WRITE  SECTOR 

03BA 

115C00 

LXI 

B  t  FCB 

t FCB  ABBRESS 

03BB 

0E15 

MV  I 

C  r  21 

f WRITE  SECTOR  COBE 

03BF 

CB0500 

CALL 

BBOS 

03C2 

B7 

ORA 

A 

r  SET  FLAGS 

03C3 

C2C703 

JNZ 

WRERR 

03C6 

C9 

RET 

03C7 

CB3604 

9 

WRERR 

CALL 

ILPRT 

03CA 

2B2B4552 

52 

BB 

'  ++ERROR 

WRITING  FILE' »CRrLFfO 

03E 1 

C3B402 

JMP 

ABORT 

9 

9 

REAB 

ONE  CHARACTER  FROM  MOBEM 

03E4 

D5 

9 

RE  CV 

PUSH 

B 

03E5 

CBB504 

MWTI 

CALL 

RBMBEM2 

f REAB  MOBEM  CHAR 

03E8 

B2F203 

JNC 

MCHAR 

t NO  CARRY=CHAR  RCVB 

03EB 

05 

BCR 

B 

;becrement  wait  ctr 

03EC 

C2E503 

JNZ 

MWTI 

{WAIT  SOME  MORE  (IN  6510) 

03EF 

B 1 

POP 

B 

03F  0 

37 

STC 

{INBICATE  ERROR 

03F 1 

C9 

RET 

03F2 

B1 

9 

MCHAR 

POP 

B 

03F3 

F5 

PUSH 

PSW 

{SAVE  A  COPY  OF  CHAR 

03F4 

81 

ABB 

c 

fABB  TO  CHECKSUM 

03F5 

4F 

MOV 

Cr  A 

{PUT  CKSUM  BACK  IN  A 

03F6 

FI 

POP 

PSW 

{GET  CHAR  BACK 

03F7 

B7 

ORA 

A 

{CLEAR  CARRY 

03F8 

C9 

RET 

03F9 

2A0100 

9 

INITABR 

LHLB 

BASE+1 

{GET  XFER  BASE  ABBRESS 

03FC 

110300 

LXI 

Dr  3 

{GET  OFFSET 

03FF 

19 

BAB 

B 

{ABB  OFFSET  FOR  STAT 

0400 

222304 

SHLB 

VSTAT+1 

{PUT  IN  CALL 

0403 

19 

BAB 

B 

{ABB  OFFSET  TO  KEYIN 

0404 

222E04 

SHLB 

VKEYIN+1 

0407 

19 

BAB 

D 

fABB  OFFSET  TO  TYPE 

0408 

221804 

SHLB 

VTYPE+1 

* 


74 

50 


(Continued  on  page  76) 
Dr.  Dobb's  Journal.  January  1985 


MBOOT  and  MODEM7  (Listing  Continued,  text  begins  on  page  62) 
Listing  One 


f 


040B 

3E0D 

CRLF 

MOI 

ArCR 

fCARRAIGE  RET 

(MOD 

CD1204 

CALL 

TYPE 

0410 

3E0A 

HOI 

A  t  LF 

fLINE  FEED 

0412 

F5 

► 

TYPE 

PUSH 

F'SW 

f  TYPE  A  CHARACTER 

0413 

C5 

PUSH 

B 

0414 

D5 

PUSH 

D 

0415 

E5 

PUSH 

H 

0416 

4F 

MOO 

C  f  A 

0417 

CD0000 

OTYPE 

CALL 

$-$ 

fCALL  BDOS 

041A 

El 

POP 

H 

04 1 B 

D1 

POP 

D 

04 1 C 

Cl 

POP 

B 

041 D 

FI 

POP 

PSU 

041 E 

C9 

RET 

04 1 F 

C5 

t 

STAT 

PUSH 

'  B 

fCONSOLE  STATUS 

0420 

D5 

PUSH 

D 

0421 

E5 

PUSH 

H 

0422 

CD0000 

OSTAT 

CALL 

$-* 

f  CALL  BDOS 

0425 

El 

POP 

H 

0426 

D1 

POP 

D 

0427 

Cl 

POP 

B 

0428 

B7 

ORA 

A 

0429 

C9 

RET 

042A 

C5 

r 

KEYIN 

PUSH 

B 

►READ  A  KEY 

042B 

D5 

PUSH 

D 

042C 

E5 

PUSH 

H 

042D 

CD0000 

OKEYIN 

CALL 

►CALL  BDOS 

0430 

El 

POP 

H 

0431 

D 1 

POP 

D 

0432 

Cl 

POP 

B 

0433 

E67F 

ANI 

7FH 

0435 

C9 

RET 

0436 

E3 

► 

ILPRT 

XTHL 

►TYPE  A  LINE 

0437 

7E 

► 

ILPLP 

MOO 

AfM 

JGET  CHARACTER 

0438 

B7 

ORA 

A 

►SET  FLAGS 

0439 

CA4304 

JZ 

ILF'RET 

f  ZERO  MEANS  DONE 

043C 

CD1204 

CALL 

TYPE 

►TYPE  CHARACTER 

043F 

23 

I  NX 

H 

fNEXT  MEM  loc 

0440 

C33704 

JMP 

ILPLP 

f  NEXT  CHARACTER 

0443 

E3 

► 

ILF'RET 

XTHL 

►MODIFIED  RETURN  TO  STACK 

0444 

C9 

RET 

0445 

D1 

f 

ERXIT 

POP 

D 

►EXIT  (NOT  ALWAYS  ERROR) 

0446 

0E09 

MOI 

C  ►  9 

►PRINT  EXIT  MESSAGE 

0448 

CD0500 

CALL 

BDOS 

044B 

CD3604 

t 

hXIT 

CALL 

ILPRT 

044E 

0D0A444F4E 

DB 

CR  t LF  ► ' 

DON' 'T  FORGET  TO  DISCONNECT 

MODEM' 

0470 

ODOAOO 

DB 

CR  f LF  f 0 

0473 

CD8704 

f 

EXIT 

CALL 

CLMDEM 

►CLOSE  RS-232  FILE 

0476 

CD0B04 

CALL 

CRLF 

►PRINT  A  FINAL  CRLF 

0479 

2A2B05 

LHLD 

STCK 

►CP/M  STACK  POINTER 

047C 

F9 

SPHL 

►BACK  INTO  SP 

047D 

C9 

RET 

►BACK  TO  CP/M 

f 

f 

6510 

SETUP  ROUTINES 

047E 

3E05 

f 

OPMDEM 

MOI 

A  f  5 

►FILE  OPEN  CMD 

(Continued  on  page  78) 

Dr.  Dobb's  Journal.  January  1985 

76 

SI 


MBOO  T  and  MODEM 7  (Listing  Continued,  text  begins  on  page  62) 

Listing  One 


0480 

320306 

STA 

MBFUNC 

{ STORE  FOR  6510 

0483 

CDD804 

CALL 

G06510 

{ TURN  ON  6510 

0486 

C9 

RET 

0487 

3E04 

9 

CLMBEM 

MVI 

A  r  4 

{  CLOSE  FILE  COBE 

0489 

320306 

STA 

MBFUNC 

5  STORE  FOR  6510 

048C 

CDD804 

CALL 

G06510 

f  TURN  ON  6510 

048F 

C9 

RET 

0490 

F5 

9 

WRMBEM 

PUSH 

PSW 

{ SAVE  A  COPY  OF  CHAR 

0491 

320406 

STA 

MBCHAR 

{ SAVE  FOR  6510 

0494 

3E03 

MVI 

A  t  3 

5  WRITE  MOBEM  COBE 

0496 

320306 

STA 

MBFUNC 

{STORE  FOR  6510 

0499 

CDD804 

CALL 

G06510 

{START  6510 

049C 

FI 

POP 

PSW 

{GET  CHAR  BACK 

04911 

C9 

RET 

049E 

3E01 

{ 

RBMBEM 

MVI 

A 1 1 

{ REAB  FUNCTION 

04A0 

320306 

STA 

MBFUNC 

{STORE  FOR  6510 

04A3 

CDD804 

CALL 

G06510 

{START  6510 

04A6 

3A0506 

LBA 

MBREC 

{SEE  IF  CHAR  RCVB 

04A9 

FE00 

CPI 

0 

{ 0  -  NONE  RECEIVEB 

04AB 

CAB304 

JZ 

RBMNC 

{GO  SET  CARRY 

04AE 

3A0406 

LBA 

MBCHAR 

{GET  CHARACTER 

04B 1 

B7 

ORA 

A 

{SHOW  CHAR  RCVB 

04B2 

C9 

RET 

04B3 

37 

RBMNC 

STC 

{SET  -  NO  CHAR 

04B4 

C9 

RET 

04B5 

3E02 

i 

RDMBEM2 

MO  I 

A  j»  2 

{REAB  WITH  WAIT  FUNCTION 

04B7 

320306 

STA 

MBFUNC 

{STORE  FOR  6510 

04BA 

CDD804 

CALL 

G06510 

{START  6510 

04  BD 

3A0506 

LBA 

MBREC 

{SEE  IF  CHAR  RCVB 

04C0 

FEOO 

CPI 

0 

{0  -  NONE  RECEIVEB 

04C2 

CACA04 

JZ 

RBMNC2 

{GO  SET  CARRY 

04C5 

3A0406 

LBA 

MBCHAR 

{GET  MOBEM  CHAR 

04C8 

B7 

ORA 

A 

{SHOW  CHAR  RCVB 

04C9 

C9 

RET 

04CA 

37 

RBMNC2 

STC 

{CARRY  -  NO  CHAR 

04CB 

C9 

RET 

04CC 

3E06 

{ 

K'YBB 

MVI 

A  r  6 

{REAB  KEYBOARB  FUNCTION 

04CE 

320306 

STA 

MBFUNC 

{STORE  FOR  6510 

04P1 

CDD804 

CALL 

G06510 

{TURN  ON  6510 

04D4 

3A0406 

LBA 

MBCHAR 

{GET  CHARACTER 

04D7 

C9 

RET 

04B8 

E5 

{ 

G06510 

PUSH 

H 

{SAVE  HL 

04U9 

210016 

LXI 

H,MB65 

{GET  6510  RTNE  ABBR 

04DC 

2206F9 

SHLB 

BIOSAB 

{STORE  FOR  BI0S65 

04DF 

3E09 

MVI 

A»  9 

{BIOS  COBE  TO  CALL  SUBPROG 

04E1 

3200F9 

STA 

BIOSFN 

{STORE  FOR  6510 

04E4 

3E01 

MVI 

A,  1 

{1  TURNS  ON  6510 

04E6 

3200CE 

STA 

0N6510 

{TURN  IT  ON 

04E9 

00 

NOP 

{ REQB  FOR  HARBWARE 

04EA 

El 

POP 

H 

{RESTORE  HL 

04EB 

C9 

f 

RET 

OAEC 

00 

RCVSNO 

BB 

0 

04ED 

00 

SECTNO 

BB 

0 

04EE 

00 

ERRCT 

BB 

0 

04EF 

BS 

60 

052B 

STCK 

BS 

2 

052D 

9 

ENB 

End  Listing  One 


78 


Dr.  Dobb  s  Journal.  January  1985 


Listing  Two 


CPMMD65.TX 


LINE# 

LOC 

CODE 

LINE 

00001 

0000 

i****™**********^ 

00002 

0000 

9 

00003 

0000 

9 

MODEM  OR  RS-232 

HANDLER 

00004 

0000 

9 

FOR  USE  WITH  CP/M  MBOOT 

00005 

0000 

9 

00006 

0000 

9 

U.G.  PIOTROWSKI 

00007 

0000 

9 

00008 

0000 

;*************************#****************** 

00009 

0000 

i 

00010 

0000 

i 

EQUATES 

00011 

0000 

i 

00012 

0000 

FILE 

=  128 

.FILE  NUMBER 

00013 

0000 

) 

00014 

0000 

SCNKEY 

=$FF9F 

fKERNAL  ROUTINES 

00015 

0000 

SETLFS 

=$FFBA 

F  ' 

00016 

0000 

SETNAM 

=$FFBD 

F  • 

00017 

0000 

OPEN 

=$FFCO 

F  * 

00018 

0000 

CLOSE 

=$FFC3 

F  * 

00019 

0000 

CHKIN 

=$FFC6 

f  ■ 

00020 

0000 

CHKOUT 

=$FFC9 

F  * 

00021 

0000 

CLRCHN 

=$FFCC 

F 

00022 

0000 

CHROUT 

=$FFD2 

F  * 

00023 

0000 

GETIN 

=$FFE4 

F  * 

00024 

0000 

f 

00025 

0000 

RIBUF 

=  $F7 

t RS-232  INPUT  BUF  ADD 

00026 

0000 

ROBUF 

=  $F9 

i RS-232  OUTPUT  BUF  ADD 

00027 

0000 

RIDBS 

=  $29B 

i RS-232  BUF  END  PTR 

00028 

0000 

RIDBE 

=  $29C 

f RS-232  BUF  STRT  PTR 

00029 

0000 

ENABL 

=  $2A1 

» RS-232  ACTIVE 

00030 

0000 

r 

00031 

0000 

XLATE 

=$2100 

t C64  TO  ASCII  TABLE 

00032 

0000 

•BUFIN 

=$2200 

» RS-232  INPUT  BUFFER 

00033 

0000 

BUFOUT 

=$2300 

i RS-232  OUTPUT  BUFFER 

00034 

0000 

i 

00035 

0000 

*=$1600 

00036 

1600 

! 

00037 

1600 

4C 

06 

16 

MODEM 

JMP  START 

00038 

1603 

00 

FUNC 

.BYTE  0 

r FUNCTION  CODE 

00039 

1604 

00 

CHAR 

.BYTE  0 

f CHARACTER  IN/OUT 

00040 

1605 

00 

CHREC 

.BYTE  0 

5  RS-232  CHAR  RCVD  FLAG 

00041 

1606 

f 

00042 

1606 

A  D 

03 

16 

START 

LDA  FUNC 

t GET  FUNCTION 

00043 

1609 

18 

CLC 

t CLEAR  FOR  ADD 

00044 

160A 

6  D 

03 

16 

ADC  FUNC 

f*2  FOR  TABLE  ACCESS 

00045 

160D 

AA 

TAX 

00046 

1 60E 

DD 

6B 

17 

LDA  ADRTBL-2 f X 

f  GET  LOW  ADDRESS 

00047 

1611 

8D 

IB 

16 

STA  JMPSUB+ 1 

r  PUT  IN  JMP 

00048 

1614 

BD 

6C 

17 

LDA  ADRTBL-1 f X 

»GET  HI  ADDRESS 

00049 

1617 

8  D 

1C 

16 

STA  JMPSUB+2 

( PUT  IN  JMP 

00050 

161A 

4C 

00 

00 

JMPSUB 

JMP  0 

f  JMP  TO  SUBPROGRAM 

00052 

161  E> 

f 

00053 

161D 

t  CLOSE  RS-232  FILE 

AND  CLEAN  UP 

00054 

161D 

9 

00055 

161  Ei 

A9 

80 

CLOSIT 

LDA  #FILE 

fFILE  NUMBER 

00056 

161F 

20 

C3 

FF 

JSR  CLOSE 

fKERNAL 

00057 

1622 

20 

57 

16 

JSR  CLRKBF 

f CLEAR  KEYBOARD  BUFFER 

00058 

1625 

60 

RTS 

00059 

1626 

9 

00060 

1626 

i  OPEN  RS-232  FILE  AND  DO  SETUP 

00061 

1626 

f 

00062 

1626 

20 

ID 

16 

OPEN IT 

JSR  CLOSIT 

fCLOSE-IN  CASE 

00063 

1629 

A9 

80 

LDA  #FILE 

fFILE  NUMBER 

00064 

162B 

A2 

02 

LDX  #2 

f RS-232  DEVICE 

00065 

162D 

AO 

FF 

LDY  *$FF 

f  NO  COMMAND 

00066 

162F 

20 

BA 

FF 

JSR  SETLFS 

} CALL  KERNAL 

(Continued  on  next  page) 


Dr.  Dobb’s  Journal,  January  1985 


79 

53 


MBOO  T  and  MODEM7  (Listing  Continued,  text  begins  on  page  62) 

Listing  Two 


00067 

1632 

A9 

02 

LDA 

♦  2 

5  TWO  CHAR  NAME 

00068 

1634 

A2 

7A 

LUX 

#<SET232 

i LO  ADDRESS 

00069 

1636 

AO 

17 

LDY 

♦>SET232 

f  HI  ADDRESS 

00070 

1638 

20 

BD 

FF 

JSR 

SETNAM 

f KERNAL 

00071 

163B 

20 

CO 

FF 

JSR 

OPEN 

r KERNAL 

00072 

163E 

A2 

00 

LDX 

♦<BUFIN 

f RS232  INBUF  LO  ADDR 

00073 

1640 

AO 

on 

LDY 

#>BUFIN 

5  HI  ADDR 

00074 

1642 

86 

F7 

STX 

RIBUF 

fMOVE  IT 

00075 

1644 

84 

F8 

STY 

RIBUF+l 

00076 

1646 

A2 

00 

LDX 

♦<BUFOUT 

f  RS232  OUTBUF  LO  ADDR 

00077 

1648 

AO 

23 

LDY 

♦>BUFOUT 

5  HI  ADDR 

00078 

164A 

86 

F9 

STX 

ROBUF 

» MOVE  IT 

00079 

164C 

84 

FA 

STY 

ROBUF+1 

00080 

164E 

20 

OE 

17 

JSR 

BLDXTB 

f  BUILD  XLATE  TABLE 

00081 

1651 

A9 

00 

LDA 

*0 

00082 

1653 

8B 

7E 

17 

STA 

LSTCHR 

5  SHOW  LAST  CHAR  AS  A  NULL 

00083 

1656 

60 

RTS 

00084 

1657 

9 

00085 

1657 

i  EMPTY  KEYBOARD  BUFFER 

00086 

1657 

9 

00087 

1657 

20 

9F 

FF 

CLRKBF 

JSR 

SCNKEY 

fKEY  PRESSED? 

00088 

165A 

20 

E4 

FF 

JSR 

GETIN 

f  GET  A  CHARACTER 

00089 

165D 

C9 

00 

CMP 

*0 

fIS  IT  A  NULL 

00090 

165F 

no 

F6 

BNE 

CLRKBF 

f NOT  A  NULL  -  GET  NEXT 

00091 

1661 

60 

RTS 

00092 

1662 

9 

00093 

1662 

i  CHARACTER  OUTPUT 

TO  RS-232 

00094 

1662 

i 

00095 

1662 

A2 

80 

OUTPUT 

LDX 

♦  FILE 

f GET  FILE  NUM 

00096 

1664 

20 

C9 

FF 

JSR 

CHKOUT 

fOPEN  FOR  OUTPUT 

00097 

1667 

90 

06 

BCC 

OTOK 

t CARRY  SET  IS  ERROR 

00098 

1669 

20 

26 

16 

JSR 

OPEN IT 

f MUST  BE  CLOSED 

00099 

166C 

4C 

62 

16 

JMP 

OUTPUT 

t TRY  AGAIN 

00100 

166F 

AD 

04 

16 

OTOK 

LDA 

CHAR 

i GET  CHARACTER  AGAIN 

00101 

1672 

20 

D2 

FF 

JSR 

CHROUT 

f  KERNAL 

00102 

1675 

AD 

A1 

02 

UAITOT 

LDA 

ENABL 

i GET  STATUS 

00103 

1678 

29 

01 

AND 

♦  1 

f STILL  RUNNING  BIT 

00104 

167A 

DO 

F9 

BNE 

UAITOT 

f HANG  UNTIL  DONE 

00105 

1 67C 

20 

CC 

FF 

JSR 

CLRCHN 

f CLEAR  CHANNEL 

00106 

167F 

60 

RTS 

00107 

1680 

f 

00108 

1680 

i  CHARACTER  INPUT 

FROM  RS-232 

00109 

1680 

i 

00110 

1680 

A2 

80 

INPUT 

LDX 

♦  FILE 

r FILE  NUMBER 

00111 

1682 

20 

C6 

FF 

JSR 

CHKIN 

fOPEN  FOR  INPUT 

00112 

1685 

90 

06 

BCC 

INOK 

i CARRY  SET  IS  ERROR 

00113 

1687 

20 

26 

16 

JSR 

OPEN IT 

f  MUST  BE  CLOSED 

00114 

1 68A 

4C 

80 

16 

JMP 

INPUT 

5  TRY  AGAIN 

00115 

168EI 

A9 

00 

INOK 

LDA 

♦  0 

f ZERO  FOR  CLEAR 

00116 

168F 

8D 

05 

16 

STA 

CHREC 

t SHOW  NO  CHAR  RCVD 

00117 

1692 

AD 

A1 

02 

LDA 

ENABL 

5STATUS  byte 

00118 

1695 

29 

02 

AND 

♦  2 

f RECEIVING  BIT 

00119 

1697 

DO 

10 

BNE 

WAIT 

^RUNNING  -  WAIT 

00120 

1699 

AD 

9B 

02 

LDA 

RIDBS 

f  GET  BUF  START  PTR 

00121 

1 69C 

CD 

9C 

02 

CMP 

RIDBE 

» COMPARE  TO  END 

00122 

169F 

FO 

ID 

BEG 

INEX 

{NOTHING  IN  BUFFER 

00123 

1 6A1 

A9 

01 

LDA 

♦  1 

fONE  FOR  SET 

00124 

1 6A3 

8D 

05 

16 

STA 

CHREC 

f SHOW  CHAR  RECEIVED 

00125 

16A6 

4C 

B8 

16 

JMP 

INGET 

f  GET  THE  CHAR 

00126 

16A9 

AD 

A1 

02 

WAIT 

LDA 

ENABL 

fSTATUS  BYTE 

00127 

16AC 

29 

02 

AND 

♦  2 

fRECEIVING  BIT 

00128 

16AE 

FO 

08 

BEG 

INGET 

fNOT  RUNNING  -  EXIT 

00129 

1 6B0 

A9 

01 

LDA 

♦  1 

fONE  FOR  SET 

00130 

16B2 

8D 

05 

16 

STA 

CHREC 

f  SHOW  CHAR  RECEIVED 

00131 

16B5 

4C 

A9 

16 

JMP 

WAIT 

f WAIT  UNTIL  IN 

00132 

16B8 

20 

E4 

FF 

INGET 

JSR 

GETIN 

f  GET  CHARACTER 

00133 

16BB 

8D 

04 

16 

STA 

CHAR 

f STORE  FOR  CP/M  . 

(Continued  on  page  82) 


80 

54 


Dr.  Dobb's  Journal.  January  1985 


MBOO  T  and  MODEM 7  (Listing  Continued,  text  begins  on  page  62) 

Listing  Two 


00134 

16BE 

20 

CC 

FF 

INEX 

JSR 

CLRCHN 

00135 

16C1 

60 

RTS 

00136 

16C2 

9 

00137 

16C2 

i  CHARACTER  INPUT 

FROM  RS-232  WITH  WAIT  LOOP 

00138 

16C2 

» 

00139 

16C2 

A9 

14 

INPUT2 

LDA 

#20 

fOUTER  LOOP  LIMIT 

00140 

1 6C4 

8D 

7D 

17 

STA 

OTCTR 

i PUT  IN  LOOP  CONTROL  LOC 

00141 

16C7 

A9 

00 

LDA 

*0 

t INNER  LOOP  COUNTS  AROUND 

00142 

16C9 

8D 

7C 

17 

STA 

INCTR 

i PUT  IN  LOOP  CONTROL  LOC 

00143 

16CC 

20 

80 

16 

IN2LP 

JSR 

INPUT 

i SEE  IF  THERE'S  A  CHARACTER 

00144 

16CF 

AD 

05 

16 

LDA 

CHREC 

t GET  RECEIVED  CHAR 

00145 

16D2 

DO 

OA 

BNE 

IN2EX 

fO  -  NONE  RECEIVED 

00146 

16D4 

CE 

7C 

17 

DEC 

INCTR 

i COUNT  DOWN  INNER  LOOP 

00147 

16D7 

DO 

F3 

BNE 

IN2LP 

t  WAIT 

00148 

16D9 

CE 

7D 

17 

DEC 

OTCTR 

» COUNT  DOWN  OUTER  LOOP 

00149 

16  DC 

DO 

EE 

BNE 

IN2LP 

?  WAIT  SOME  MORE 

00150 

16DE 

60 

IN2EX 

RTS 

00151 

16DF 

5 

00152 

1 6DF 

5  INPUT  KEYBOARD 

CHARACTER  AND  DEBOUNCE 

00153 

16DF 

) 

00154 

16DF 

20 

9F 

FF 

KBDCHR 

JSR 

SCNKEY 

t SCAN  THE  KEYBOARD 

00155 

16E2 

20 

E4 

FF 

JSR 

GETIN 

i GET  THE  CHARACTER 

00156 

16E5 

CD 

7E 

17 

CMP 

LSTCHR 

i SAME  AS  LAST  CHAR? 

00157 

16E8 

DO 

05 

BNE 

KBNCHR 

i NO  -  NEW  CHARACTER 

00158 

1 6E  A 

A9 

00 

LDA 

*0 

i SAME  -  NEED  A  NULL 

00159 

16EC 

4C 

F2 

16 

JMP 

KBDBNC 

, GO  TO  DEBOUNCE  LOOP 

00160 

16EF 

8D 

7E 

17 

KBNCHR 

STA 

LSTCHR 

» NEW  CHAR  -  SAVE  IT 

00161 

1 6F2 

AO 

07 

KBDBNC 

LDY 

#7 

i DEBOUNCE  OUTER  LOOP 

00162 

16F4 

A2 

00 

LDX 

#0 

i INNER  LOOP  INIT 

00163 

16F6 

C9 

00 

CMP 

#0 

fREPEATING  CHAR  LO  LIM 

00164 

16F8 

FO 

06 

BEQ 

KBDDLY 

5  NULL  IS  OK 

00165 

16FA 

C9 

21 

CMP 

*33 

» SPACE  IS  HI  LIM 

00166 

16FC 

BO 

02 

BCS 

KBDDLY 

f 33  OR  MORE  IS  OK 

00167 

1 6FE 

AO 

32 

LDY 

#50 

fLONG  DELAY  FOR  RPTG  CHAR 

00168 

1700 

CA 

KBDDLY 

DEX 

i DELAY  LOOP 

00169 

1701 

DO 

FD 

BNE 

KBDDLY 

r INNER  LOOP 

00170 

1703 

88 

DEY 

00171 

1704 

DO 

FA 

BNE 

KBDDLY 

fOUTER  LOOP 

00172 

1706 

AA 

TAX 

f CHARACTER  TO  A 

00173 

1707 

BD 

00 

21 

LDA 

XLATE  » X 

f GET  ASCII  VALUE 

00174 

170A 

8D 

04 

16 

STA 

CHAR 

» STORE  FOR  Z-80 

00175 

1 70D 

60 

RTS 

00176 

170E 

f 

00177 

170E 

i  BUILD  ASCII  TRANSLATION  TABLE 

00178 

1 70E 

9 

00179 

1 70E 

A2 

00 

BLDXTB 

LDX 

#0 

$  LOOP  TO  CLEAR  TABLE 

00180 

1710 

A9 

00 

LDA 

*0 

00181 

1712 

9D 

00 

21 

BLDX1 

STA 

XLATE, X 

00182 

1715 

E8 

INX 

00183 

1716 

DO 

FA 

BNE 

BLDX1 

f  LOOP 

00184 

1718 

9 

00185 

1718 

A2 

00 

LDX 

*0 

f START  INDEX 

00186 

171A 

AO 

41 

LDY 

#65 

, STOP  INDEX+1 

00187 

171C 

A9 

00 

LDA 

*0 

f  NULL 

00188 

171E 

20 

5D 

17 

JSR 

MVINTB 

00189 

1721 

A2 

41 

LDX 

#65 

>  LOWER  CASE 

00190 

1723 

AO 

5B 

LDY 

*91 

00191 

1725 

A9 

61 

LDA 

#97 

00192 

1727 

20 

5  Ei 

17 

JSR 

MVINTB 

00193 

172A 

A2 

5B 

LDX 

*91 

f  MISC  CHARACTERS 

00194 

1 72C 

AO 

60 

LDY 

#96 

00195 

1 72E 

A9 

5B 

LDA 

#91 

00196 

1730 

20 

5  Ei 

17 

JSR 

MVINTB 

00197 

1733 

A2 

Cl 

LDX 

#193 

» UPPER  CASE 

00198 

1735 

AO 

DB 

LDY 

*219 

00199 

1737 

A9 

41 

LDA 

#65 

(Continued  on  page  84) 


Dr.  Dobb's  Journal.  January  1985 


82 

55 


MBOOT  and  MODEM7  (Listing  Continued,  text  begins  on  page  62) 

Listing  Two 


00200 

1739 

20 

5D 

17 

JSR  MVINTB 

00201 

173C 

A9 

00 

LDA  #0 

00202 

173E 

8D 

1C 

21 

STA  XLATE+28 

00203 

1741 

8D 

ID 

21 

STA  XLATE+29 

00204 

1744 

8D 

IE 

21 

STA  XLATE+30 

00205 

1747 

8D 

IF 

21 

STA  XLATE+31 

00206 

1 74A 

A9 

08 

LDA  #8 

f  BACKSPACE 

00207 

174C 

8D 

14 

21 

STA  XLATE+20 

rlNST  DEL 

00208 

174F 

8D 

94 

21 

STA  XLATE+148 

fINST  DEL  UC 

00209 

1752 

A9 

OD 

LDA  #13 

fCR 

00210 

1754 

8D 

8D 

21 

STA  XLATE+141 

f RETURN  UC 

00211 

1757 

A9 

20 

LDA  #32 

f SPACE 

00212 

1759 

8D 

AO 

21 

STA  XLATE+160 

f SPACE  UC 

00213 

175C 

60 

RTS 

00214 

175D 

V 

00215 

175D 

8C 

79 

17 

MVINTB 

STY  MXSTOP 

fSAVE  STOP  VAL  IN  MEMORY 

00216 

1760 

9D 

00 

21 

MVINT1 

STA  XLATErX 

f PUT  ASCII  EQUIV  IN  TABLE 

00217 

1763 

18 

CLC 

f CLEAR  FOR  ADD 

00218 

1764 

69 

01 

ADC  #1 

fNEXT  CHAR  CODE 

00219 

1766 

E8 

INX 

fNEXT  TABLE  SLOT 

00220 

1767 

EC 

79 

17 

CPX  MXSTOP 

f COMPARE  TO  STOP  VALUE 

00221 

176A 

DO 

F4 

\ 

BNE  MVINT1 

fLOQP 

00222 

176C 

60 

RTS 

00223 

176D 

i 

00224 

176D 

i  DATA 

00225 

176D 

r 

00226 

176D 

80 

16 

ADRTBL 

.WORD  INPUT 

f SUBPROG  XFER  TABLE 

00227 

176F 

C2 

16 

.WORD  INPUT2 

f  IN  ORDER  OF  REG  CODE 

00228 

1771 

62 

16 

.WORD  OUTPUT 

00229 

1773 

ID 

16 

.WORD  CLOSIT 

00230 

1775 

26 

16 

.WORD  OPEN IT 

00231 

1777 

DF 

16 

.WORD  KBDCHR 

00232 

1779 

00 

MXSTOP 

.BYTE  0 

00233 

177A 

06 

SET232 

.BYTE  6 » 0 

f  RS232  PARAMS 

00233 

177B 

00 

00234 

177C 

00 

INCTR 

.BYTE  0 

fLOOP  CTR  -  INPUT2 

00235 

177D 

00 

OTCTR 

.BYTE  0 

fLOOP  CTR  -  INPUT2 

00236 

177E 

00 

LSTCHR 

.BYTE  0 

fLAST  KEYBOARD  CHAR 

00237 

177F 

.END 

ERRORS  =  00000 
SYMBOL  TABLE 


SYMBOL  VALUE 


ADRTBL 

176D 

BLDX1 

1712 

BLDXTB 

170E 

BUFIN 

2200 

BUFOUT 

2300 

CHAR 

1604 

CHKIN 

FFC6 

CHKOUT 

FFC9 

CHREC 

1605 

CHROUT 

FFD2 

CLOSE 

FFC3 

CLOSIT 

161D 

CLRCHN 

FFCC 

CLRKBF 

1657 

ENABL 

02A1 

FILE 

0080 

FUNC 

1603 

GETIN 

FFE4 

IN2EX 

16DE 

IN2LP 

16CC 

INCTR 

177C 

INEX 

16BE 

INGET 

16B8 

INOK 

168D 

INPUT 

1680 

INPUT2 

16C2 

JMPSUB 

161 A 

KBDBNC 

16F2 

KBDCHR 

16DF 

KBDDLY 

1700 

KBNCHR 

16EF 

LSTCHR 

177E 

MODEM 

1600 

MVINT1 

1760 

MVINTB 

1 75D 

MXSTOP 

1779 

OPEN 

FFCO 

OPENIT 

1626 

OTCTR 

177D 

OTOK 

1 66F 

OUTPUT 

1662 

RIBUF 

00F7 

RIDBE 

029C 

RIDBS 

029B 

ROBUF 

00F9 

SCNKEY 

FF9F 

SET232 

177A 

SETLFS 

FFBA 

SETN AM 
XLATE 

FFBD 

2100 

START 

1606 

WAIT 

16A9 

WAITOT 

1675 

END  OF  ASSEMBLY 


84 

56 


End  Listing 

Dr.  Dobb’s  Journal,  January  1985 


Unstructured  Forth 

Programming 

An  Introduction 

by  Richard  Wilton  ne  of  the  features  of  Forth  is 

structions,  were  somehow  built  into 

1  1  that  it  is  a  “structured”  lan- 

Forth?  For  that  matter,  wouldn’t  it  be 

guage.  This  means  that  Forth 

nice  every  once  in  a  while  to  be  able  to 

programs  are  written  in  variously  sized 

fall  back  on  that  old  Fortran  standby, 

chunks  that  fit  together  like  a  Chinese 

the  computed  GOTO? 

box  puzzle.  This  is  in  contrast  to  “un- 

Of  course  it  would!  It  is  the  purpose 

structured”  Fortran  or  BASIC  pro- 

of  this  little  article  to  fill  in  the  gaps  in 

grams,  which  are  written  in  variously 

the  pristine  structure  of  Forth,  to  repair 

sized  chunks  that  fit  together  like  the 

the  glaring  omissions  we’ve  just  men- 

noodles  on  a  plate  of  spaghetti.  This  is 

tioned,  and  to  restore  a  little  unstruc- 

what  makes  “structured”  program- 

tured  sanity  to  the  deeply  nested,  block- 

ming  languages  such  as  Forth  so  much 

structured  world  of  the  dedicated  Forth 

superior  to  “unstructured"  languages 

hacker.  Note:  all  examples  are  written 

such  as  Fortran. 

in  Forth-83.  FIG-Forth  and  Forth-79 

Nowadays,  Fortran  has  acquired 

users  will  have  to  adjust  the  “tick”  and 

roughly  the  same  status  among  recent 

ROLL  references  accordingly. 

computer  science  graduates  as  Middle 

English  or  Aramaic.  Nevertheless, 

The  GOTO  Statement 

there  are  still  some  of  us  around  who 

not  only  remember  what  Fortran  is,  but 

:  GOTO  (  cfa  —  )  R>  DROP 

have  actually  written  programs  in  it.  In 

EXECUTE; 

fact,  there  are  still  a  few  people  who 

think  that  “Forth”  is  just  an  abbrevia- 

This  little  gem  of  a  definition  expects 

tion  for  IBM’s  Fortran  4(H)  compiler. 

the  code  field  address  of  a  Forth  defini- 

Let’s  face  it:  despite  the  universal 

tion  on  the  stack.  You’ll  note,  however, 

" What  Forth  programmer  doesn't  long  for  a  simple , 

unstructured \  unconditional  GOTO  every 

once  in 

awhile ?" 

scorn  heaped  upon  it  by  “modern” 

that  control  passes  unconditionally  to 

structured  programming  disciples, 

that  definition.  This  eliminates  the 

good  old  Fortran  has  its  strong  points. 

need  for  leaving  status  flags,  return 

In  fact,  in  certain  situations,  the  oblig- 

codes,  and  other  assorted  garbage  on 

atory  structured  nature  of  Forth 

the  data  stack.  Using  GOTO  also 

makes  elegant  programming  impossi- 

makes  it  easy  to  extract  yourself  from 

ble.  What  Forth  programmer  doesn’t 

those  messy  BEGIN-WHILE-RE- 

long  for  a  simple,  unstructured,  uncon- 

PEAT’S  and  IF-ELSE-THEN’s  you’ve 

ditional  GOTO  every  once  in  a  while? 

nested  ten  deep. 

Who  doesn’t  wish  that  the  nice, 

straightforward  arithmetic  IF,  which 

The  Arithmetic  IF  Statement 

can  be  expressed  so  concisely  in  For- 

tran  or  in  two  or  three  machine  in- 

:  AIF  (  cfa_A  cfa_B  cfa_C  n  —  ) 

?  DUP  0  = 

IF  ROT 

Richard  Wilton,  Laboratory  Micro- 

ELSE  0> 

systems,  P.O.  Box  10430,  Marina  Del 

IF  ROT  ROT 

Rey,  CA  90295. 

THEN 

Dr.  Dobb’s  Journal,  January  1985 


86 

57 


THEN  2DROP  GOTO ; 

The  arithmetic  IF  statement  is  obvi¬ 
ous  in  concept:  transfer  control  to  point 
A  if  n  is  negative,  to  point  B  if  it’s  zero, 
and  to  point  C  if  it’s  positive.  This  is 
the  sort  of  thing  you  do  all  the  time  in 
assembly  language: 

OR  AX, AX 
JS  POINT_A 
JZ  PO!NT_B 
POINT_  C: 

But  just  try  to  express  that  succinct¬ 
ly  in  Forth: 

DUP  0<  IF  [’]  POINT _ A 

ELSE  0  =  IF  [’]  POINT_B 
ELSE  [’]  POINT_C 
THEN 
THEN 
EXECUTE 

Pretty  clumsy,  right?  Now  here’s 
the  elegant  solution,  which  is  concise 
and  straightforward  because  it  ignores 
the  dogma  of  block-structuring: 

:  EXAMPLE  (  n  —  ) 

[’]  POINT_A 


[’]  POINT_B 
[’]  P01NT_C 
3  ROLL  AIF; 

The  Computed  GOTO 
Statement 

As  a  final  example,  we  implement  For¬ 
tran’s  familiar 

GOTO  (xl,x2,x3,  .  .  .  ,xn),i 

a  control  statement  so  elegant  that  it 
was  copied  almost  verbatim  by  the  cre¬ 
ators  of  BASIC.  Making  it  work  in 
Forth  requires  setting  up  a  table  of 
code  field  addresses  in  advance.  Then 
you  simply  index  the  table  and  GOTO 
the  right  address. 

:  CGOTO  (  cfa_table  n  —  )  2*  + 

@  GOTO ; 

The  utility  of  using  CGOTO,  rather 
than  a  viper’s  nest  of  IF-ELSE-THEN’s 
or  CASE  statements,  is  obvious  to  any 
competent  programmer  and  is  not 
worth  belaboring  here. 

The  more  astute  reader  might  ob¬ 
serve  at  this  point,  “But  what  about 
statement  numbers?”  Well,  folks,  if 


you  think  about  it  for  a  moment,  you’ll 
realize  that  statement  numbers  make 
good  sense  in  Forth.  For  one  thing, 
numbered  statememts  would  make  it 
easy  to  do  “source-directed”  editing, 
thus  eliminating  the  need  for  those 
clumsy,  space-wasting  screen  files. 
Also,  silly  philosophical  debates  about 
the  “proper”  names  for  Forth  defini¬ 
tions  would  be  unnecessary  if  Forth 
definitions  were  numbered  instead  of 
named. 

By  now  it  should  be  obvious  how 
much  can  be  gained  by  writing  un¬ 
structured  Forth  programs.  No  doubt, 
further  improvements  could  be  ob¬ 
tained  by  incorporating  elements  of 
PL/1  or  even  COBOL  into  Forth. 

Of  course,  such  improvements  must 
be  left  as  an  exercise  for  the  reader. 
We’re  too  busy  adapting  the  syntax  of 
Forth  to  fit  onto  80-column  punched 
cards. 


DD) 

Reader  Ballot 

Vote  for  your  favorite  feature/article. 
Circle  Reader  Service  No.  195. 


87 


Dr.  Dobb  s  Journal,  January  1985 


APL*PLUS/  PC,  Version  3.1; 
TOOLS,  Volume  1;  FINAN¬ 
CIAL  8.  STATISTICAL 
LIBRARY, 

Version  1 .0 

Company:STSC,  Inc.,  2115  East 
Jefferson  St.,  Rockville,  MD 
20852 

Computer:  192K  IBM  PC  (or  com¬ 
patible)  with  one  or  more  disk 
drives;  TOOLS  requires  256K 
and  MSDOS  Version  2.0  or  later 
Price:  $595for  APL*PLUS/PC; 

$295  for  TOOLS;  $275  for 
LIBRARY 

Circle  Reader  Service  No.  129 
Reviewed  by  Stefan  H.  Unger 

I  spent  several  months  wondering 
which  advanced  language  I  would  get 
for  my  PC.  At  the  time  there  was  a  lot 
of  press  about  C,  but  in  the  back  of  my 
mind  I  really  wanted  to  get  APL.  The 
best  APL  was  reputedly  from  STSC, 
Inc.,  but  I  felt  it  was  a  little  too  expen¬ 
sive  for  my  home  use. 

I  had  programmed  in  APL  since 
about  1972,  mostly  statistical  routines 
that  I  needed  in  my  work  in  computer- 
aided  drug  design.  In  fact,  I  had  even 
published  some  APL  functions,  but  I 
was  certainly  not  what  you  would  call 
a  hard-core  convert  to  APL.  This  lan¬ 
guage  simply  offered  me — as  a  chem¬ 
ist  and  not  as  a  programmer — the 
greatest  freedom  to  be  creative  without 
worrying  about  the  stultifying  detail 
required  by  most  other  languages.  At 
work  I  originally  had  used  a  STSC  APL 
time-sharing  service,  switched  to  an¬ 
other  APL  time-sharing  service  for  sev¬ 
eral  years,  then  ported  everything  to 
our  inhouse  IBM  3081  VS  APL  4.0  sys¬ 
tem.  However,  recently  I  have  been 
busy  with  computer  graphics  and 
quantum/molecular  mechanics  and 
not  statistics  (i.e.,  APL)  at  all. 

Having  been  exposed  to  the  ease  and 
elegance  of  APL,  I  found  that  learning 


BASIC  for  my  PC  was  more  self-flagel¬ 
lation  than  enlightenment.  It  was  slow, 
awkward,  and,  well,  silly  (Isn’t  it  silly 
to  have  to  dimension  variables?  After 
all,  computers  should  do  that  kind  of 
stuff  for  you. . . .). 

Turbo  Pascal — with  its  easier  user 
interface — was  not  yet  getting  much 
press,  and  the  language  didn’t  seem  to 
offer  any  particular  advantages  over 
Fortran  or  BASIC  in  general  approach. 
So  I  bought  a  C  compiler,  knowing  C 
might  come  in  handy  at  work.  At  least 
it  would  run  faster.  Unfortunately,  it 
was  a  disaster.  I  couldn’t  accept  the 
amount  of  time  it  took  to  compile  and 
link  after  each  minor  debugging  step, 
and,  worst  of  all,  I  couldn’t  really  make 
much  sense  of  the  I/O  function  calls . . . 
it  all  seemed  so  needlessly  complex. 

At  this  low  point  in  my  experience 
with  C,  I  was  given  the  opportunity  of 
reviewing  STSC’s  APL*PLUS/PC  sys¬ 
tem  for  DDJ.  The  irony  of  this  is  that 
DDJ  is  such  a  staunch  advocate  of  C 
(which  means  that  I  have  now  enraged 
most  of  my  audience).  Of  course,  I’m 
not  proposing  that  APL  and  C  are 
equivalent  or  even  on  the  same  level. 
For  example,  some  implementations  of 
APL  have  been  done  in  C  (e.g.,  Dyalog 
APL),  but  not  vice  versa.  (A  recent  ar¬ 
ticle  by  D.  Saunders,  “Unix,  C  and 
APL,”  Unix/World,  1[6]  ,59,  1984,  in 
fact  argues  that  the  two  languages  are 
naturally  complementary.)  But  for  the 
average  PC  applications  developer,  is 
APL  worth  a  serious  look?  Read  on! 

First  of  all,  I  think  learning  well- 
fined  APL  symbols  is  less  ridiculous 
than  trying  to  match  braces  in  C  or 
remembering  the  dozens  of  function 
calls  with  their  nonstandard  syntax 
and  location.  The  only  real  problem 
with  APL  is  the  character  set  and  some 
of  the  difficulties  that  it  causes  in 
graphics/printer  interfaces.  Of  course, 
C  “gets  closer  to  the  machine,”  but 
STSC  APL*PLUS/PC  allows  interfaces 


to  outside  assembly  code,  DOS,  DOS 
files,  interrupts,  peeking/poking,  and 
so  on.  It  is  also  much  easier  to  learn, 
debug,  and  use,  being  what  Saunders 
calls  a  “very  high-level  language.” 
From  descriptions  of  the  user  interface 
to  Turbo  Pascal  (full-screen  editing, 
pointers  between  source  and  object 
code,  fast  compilations),  I  would  say 
that  APL  is  at  least  as  good  as — if  not 
considerably  better  than — Turbo  Pas¬ 
cal  in  this  regard. 

The  System 

The  APL*PLUS/PC  system  is  supplied 
on  two  diskettes,  and  a  special  APL 
character  ROM  replaces  the  one  on 
your  graphics  card  (the  IBM  version  of 
APL  uses  software-generated  charac¬ 
ters  and  requires  both  a  color  monitor 
and  8087).  Special  ROMs  are  available 
for  IBM,  AT&T,  Compaq,  Columbia, 
Eagle,  Hyperion  and  Televideo  micro- 
comuters.  Corona  is  supported,  but 
without  graphics,  Seequa  is  supported 
without  communications  and  Wang  is 
supported  without  graphics  and  sound. 
The  IBM  AT,  other  MSDOS  computers 
and  the  DEC  Rainbow  are  scheduled 
for  support.  (Although  all  of  these  ma¬ 
chines  are  “supported”,  there  might  be 
some  tradeoffs  in  performance.  Not  all 
graphics  boards  are  supported,  either; 
check  carefully.) 

Only  the  least  useful  characters  of 
the  original  character  set  have  been  re¬ 
placed  by  the  special  APL  characters, 
so  the  system  has  virtually  no  impact 
on  ordinary  word  processing,  spread¬ 
sheets,  and  so  on.  The  system  requires 
192K  and  PC  or  MSDOS  with  one  or 
more  disk  drives.  Documentation  in  is 
four  IBM-style  loose-leaf  binders  with 
sections  on  installation,  user’s  guide, 
introductory  tutorial,  formatting,  files, 
programmer’s  manual,  system  func¬ 
tions,  and  a  comprehensive  index.  Col¬ 
or-coded  APL  keyboard  labels  are  sup¬ 
plied — the  tackiest  part  of  the 


Dr.  Dobb's  Journal,  January  1985 


88 

59 


package.  The  Gilman  and  Rose  text¬ 
book  classic  APL:  An  Interactive  Ap¬ 
proach  and  a  quarterly  newsletter  are 
also  provided.  You  also  get  on  the 
STSC  mailing  list,  which  will  definitely 
keep  your  mailbox  full. 

Other  sources  of  information  are 
available  for  additional  cost:  two  vol¬ 
umes  of  Collected  Whizbangs  by  Roy 
Sykes,  Jr.  (a  fantastic  collection  of  ad¬ 
vanced  tricks)  and  a  volume  entitled 
APL  in  Practice:  What  You  Need  to 
Know  to  Install  and  Use  Successful 
APL  Systems  and  Major  Applications, 
edited  by  Rose  and  Schick  (to  assuage 
any  remaining  doubt  that  APL  is  not 
taken  seriously  by  the  business  world). 
The  Rose  and  Schick  book  is  directed 
toward  mainframe  APL  uses,  but  the 
overview  is  good  and  a  number  of 
chapters  contain  some  generally  useful 
advice,  particularly  in  the  third  part  of 
the  book:  “The  Core  of  APL.” 

STSC  has  also  announced  the  first 
collection  of  application  development 
TOOLS  ($295,  requires  256K,  PC  or 
MSDOS  at  2.0  or  later  and  APL* 
PLUS/PC  at  3.0  or  later),  which  pro¬ 
vides  communications,  screen  manage¬ 
ment,  report  and  output  formatting, 
disk  and  file  management,  software 
development  tools,  and  two  games. 

Additionally,  STSC  has  developed  a 
Financial  and  Statistical  Library 
($275)  for  the  PC,  containing  many 
primitive  functions  that  can  be  incor¬ 
porated  into  application  programs;  this 
means  no  window  dressing  on  the  out¬ 
put,  although  “dressed”  versions  are 
also  available  for  some  of  the  func¬ 
tions.  The  package  is  overpriced  for 
what  you  get.  For  example,  my  favor¬ 
ite  correlation  coefficient  program  is 
faster  than  the  one  supplied  in  STA¬ 
TISTS  not  all  of  the  terms  are  defined 
in  the  manual  (e.g.,  Durbin- Watson 
Statistic);  there  doesn’t  appear  to  be 
any  ANOVA,  a  common  feature  of 
most  statistical  packages  (at  least  not 
in  the  index  nor  any  of  the  obvious 
places);  and  we  get  the  program  FED- 
TAX79,  my  favorite  ...  it  is  1984,  isn’t 
it?  The  list  goes  on. 

A  new  addition  to  STSC’s  product 
line  is  Pocket  APL,  which  is  a  simpli¬ 
fied  and  more  affordable  version  of 
APL  (only  $95).  It  requires  1 28K  RAM 
but  uses  a  soft  character  set  (instead  of 
the  special  ROM)  for  color  systems 
and  the  keyword  APL  system  for 

89 


monochrome.  Communications, 
graphics,  full  files  and  workspaces,  and 
the  full-screen  editor  are  not  support¬ 
ed,  nor  is  there  an  interface  to  DOS. 
However,  it  does  supply  on-line  calcu¬ 
lator  mode,  full-screen  cursor  control, 
on-line  help,  a  file  system,  []  FMT  for¬ 
matting,  error  trapping  features,  and 
over  50  system  functions.  STSC  appar¬ 
ently  believes  that  Pocket  APL  will  be 
the  Turbo  of  the  APL  world. 

STSC  has  also  solved  the  major 
problem  for  applications  developers 
who  want  to  use  APL,  namely  the  re¬ 
quirement  of  a  full  APL  interpreter. 
Because  the  cost  of  the  STSC  system  is 
at  least  $595,  this  used  to  be  a  major 
drawback.  Now,  a  Run  Time  System  is 
available  for  a  license  fee  of  from  $50 
to  $100,  depending  upon  quantity  and 
possibly  your  negotiating  skills.  The 
Run  Time  System  allows  applications 
packages  that  buffer  the  user  from 
APL;  the  user  has  no  APL  directly 
available.  Neither  calculator  mode  nor 
definition-editing-display  of  functions 
is  available.  In  fact,  the  APL  character 
ROM  is  not  required,  and  the  keyboard 
stays  in  text  mode.  The  full-screen  edi¬ 
tor  can  be  used  only  to  edit  variables. 
Applications  developers  are  offered  a 
software  publishing  program  with  roy¬ 
alties  of  “5  to  15  percent.” 

Generally,  all  of  the  documentation 
is  very  well  done  with  good  quality  pa¬ 
per,  typesetting,  editing,  and  so  on.  Of 
course,  nothing  is  perfect.  The  Gilman 
and  Rose  textbook  unfortunately  is 
weakest  on  the  most  difficult  aspects  of 
APL  (a  consistent  theme,  as  you  shall 
see),  and  there  are  a  number  of  minor 
editing  problems.  The  front  cover  pro¬ 
claims  “Includes  APL2  and  APL  for 
PC’s,”  but  this  is  not  really  explained 
— in  a  forward,  for  example.  In  fact,  I 
didn’t  know  what  APL2  was  and  eager¬ 
ly  awaited  the  revelation  inside.  Alas, 
the  best  I  could  do  was  to  figure  out 
that  it  was  an  IBM  product.  The  index 
didn’t  help  either.  There  were  only  a 
few  references  to  “APL  for  PC’s”  in¬ 
side  the  text,  despite  the  1983  publica¬ 
tion  date,  and  a  reference  to  “see  inside 
the  back  cover  for  details”  in  the  pre¬ 
face  . . .  very  funny!  It’s  blank. 

Two  serious  deficiencies  in  the  pack¬ 
age  are  the  lack  of  quick  reference 
cards  and  the  need  for  more  detailed 
tutorial  material  on  graphics,  memory, 
and  machine  language  features — the 


most  difficult  aspects  of  the  extended 
language.  This  makes  it  tough  on  the 
neophyte  or  convert.  A  workspace 
demo  on  graphics  was  so  confusing 
that  I  couldn’t  make  it  do  anything 
useful.  Documentation  for  the  inter¬ 
rupt  handler  []  I  NT  gives  only  a  few  ex¬ 
amples,  a  stern  warning  about  “im¬ 
properly  constructed  arguments,”  and 
a  reference  to  the  section  on  []  CALL. 
Documentation  for  the  latter  system 
function  is  more  extensive  but  still  dif¬ 
ficult  to  follow. 

To  whet  the  microsystem  user’s  ap¬ 
petite,  I  should  mention  that  STSC  has 
announced  two  interesting  develop¬ 
ments:  the  maxi/mini  APL*PLUS  sys¬ 
tem  will  be  available  under  Unix, 
which  means  speed,  nested  arrays,  and 
a  good  multiuser  environment,  and  an 
APL  “compiler”  for  IBM  mainframe 
VM/CMS  and  MVS/TSO  will  be  avail¬ 
able,  promising  a  three-fold  enhance¬ 
ment  in  speed.  (There  are  quotes 
around  compiler  because  you  still  must 
run  the  compiled  functions  under  the 
standard  APL  environment.)  STSC  is 
the  most  active,  broadest-based  APL 
vendor  in  the  world,  which  I  hope 
speaks  well  for  its  commitment  toward 
APL  for  the  PC. 

Finally,  even  as  I  write  this  review  of 
Version  3.1,  Version  4.0  is  being  an¬ 
nounced.  Included  is  documentation  on 
all  workspaces,  new  stickers,  and  a 
placard  with  the  APL  character  set. 
Speedups  for  )LOAD,  )COPY,  []  DEF, 
andQS^  (substring  search),  exponenti¬ 
ation,  membership,  and  inner  product 
are  implemented  (some  up  to  10  times 
faster).  A  soft  character  set  for  use  on 
an  IBM  color  monitor  with  IBM  color 
graphics  card  eliminates  the  special 
APL  ROM  at  the  considerable  sacri¬ 
fice  of  off-screen  scrolling  and  full¬ 
screen  editor.  Several  new  APL  key¬ 
board  facilities,  new  enhancements  to 
the  full-screen  editor,  graphics,  and 
some  new  language  features  (e.g.,  en¬ 
hanced  validity  checking  of  certain  pri¬ 
mitives)  are  implemented.  The  work¬ 
spaces  have  all  been  “reviewed  and 
revised.”  Communications  using  the 
smart  terminal  mode  have  been  im¬ 
proved  to  allow,  for  example,  the  host  to 
execute  a  line  in  the  PC.  An  upgrade 
for  existing  users  will  be  about  $90. 

Features 

STSC  APL*PLUS/PC  is  a  nearly  com- 
Dr.  Dobb's  Journal,  January  1985 


plete  version  of  standard  APL,  lacking 
only  some  of  the  newer  extensions  such 
as  nested  arrays  and  some  of  the  exten¬ 
sions  of  operators  found  in  the  IBM 
mainframe  APL2  or  STSC’s  Unix- 
based  system  for  maxis/minis.  I  will 
review  a  large  number  of  the  existing 
enhancements. 

A  keyword  mode  is  available  through 
a  function  key  toggle;  in  this  mode,  APL 
symbols  are  replaced  by  English  words. 
An  on-line  HELP  facility  can  be  cus¬ 
tomized  for  use  in  applications.  User- 
definable  function  keys  are  available 
through  a  system  function. 

Workspace  size  expands  to  available 
memory.  On  my  256K  system  a 
)CLEAR  workspace  is  112,336  bytes, 
but  memory  can  be  partitioned  for  a 
non-APL  functionality  under  DOS;  for 
example,  TOOLS  supplies  a  RAM  disk 
program  (although  I  find  the  AST  Su¬ 
perdrive  much  easier  to  use).  Maxi¬ 
mum  object  size  is  65,536  bytes,  includ¬ 
ing  overhead.  In  a  clear  workspace,  this 
translates  to  about  8,190  floating-point 
and  32,761  integer  numbers.  Floating¬ 
point  numbers  are  eight  bytes  (17  sig¬ 
nificant  digits  in  range  -I — 
1.797  ....  E308);  both  integer  and 
Boolean  are  two  bytes  (range  H — 
32767).  No  bit  packers  need  apply! 

An  optional  8087  math  coprocessor 
is  automatically  detected  by  the  system 
and  can  be  toggled  on/off  with  a 
[ ~\POKE .  It  significantly  speeds  up  ex¬ 
ponential,  transcendental,  and  other 
functions.  See  the  section  on 
benchmarks. 

High-resolution  black-and-white  or 
color  graphics  and  sound  are  supported 
with  system  functions.  Use  of  the 
graphics  primitives  is  nontrivial,  dan¬ 
gerous  in  certain  circumstances  (misuse 
can  blitz  your  graphics  board!),  and 
lacks  a  good  tutorial  description.  The 
only  documentation  is  in  the  system 
function  outline  and  a  brief  section  in 
the  user’s  guide.  Using  the  session  pa¬ 
rameter  E  =  64  to  reduce  “snow”  will 
cause  a  significant  slowdown  in  the 
speed  of  color  graphics;  without  this 
setting,  color  graphics  literally  flash  on 
the  screen,  but  with  the  setting,  it  takes 
about  3  seconds  to  paint  a  full  screen. 
The  sound  primitive  [ ~\SOUND  is  ex¬ 
tremely  easy  to  use,  and  a  fascinating 
music-generating  program  in  one  of  the 
workspaces  produces  pleasing  little  dit¬ 
ties.  Simple  windows  and  screen  scroll¬ 


ing  control  are  available;  the  TOOLS  I 
package  offers  further  enhancements 
such  as  menu  processing,  maintenance, 
screen  input  facility,  and  forms  mainte¬ 
nance. 

Communications  facilities  for 
uploading  and  downloading  from 
mainframe  or  other  computers  com¬ 
prise  several  system  functions,  plus  a 
number  of  programs  in  one  workspace 
as  illustrative  examples.  Additional 
3278  and  IRMA  facilities  are  in 
TOOLS  I.  One  of  STSC’s  ads  shows  an 
1 1-line  program  that  sorts  a  DOS  file, 
plots  the  results  as  a  histogram,  calcu¬ 
lates  mean  and  variance,  uses  a  full¬ 
screen  editor  to  create  a  memo  com¬ 
bining  histogram,  statistics,  and 
descriptive  test,  issues  a  DOS  com¬ 
mand  to  the  PC,  dials  a  host  computer, 
and  ships  off  the  memo  .  . .  not  bad  for 
1 1  lines.  A  built-in  terminal  emulator 
can  be  toggled  on /off  with  a  function 
key  so  that  executing  programs  on  the 
host  or  PC  can  continue  executing. 
The  simulated  terminal  mode  is  the  ex¬ 
tended  Datamedia  1 520,  but  some  cus¬ 
tomization  is  possible. 

Both  full-screen  and  del  editors  are 
available.  The  full-screen  editor  is 
nicely  done,  but  on  my  computer  and 
graphics  card  (both  Columbia),  I  must 
operate  with  the  session  parameter 
I  E=64  to  reduce  the  small  band  of 
dashes  that  flashes  randomly  when  any 
key  is  hit.  This  is  supposed  to  disable 
the  full-screen  editor,  but  in  fact  it  still 
works.  The  only  degradation  in  perfor¬ 
mance  is  that  the  editor  does  not  exit  in 
a  way  to  restore  the  original  screen: 
line  numbers  and  a  reverse  video  ED- 
ITIN  are  left  and  they  do  not  scroll  off 
screen.  The  full-screen  editor  works 
fine  if  I  am  willing  to  tolerate  snow  for 
each  key  press;  alternately,  I  could  de¬ 
fine  a  function  key  to  clear  the  screen 
following  exit  from  the  full-screen  edi¬ 
tor.  These  are  relatively  minor  incon¬ 
veniences,  but  you  may  feel  that  for 
$595  you  should  get  all  features  of  the 
“supported”  hardware. 

TOOLS  I  contains  a  large  collection 
of  special  functions  for  maintaining 
other  functions  and  workspaces,  trans¬ 
ferring  a  workspace  to  a  file  and  back, 
and  documenting  workspaces.  Most  of 
these  are  of  interest  only  to  the  profes¬ 
sional  applications  developer. 

APL*PLUS/PC  allows  interfaces  to 
non-APL  programs  such  as  assembler 


or  DOS  and  allows  []PEEK -  and 
[]POKE\ng.  The  only  documentation  is 
in  the  reference  manual  and  is  difficult 
to  follow;  reportedly  STSC  has  im¬ 
proved  this  in  Version  4.0. 

Both  a  logical  set  of  APL  file  system 
commands  (compared  to  the  awkward 
IBM  shared  variables)  and  an  interface 
to  “native”  DOS  files  are  provided.  Up 
to  20  files  can  be  tied  at  one  time. 
There  is  complete  forward  compatibil¬ 
ity  with  APL  files,  but  Versions  2.0  or 
2.6  will  not  work  on  Version  3.1  files. 

A  set  of  powerful  formatting,  error 
trapping,  and  string  searching  primi¬ 
tives  is  supplied.  l\\c[]FMT ,  adapted 
from  the  mainframe  APL*PLUS,  is  par¬ 
ticularly  well  suited  to  business  report 
formatting.  Some  additional  functions 
in  a  workspace  help  you  set  up  tables, 
and  even  more  functions  are  available 
in  TOOLS  I,  including  functions  that 
can  direct  output  to  screen,  APL  file, 
DOS  file,  or  printer  and  provide  pagina¬ 
tion,  page  breaks,  and  so  on. 

APL*PLUS/PC  has  135  system  func¬ 
tions  and  over  200  utility  programs. 
About  26  of  these  are  also  system  com¬ 
mands  of  the  )xxx  type  long  familiar  to 
APL  users,  but  most  of  the  system  func¬ 
tions  are  usable  within  functions.  In 
Version  3.1,  )S4  VE,  )LOAD,  and  )COPY 
can  be  painfully  slow,  with  lots  of 
thrashing  about  by  the  disk  drives; 
)LOAD  and  )COPY  have  been  speeded  | 
up  in  Version  4.0.  An  alternative  is  to 
use  a  RAM  disk  for  safety,  instead  of 
using  )OFF,  you  can  write  a  small  SIG- 
NOFF  function  that  will  copy  files/ 
workspaces  to  permanent  storage  and 
exit  via  []  S/1  OFF. 

Enhancements 

Many  of  the  enhancements  to  APL,  es¬ 
pecially  adaptation  to  the  PC  environ¬ 
ment,  come  in  the  form  of  special  sys¬ 
tem-level  functions,  variables,  and 
constants.  Because  they  are  system-lev¬ 
el  features,  they  are  always  available  in 
every  workspace.  These  are  largely,  but 
not  entirely,  unique  to  each  vendor,  and 
any  APL  program  that  incorporates  any 
of  the  nonstandard  system  features  will 
not  be  compatible  with  other  products. 
However,  because  STSC  provides  the 
Run  Time  System  as  a  total  environ¬ 
ment  for  applications,  this  is  of  little 
concern.  It  would  be  a  problem  only  for 
an  individual  trying  to  port  applications 
between  a  mainframe  (even  between 


Dr.  Dobb's  Journal,  January  1985 


90 

61 


some  STSC  products)  and  APL*PLUS/ 
PC  or  between  friends  with  different 
systems. 

The  135  system  features  in  the  APL 
2*PLUS/PC  system  provide  informa¬ 
tion  about  the  session,  active  work¬ 
space,  and  objects  in  the  workspace; 
retrieve  objects  or  entire  workspaces 
from  disk;  assist  in  debugging;  commu¬ 
nicate  with  other  devices;  manipulate 
screen  (cursor,  window,  graphics,  and 
so  on)  and  sound  effects;  and  observe 
and  manipulate  the  operating 
environment. 

System  variables  contain  informa¬ 
tion  about  the  environment,  such  as 
comparison  tolerance,  index  origin, 
printing  precision,  and  random  link, 
about  latent  expressions,  HELP  file¬ 
names,  what  action  to  take  if  execution 
stops  for  immediate  input,  and  memo¬ 
ry  segment  for  peek/poke.  Other  sys¬ 
tem  variables  are  for  cursor  position, 
keyword  setting,  printing  width,  and 
screen  window  control. 

System  constants  comprise  a  dozen 
constants  that  control  the  character  set 
(atomic  vector)  and  generate  bell, 
backspace,  delete,  escape,  form/line 
feed,  new  line  feed,  null  character,  and 
so  on. 

Table  I  (below)  gives  an  indication 
of  the  depth  of  support. 


Benchmarks 

An  excellent  comparison  of  STSC  APL 
♦PLUS/PC  (Version  2.6)  and  IBM  APL 
(Version  1.00)  appeared  in  the  March 
1 984  issue  of  Byte.  The  only  weakness 
of  these  comparisons  was  that  the  exact 
timing  algorithm  was  not  stated.  I  have 
used  the  same  benchmarks  but  have  re¬ 
placed  J.  Bensimon’s  large  chess  prob¬ 
lem  with  the  DDJ  Savage  floating  point 
benchmark  and  have  used  the  docu¬ 
mented  TIMER  function  supplied  by 
STSC  in  the  workspace  C/AO  (Listings 
One  -  Four,  page  98).  This  TIMER  cor¬ 
rects  for  all  overhead. 

All  benchmarks  were  run  first  on  my 
Columbia  MPC  1600-1  without  an 
8087.  To  include  comparisons  with  the 
earlier- version  of  STSC  APL*PLUS/PC 
used  in  the  Byte  article  (Version  2.6) 
and  to  obtain  8087  data,  I  enlisted  the 
unsuspecting  help  of  the  STSC  small 
systems  hotline  staff  who  happily  per¬ 
formed  tests  No.  1-19  on  both  Ver¬ 
sions  2.6  and  3.1,  both  with  and  without 
the  8087  (a  POKE  turns  the  8087  off, 
and  the  machine  acts  as  if  it  were  not 
there  in  all  respects). 

The  comparable  STSC  benchmarks 
were  all  uniformly  faster  than  mine; 
this  precipitated  a  number  of  phone 
calls  to  both  Columbia  and  STSC  and 
between  the  two  vendors.  We  could  not 


discover  any  obvious  reason  for  this  dis¬ 
crepancy,  outside  of  a  difference  in  the 
way  the  benchmarks  were  run:  the 
STSC  benchmarks  were  run  using  a 
master  program  to  feed  the  arguments 
to  TIMER ,  while  my  benchmarks  were 
originally  performed  manually.  The 
STSC  benchmarks  for  No.  5  were  per¬ 
formed  on  the  wrong  variable,  so  I  have 
omitted  No.  5,  but  the  STSC  bench¬ 
mark  No.  2  was  still  about  half  the  val¬ 
ue  that  I  obtained  on  the  Columbia. 

I  had  originally  decided  to  report 
only  ratios  for  the  STSC  data,  but  I  lat¬ 
er  decided  to  check  the  performance  on 
an  IBM  myself.  With  the  cooperation  of 
two  colleagues  at  work,  1  was  able  to 
boot  my  exact  workspace  on  an  IBM  XT 
with  640K  RAM  and  to  perform  all 
benchmarks  in  the  same  manner  as  on 
my  Columbia  at  home.  The  XT  without 
8087  gave  much  closer  agreement  to 
the  Columbia  values,  including  a  value 
of  about  9  msec  for  No.  2. 

The  disagreement  with  STSC’s  val¬ 
ues  still  bugged  me,  so  I  coded  the  mas¬ 
ter  program  and  reran  all  benchmarks 
on  my  Columbia.  The  discrepancies 
were  still  there:  I  then  copied  all  vari¬ 
ables  and  functions  into  a  )CLEAR 
workspace  and  reran  all  benchmarks. 
Lo  and  behold,  the  value  of  4.5  flashed 
up  for  No.  2,  in  close  agreement  with 


Files:  2 1  for  APL  and  1 3  for  DOS 
Character  constants:  9 
Debugging  tools:  8 
Detached  I/O:  4 
Disk:  4 
DOS:  1 

Exception  handling:  5 
Execution:  6 
Function  Definition:  11 
General  Information:  12 
Graphics/Screen:  21 
Help:  3 

Input/Output:  10 
Keyboard:  6 
Latent  Expression:  3 

Machine  Language:  3  (call/interrupt/symbol  table  pointer) 
Memory:  3  (peek/poke/seg) 

Object:  9 
Session:  4 
State  Indicator:  6 

Workspace/Object  Manipulation:  1 2 
Workspace:  9 

Other:  1  substring  searching,  1  delay  execution 

Table  I 

List  of  System  Features  by  Type 


62 


Dr.  Dobb's  Journal,  January  1985 


NEWS  newsletter  are  a  financial  sys¬ 
tem  and  a  DBMS  for  the  broadcasting 
industry. 

R.W.  Butterworth  makes  some  in¬ 
teresting  points  -albeit  directed  main¬ 
ly  toward  mainframe  systems— in  his 
article  “When  APL  is  Inappropriate” 
(in  APL  in  Practice ,  page  43).  The 
main  criterion  is  who  or  what  reads  the 
program  most  often  or  importantly 
during  its  full  life  cycle.  Low-level  lan¬ 
guages  (e.g.,  assembly  code  or  even  C) 
favor  machine  readability,  while  very 
high-level  languages  favor  people 
readability.  Therefore,  general  support 
utilities,  high-volume  processing,  or 
very  complex  calculations  (“number 
crunching”  scientific  applications)  are 
not  good  for  APL  because  they  are  too 
machine  intensive.  On-line  real-time 
applications  involving  complex  tasks 
are  also  not  good:  portability  is  some¬ 
what  limited  if  the  code  involves  lots  of 
enhancements,  but  this  is  true  of  most 
high-level  languages.  Hybrid  solu¬ 
tions,  however,  offer  many  benefits; 
APL*PLUS/PC  offers  interfaces  to  ma¬ 
chine  code  and  communications. 

When  is  development  on  micros  us¬ 
ing  APL  warranted?  Any  project  that 
involves  a  lot  of  ad  hoc  analysis  is  a 
good  candidate  for  APL.  However, 
with  the  Run  Time  System,  all  possible 
ad  hoc  variants  must  be  predeter¬ 
mined,  and  either  menu  choices  or  pre¬ 
defined  function  names  are  required  to 
determine  which  one  will  be  used.  This 
is  probably  not  a  limitation.  If  the  envi¬ 
ronment  cannot  be  predefined,  you 
have  only  two  choices.  Either  your 
APL  application  using  the  Run  Time 
System  must  have  a  command  simula¬ 
tion  processor  to  allow  the  user  to  enter 
equations,  possibly  with  the  APL 
keyword  system,  or  you  should  suggest 
the  purchase  of  the  full  APL  system. 
You  can  then  supply  a  more  “stan¬ 
dard”  set  of  functions  and  perhaps 
some  training  in  APL. 

Probably  the  most  useful  application 
of  the  APL  Run  Time  System  would  be 
situations  where  the  package  needs  to 
be  customized  quickly  or  frequently  in 
a  manner  that  cannot  be  achieved  by 
menu  choices  of  options,  input  of  for¬ 
mulae,  and  so  on,  or  if  your  business 
consists  of  lots  of  small  custom  jobs.  If 
these  concepts  are  correct,  then  I  find 
the  pricing  of  the  Run  Time  System  to 
be  out  of  whack  because  it  seems  to  pre- 


STSC’s  values;  all  other  values  im¬ 
proved  significantly  in  agreement. 

An  inexplicable  bug  apparently  had 
crept  into  the  original  workspace  and 
affected  timing,  not  accuracy,  in  a 
manner  not  due  to  pending  functions, 
limited  []  WA,  or  any  other  obvious 
problem;  I  have  shipped  a  copy  of  the 
“good”  and  “bad”  workspaces  off  to 
STSC  for  examination.  Benchmarks  in 
APL  are  not  as  reproducible  as  they 
may  be  in  other  languages  because  a  lot 
of  bookkeeping  goes  on  “behind  closed 
doors.”  The  state  of  the  workspace  de¬ 
pends  to  some  extent  on  what  has  pre¬ 
ceded  it.  Therefore,  I  now  store  a  clean 
workspace  for  benchmarking  and 
)LOAD  it  fresh  for  each  run. 

First,  let’s  look  at  the  performance  of 
Version  2.6  (the  Byte  review  version) 
compared  with  the  current  Version  3. 1 
without  an  8087.  Curiously,  some  of  the 
simple  functions  have  gotten  slightly 
worse  (about  10%  worse  for  No.  1  plus 
reduction,  No.  3  maximum  reduction, 
No.  6  indexing,  No.  8  take,  and  No.  10 
transposition);  however,  much  more 
substantial  improvements  were  realized 
in  No.  14  matrix  division  (66%),  No.  15 
Fibonacci  series  (33%),  and  No.  17  di¬ 
vision  (14%).  Comparing  the  two  ver¬ 
sions  with  an  8087  shows  improvements 
in  the  newer  version  of  69%  for  No.  1 
plus  reduction  and  87%  for  No.  14  ma¬ 
trix  division,  with  smaller  improve¬ 
ments  in  Nos.  2,  13,  15,  16,  17,  and  19. 
There  are  10-15%  decrements  in  Nos. 
3,  6,  and  10. 

Use  of  the  8087  math  coprocessor 
gives  improvements  of  100  -  3600%  in 
some  benchmarks  (Version  3.1;  Nos. 
1,  4,  13,  14,  16,  17,  18  and  19),  partic¬ 
ularly  the  logarithm-exponential  and 
transcendental  benchmarks. 

A  comparison  of  the  Columbia  MPC 
1600-1  benchmarks  with  those  for  the 
IBM  XT,  both  without  8087,  shows  the 
Columbia  (256K  with  []  WA  of  95K) 
running  fairly  consistently  at  about 
96%  of  the  speed  of  the  XT  (640K.  with 
[]WA  of  469K).  There  are  insignifi¬ 
cant  differences  between  the  VP  and 
MPC  (Cols.  E+H)  and  between  the 
XT  and  PC  (Cols.  C  +  F  and  D  +  G), 
showing  that  [JWA  is  not  an  important 
variable. 

The  well-known  Sieve  of  Eratosthe¬ 
nes  (No.  20)  involves  a  lot  of  branch¬ 


ing  and  showed  little  enhancement  us-  ( 
ing  the  8087,  as  might  be  expected. 
The  Savage  floating-point  benchmark 
(No.  21)  can  be  coded  easily  and  ele¬ 
gantly  in  APL  as  shown  in  Listing 
Three.  The  first  line  prints  a  time- 
stamp  for  benchmarking,  initializes 
two  variables,  and  precomputes  the 
branchpoints  for  the  iteration.  The  sec¬ 
ond  line  evaluates  the  expression  and 
tests  for  iteration  number.  The  last  line 
prints  a  second  time-stamp.  Without 
an  8087  coprocessor  (which  signifi¬ 
cantly  enhances  exactly  the  primitives 
used  in  this  calculation),  the  speed  was 
about  equal  to  double-precision  BA¬ 
SIC,  855  seconds  on  the  Columbia  and 
833  seconds  on  the  XT,  but  there  was  a 
low  error  of  IE-9.  With  the  8087,  the 
time  was  only  198  seconds,  which  is 
not  bad  for  an  interpreted  language. 
This  compares  favorably  with  several 
compiled  languages  (e.g.,  Supersoft 
Fortran  on  the  PC),  and  is  slower  but 
more  accurate  than  the  only  other  APL 
listed,  IBM  APL  (with  8087  implied), 
but  I  have  not  seen  the  coding  used. 

Users  usually  base  their  choices  be¬ 
tween  upgrading  to  newer  versions  or 
between  different  vendors  more  on  fea¬ 
tures  than  on  small  differences  in 
“speed”  benchmarks.  However,  STSC 
has  clearly  succeeded  (intentionally  or 
not)  in  reducing  any  glaring  weakness¬ 
es  that  the  Byte  comparison  might 
have  showcased,  particularly  the  lag¬ 
gard  performance  of  matrix  division  on 
the  older  Version  2.6. 

Finally,  some  minor  quibbles:  I  have 
used  the  Byte  benchmarks  for  consis¬ 
tency,  although  some  of  these  (Nos.  14 
and  19)  involve  two  primitives  when  a 
new  variable  might  be  more  appropri¬ 
ate.  The  PRIMES  benchmark  was  from 
the  Byte  article;  the  variables  should 
have  been  localized  in  the  header. 

Development  Package? 

What  kinds  of  applications  would  you 
develop  in  APL?  Surprisingly,  the 
main  revenue  stream  of  the  commer¬ 
cial  APL  systems  is  business  and  not 
scientific  applications.  For  example, 
APL  did  not  come  to  our  IBM  3081 
mainframe  because  the  scientists  or 
statisticians  wanted  it;  it  came  because 
a  tie-wearing  manager  type  wanted 
ADRS,  which  just  happens  to  be  writ¬ 
ten  in  APL.  Two  examples  of  applica¬ 
tions  from  the  recent  STSC  PLUS* 


Dr.  Dobb's  Journal,  January  1985 


92 

63 


elude  low-volume  projects.  (Perhaps  a 
better  pricing  scheme  for  STSC  would 
be  a  higher  initial  purchase  price  with 
possibly  a  small  royalty  fee.) 

Programming  in  APL*PLUS/PC 

To  illustrate  some  of  the  fun  and  prob¬ 
lems  involved  in  using  APL*PLUS/PC, 
I  decided  to  relate  my  experiences  in 
converting  a  BASIC  program  (Listing 
Five,  page  99)  that  I  had  written  into 
the  APL*PLUS/PC  system  (Listing  Six, 
page  101  and  Figure  page  100).  My 
Columbia  came  with  Perfect  Filer,  one 
of  the  weakest  members  of  the  other¬ 
wise  quite  good  Perfect  series.  Perfect 
Filer  does  not  have  math  capabilities,  so 
to  do  a  billing  system  for  my  wife’s 
small  private  practice,  I  used  the  Filer 
simply  to  store  the  raw  data.  The  bills 
then  were  written  to  a  native  DOS  file 
(amewaku.mss).  The  BASIC  program 
read  through  this  file  line  by  line  (or 
record  by  record)  and  looked  for  certain 
keywords/symbols.  It  then  totaled  cur¬ 
rent  month,  added  balance  forward, 
and  subtracted  any  payments.  The  total 
was  written  in  the  appropriate  spot,  and 
if  the  balance  forward  was  greater  than 
the  payments  received,  a  message  was 
written  that  payment  in  full  is  required 
each  month;  the  revised  bill  for  the  first 
client  was  written  out  (amewOak.mss) 
then  on  to  the  next  client. 

Although  processing  the  file  line  by 
line  is  probably  the  most  natural  way 
for  a  BASIC  programmer  to  attack  this 
problem  (and  was  the  approach  I  origi¬ 
nally  took  before  I  had  APL*PLUS/ 
PC),  an  APL  programmer  knows  that 
you  really  want  to  process  all  records 
simultaneously.  The  only  problem  is  to 
differentiate  between  the  clients.  The 
APL  program  took  about  six  hours 
compared  to  about  twenty  hours  for 
the  original  BASIC  program.  One  of 
the  nicest  features  of  APL*PLUS/PC  is 
the  full-screen  editor  and  scrolling 
buffer.  These  allow  you  to  try  many 
algorithms  easily;  for  example,  you  can 
use  calculator  mode  to  run-modify- 
run-modify  and  so  on  by  editing  on  the 
screen.  Then,  at  some  point,  you  can 
write  a  function  and  further  modify  it 
using  the  full-screen  editor  )EDIT. 

A  native  DOS  file  is  simply  a  long 
string  of  bytes  in  APL  (8501  in  this  ex¬ 
ample),  so  it  is  easy  to  search  the  string 
for  all  occurrences  of  the  keywords/ 
symbols  (variable  ALL  gives  the  posi- 

94 

64 


tions).  Originally  I  did  this  with  a  se¬ 
ries  of[]55  substring  searches.  The  ac¬ 
tual  numbers  were  then  a  fixed 
number  of  bytes  away  from  these  loca¬ 
tions  (line  [6]  of  PROCESS).  A  func¬ 
tion  {CLEAN)  was  used  to  clean  up  any 
stray  characters,  to  convert  the  normal 
minus  to  an  APL  negative,  and  to  insert 
000  in  blank  “numeric”  fields.  The 
characters  then  were  converted  to 
numbers.  The  resulting  vector  CM  was 
a  list  of  all  session  charges  in  order. 

How  to  sum  by  client?  There  are 
usually  several  ways  to  do  almost  any¬ 
thing  in  APL.  Much  of  what  follows 
could  have  been  avoided  by  the  use  of 
extended  operators  in  APL2  or  even  a 
loop  with  the  take  primitive.  However, 
the  method  that  I  found  using  APL 
♦PLUS/PC  is  more  typical.  Line  [7]  of 
PROCESS  reshaped  the  vector  into  a 
matrix  that  had  number  columns  = 
number  of  clients  (4  here)  and  number 
rows  =  total  number  of  sessions  (12; 
matrix  A).  Each  column  contained  the 
original  vector  of  session  fees.  What 
was  then  needed  was  to  discover  a  mask 
of  0,1’s  that  could  be  used  to  multiply 
this  matrix  so  that  the  columns  (clients) 
could  be  summed  to  give  the  current 
month’s  total  charges  for  each  client. 

Because  of  the  layout  of  the  bill,  the 
location  of  the  balance  forwards  al¬ 
ways  occurred  right  after  the  current 
month’s  charges.  Therefore,  a  matrix 
formed  by  finding  the  location  of  all 
keywords/symbols  less  than  the  bal¬ 
ance  forwards  (matrix  B)  would  iden¬ 
tify  each  client—  the  first  client  would 
have  all  columns  filled,  the  second  n-1 
columns,  and  so  on  to  the  last  client, 
which  would  have  only  the  last  column 
filled.  The  last  step  would  be  to  keep 
only  the  first  1  for  each  client;  this  was 
accomplished  using  the  “less  than 
scan”  (matrix  C).  The  final  expression 
gives  the  sums  CSUMS. 

The  program  actually  ran  rather 
slowly.  This  was  obviously  due  to  the 
multiple  string  searches  using  [] 55  (re¬ 
portedly  speeded  up  in  Version  4.0).  It 
would  be  nicer  and  faster  to  search  the 
main  string  only  once,  at  least  for  the 
new  sessions  if  not  for  everything.  This 
proved  possible  by  using  the  member¬ 
ship  function  (see  FINDALL)  to  find  all 
occurrences  of  the  symbols  i,  g,  A,  and 
W  (for  individual,  group,  administra¬ 
tive,  and  workshop).  This  would  include 
any  words  that  happened  to  have  these 


letters  present,  but  the  actual  location 
would  have  a  blank  on  either  side. 
Therefore,  all  you  had  to  do  was  exam¬ 
ine  the  locations  to  the  left  and  right, 
test  for  blanks,  and  do  a  logical  AND 
(line  [5]  of  PROCESS).  The  resulting 
algorithm  was  twice  as  fast  as  using  the 
multiple  []55’s. 

The  APL  version  contains  about  the 
same  number  of  lines  -excluding  com¬ 
ments  and  function  headers — but  runs 
almost  exactly  three  times  faster!  Fur¬ 
ther  optimization  is  undoubtedly 
possible. 

Having  the  APL*PLUS/PC  system 
in  the  first  place  would  have  made  it 
unnecessary  to  improve  Perfect  Filer 
because  the  proper  data  base  could 
have  been  set  up  easily.  However,  the 
example  illustrates  many  of  the  fea¬ 
tures  of  programming  in  APL*PLUS/ 
PC:  it  is  fast,  fun,  well-adapted  to  the 
PC  environment,  and  encourages  ele¬ 
gant  global  solutions.  Of  course,  what 
may  be  elegant  for  me  may  be  pedes¬ 
trian  for  another  APL  user. 

APL  was  designed  as  a  heuristic  tool 
before  it  became  a  computer  language. 
I  find  describing  the  conceptualization 
in  English  much  more  difficult  than 
the  symbolic  description  in  APL  shown 
in  Listing  Six.  Unless  you  have  some 
experience  with  APL,  you  may  find  the 
opposite  to  be  true.  Those  with  some 
APL  under  their  belt  tend  to  program 
in  a  twilight  zone  between  the  English 
verbalization  and  the  vector/matrix 
concepts  of  APL.  In  many  instances, 
you  know  that  there  is  a  trick;  you  just 
have  to  remember  it,  probably  by  fid¬ 
dling  on  screen.  In  this  case,  I  knew 
there  was  a  scan  function  that  would 
do  it — line  [7] —  I  just  couldn’t  re¬ 
member  which  one. 

Menu  a  la  TOOLS 

Having  received  the  TOOLS  package 
just  as  I  was  completing  the  major  por¬ 
tion  of  this  review,  I  have  not  had  a  lot 
of  time  to  fully  evaluate  all  of  its  fea¬ 
tures.  I  have  spent  most  of  my  time  with 
the  menu  and  screen  design  sections. 

The  benchmarks  table  (Table  II, 
page  95)  was  produced  using  functions 
in  the  distribution  workspace  FOR¬ 
MAT  and  OUTPUT  workspace  from 
TOOLS.  Due  to  scanty  documentation, 
most  of  the  minor  difficulties  involved 
function  COLNAMES  from  FORMAT , 
which  required  a  bit  of  fussing  to  work 

Dr.  Dobb  s  Journal.  January  1985 


A 

B 

C 

D 

E 

F 

G 

H 

Computer 

PC 

PC 

PC 

PC 

VP 

XT 

XT 

MPC 

APL  Oersion 

2.6 

2.6 

3.1 

3.1 

3.2 

3.1 

3.1 

3.1 

8087? 

8087 

8087 

8087 

OUA 

410K 

410K 

302K 

302K 

87K 

480K 

480K 

95K 

RAM 

512 

512 

512 

512 

512 

640 

640 

256 

1.  PLUS  REDUCTION 

Z+-+/VI 

104 

158 

32 

169 

174 

32 

169 

175 

2.  LOGICAL  REDUCTION 

Z*v/VL 

5 

6 

4 

4 

5 

5 

4 

5 

3.  MAXIMUM  REDUCTION 

Zer/CUMI 

30 

30 

35 

34 

35 

35 

35 

36 

4.  EXPONENTIATION 

Z«-VI*.i 

1697 

9571 

1704 

9249 

9426 

1704 

9250 

9473 

5.  ABSOLUTE  OALUE 

ZHVR 

130 

65 

120 

130 

6.  INDEXING 

Z*VR[VICv20]] 

22 

23 

26 

26 

26 

25 

25 

26 

7.  SORTING 

ZPVICtVI] 

118 

118 

119 

119 

125 

116 

116 

122 

8.  TAKE 

Z*  *2  ltMR 

29 

29 

29 

30 

31 

29 

30 

31 

9.  MEMBERSHIP 

Z+VIeVI 

149 

149 

154 

153 

154 

154 

153 

156 

10. TRANSPOSITION 

Z*2  1UMC 

64 

64 

70 

70 

71 

71 

71 

72 

11. OUTER  PRODUCT,  CHARACTER 

Z*VC».=VC 

131 

130 

137 

137 

143 

136 

136 

143 

12. OUTER  PRODUCT,  INTEGER 

Z+-(  v50) ».  +(,50 

445 

445 

440 

441 

465 

449 

444 

467 

13. INNER  PRODUCT.  REAL 

Z+VRL.+VR  ' 

346 

553 

280 

575 

591 

280 

575 

594 

14. MATRIX  DIVISION 

Z+MRBlOtVR 

1486 

2217 

196 

755 

778 

196 

755 

782 

15. FIBONACCI  SERIES 

Z+FBNCC  (LISTING  1) 

3832 

3945 

2487 

2687 

2801 

2435 

2638 

2781 

16. MULTIPLICATION 

Z+VRX3.14 

146 

478 

112 

490 

507 

113 

489 

509 

17. DIVISION 

Z+VR+3.14 

152 

733 

118 

632 

652 

118 

632 

657 

18. LOGARITHM 

Z+*VR 

131 

5085 

131 

4872 

4970 

131 

4872 

4994 

19. SINE 

Z+loVRx.  1 

484 

6060 

453 

6103 

6224 

444 

6094 

6243 

20. SI  EVE  OF  ERATOSTHENES 

(LISTING  2)  SEC.xlO 

1634 

1666 

1755 

21. SAVAGE  FLOATING  PT. 

(LISTING  3)  SEC. 

198 

833 

855 

1)  Times  are  in  milliseconds  per  execution  for  Benchmarks  01-19  and 

seconds  for  020-21.  020  is  reported  for  10  executions,  as  per  convention. 

2)  TIMER  ( APL*PLUS/PC  Workspace  C1N0,  see  Listing  4>  was  with  100  repetitions  and  with 
'  Be-0 '  for  NULLEXP,  except  for  015  where  1 R+N0NFN1  was  used.  N0NFN  is  nil- 

adic  and  simply  assigns  R+-0.  Benchmarks  020  and  021,  since  they  are  full 
functions  and  are  relatively  slow,  were  timed  with  OAIC23,  as  shown  in  the 
Listings  2-3. 

3)  The  PC  and  OP  had  monochrome  monitors,  the  XT  had  both  monochrome 
and  color  monitors  and  boards  and  the  MPC  had  color  monitor.  All  tests 
were  done  in  monochrome  mode.  The  PC, OP  and  XT  used  BOS  2.1,  the  MPC  2.0. 

4)  The  variables,  as  in  the  Byte  March  1984  review,  are: 

MI+-10  10pVI+-(  500  pO  1  0  0  l)/i500 
VL+-1  0  1  1  0  0  0  1 
MR«- 10  iOpVR+VI  +0. 1 

MC+-26  26pOC* ' ABCDEFGHI JKLMNOPQRSTUVUXYZ' 


Table  II 


Dr.  Dobb's  Journal.  January  1985 


95 

65 


properly.  Use  of  the  functions  in  OUT¬ 
PUT  was  relatively  straightforward. 

To  test  menu  and  screen  functions 
from  TOOLS,  I  decided  to  adapt  some 
recombinant  DNA  programs  to  the  PC 
using  these  features.  A  menu  is  divided 
into  title,  text,  choice,  and  footnote  sec¬ 
tions  and  allows  multiple  choices  with 
validity  checking;  it  has  one  write-in 
area,  time-out  features,  and  full  color/ 
monochrome  decorations.  An  enor¬ 
mous  number  of  possible  options  are 
available.  A  screen  is  similar  but  more 
general;  for  example,  multiple  write-ins 
can  be  made.  Some  run-of-the-mill 
screens  can  be  set  up  very  simply: 
'MENU  MENU '  CHOICE  FROM  'IAU 
CHAMBERTIN/PEARL  RIVER  SEA¬ 
FOOD/CHUCK  E.  CHEESE 

In  setting  up  the  main  DNA  menu,  I 
decided  it  would  be  useful  to  have  the 
name  of  the  registered  owner  appear 
somewhere.  It  was  not  at  all  obvious 
how  to  enter  current  information  such 
as  date,  time,  or  user  into  the  menu. 
The  screen  is  first  designed  and  then 
“compiled”  for  fast  display  (not  a  true 
compilation,  rather  an  interpretation 
into  more  primative  APL  functions). 
Several  of  the  key  functions  are  locked 
and  the  terse  error  messages  are  not 
very  helpful.  The  documentation  is 
weak  on  how  to  create  certain  effects; 
some  of  these  can  be  gleaned  only  from 
the  single  example  in  the  workspace 
(for  example,  how  to  test  for  color 
boards  and  change  attributes  appropri¬ 
ately  or  how  to  center  text).  After  a 
few  calls  to  the  hotline,  I  found  that 
the  only  way  to  enter  current  informa¬ 
tion  is  to  use  some  special  editing  func¬ 
tions  to  replace  defined  fields  in  the 
“compiled”  menu. 


Once  the  menu  is  “compiled,”  it 
flashes  instantly  on  the  screen  unless 
you  are  using  E=64  at  sign-on,  in 
which  case  it  takes  several  seconds  to 
paint  the  screen.  Despite  the  shortcom¬ 
ings  of  the  documentation,  once  you 
get  the  hang  of  it,  you  can  design  some 
very  nice  menus. 

I  also  had  several  problems  with  the 
screen  functions  and  documentation. 
In  the  step-by-step  discussion  of  how  to 
design  a  sample  screen,  a  critical 
“press  enter”  was  omitted  following 
the  naming  of  the  first  field  (EXT).  No 
other  exiting  gambit  works.  The  docu¬ 
mentation  is  also  confusing  concerning 
the  use  of  the  function  SCFKEYSOFF 
with  SCCLOSE-,  you  must  not  use  these 
together.  Instead,  if  you  want  to  set 
function  keys,  you  must  follow  a  more 
complicated  procedure  that  is  outlined 
in  the  documentation,  but  the  incom¬ 
patibility  is  not  made  clear. 

SCINIT  calls  the  undocumented 
SCCURSORSET ,  which  is  []IO  sensi¬ 
tive  (=0  works),  a  function  similar  in 
purpose  to  WHITEWASH  in  the  MENU 
workspace  for  color/mono  conver¬ 
sions.  If  you  want  to  re-use  a  screen  (as 
I  did),  you  must  blank  out  any  filled-in 
fields  that  you  do  not  want  to  carry  for¬ 
ward  before  you  store  it  using  SCOUT- 
PUT  ‘  After  finally  working  out  all  of 
these  details,  I  had  a  screen  that  looked 
nice,  only  lit  up  the  write-in  field  when 
a  function  key  was  pressed  and  waited 
for  proper  input;  unfortunately,  even 
when  compiled  the  screen  took  about 
three  seconds  to  appear,  much  slower 
than  a  menu.  The  SCREEN  workspace 
was  the  least  satisfactory  of  the  TOOL 
features  that  I  was  able  to  test. 


You  Get  What  You  Pay  For 

STSC  APL*PLUS/PC  is  not  a  cheap 
package,  but  it’s  worth  every  penny 
you  spend  on  it.  The  claim  is  that  the 
package  is  a  development  tool .  .  .  and 
it  is.  It’s  possible  to  develop,  serious 
packages  or  to  use  the  system  in  con¬ 
junction  with  LANs  or  mainframes  to 
do  serious  work  (for  example,  work 
done  in  one  of  our  statistics  groups). 
Certain  low-volume  applications  may 
not  have  a  low  price  tag  (because  of  the 
$50-5100  Run  Time  System  license 
fee),  but  this  may  change. 

I  find  the  most  curious  aspect  of 
APL  to  be  its  marketing  by  STSC,  IBM, 
and  others.  STSC  seems  to  be  following 
IBM’s  lead  ...  or  non-lead.  Although 
STSC  runs  occasional  full-page  ads  in 
the  computer  rags,  there  is  a  minimum 
of  second  sourcing.  For  example,  I 
have  seen  STSC  APL*PLUS/PC  of¬ 
fered  from  only  two  alternate  vendors. 
One  is  a  well-known  programmers 
shop  (at  about  a  16%  discount),  and 
the  other  is  a  vendor  specializing  in 
8087  chips  and  related  software.  Of 
course,  IBM  APL  is  never  advertised  in 
the  usual  computer  rags  and  is  never 
second  sourced  (I  believe).  Apparently 
a  lot  of  work  gets  done  at  IBM  in  APL, 
but  the  marketing  mavens  believe  in 
benign  neglect  for  this  product.  Why 
STSC  is  not  more  vigorous  in  promot¬ 
ing  its  PC  version  will  probably  remain 
a  mystery.  The  new  Pocket  APL  may 
be  an  attempt  to  popularize  their  ex¬ 
cellent  products;  if  so,  time  will  tell. 

DD| 

( More  reviews  on  page  102) 


Software  Reviews  (Text  begins  on  page  88) 

Listing  One 


?  Z«-FBNCC 
m  Z«-  1  i 

[2]  L :-»( 100  >f>Z«-Z,  +/  ”2tZ)/L 


v  R«-N0NFN 
[1]  R«-0 

v 


End  Listing  One 


96 

66 


Dr.  Dobb’s  Journal.  January  1985 


Listing  Two 

PRIMES 

[1]  QAI[23  ❖  1*1  0  F*(  8191pY) ,  E 

[2]  YiFC  I  +Pxvl<  8191-1  >*P«-1  +  I  +  I3«-N 
C  3]  N  :-»FC  1*1+13 

[43  E:(5Y+ .  =F) , '  PRIMES'  0  DAK23 

7  End  Listing  Two 


Listing  Three 

?  A*FPBENCH; N;  R 

[13  DAI  [  23  0  A*N*i  9  R*(  2499#>L> ,  E 
[23  L:A*l+3o'3o*«< AxA)*0.5  0  -»R[N*N+13 

[33  E :  □  A I C  23 

^  End  Listing  Three 


Listing  Four 

v  44R+44N  timer  ^E;AATi;^T2;^T3;^T4 

[13  n  Result  4$R  is  the  time  in  milliseconds  to  execute  4$E  44N  times. 

[23  n  NULLEXP  is  the  APL  expression  whose  time  is  to  be  subtracted  from 
[33  «  the  timing.  It  is  typically  'S«-0'  if  44E  begins  with  '  S*1. 

[43  A4T2*(44Nxl  +  pNULLEXP>pNULLEXP, 'O'  0  44T4*(44Nxl+p44E)p44E,  'O' 

[53  pi  QUA  on  next  lines  forces  a  garbage  collection. 

[63  64R*0UIA  0  44T1*0TS  0  S44T2  0  44T2*DTS 

[73  44R«-DUA  0  44T3*0TS  0  S44T4  0  44T4*0TS 

[83  44  Ti*  60  60  60  1000  l  '4tg$Tl 

[93  44T2*  60  60  60  1000  i  *4t44T2 

C 103  44T3*  60  60  60  1000  i  _4t44T3 

[113  44  T4*  60  60  60  1000  l  '4t44T4 

C123  44R«-(44T4-44T3)-44T2-44T1  End  Listing  Four 

7 


Listing  Five 

90  PRINT  TIME*:  SUM=0 ! : LS*=" " 

100  OPEN  "I",#l, "asnewaku. mss" 

105  OPEN  "0",#2, "atnewOak. mss" 

£00  LINE  I N PUT # 1 ,  X * : IF  EOF ( 1 )  THEN  9000  ELSE  £01 

£01  IF  X*=CHR*(13>  THEN  PRINT#£, X* :GOTO  £00 

£04  ft=INSTR(X$,  *"  ):IF  A  THEN  lOOO  ELSE  PRINT  #£,  X* 

£05  REM: find  name 

£06  N=INSTR (X*, R. " ) : IF  N  GOTO  £09  ELSE  £90 
£09  L=LEN(X*)-(N+4> : RN*=R I GHT* ( X*, L ) 

?A 1  L= 1 +LEN ( RN* ) — I NSTR ( RN*, CHR* (77) ) : NA*= RIGHT* ( RN*, L) : GOTO  £00 
£90  FP=INSTR (X*, "Payment" ): IF  FP  GOTO  315  ELSE  F I = I NSTR ( X*, " /8 " ) 

300  IF  FI  GOTO  301  ELSE  310 

301  R*  =  R I GHT  * ( X*, LEN(X*)-£7)  :L*=MID*(X*, 8, 5 ) +STR* ( VAL ( R* )  ) 

30£  SUM=SUM+VAL (R*> : LS*=LS*+L*+"  ":GOTO  £00 


(Continued  on  next  page ) 


Dr.  Dobb's  Journal,  January  1985 


99 

67 


Software  Reviews  (Listing  Continued,  text  begins  on  page  88) 

Listing  Five 

310  0= I NSTR ( X$, "  +  ) "> : IF  A  THEN  311  ELSE  315 

311  E>A=VAL(MID*  (X*,  A+£,  5)  )  : SUM=SUM+BA : GOTO  200 

315  A=INSTR<X»,  "  —  IF  A  THEN  316  ELSE  £00 

316  PA=VAL(MID* (X$, A+2, 6) ) :SUM=SUM-PA 

317  IF  (BA-PA)  <=0!  GOTO  200  ELSE  PRINT#£,  "**•*  PAYMENT  IN  FULL  IS  REQUIRED  EACH  MO 
NTH  !  ***" 

350  GOTO  200 

1000  REM  write  total  now  due  to  invoice 
1005  Y$=STRING* (48, 32) +" Total  Now  Due:  S" 

1010  PRINT#2, Y*, SUM 
1050  SUM=0 ! : LS$=" " 

1099  REM  next  bill 

1100  GOTO  200 

9000  CLOSE:  PRINT  TIMEt  End  Listing  Five 


PR 

8501 

BF 

1286  3413  5510  7672 
PR 

1345  3471  5569  7728 
TND 

1534  3658  5756  7913 
ALL 

769  838  894  2908  2977  5018  5074  7107  7176  7232  7301  7357  7426  7482 
CM 

25  25  25  35  30  50  65  45  45  45  45  65  65  45 
CSUMS 

75  65  115  355 
CBF 

290  50  -49  0 
CPR 

25  50  15  0 
CTND 

340  65  51  355 


Matrix  A 


Matrix  B 


Matrix  C 


0  ((pBF),pCM)pCM 


ALL°.<BF 


<\(ALL°.<BF) 


25 

25 

25 

25 

1 

1 

1  1 

1 

0 

0 

0 

25 

25 

25 

25 

1 

1 

1  1 

1 

0 

0 

0 

25 

25 

25 

25 

1 

1 

1  1 

1 

0 

0 

0 

35 

35 

35 

35 

0 

1 

1  1 

0 

1 

0 

0 

30 

30 

30 

30 

0 

1 

1  1 

0 

1 

0 

0 

50 

50 

50 

50 

0 

0 

1  1 

0 

0 

1 

0 

65 

65 

65 

65 

0 

0 

1  1 

0 

0 

1 

0 

45 

45 

45 

45 

0 

0 

0  1 

0 

0 

0 

1 

45 

45 

45 

45 

0 

0 

0  1 

0 

0 

0 

1 

45 

45 

45 

45 

0 

0 

0  1 

0 

0 

0 

1 

45 

45 

45 

45 

0 

0 

0  1 

0 

0 

0 

1 

65 

65 

65 

65 

0 

0 

0  1 

0 

0 

0 

1 

0 

0 

0  1 

0 

0 

0 

1 

0 

0 

0  1 

0 

0 

0 

1 

Figure 


+  +  (<\(ALL°.<BF))xo  ((pBF),pCM)pCM 
75  65  115  355 


100 

68 


Dr.  Dobb’s  Journal,  January  1985 


Listing  Six 


PROCESS  NAME; R; BF; PR; TND; ALL; A; CM; CSUMS;  CBF : CPR; CTND; NP ; MES 
CIO  DAIC23 

C  2D  NAME  ONTIE  "1  0  R+-DNREAD  '1  82  .(ONSIZE  *1>,0  n  TIE  FILES  AND  READ  INTO  US 
C  3D  BF«-R  FIND  '♦>’  0  PR«-R  FIND 
C 4D  TND+R  FIND  '  !  *' 

C  5D  ALL«-<  <  RC  A+iD  =  •  '  >^RC  A-l  D  = '  ’  >/A«-R  FINDALL  'igAU' 

[  6D  CM*-*,  (CLEAN  R[ALL*.+  28  29  30]),'  '  «  CLEAN  REMOVES  JUNK  CONVERTS  >-»- 

[73  CSUMS«-+r‘(<\(ALL*.<BF))xSi((pBF),pCM>pCM  «  SUM  SESSIONS  BY  CLIENT 
[83  CBF*-*, (CLEAN  R[  BF*. +1  +  1.33  ),  '  '  0  CPR*-*,  (  CLEAN  R[ PR* . +1+133 > , ’  ’ 

[93  CTND+-CSUMS  +  CBF-CPR 

[103  -*(~v/NPfO>CPR-CBF)/L  0  MES«-NP/PR  0  R[  MES* . +5+v453«- ’***PAYMENT  IN  FULL  IS  REQUIRED  EACH  MONTH!***' 
[113  L  '•  R[  TND* .  +  2  +  v43*-<  2p(  #>TND)  )+> '  M<  -  >14  '  OFMT  CTND 

[123  NAMEC4*2x+/NAME£' : ' 3*-'0'  A  NAME  ONTIE  “2  A  0  ONRESIZE  '2  «  R  ONAPPEND  "2 
[133  ONUNTIE  ONNUMS 
[143  'DONE' '  A  OAIC23 

7 

7  Z«-R  FIND  F 

C13  ^SUBSTRING  SEARCH  OF  R  FOR  F,  GIVES  LOCATION 
[23  Z«-(R  DSS  F)/vpR 

7 

7  Z«-R  FINDALL  S 

[13  nFINDS  ALL  OCCURRANCES  OF  S  IN  R 
[23  Z«-(RtS)/tpR 

7 


7  Z+-CLEAN  V;  Q 

[13  ^REMOVES  ANY  STRAY  CHARACTERS  AND  CONVERTS  NEG.  TO  MINUS 
[23  Q«-,V  0  Z*-QC  (  j'-Qe  '  0123456789+-*.  '  )/vpQ3«-'  ' 

[33  Q«-(  pV) pMI NUS  Q 

[43  QC < A/Qe '  '  >/vltpV;3«-'0'  0  Z«-Q 

7 

*  Z«-MINUS  M;  N;  RM;  NM 

[13  «  CONVERTS  "TO-  OR  -TO*; NO  EFFECT  IF  *-  NOT  PRESENT 

[23  N*-pRM*-,  M 

[33  -*(0<*/NM*-,M£'-' >/L  A  *<  0< +/NM*-,  Me '  )/LL  0  Z«-M  A  *0 

[43  L  •  RM[  NM/vN3*  '  *'  A  -*E 

[53  LL !  RMC  NMAN3*- '  -  '  n  DONT  USE  'M<->...'  OFMT  CAUSE 
[63  E  :  Z«-<  pM)  pRM  n  DONT  KNOW  FORMAT  AHEAD  OF  TIME 

7  End  Listings 


VSI  -  Virtual  Screen 

Interface,  Version  2.09 

Company:  Amber  Systems,  Inc., 
1171  S.  Saratoga-Sunny  vale 
Rd„ 

San  Jose,  CA  95129 
Computer:  IBM  PC  and  direct 
compatibles 

Circle  Reader  Service  No.  131 
Reviewed  by  Ronald  G. Parsons 

VSI  is  a  programmer’s  tool  kit  for 
building  applications  that  manipulate 
the  PC’s  screen  to  take  advantage  of 
overlapping  windows  and  features 
such  as  color  and  borders.  The  authors 


of  the  program  liken  VSI  for  screens  to 
a  file  system  for  disks.  Using  VSI,  ap¬ 
plication  programmers  need  not  be 
concerned  with  the  details  of  the  physi¬ 
cal  screen,  just  as  they  are  not  con¬ 
cerned  with  the  tracks  and  sectors  of 
the  disks  they  are  writing  on.  VSI 
greatly  facilitates  the  now  common 
process  of  showing  a  menu  on  a  pop-up 
window,  prompting  the  user  to  make  a 
selection  with  a  pointing  device  such  as 
a  mouse  or  keyboard,  erasing  the 
menu,  and  prompting  the  user  through 
the  rest  of  the  selection. 

Windows  can  be  created,  moved,  en¬ 
larged  and  reduced,  placed  behind  or  in 


front  of  other  windows,  and  closed  with 
simple  calls  to  the  interface.  Text  can 
be  placed  into  the  windows,  with  auto¬ 
matic  wrapping  at  the  boundaries  of  the 
window,  at  the  programmer’s  option. 
Similarly,  when  the  input  cursor 
reaches  the  edge  of  the  window,  the 
window  will  automatically  scroll  to 
keep  the  cursor  in  the  window.  The  box 
around  a  window  can  be  of  any  color, 
character,  and  attribute;  this  allows 
easy  creation  of  “attention”  windows 
for  urgent  messages.  Windows  are  giv¬ 
en  a  priority:  windows  of  higher  priority 
appear  in  front  of  those  of  lower  priori¬ 
ty.  Best  of  all,  the  programmer  does  not 


Dr.  Dobb's  Journal.  January  1985 


101 

69 


need  to  bother  with  the  details  of  all  this 
screen  action.  Writing  a  program  to  do 
all  this  is  very  simple  using  VSI  and  pro¬ 
duces  a  fast  window  action. 

VSI  supports  both  color  and  mono¬ 
chrome  cards  on  the  IBM  PC,  but  not 
graphics.  The  VSI  interface  is  in  two 
levels:  a  level  0  interface  to  assembly 
language  and  a  level  1  interface  to 
high-level  languages  -Lattice  C  in  the 
copy  reviewed.  Both  interfaces  provide 
similar  functions  because  the  level  1 
interface  is  built  on  the  level  0  inter¬ 
face.  The  C  interface  is  especially  easy 
to  use:  functions  with  parameters  carry 
out  the  instructions  on  the  screen. 

Additional  functions  available  in¬ 
clude  an  exit  reset  function,  several 
clear  functions,  a  copy  function,  func¬ 
tions  to  read  and  write  to  a  window, 
and  many  others. 

Up  to  255  different  screens  may  be 
defined  with  sizes  from  one-by-one  to 
255  rows  or  columns.  VSI  can  manage 
up  to  65K  of  screen  space.  ANSI  es¬ 
cape  sequences  may  be  used,  and  VSI 
will  run  under  DOS  1.1  or  2.x.  Diag¬ 
nostic  trace  capabilities  are  provided 
for  debugging.  A  VSI  exerciser  (VSIX) 
program  that  interprets  commands 
from  a  file  can  be  used  to  demo  the 
system  or  for  help  in  understanding  the 
VSI.  Mice  can  be  used  as  input  devices 
to  VSIX  if  they  place  cursor  characters 
into  the  keyboard  buffer. 

The  VSI  Distribution  Kit 
As  indicated  earlier,  the  version  of  VSI 
reviewed  included  an  assembly  lan¬ 
guage  interface  and  a  C  interface  con¬ 
figured  for  Lattice  C  VI. 03.  Other  in¬ 
terfaces  are  available  for  Pascal  (IBM 
or  Microsoft),  Compiled  BASIC 
(IBM),  Fortran  (IBM),  PL/I  (DRI), 
and  COBOL  (Realia);  these  cost  $199 
each.  All  necessary  object  modules  are 
included  to  assemble/compile  and  link 
a  program.  In  addition,  source  mod¬ 
ules  are  supplied  for  the  level  I  C  inter¬ 
face,  for  assembly  language  routines  to 
interface  the  C  code,  and  for  bulTer  as¬ 
signment.  The  source  code  for  VSIX  is 
also  included.  This  program  contains 
many  good  hints  for  writing  VSI  code. 
An  assembly  language  interface  for 
making  VSI  a  resident  program  allows 
the  VSI  interface  to  be  accessed 
through  a  software  interrupt. 

A  PC-size  manual  is  provided,  along 
with  two  reference  cards  for  the  level  0 
and  I  interfaces.  The  documentation  is 

103 

70 


clear  and  concise. 

A  Test  of  Portability 

As  a  test  of  the  portability  of  the  VSI 
interface,  I  converted  the  system  to  use 
the  Computer  Innovations  (Cl)  C86  C 
V2. 1  compiler  rather  than  the  Lattice  C 
V2.0  compiler  used  in  other  parts  of 
this  review.  The  C  interface  is  supplied 
in  a  version  tested  with  the  Lattice  C 
VI.03  compiler.  Both  the  level  1  C  in¬ 
terface  code  and  the  C-based  exerciser 
are  supplied  in  source  form  and  were 
recompiled  and  linked  with  Lattice  C 
V2.0  using  the  small  model.  No  prob¬ 
lems  were  encountered.  Then  the  C  in¬ 
terface  code  was  compiled  using  the 
small  model  of  Cl  C86.  Since  the  only 
linkage  between  the  C  code  and  the  rest 
of  the  interface  is  through  one  function, 
which  passes  the  function  parameters  to 
the  VSI  system  (this  function  is  imple¬ 
mented  in  assembly  language),  the  as¬ 
sembly  language  interface  had  to  be  re¬ 
assembled.  Because  the  assembly 
language  interface  is  similar  in  Lattice 
C  and  Cl  C86,  only  the  group  and  seg¬ 
ment  names  in  the  supplied  source 
needed  to  be  changed  to  conform  to 
their  respective  conventions.  Once  these 
two  steps  were  done,  my  test  programs 
were  compiled  and  linked  with  Cl  C86, 
and  they  worked  as  before  except  for 
differences  in  the  two  C’s  I/O  code.  No 
changes  were  required  in  the  VSI  code. 
The  steps  were  simple  and  took  little 
time  to  complete.  Thus,  the  VSI  system 
appears  to  be  quite  portable  among  C 
compilers. 

Conclusion 

If  you  will  be  writing  a  windowing  ap¬ 
plication,  seriously  consider  VSI.  It  is 
fast,  easy  to  use,  and  full  of  capabili¬ 
ties,  and  it  seems  to  be  bug  free. 

The  Fancy  Font  System, 
Version  2.0 

Company:  SoftCraft,  Inc.,  222 
State  Street,  Madison,  Wl 
53703  (606) 257-3300 
Price:  $180 

Computer:  CP/M,  Apple  CP/M,  IBM 
PC,  Osborne,  Kaypro,  Victor 
900,  Epson  QX-10  computers 
and  Epson  MX,FX,  RX,  Gemini 
10X,  IBM  Graphics  Printer, 
Riteman  Inforunner,  Tl  855/ 
850,  or  C.  Itoh  and  NEC8023 
(IBM  only)  printers 
Reviewed  by  David  D.  Clark 

The  Fancy  Font  System  is  a  personal 


typesetting  system  for  creating  nicely 
printed  documents  using  a  personal 
computer  and  a  dot  matrix  printer.  It 
uses  the  high-resolution  graphics  capa¬ 
bilities  of  popular  printers  to  produce 
high  quality  printing.  You  can  mix  dif¬ 
ferent  font  styles,  sizes,  and  special 
symbols  in  the  same  document.  You 
can  use  the  fonts  supplied  or  create  and 
customize  your  own  alphabets  and  spe¬ 
cial  symbols. 

The  Fancy  Font  System  can  no  long¬ 
er  be  considered  a  new  product;  you 
have  surely  seen  the  advertisements  by 
now.  I've  had  a  copy  for  over  a  year.  I 
originally  bought  version  1.7  of  the 
system,  and  frankly  it  was  a  pain  to 
use.  It  did  produce  beautiful  print  but 
only  after  a  lot  of  work.  The  purpose  of 
this  article  is  to  review  the  updated  sys¬ 
tem.  The  new  version  2.0  is  a  real  im¬ 
provement.  It  is  much  easier  to  use,  has 
lots  of  additional  options  and  controls, 
includes  improved  documentation,  and 
is  available  on  more  computers  and 
printers.  If  you  have  an  Epson  FX-80 
printer,  the  news  is  even  better:  it  pro¬ 
duces  even  higher  quality  print. 

System  Contents 

What  you  get  depends  on  the  computer 
and  printer  you  use.  I  have  a  CP/M  2.2- 
based  system  with  an  Epson  FX-80 
printer.  My  update  from  version  1.7 
consisted  of  a  new  user  manual,  a  quick 
reference  card,  and  two  disks  contain¬ 
ing  the  font  printing,  editing,  and  creat¬ 
ing  programs  as  well  as  a  number  of 
fonts  and  the  Hershey  character  data 
base.  Also  included  were  files  to  auto¬ 
matically  create  some  additional  fonts 
that  would  not  fit  on  the  disks. 

Besides  the  standard  fonts  supplied 
with  the  system,  SoftCraft  is  building  a 
font  library  from  which  it  will  sell  addi¬ 
tional  fonts.  They  are  actively  soliciting 
users  to  submit  any  fonts  that  they  may 
have  developed  themselves.  The  compa¬ 
ny  also  puts  out  a  periodic  newsletter 
informing  users  of  new  developments 
and  tips  on  using  the  system. 
Documentation 

The  user  manual  and  quick  reference 
card  are  excellent  examples  of  some  of 
the  capabilities  of  the  system:  they 
were  printed  with  Fancy  Font.  They 
are  very  pleasant  to  the  eye,  besides 
containing  well-written  documenta¬ 
tion  for  the  system. 

The  manual  starts  with  an  introduc¬ 
tion  followed  by  three  large  sections. 


one  for  each  of  the  three  programs,  and 
a  number  of  appendices.  The  docu¬ 
mentation  follows  the  same  format  for 
each  program.  Each  section  starts  with 
an  introduction  outlining  the  purpose 
and  use  of  the  program.  After  the  gen¬ 
eralities  are  taken  care  of,  the  manual 
describes  the  commands  in  detail,  de¬ 
voting  one  or  more  pages  to  each.  The 
detailed  descriptions  consist  of  usage 
instructions,  examples,  and  notes.  De¬ 
pending  on  the  command,  there  may 
also  be  sections  on  default  values,  legal 
values,  errors,  and  suggestions. 

The  appendices  contain  a  glossary, 
font  descriptions  and  samples,  hints  for 
using  Fancy  Font  with  word  proces¬ 
sors,  a  summary  of  error  messages,  a 
description  of  the  distribution  files,  a 
print-out  of  the  Hershey  data  base,  a 
description  of  data  file  formats,  the 
ASCII  character  set,  and  a  parameter 
and  command  summary.  The  quick 
reference  card  is  keyed  to  the  manual 
by  page  number;  if  the  card  doesn’t 
give  you  all  the  information  you  want, 
it  is  simple  to  look  it  up. 

Pfont 

Pfont,  the  printing  program,  is  the  pro¬ 
gram  you  will  probably  use  most  often. 
Before  using  Pfont,  you  must  use  your 
word  processor  or  text  editor  to  create  a 
file  containing  embedded  Pfont  format¬ 
ting  commands.  Then  you  start  Pfont 
and  set  up  the  printing  parameters. 

There  are  27  embedded  formatting 
commands,  usually  with  several  varia¬ 
tions  available  for  each  one.  They  con¬ 
trol  things  like  font  selection,  horizon¬ 
tal  and  vertical  movement, 
underlining,  justification,  word  wrap, 
indentation,  paging,  centering,  and  so 
on.  In  general,  you  use  the  formatting 
commands  the  same  way  you  would  in 
a  powerful  word  processor.  Pfont  is 
more  advanced  than  a  typical  word 
processor  in  some  ways  but  more  prim¬ 
itive  in  others.  For  example,  how  many 
word  processors  can  you  tell  to  switch 
from  an  8-point  font  to  a  40-point  font 
or  to  move  backwards  up  the  page  2.63 
cm  before  printing  the  next  line?  On 
the  other  hand,  it’s  pretty  tough  to  cre¬ 
ate  a  five-line  page  header  with  Pfont. 

Once  you  have  prepared  the  text  file 
containing  the  formatting  commands, 
Pfont  is  executed  to  print  the  file.  Be¬ 
fore  printing  begins,  you  have  the  op¬ 
portunity  to  enter  a  number  of  printing 

Dr.  Dobb's  Journal,  January  1985 


parameters.  About  30  printing  param¬ 
eters  control  things  like  print  quality 
(and  speed),  number  of  copies,  line 
width,  margins,  first  and  last  page  to 
be  printed,  pause  after  each  page,  page 
length,  and  so  on.  You  are  required  to 
enter  only  the  names  of  the  files  to  be 
printed  and  the  names  of  the  fonts  to 
be  used  during  printing;  all  of  the  other 
parameters  have  reasonable  default 
values.  The  parameters  can  be  entered 
interactively  or  retrieved  from  a  pa¬ 
rameter  file. 

Because  of  the  relatively  heavy  use 
Pfont  will  receive  relative  to  the  other 
two  programs,  it  has  been  constructed 
with  a  powerful  user  interface.  You  can 
obtain  help  with  individual  parameters 
and  review  the  settings  of  all 
parameters. 

Once  printing  has  started,  another 
group  of  commands  becomes  active. 
These  commands  will  immediately 
stop  printing,  skip  to  the  next  page, 
skip  to  the  next  file,  pause  after  the 
current  page,  or  allow  the  user  to 
change  the  draft  mode. 

Efont 

Efont  is  the  font  editing  program.  It  can 
be  used  to  alter  existing  fonts  or  to  cre¬ 
ate  new  ones  from  scratch.  Of  the  three 
programs,  this  one  is  probably  the  most 
difficult  to  use — not  because  of  inher¬ 
ent  problems  in  the  program  but  be¬ 
cause  the  mechanics  involved  in  editing 
a  font  can  become  quite  involved. 

The  fundamentals  of  the  process  in¬ 
volve  loading  a  font,  creating  expanded 
versions  of  some  characters  in  a  group 
of  ASCII  text  files,  editing  those  files, 
replacing  elements  of  a  font  with  the 
edited  version,  and  saving  the  altered 
font.  It  is  also  possible  to  create  charac¬ 
ters  from  scratch  by  creating  an  ASCII 
text  file  of  the  proper  type  and  using  the 
“replace”  command  for  an  otherwise 
empty  font  file.  Additional  commands 
print  characters,  alter  font  information, 
move  a  character  set  up  or  down  (to 
create  super-  and  subscripts,  for  exam¬ 
ple),  modify  the  margins  for  a  set  of 
characters,  and  so  on. 

When  a  particular  character  or 
range  of  characters  is  to  be  edited,  the 
program  will  create  a  series  of  ASCII 
text  files,  one  for  each  character,  that 
contain  a  line  of  information  about  the 
character  (the  left  and  right  margins 
and  a  value  related  to  the  height  of  the 


character)  followed  by  the  actual  char-  1 
acter  represented  as  asterisks.  One  as¬ 
terisk  in  the  text  file  represents  one  pin 
strike  in  the  finished  character.  To  al¬ 
ter  a  character,  you  must  exit  the 
Efont  program  and  use  a  text  editor  to 
add,  delete,  or  move  asterisks  around 
and  to  make  appropriate  changes  to 
the  information  line.  When  the 
changes  are  complete,  the  Efont  pro¬ 
gram  is  entered  again  to  read  the  al¬ 
tered  text  file  and  create  an  internal 
character  representation  from  it. 

It  is  sometimes  difficult  to  get  a 
good  feel  for  how  the  finished  charac¬ 
ter  will  look:  the  aspect  ratio  of  the  text 
file  is  different  than  the  aspect  ratio  in 
the  printed  character.  By  aspect  ratio  I 
mean  the  ratio  of  horizontal  to  vertical 
elements  in  a  unit  square.  For  exam¬ 
ple,  when  Pfont  is  used  to  print  a  char¬ 
acter  on  my  printer,  there  are  up  to  240 
horizontal  positions  by  216  vertical  po¬ 
sitions  per  square  inch;  240/216  yields 
an  aspect  ratio  of  about  1.11.  Howev¬ 
er,  on  my  terminal  that  same  square 
inch  can  have  about  nine  horizontal  el¬ 
ements  and  four  vertical  elements,  giv¬ 
ing  an  aspect  ratio  of  2.25.  Because  of 
this  difference  in  aspect  ratios,  the 
character  in  its  text  file  representation 
always  looks  taller  and  thinner  than  it 
will  look  when  printed. 

In  practice,  it  is  not  difficult  to  make 
simple  changes  to  only  a  few  charac¬ 
ters.  An  example  in  the  user  manual 
runs  through  the  process  of  adding  a 
tilde  to  an  n.  It  is  a  fairly  simple  pro¬ 
cess  and  well  explained.  As  your  edit¬ 
ing  needs  become  more  complex,  it  will 
be  necessary  to  learn  some  technical 
aspects  of  the  data  representations  and 
conventions  used  for  character  fonts. 
Most  users,  however,  will  probably 
never  need  to  use  this  program. 

Cfont 

Cfont  is  used  in  conjunction  with  the 
Hershey  character  data  base  to  create 
new  fonts  and  special  symbols.  The 
Hershey  data  base  was  created  by 
Alan  V.  Hershey  for  the  National  Bu¬ 
reau  of  Standards.  Besides  various 
styles  and  sizes  of  Roman  alphabets,  it 
contains  old  English  styles,  Greek  al¬ 
phabets  of  different  sizes,  and  even  the 
Cyrillic  (Russian)  alphabet.  There  are 
also  a  number  of  special  symbols  and 
graphics  from  mathematics,  music, 
and  who  knows  where  else. 

104 

71 


Creating  a  font  from  the  Hershey 
data  base  is  well  described  in  the  man¬ 
ual  and  really  very  easy.  It  consists  of 
creating  a  mapping  between  the  num¬ 
ber  of  a  character  in  the  data  base  and 
a  corresponding  ASCII  number.  Then 
you  supply  Cfont  with  scale  factors 
that  determine  the  size  of  the  font  cre¬ 
ated  and  a  baseline — almost  always  a 
seven  for  technical  reasons  explained 
in  the  manual.  The  process  can  be  per¬ 
formed  interactively,  or  you  can  create 
a  file  of  mappings.  Examples  of  both 
methods  are  provided.  My  disk  came 
with  map  files  and  a  submit  file  to 
automatically  create  three  sizes  each 
of  a  script  font  and  an  old  English  font. 

General  Impressions 

With  any  complicated  program,  you 
can  usually  expect  some  problems.  I 
found  one,  but  I’m  not  sure  about  it. 
When  I  updated  my  system  from  ver¬ 
sion  1.7  to  2.0,  a  font  conversion  utility 
was  provided  to  create  fonts  from  my 
old  fonts  that  would  be  compatible 
with  the  new  version.  The  copy  of  the 
program  I  got  didn’t  work  correctly 
when  parameters  were  supplied  from 
the  command  line.  I  bashed  a  couple  of 
files  this  way  before  switching  to  the 
interactive  mode.  Then  things  worked 
fine.  I  don’t  know  if  the  problem  was 
with  the  program  or  just  with  my  copy, 
but  since  it  worked  correctly  in  the  in¬ 
teractive  mode,  I  didn’t  bother  to  get  a 
new  copy  from  SoftCraft. 

The  biggest  drawback  to  Fancy  Font 
is  speed.  Although  it  prints  beautifully, 
it  prints  slowly.  This  isn’t  really  a  prob¬ 
lem  with  the  program,  though;  it’s  a 
limitation  of  the  printer.  The  program 
uses  the  graphics  capabilities  of  the 
printer,  driving  it  as  fast  as  the  printer 
can  run.  In  the  highest  quality  print 
mode  on  an  FX-80  printer,  the  program 
makes  at  least  six  passes  over  a  line  of 
text,  usually  more,  depending  on  the 
size  of  the  letters.  It  can  take  quite  a 
while  to  print  out  a  page.  Versions  con¬ 
figured  to  the  Toshiba  1350  or  Epson 
LQ- 1 500  can  print  in  one  pass  accord¬ 
ing  to  the  manual  and,  I  presume, 
would  be  faster.  (Although  the  Toshiba 
and  LQ-1500  printers  are  not  men¬ 
tioned  in  the  advertising,  they  are  dis¬ 
cussed  in  the  user  manual.  You  should 
probably  call  SoftCraft  to  find  out  if 
versions  for  those  printers  are 
available.) 


Another  difficulty  on  my  system  is 
the  size  of  the  font  files.  When  Pfont 
prints  a  file,  it  loads  as  many  fonts  as  it 
can  into  main  memory  and  reads 
pieces  of  the  rest  from  disk  as  it  needs 
them.  My  64K  CP/M  system  usually 
has  room  to  keep  only  one  font  in  mem¬ 
ory.  My  disks,  however,  are  fast 
enough  to  just  about  keep  up  with  the 
printer  when  Pfont  is  reading  font  in¬ 
formation  from  the  disk.  On  systems 
with  slower  disks,  I  have  seen  the  print 
speed  decrease  dramatically.  For 
printing  rough  drafts,  the  selection  can 
be  changed  so  that  all  of  the  informa¬ 
tion  in  a  font  file  need  not  be  read  in. 
On  IBM  systems  with  larger  memories 
or  on  systems  that  use  a  printer  with 
lower  resolution  than  the  FX-80,  this 
may  not  be  a  problem. 

This  program  produces  very  nice 
print,  but  in  general  it  is  not  quite  as 
good  as  that  produced  by  a  daisy-wheel 
printer.  On  my  printer,  the  horizontal 
resolution  is  240  dpi  (dots  per  inch) 
with  a  vertical  resolution  of  216  dpi. 
Although  human  eyes  have  a  resolu¬ 
tion  of  over  1000  dpi  under  favorable 
circumstances,  ink  on  paper  usually 
cannot  be  resolved  that  finely.  On  rela¬ 
tively  rough  paper,  such  as  that  used  in 
computer  printers  as  opposed  to  that 
used  to  print  Scientific  American ,  the 
highest  resolution  appears  to  be  about 
400  -  500  dpi.  Fancy  Font  approaches 
this  resolution  but  can’t  quite  achieve 
it.  This  is  most  noticeable  on  light¬ 
weight  diagonal  strokes.  On  vertical 
and  horizontal  strokes,  the  printed  im¬ 
age  is  perfect,  as  you  might  expect;  the 
individual  pin  strikes  cannot  be  dis¬ 
cerned.  One  method  to  overcome  the 
resolution  limits  of  the  printer  is  to 
print  the  text  larger  than  desired  then 
reduce  the  image.  The  user  manual, 
which  was  created  this  way,  looks 
good.  The  justified,  proportionally 
spaced  output  of  the  program  looks 
better  to  my  eye  than  that  produced  by 
most  word  processors  with  daisy-wheel 
printers.  Using  Fancy  Font  is  also  con¬ 
siderably  more  fun. 

In  summary,  the  program  produces 
good  output  and  works  reliably.  Except 
for  the  glitch  with  the  font  conversion 
utility,  I  have  never  had  a  problem  with 
the  system.  The  new  additions  made  to 
version  2.0  make  the  system  considera¬ 
bly  more  flexible  and  easy  to  use. 

DDJ 


105 

79 


Dr.  Dobb's  Journal.  January  1985 


BOOK  REVIEWS 


The  C  Programming  Tutor 
by  Leon  A.  Wortman  and 
Thomas  O.  Sidebottom 
Published  by  Robert  J.  Brady  Co. 
274  pages,  paperback 
Reviewed  by  Ian  Ashdown 

This  is  most  decidedly  not  your  aver¬ 
age  book  on  the  C  programming  lan¬ 
guage.  While  the  authors  follow  the 
usual  format  of  explaining  the  features 
of  the  language  with  examples,  it  is  the 
examples  that  set  this  book  apart  from 
all  others. 

Kernighan  &  Ritchie  in  The  C  Pro¬ 
gramming  Language  (Prentice-Hall) 
may  have  set  the  stage  for  The  C  Pro¬ 
gramming  Tutor  through  their  own 
examples.  These  included  a  fully  func¬ 
tional  word  frequency  utility  using  bi¬ 
nary  trees  and  a  very  elegant  demon¬ 
stration  of  recursion.  Wortman  and 
Sidebottom,  however,  have  taken  the 
concept  of  instructive  examples  one  gi¬ 
ant  step  further  by  presenting  nontrivi¬ 
al  and  useful  programs  to  demonstrate 
C.  They  explain  each  feature  of  the 
language  in  the  context  of  an  overall 
program  rather  than  as  an  isolated 
example. 

To  be  more  specific,  these  programs 
include  (in  order  of  appearance  and 
complexity)  a  simple  calculator,  a  his¬ 
togram  generation  utility,  a  file  encryp¬ 
tion  program,  a  printer  configuration 
utility,  three  text  readability  analyzers, 
a  word  frequency  analyzer,  a  C  pro¬ 
gram  cross-reference  listing  utility,  and 
a  chart  generator  for  C  program  func¬ 
tion  calls.  By  the  time  the  authors  get 
around  to  presenting  the  function  call 
chart  generator,  they  have  interspersed 
the  text  with  some  very  complex  C 
code.  It  is  all  explained  in  detail,  howev¬ 
er,  so  that  you  can  understand  and 
learn  from  the  listings  as  you  key  them 
into  your  computer. 

The  book  comprises  two  parts:  “Tu¬ 
torial”  and  “Useful  Programs.”  The  tu- 

Dr.  Dohb’s  Journal,  January  1985 


torial  takes  you  by  the  hand  through 
the  usual  programming  examples  to 
demonstrate  the  various  features  of  the 
language — but  with  a  firm  emphasis  on 
solving  problems.  Each  example  is  stat¬ 
ed  first  as  a  problem  with  the  solution 
blocked  out  in  English-language  state¬ 
ments.  The  following  text  then  shows 
how  you  can  implement  these  state¬ 
ments  in  C.  Very  much  in  the  spirit  of 
C,  the  authors  use  the  resultant  func¬ 
tions  later  for  the  solution  of  more  com¬ 
plex  problems.  As  more  interesting  fea¬ 
tures  of  C  are  discussed,  many  of  the 
functions  are  progressively  rewritten  to 
include  them. 

I  should  say  here  and  now  that  1  do 
not  recommend  The  C  Programming 
Tutor  as  a  first  and  only  guide  to 
learning  the  C  language.  Learning  how 
to  program  in  C  is  not  particularly 
easy,  and  the  fast  pace  of  this  book  is 
likely  to  leave  the  novice  confused  and 
frustrated.  Learn  C  from  a  book  like 
Jack  Purdum’s  C  Programming  Guide 
(Que  Corp.)  then  use  this  book  to  pol¬ 
ish  your  skills. 

Since  this  book  teaches  C  by  way  of 
programming  examples,  1  also  do  not 
recommend  it  as  a  reference  guide  to 
the  language.  While  most  of  C  is  de¬ 
scribed  in  detail,  the  information  is 
subordinate  to  the  programs.  Unless 
you  read  the  book  from  cover  to  cover, 
you  will  have  a  hard  time  trying  to  find 
specific  information  about  some  of  C’s 
features.  As  an  example,  looking  up 
“#define”  tells  you  only  that  it  is  a 
compiler  directive  used  for  defining 
constants.  You  have  to  know  already 
about  its  macro  capabilities  to  realize 
that  the  index  entry  for  “code  macros 
-expanding  the  macro”  is  an  impor¬ 
tant  related  topic. 

One  nice  touch  is  the  ongoing  dis¬ 
cussion  of  various  C  compilers,  includ¬ 
ing  both  Unix  and  microcomputer  im¬ 
plementations.  While  C  is  nominally 
“the”  transportable  compiler,  every 


compiler  writer  seems  to  have  his  or 
her  own  ideas  about  compatibility.  The 
authors  frequently  warn  you  that  your 
particular  compiler,  especially  micro¬ 
computer  versions,  may  not  support 
the  feature  currently  under  discussion. 

The  chapter  on  strings,  whimsically 
named  “Things  Called  Strings,”  pre¬ 
sents  a  bonus:  complete  implementa¬ 
tions  of  strlen,  strcpy,  strcat,  strcmp, 
and  index.  A  recent  trend  among  the 
companies  distributing  C  compilers  is 
to  sell  the  source  code  to  the  standard 
library  functions  at  additional  cost,  if 
at  all.  This  is  most  unfortunate,  for  this 
code  is  an  invaluable  resource  when  it 
comes  to  learning  the  language  and 
writing  variations  of  these  functions. 
While  there  are  many  more  functions 
in  the  standard  library,  the  code  pre¬ 
sented  is  nonetheless  instructive. 

“Things  Called  Strings”  also  pre¬ 
sents  the  source  code  for  a  pattern¬ 
matching  utility  that  finds  a  string  in  an 
ASCII  file.  While  having  nowhere  near 
the  power  and  flexibility  of  the  Unix 
command  grep,  it  is  again  instructive  as 
a  program  example.  With  a  bit  of  pro¬ 
gramming  initiative,  you  could  expand 
it  into  something  quite  useful. 

The  next  chapter,  entitled  “Paths  And 
Pointers,”  illustrates  the  interesting  ap¬ 
proach  the  authors  have  taken  to  this  of¬ 
ten  confusing  topic.  The  C  Program¬ 
ming  Tutor  likens  pointers  to  paths  that 
lead  to  various  places.  Between  this 
analogy  and  the  illustrations,  the  mys¬ 
tery  of  pointers  is  clearly  explained. 

The  tutorial  part  of  the  book  ends 
with  the  histogram  generation  utility, 
version  10.  Part  Two,  “Useful  Pro¬ 
grams,”  is  a  bit  of  a  misnomer,  for  it 
covers  such  essential  C  language  fea¬ 
tures  as  the  preprocessor’s  macro  ca¬ 
pabilities  (the  preprocessor  itself  is 
never  discussed),  memory  allocation, 
structures  (in  two  pages!),  initializa¬ 
tion,  typedefs,  and  buffering.  Other 
features  such  as  separate  compilation 

106 


73 


of  modules,  storage  classes,  pointers  to 
functions,  external  functions,  unions, 
and  bit  fields  are  ignored  altogether. 

If  you  know  anything  at  all  about  C, 
you  may  think  from  the  last  statement 
that  The  C  Programming  Tutor  is 
somewhat  lacking  as  a  tutorial  on  the 
language.  The  authors  fully  admit 
this- -they  did  not  intend  for  their 
book  to  cover  every  aspect  of  C.  The 
purpose  of  The  C  Programming  Tutor 
is  to  teach  the  reader  how  to  program 
effectively  in  C,  not  to  teach  the  lan¬ 
guage  per  se. 

Five  major  programs  are  presented, 
ranging  from  EPSET,  a  simple  utility 
program  for  configuring  software-pro¬ 
grammable  dot  matrix  printers,  to 
CALLS,  which  prints  a  chart  of  how 
functions  in  a  C  program  call  one  an¬ 
other.  (To  be  candid,  CALLS  is  the 
reason  I  bought  this  book.  It  has  prov¬ 
en  useful  as  a  debugging  and  documen¬ 
tation  tool,  well  worth  the  price  of  the 
book  in  itself.) 

Three  versions  of  a  text  analysis  pro¬ 
gram  are  given.  One  is  an  extended 
version  of  the  Unix  command  wc 
(word  count)  that  counts  the  number 
of  characters,  words,  lines,  and  sen¬ 
tences  in  a  file.  The  second  version  ap¬ 
plies  a  definitive  “readability”  formula 
from  Rudolf  Flesch’s  The  Art  of 
Readable  Writing  (Harper  &  Row) 
that  uses  the  count  of  words,  average 
sentence  length,  syllable  count,  names, 
and  personal  pronouns  to  determine 
the  ease  of  reading  and  human  interest 
content  of  a  body  of  text. 

The  third  version  uses  the  same  for¬ 
mula  but  optimizes  its  performance  by 
substituting  a  hash  search  method  for 
the  linear  method  used  in  the  second 
version.  The  discussion  on  hashing  is 
very  instructive  and  produces  some  in¬ 
teresting  code  that  can  be  adapted  for 
use  in  many  other  programs. 

The  next  program  is  the  word  fre¬ 
quency  analyzer  WFREQ.  While  of 
some  interest,  this  program  is  eclipsed 
by  Kernighan  &  Ritchie’s  version  in 
The  C  Programming  Language.  The 
inherent  sorting  of  words  performed  by 
their  recursive  transversal  of  a  binary 
tree  is  far  more  interesting  than  the 
hash  table  entry  and  shell  sort  methods 
used  here.  (With  the  power  of  recur¬ 
sion  available  in  C,  Wortman  and 
Sidebottom  did  not  bother  to  discuss 
Quicksort ....  Shame!) 


X REF  is  a  C  program  cross-reference 
listing  utility  that  reads  a  file  of  C 
source  code,  adds  line  numbers,  and 
makes  an  alphabetically  sorted  list  of 
all  the  names  (tokens)  used  in  the  file, 
along  with  a  record  of  the  line  numbers 
associated  with  each  name.  Apart  from 
its  obvious  usefulness  in  debugging  and 
documenting  other  C  programs,  XREF 
affords  the  authors  a  chance  to  discuss 
such  topics  as  linked  lists  and  com¬ 
pound  data  structures.  The  code  itself  is 
quite  complex  but  well  documented.  As 
with  the  text  analysis  programs,  some 
of  the  developed  functions  are  useful  in 
their  own  right. 

Finally,  there  is  CALLS.  As  stated 
earlier,  this  program  is  the  reason  I 
bought  The  C  Programming  Tutor.  In 
operation,  CALLS  scans  a  C  source 
code  file  looking  for  functions.  When  it 
finds  one,  it  scans  the  body  of  the  func¬ 
tion  for  calls  made  to  other  functions. 
When  it  has  done  this  for  all  functions, 
a  chart  is  printed  showing  the  functions 
in  their  order  of  occurrence.  Function 
calls  are  indicated  by  indenting  the 
names,  recursive  functions  are  identi¬ 
fied,  and  repetitively  called  functions 
have  their  first  occurrences  referenced 
by  line  number.  A  key  advantage  of  the 
call  chart  output  is  that  it  is  a  graphic 
display  of  the  program  structure. 

The  book  finishes  with  four  appendi¬ 
ces.  Appendix  A  discusses  the  various 


C  compilers  available  (as  of  July  1983) 
for  CP/M-80,  CP/M-86,  and  MSDOS. 
The  information  presented  is  interest¬ 
ing  but  was  already  out  of  date  by  the 
time  the  book  was  published.  Only  one 
thing  remains  constant  in  the  world  of 
microcomputer  C  compilers:  they  will 
all  undergo  continual  improvement  un¬ 
til  they  are  fully  compatible  with  their 
Unix  brethren. 

The  remaining  three  appendices  dis¬ 
cuss  the  C  header  files  ctype.h  and 
math.h.  The  source  code  is  given  for 
ctype.h,  which  defines  the  character 
classification  and  conversion  functions 
(isascii,  iscntrl,  toascii,  etc.)  of  C’s 
standard  library.  The  source  code  is 
also  given  for  math.h,  but  the  tran¬ 
scendental  functions  are  declared  as 
“external.”  The  authors  simply  note 
that  if  a  compiler  supports  float  and 
double  data  types  but  does  not  provide 
mathematical  functions,  an  experi¬ 
enced  programmer  can  write  them  in 
C  and  declare  them  using  math.h. 

Conclusion?  If  you  program  in  C, 
buy  this  book  for  CALLS  and  XREF 
alone.  The  rest  of  the  book  will  be  of 
varying  usefulness  and  interest  to  dif¬ 
ferent  people,  but  most  will  learn  and 
profit  from  reading  it. 

DDJ 


107  Dr.  Dobb's  Journal,  January  1985 

74 


PCDOS  version  3.0 

Along  with  the  recently  announced  PC/ 
AT  computer,  IBM  announced  release 
3.0  of  PCDOS.  Together  with  a  bad 
case  of  bloat  (version  3.0  occupies  some 
48  kilobytes  of  memory  on  a  PC/AT 
with  hard  disk),  the  new  release  of 
PCDOS  included  a  number  of  addition¬ 
al  or  modified  function  calls  compared 
to  version  2.  Since  the  DOS  Technical 
Manual  is  still  a  very  scarce  commod¬ 
ity,  I  will  provide  an  overview  of  the  sig¬ 
nificant  changes  in  this  month’s 
column. 

One  of  the  most  interesting  features 
of  the  release  of  PCDOS  3.0  is  the  vir¬ 
tual  disappearance  of  its  originator, 
Microsoft,  into  the  background  noise. 
In  fact,  the  word  ‘Microsoft’  doesn’t 
appear  at  all  in  sign-on  messages  or  the 
various  DOS  manuals,  and  can  be 
found  only  in  very  small  print  on  the 
diskette  label.  Although  IBM  never 
discusses  its  future  product  plans,  still, 
if  you  were  in  charge  of  the  Entry  Sys¬ 
tems  Division  and  you  saw  Bill  Gates 
on  television  advertising  the  Macin¬ 
tosh,  what  would  you  be  motivated  to 
do?  More  on  this  subject  later,  but  re¬ 
flect  on  the  fact  that  TopView  is  a 
high-performance,  windowing  envi¬ 
ronment  that  completely  hides  PCDOS 
and  has  been  earmarked  as  an  IBM 
“Strategic  Product,”  and  you  may  get 
an  idea  which  way  the  wind  is  blowing. 

But  back  to  the  new  software.  The 
Critical  Error  Handler  interrupt  (Int 
24H)  has  been  slightly  modified,  with 
the  addition  of  some  new  status  infor¬ 
mation  passed  in  AH  and  a  new  DOS 
response  upon  return  (the  application 
can  ask  for  the  offending  system  call  to 
be  “failed”  as  well  as  just  ignore,  retry, 
or  terminate  the  program).  The  docu¬ 
mentation  on  how  to  field  the  critical 
error  interrupt,  which  was  rather 
sketchy  before,  is  still  somewhat  cryp¬ 
tic,  but  has  been  expanded  to  the  point 

Dr.  Dobb’s  Journal,  January  1985 


of  being  comprehensible. 

Interrupt  2FH  is  new,  and  is  essen¬ 
tially  an  interface  between  application 
software  and  the  print  spooler.  A  user 
program  can  add  files  to  the  print 
queue,  cancel  files,  or  examine  the 
spooler’s  list  of  waiting  files. 

Under  the  general  heading  of  Inter¬ 
rupt  21 H  (DOS  function  calls),  the  fol¬ 
lowing  three  functions  were  modified: 

Function  38H  (Get  or  Set  Country  In¬ 
formation)  was  radically  expand¬ 
ed:  first,  with  the  ability  to  set  a 
country  code  as  well  as  to  interro¬ 
gate  it;  next,  with  the  capacity  for 
more  than  255  country  codes;  and 
last,  with  much  more  information 
passed  back  in  the  data  block  re¬ 
garding  country  specific  delimit¬ 
ers  and  formats. 

Function  3DH  (Open  File)  was  en¬ 
hanced  to  support  both  multi-task¬ 
ing  and  networking.  When  a  file  is 
opened,  the  application  can  speci¬ 
fy  whether  it  will  be  “inherited” 
by  a  child  process  running  in  the 
same  network  node,  and  whether 
it  can  be  “shared”  (i.e.  indepen¬ 
dently  opened)  by  another  task 
running  in  the  same  or  another 
network  node. 

Function  44H  (I/O  Device  Control) 
has  two  new  capabilities:  to  inter¬ 
rogate  whether  a  particular  block 
device  has  removable  media,  and 
to  set  the  number  of  retries  and  the 
delay  between  retries  for  “file 
sharing  conflicts.” 

There  are  also  five  completely  new 
documented  function  calls  for  Inter¬ 
rupt  21  H: 

Function  59H  (Get  Extended  Error) 
can  be  called  by  an  application  af¬ 
ter  an  error  code  is  returned  by 
some  other  function  to  get  more 
detailed  information  about  the 


failure,  the  class  of  failure  (tempo¬ 
rary,  system  internal,  hardware, 
etc.),  and  the  recommended  re¬ 
sponse  for  the  application  (retry, 
delay  and  retry,  abort,  etc.) 
Function  5AH  (Create  Temporary 
File)  is  passed  a  path  string  by  the 
application,  generates  a  unique 
filename  string,  creates  the  file  in 
the  specified  subdirectory,  and  re¬ 
turns  a  complete  path  and  file 
specification  string.  The  file  can 
then  be  opened  by  the  application 
and  used  in  any  way  desired.  The 
file  is  not  deleted  automatically 
when  the  application  terminates. 
Function  5BH  (Create  File)  is  exactly 
like  the  DOS  2.0  function  3CH 
(Create  File),  except  that  it  will 
fail  if  the  file  already  exists.  Func¬ 
tion  3CH,  on  the  other  hand,  trun¬ 
cates  a  previously  existing  file  by 
the  same  name  to  zero  length,  then 
returns  a  success  code. 

Function  5CH  (Lock/Unlock  File  Ac¬ 
cess)  provides  a  general  mecha¬ 
nism  for  a  task  to  gain  exclusive  ac¬ 
cess  to  a  region  of  a  file,  even  if  the 
file  is  being  shared  with  other 
tasks. 

Function  62H  (Get  Program  Segment 
Prefix  Address)  is  self-explana¬ 
tory.  This  will  be  handy  for  writers 
of  EXE-type  programs. 

With  release  3.0  of  PCDOS,  it  is  in¬ 
deed  clear  that  this  operating  system  is 
going  to  continue  to  get  more  and  more 
complex  and  powerful,  even  if  it  (or  its 
descendent)  is  eventually  completely 
submerged  in  TopView  or  some  other 
IBM-proprietary  shell.  I  suggest  that 
the  following  guidelines  for  “well-be¬ 
haved”  applications  will  make  the  pro¬ 
grammer’s  life  easier  in  the  brave  new 
world  of  multi-tasking  and  networking 
that  is  nigh  upon  us: 

•  Use  the  Extended  File  functions 

108 


75 


(3CH-42 H)  to  open,  close,  read,  and 
write  files,  rather  than  the  old  “FCB” 
type  calls  which  were  cloned  from 
CP/M.  The  extended  functions  are 
more  powerful,  have  better  error  re¬ 
porting,  and  support  the  hierarchical 
file  structure  and  file  sharing. 

•  Use  the  Modify  Allocated  Memory 
Blocks  call  to  release  any  memory  that 
is  not  required  by  the  application  pro¬ 
gram.  In  the  same  vein,  be  sure  to  ex¬ 
amine  the  program  segment  prefix  to 
find  the  amount  of  memory  allocated 
to  your  application,  and  do  not  access 
memory  outside  those  bounds  (even  if 
you  can  establish  that  more  memory  is 
physically  present). 

•  Don’t  access  the  interrupt  vectors  di¬ 
rectly;  use  the  DOS  function  calls  25H 
and  35H  to  set  and  get  the  contents  of 
interrupt  vectors  respectively.  This  has 
implications  for  TopView  as  well  as  for 
the  80286  in  the  PC/AT,  which  can 
support  multiple  tables  of  interrupt 
vectors  in  protected  mode. 

•  When  your  program  gets  control,  use 
function  30H  to  get  the  DOS  function 
number.  If  your  program  is  running 
under  DOS  3.x,  interrogate  the  extend¬ 
ed  error  codes  (function  59H)  when  a 
DOS  function  fails  to  get  more  detailed 
information  and  the  suggested  action. 
In  any  case,  employ  the  critical  error 
handler  interrupt  capabilities  to  make 
your  program  more  forgiving  of  hard¬ 
ware  errors. 

•  Use  the  Exec  call  to  spawn  other  tasks 
or  load  overlays;  don’t  try  to  set  up  Pro¬ 
gram  Segment  Prefixes  yourself. 

•  Write  directly  to  the  video  buffer  if 
you  must,  but  perform  all  mode 
changes  through  calls  to  the  ROM 
BIOS.  Cause  all  video  code  to  access  a 
variable  that  contains  the  buffer  seg¬ 
ment  address.  This  will  make  modifi¬ 
cation  of  a  program  for  operation  un¬ 
der  TopView  (where  a  “shadow 
buffer”  is  assigned)  much  simpler. 

•  If  writing  to  a  standard  device  or  a 
file,  send  strings  rather  than  single 
characters  whenever  possible.  Take 
advantage  of  the  buffering  and  fast 
transfer  logic  written  into  the  DOS 
drivers.  Conversely,  don’t  waste  your 
program’s  resources  on  internal  block¬ 
ing  and  deblocking  of  records  or  char¬ 
acters  when  the  DOS  is  already  impos¬ 
ing  multiple  layers  of  buffering 
between  you  and  the  device. 

•  Before  exiting  the  application,  be 

109 


careful  to  close  all  file  control  blocks 
and/or  handles.  If  regions  of  a  file 
have  been  locked,  unlock  them  before 
closing  the  file. 

•  Terminate  your  program  with  a  re¬ 
turn  code  via  function  4CH  of  Inter¬ 
rupt  21  H,  rather  than  the  previously 
accepted  mechanism  of  function  0  or 
Int  20H.  The  return  code  can  be  inter¬ 
rogated  by  the  invoking  process  or  by 
the  batch  subcommands  IF  and 
ERRORLEVEL. 

•  If  writing  a  resident  driver,  use  Inter¬ 
rupt  21 H  Function  31 H  to  terminate 
and  stay  resident,  rather  than  the  pre¬ 


viously  approved  Interrupt  27H.  This 
allows  for  return  information  to  be 
passed,  and  also  for  resident  programs 
larger  than  64  kilobytes. 

Graphics  Routines  for  the 
IBM  PC 

Bruce  A.  Smith  writes:  “Enclosed  is  an 
8088  assembly  listing  of  a  high  perfor¬ 
mance  line  drawing  and  point  plotting 
routine  for  the  IBM  PC  or  PCjr.  These 
routines  were  written  for  utilization  in 
application  programs  that  I  am  devel- 


Dr.  Dobb  s  Journal,  January  1985 


oping  in  ‘C\  I  wish  to  encourage  the 
integration  of  graphics  in  software  and 
I  thought  these  two  fast  ‘nuts  and 
bolts’  plotting  routines  were  worth 
sharing  with  your  readers. 

“Listing  One  (page  1 10)  contains  the 
two  procedures  to  plot  a  point  or  draw  a 
line  on  the  medium  resolution  screen  of 
the  PC.  The  line  drawing  routine  uses 
self-modifying  code.  They  are  both 
written  to  OR  the  color  bits  onto  the 
screen.  The  line  drawing  routine  is  ap¬ 
proximately  three  times  faster  than  the 
one  listed  in  Morgan’s  Bluebook  of  As¬ 
sembly  Routines  for  the  IBM  PC  & 
XT.  It  is  also  three  times  faster  than  the 
line  drawing  routine  used  in  the  ROM 
Basic  on  the  PCjr.  The  point  plotting 
routine  is  a  little  more  difficult  to  com¬ 
pare,  because  the  overhead  of  the  call 
and  the  computation  for  the  next  point 
become  significant.  In  performance 
tests,  it  plotted  points  half  again  as  fast 


as  the  routine  in  Morgan’s  book,  and  in 
spite  of  the  overhead,  it  performed  four 
times  faster  than  the  ROM  BIOS  point 
plotting  routines. 

“Listing  Two  (page  122)  is  a  ‘C’  pro¬ 
gram  which  illustrates  use  of  these  rou¬ 
tines  from  a  high  level  language.”  In¬ 
terested  readers  can  contact  Bruce 
Smith  at  305  E.  Edgewood  Blvd.,  Apt. 
5,  Lansing,  MI  48910. 

The  line  drawing  routine  sent  by 
Mr.  Smith  employs  Bresenham’s  Al¬ 
gorithm,  which  is  particularly  well 
suited  to  microcomputer  graphics  rou¬ 
tines  because  it  has  a  simple  inner  loop 
and  uses  only  integer  arithmetic.  It 
was  first  published  by  J.  E.  Bresenham 
in  the  article  “Algorithm  for  Comput¬ 
er  Control  of  Digital  Plotters,”  IBM 
Systems  Journal ,  4  (1)1 965,  pages 
25-30. 

Those  of  you  delving  into  graphics 
on  any  microcomputer  will  find  the  fol- 


16-Bit  (Text  begins  on  page  108) 

Listing  One 

PAGE  255,132 


Plot  Point  or  Draw  Line  on  IBM  Color  Graphics  Adaptor 

"or  line. asm" 

"orpt.asm" 

written  by  Bruce  A.  Smith  7/24/84 


DGROUP 

GROUP 

DATA 

DATA 

SEGMENT 

WORD  PUBLIC  'DATA' 

ASSUME 

DS:  DGROUP 

PUBLIC 

COLOR, Xl,X2, VI, Y2 

t 

;  the  order  and 

type  of  declaration 

is  important 

i 

y2 

dw 

0 

x2 

dw 

0 

yi 

dw 

0 

X  1 

dw 

0 

color 

db 

0 

/ 

;  color 

=  color 

*4+1,  color  0  == 

mask 

r 

ctableh 

db 

03Fh,0CFh,0F3h,0FCh 

db 

040h, 010h, 004h, 001h 

db 

080h,020h,008h,002h 

db 

0C0h, 030h, 00Ch, 003h 

f 

f akedw 

=  1000h 

DATA 

ENDS 

lowing  two  books  to  be  invaluable: 

Fundamentals  of  Interactive  Comput¬ 
er  Graphics ,  by  J.  D.  Foley  and  A. 
Van  Dam,  Addison- Wesley,  1982. 
This  book  is  the  standard  by  which 
all  future  graphics  texts  will  be 
measured.  Incidentally,  it  contains 
a  nice  explanation  of  the  Bresen¬ 
ham  algorithm  on  pages  433  -  436. 
on  pages  433-436. 

Applied  Concepts  in  Microcomputer 
Graphics ,  by  Bruce  A.  Artwick 
(author  of  the  IBM  PC  Flight  Sim¬ 
ulator  program),  Prentice  Hall, 
1984.  No-nonsense,  practical  ad¬ 
vice  from  a  guy  who  has  done  it  all. 

DDJ 


(Continued  on  page  112) 


Dr.  Dobb's  Journal,  January  1985 


110 

77 


16-Bit 


(Listing  Continued,  text  begins  on  page  108) 


Listing  One 


PGROUP  GROUP  PROG 
PROG  SEGMENT  BYTE  PUBLIC  'PROG' 
PUBLIC  ORL I NE , ORPT 
ASSUME  CS: PGROUP 


ocline.asm 


ROUTINE  TO  OR  A  LINE  ONTO  MEDIUM  RESOLUTION  SCREEN 
uses  Bresenham's  algorithm 
orline  proc  near 


push 

bp 

;  save  calling  bp 

;  only  reg  needed 

push 

ds 

;  save  ds 

get  x 

&  y  values 

mov 

si, OFFSET  y2 

;  addr  y2 

lodsw 

;  ax  =  y 2 

xchg 

ax,dx 

;  dx  =  y2 

lodsw 

;  ax  =  x2 

xchg 

ax  ,di 

;  di  =  x2 

lodsw 

;  ax  =  yl 

xchg 

ax,cx 

;  cx  =  yl 

lodsw 

;  ax  =  xl 

mov 

bx ,  s  i 

;  bx  =  addr  color 

xchg 

ax , si 

;  si  =  xl 

cmp 

si  ,di 

;  cmp  xl,x2 

jle 

swapxy 

;  skip  if  (xl<=x2 

xchg 

cx  ,dx 

;  (xl>x2) :  swap  y 

xchg 

si  ,di 

;  swap  xl,x2 

japxy : 

l 

H 

i 

L  1 

ax  |  |  | 

bx  | 

addr  color 

1 

cx  | 

0  | 

yi  | 

dx  | 

0  | 

y2  | 

si  | 

xl 

1 

di  | 

x2 

1 

bp  |  | 

ch  = 

deldy  = 

(yl>y2)  ?  -80  : 

80 

dx  = 

|y2-yl| 

sub 

dx  ,cx 

;  y 2-y 1 

mov 

al ,  80 

;  deldy  =  80 

jge 

ydown 

;  skip  if  (yl<=y2 

neg 

dx 

;  |y2-yi| 

neg 

al 

;  deldy  =  -1 

lown : 

sub 

di ,  s  i 

;  x2-xl 

if  (  xl  >  x2  ) 
swap  (xl,yl)  with 
(x2,y2) 

ie. 

xchg  cx,dx 
xchg  si,di 


;  di  =  |x2-xl 


112 


(Continued  on  page  115) 


"70 


Dr.  Dobb’s  Journal.  January  1985 


16-Bit  (Listing  Continued,  text  begins  on  page  108) 

Listing  One 


1  H  |  L  | 

ax  | 

|  (yl>y2) ? 

-80:  80 

1 

bx  | 

addr  color 

1 

cx  | 

S3 

1— * 

1 

dx  | 

0  |  absdy  = 

|y2-yi| 

1 

si  | 

x  1 

1 

di  | 

absdx  =  x2-xl 

1 

bP  1  1 

al  =  80 

dx-cx  =  y2-yl 
if  neg  (yl>y2) 
neg  dx  =  |  y 2-y 1 1 
al=-80 


deldy  =  al 


di-si 
=  | x2-x 1 | 


cmp 

lahf 

di  ,dx 

;  absdx, absdy 

minmax 
;  dx  = 
;  di  = 

jnl 

xchg 

dmin 

dmax 

minmax 
di  ,dx 

;  skip  if  (absdx>=absdy ) 

H  1 

L 

1 

ax 

flags  absdx, absdy | 

deldy 

1 

bx 

addr  color 

1 

cx 

0  | 

yi 

1 

dx 

0  | 

dmin 

1 

si 

xl 

1 

di  1 

dmax 

1 

bP  |  | 

if  (absdx  <  absdy) 
cmp  di,dx  lahf 
jnl  - 

swap (absdx, absdy) 
xchg  di,dx 


xchg 

ax, bp 

bp=flags (absdx, absdy)  &  deldy 

ROUTINE  TO 

FIND  INITIAL  Y-ADDR 

X-ADDR ,  AND  ROTATED  COLOR 

multiply  y 

-coord  by  bytes  per  row  and  adjust  for  even/odd  lines 

ror 

cl,  1 

adjust  odd/even 

mov 

ax ,  cx 

ax  =  cx  =  adj  y-coord 

and 

al ,  7Fh 

page  mask 

sal 

cx ,  1 

times  2 

sa  1 

cx ,  1 

times  4 

add 

cx ,  ax 

y-coord  times  5 

sal 

cx,  1 

times  10 

sal 

cx ,  1 

times  20 

sal 

cx ,  1 

times  40 

sal 

cx ,  1 

times  80 

1 

H  1 

L  1 

ax  | 

0  | 

i 

(Continued  on  next  page) 


Dr.  Dobb’s  Journal,  January  1985 


115 

79 


16-Bit 


(Listing  Continued,  text  begins  on  page  108) 


Listing  One 


bx 

addr  color  =  addr 

ctableh-1 

1 

cx 

y-addr 

1 

dx 

0  | 

dmin 

1 

si 

xl 

1 

di 

dmax 

1 

bp 

flags  absdx,absdy| 

deldy 

1 

compute  the  rotated  mask  and  co 
bx  =  ctableh-1  =  addr  color 


mov 

al ,  3 

and 

ax ,  s  i 

add 

al , [bx] 

xlat 

or 

pixel  position  mask 
just  the  bit  count  into  the  index 
pixel  position  +  color  (*  4  +  1) 
look  up  the  masks  al=(al+bx) 


mov  bx , 0B800H 

mov  ds,bx 


disp  seg  base  addr 
ds  =  display  base  addr 


al  =  rotated  color 
cx  =  y-addr  offset 
ds  =  display  addr 


i 

H 

1  L 

1 

ax  | 

0 

rotated  color 

1 

bx  |  |  | 

cx  | 

y-addr 

1 

dx 

0 

dmin 

1 

si 

xl 

1 

di 

dmax 

1 

bp 

flags  absdx,absdy|  deldy 

J 

sal 

dx ,  1 

t 

dx  =  delse  =  dmin  *  2 

xchg 

cx,di 

t 

cx  =  dmax,  di  =  y-addr 

xchg 

ax  ,dx 

} 

ax=delse,  dx=rotated  color 

xchg 

ax ,  bp 

1 

ax=flags (absdx,absdy) ,  bp=delse 

|  H  |  L  | 

ax 

1 

flags  absdx,absdy|  deldy 

1 

bx  1  |  | 

cx 

1 

dmax 

1 

dx 

1 

0  |  rotated  color 

1 

si 

1 

xl 

1 

di 

1 

y-addr 

1 

bp 

1 

dmin  *  2  =  delse 

J 

116 


Dr.  Dobb’s  Journal,  January  1985 


9 

sahf 

9 

cmp  absdx,absdy 

pushf 

9 

cbw 

9 

ax  =  deldy 

mov 

cs:delsy,ax 

9 

save  deldy 

mov 

mov 

mov 

cs:deldy,ax 
cs :deldy2 ,ax 
bx  ,di 

9 

bx  =  y-addr 

or 

js 

ax, ax 
negdeldy 

9 

if  deldy<0  jmp 

test 

bh,20H 

9 

is  page  bit  set? 

jz 

toggle 

9 

skip  to  toggle  page 

add 

bx  ,ax 

9 

add  deldy  to  y-addr 

jmp 

toggle 

9 

skip  to  toggle  page 

negdeldy : 

test 

bh,20H 

9 

is  page  bit  set? 

jnz 

toggle 

9 

skip  to  toggle  page 

add 

bx  ,ax 

9 

add  deldy  to  y-addr 

toggle: 

xor 

bh , 20H 

9 

flip  page  bit 

;  bx  =  next  y- 

addr 

|  H  |  L  | 

ax  1  1  1 

bx 

1 

y-addr  (next) 

1 

cx 

1 

dmax 

1 

dx 

1 

0  |  rotated  color 

1 

si 

1 

xl 

1 

di 

1 

y-addr 

1 

bp 

1 

dmin  *  2  =  delse 

_l 

xchg 

ax  ,bp 

mov 

cs:delse,ax 

mov 

cs:delse2,ax 

sub 

ax ,  cx 

mov 

bp,  ax 

sub 

ax ,  cx 

mov 

cs:delde,ax 

mov 

cs:delde2,ax 

xchg 

ax  ,bx 

figure  x-coord  address 
mov  bx,si 

sar  bx,l 

sar  bx,l 

bx  *>  x-addr  offset 


ax  =  dmin  *  2  =  delse 
save  delse 
save  delse 

ax  =  dmin  *  2  -  dmax  =  d 
bp  =  d  =  error  term 
ax  =  dmin  *  2  -  dmax  *  2 
save  delde 
save  delde 

ax  =  next  y-addr 


;  get  x-coordinate 
;  divide 
;  by  4 


1 

H 

1  L  1 

ax  | 

y-addr  (next) 

bx  | 

x-addr 

cx  | 

loop  count 

Dr.  Dobb's  Journal,  January  1 985 


(Continued  on  next  page ) 


117 

81 


16-Bit 


(Listing  Continued,  text  begins  on  page  108) 


Listing  One 


dx  |  |  rotated  color 


i  si  | 

x  value 

1 

;  di  | 

y-addr 

1 

;  bp  | 

error  term 

9 

i 

} 

popf 

jns 

delsx2 

;  cmp  absdx, absdy 
;  if  (absdx>=absdy )  goto  delsx2 

;  delsx 

=  0 

(absdx  <  absdy) 

or 

jcxz 

or 

jge 

[bx] [di] ,dl 
lineexit 
bp, bp 
diagonal 

or  disp  with  color  (plot 
quit  when  cx=0 
set  bp  flags 
if  bp>=0  jmp 

point) 

e 

;  case  for  straight  move 
straight : 

xchg  ax,di 

delsy  =  $+1 

add  ax,fakedw 

every  other  for  page  adj 

++y 

9 

or 

dec 

jz 

[bx]  [di] ,dl 
cx 

lineexit 

or  disp  with  color  (plot 
— loop  counter 
quit  when  cx=0 

point) 

9 

delse 

=  $  +  2 

add 

js 

bp,fakedw 

straight 

update  error  term 
if  bp<0  goto  straight 

9 

;  case  for  diagonal  move 
diagonal : 

inc  si 

mov  bx,si 

sar  bx,l 

sar  bx,l 

++x  value 
bx  =  x  value 

bx  =  x  addr  offset 

9 

ror 

ror 

dl ,  1 
dl ,  1 

adjust  color  position 

9 

deldy 

xchg 
=  $  +  1 
add 

ax  ,di 

ax , f akedw 

every  other  for  page  adj 

++y 

t 

or 

dec 

jz 

[bx]  [di] ,dl 
cx 

lineexit 

or  disp  with  color  (plot 
;  — loop  counter 
quit  when  cx=0 

point) 

r 

delde 

9 

=  $  +  2 
add 

js 

jmp 

bp , f akedw 

straight 

diagonal 

update  error  term 
;  if  bp<0  goto  straight 
;  if  bp>=0  goto  diagonal 

i 

delsx2: 

;  delsx 

=  1 

(absdx  >=  absdy) 

9 

or 

jcxz 

or 

jge 

[bx] [di] ,dl 
lineexit 
bp,  bp 
diagonal2 

;  or  disp  with  color  (plot 
;  quit  when  cx=0 
;  set  bp  flags 
;  if  bp>=0  jmp 

point) 

118 


(Continued  on  page  120) 
Dr.  Dobb's  Journal,  January  1985 


16-Bit  (Listing  Continued,  text  begins  on  page  108) 

Listing  One 


;  case  for  straight  move 
straight2: 


inc 

si 

9 

++x  value 

mov 

bx,si 

9 

bx  =  x  value 

sar 

bx ,  1 

sar 

bx ,  1 

9 

bx  =  x  addr  offset 

9 

ror 

dl,  1 

9 

adjust  color  position 

ror 

dl,  1 

9 

or 

[bx] [di ] ,dl 

9 

or  disp  with  color  (plot 

point) 

dec 

cx 

9 

— loop  counter 

jz 

lineexit 

9 

quit  when  cx  =  0 

9 

delse2 

=  $  +  2 

add 

bp, fakedw 

9 

update  error  term 

js 

straight2 

9 

if  bp<0  goto  straight 

9 

;  case 

for  diagonal  move 

diagonal2: 

inc 

si 

9 

++x  value 

mov 

bx,si 

9 

bx  =  x  value 

sar 

bx ,  1 

sar 

bx ,  1 

9 

bx  =  x  addr  offset 

9 

ror 

dl ,  1 

9 

adjust  color  position 

ror 

dl ,  1 

9 

xchg 

ax,di 

9 

every  other  for  page  adj 

deldy2 

=  $  +  1 

add 

ax , fakedw 

9 

++y 

9 

or 

[bx] [di] ,dl 

9 

or  disp  with  color  (plot 

point) 

dec 

cx 

9 

— loop  counter 

jz 

lineexit 

9 

quit  when  cx=0 

delde  2 

=  $  +  2 

add 

bp, fakedw 

9 

update  error  term 

js 

straight2 

9 

if  bp<0  goto  straight 

9 

jmp 

diagonal2 

9 

if  bp>=0  goto  diagonal 

lineexit: 

pop  ds  ;  restore  ds 

pop  bp  ;  restore  calling  bp 

ret 
i 

orline  endp 


orpt.asm 


;  ROUTINE  TO  OR  A  POINT  ONTO  MEDIUM  RES  COLOR  SCREEN 

9 

orpt  proc  near 


;  get  initial  values  for  x  and  y 
mov  si, OFFSET  yl 

lodsw 

9 

;  multiply  y-coord  by  bytes  per 
r or  al , 1 

9 

mov  dx,0B87FH 


f  d'JUl  y± 

;  ax  =  yl 

row  and  adjust  for  even/odd  lines 
;  adjust  odd/even 

;  disp  addr  and  page  mask 


(Continued  on  page  122) 


Dr.  Dobb’s  Journal,  January  1985 


120 

83 


16-Bit 


(Listing  Continued,  text  begins  on  page  108) 


Listing  One 


and 

dl,al 

;  mask  page  bit,  disp  +  y. coord 

sal 

ax ,  1 

;  times  32 

sal 

ax ,  1 

;  times  64 

add 

dx ,  ax 

;  addr  disp  seg  +  y-coord  times  5  (80) 

9 

;  compute  x-coord  address 

offset 

lodsw 

;  ax  =  xl 

mov 

di  , ax 

;  get  x-coordinate 

sar 

di,  1 

;  divide 

sar 

di ,  1 

;  by  4 

9 

;  compute  the 

rotated  mask 

and  color 

and 

al ,  3 

;  just  the  bit  count  into  the  index 

add 

al, [si ] 

;  pixel  position  +  color  (*  4  +  1) 

mov 

bx  ,si 

;  bx  =  ctableh  -  1 

mov 

si  ,ds 

;  save  ds 

xlat 

;  look  up  the  masks  al=[al+bx] 

mov 

ds,dx 

;  set  seg  to  disp  +  y-addr 

or 

[di] ,al 

;  or  the  byte  with  the  color 

9 

mov 

ds,si 

;  restore  ds 

ret 

9 

orpt 

9 

endp 

9  mmm 

PROG 

ENDS 

END 

Listing  Two 

/* 

This  is  an  example  written  in  'C'  using  the  line  drawing  routine. 
—  Bruce  Smith. 

*/ 

extern  int  xl,yl,x2,y2; 
extern  char  color; 

main  ( ) 

{ 

int  incr=3; 

pcvsvm(4);  /*  color  med  res  graphics  */ 

color= (2<<2) +1 ; 

xl=160;  yl=100 ;  x2=0;  y2=0; 

for  (;  x2<319;  x2+=incr)  orline();  /*  draw  line  */ 
x2=319; 


for  (;  y2<199;  y2+=incr)  orline();  /*  draw  line  */ 
y2=199; 


for  ( ; 
x2=0 ; 

x2>0 ; 

x2- 

=incr) 

or  line  ( ) ; 

/* 

draw 

line 

V 

for  (; 

y2>0 ; 

y  2- 

=incr ) 

or  line  ( ) ; 

/* 

draw 

line 

V 

cget();  /*  wait  for  keypress  */ 

pcvsvm(2);  /*  80  col  b  &  w  */ 

} 


End  Listing  One 


122 


End  Listing  Two 

Dr.  Dobb’s  Journal,  January  1985 


THE  SOFTWARE  DESIGNER 


Tom  Evslin  and  his  company,  Solu¬ 
tions  Inc.,  have  written  seven  programs 
for  the  Macintosh  so  far,  including 
Dow  Jones  Straight  Talk  and  Spread¬ 
sheet  Link,  Rags  to  Riches  Payable 
and  Receivables,  and  learning  games 
for  Que.  Eighty  percent  of  the  compa¬ 
ny’s  development  work  is  software  for 
the  Mac.  As  the  first  person  in  his 
company  to  do  Mac  development,  Evs¬ 
lin  has  some  opinions  about  what  it’s 
like  to  develop  software  for  the  ma¬ 
chine  named  after  a  raincoat. 

Evslin:  As  everyone  has  been  saying, 
the  Mac  presents  a  bigger  hurdle  to 
software  development  than  any  past 
microcomputer.  The  vocabulary  of  ca¬ 
pabilities  represented  by  those  utilities 
in  ROM  is  powerful,  but  the  Mac  gives 
you  no  easy  way  to  start  using  them. 
Traditionally,  when  you  were  going  to 
write  a  program  for  an  Apple  comput¬ 
er,  you  started  by  getting  into  BASIC 
and  writing  a  Hello  program.  Well, 
there’s  no  way  to  do  that  on  the  Mac. 
There  is  no  immediately  obvious  way 
to  write  to  the  screen,  no  obvious  way 
to  grab  keyboard  input.  This  was  in¬ 
tentional;  it  forces  you  to  get  into  and 
use  that  excellent  interface.  But  at 
first,  it’s  discouraging.  But  then  there 
follows  a  period  of  euphoria.  Each  pro¬ 
grammer  on  our  team  has  gone 
through  this  depression,  when  there 
appear  to  be  random  events  going  on. 
Then  the  euphoria  comes  as  he  begins 
to  learn  how  to  make  pulldown  menus 
and  scrollbars  work. 

DDJ:  I’m  sure  that’s  a  nice  feeling,  but 
what’s  the  practical  implication? 
Evslin:  Overall,  you  become  a  more 
productive  software  developer.  You 
just  wouldn’t  build  that  kind  of  user 
interface  into  every  product  if  you  had 
to  do  it  from  scratch  every  time.  The 
Mac  software  environment  puts  that 
interface  into  every  product  for  you. 
DDJ :  What  problems  do  you  run  into 
in  porting  software  into  the  Mac 


environment? 

Evslin:  You  don’t  port  software  to  the 
M  ac.  Or  we  don’t.  Our  products  are  all 
new  code.  We  look  at  the  old  code;  we 
have  it  to  look  at  as  we  work;  but  what 
we  write  is  all  new  code.  You  can’t  just 
translate  software  from  another  envi¬ 
ronment  to  the  Mac;  when  you  do,  the 
result  is  a  piece  of  junk. 

DDJ:  What  kind  of  support  do  devel¬ 
opers  get  from  Apple  in  learning  to 
work  in  the  environment,  learning  to 
develop  software  for  the  machine?  Is 
Mac  college  helpful? 

Evslin:  Apple  runs  a  Mac  college  but 
until  recently  there  was  no  Mac  high 
school.  There  was  no  way  to  get  there 
from  here.  Now  there  are  a  few  people 
providing  that  level  of  training,  but 
most  people  have  had  to  get  there  by 
the  school  of  hard  knocks.  Mark  Ur- 
sino  (formerly  of  Microsoft)  is  one  of 
these  teaching  at  the  lower  level;  Mark 
has  taught  a  Mac  high  school  at  Apple. 
That  was  helpful. 

DDJ:  The  user  documentation  for  the 
Mac  is  attractive  and  well  organized, 
but  I  was  a  bit  surprised  when  I  first 
saw  the  pile  of  photocopied  pages  that 
make  up  the  documentation  a  develop¬ 
er  gets.  Isn’t  that  documentation  a 
handicap? 

Evslin:  The  lack  of  high-level,  struc¬ 
tured  documentation  has  been  a  prob¬ 
lem.  Apple  has  given  programmers  one 
level  of  documentation,  with  no  way  to 
get  an  overview.  You  read  the  window 
management  section  and  you  will  prob¬ 
ably  learn  more  than  you  would  ever 
want  to  know  about  window  manage¬ 
ment  in  the  Mac  environment,  but 
there’s  no  way  to  know,  short  of  exhaus¬ 
tive  search,  if  what  you  want  to  know 
about  a  particular  point  in  window 
management  is  in  the  documentation. 
DDJ:  What  about  support? 

Evslin:  There  are  bugs,  but  few  of 
them,  considering  all  that  is  in  the 
ROM.  And  there  is  developer  support. 


Not  every  programmer  gets  all  the  ac¬ 
cess  to  the  developers  that  he  wants 
when  he  wants  it,  but  contrast  this  with 
IBM,  where  developers  are  just  inac¬ 
cessible.  Right  now,  you  can  actually 
talk  to  the  people  who  were  involved  in 
the  development  of  the  Mac  software. 
You  can  get  at  the  actual  developers. 
DDJ:  Has  it  been  a  hindrance  that  you 
have  to  work  on  the  Lisa? 

Evslin:  Having  to  do  development  on 
another  machine  is  a  barrier.  It’s  not  so 
bad  for  a  company  like  ours,  but  it  is 
difficult  for  somebody  working  in  his 
basement  to  have  to  go  out  and  buy 
another  machine  to  develop  software 
for  the  Mac. 

DDJ:  Do  you  think  that  the  5 1 2K  Mac 
is  going  to  make  things  easier  for  soft¬ 
ware  developers? 

Evslin:  The  512K  Mac  will  not  be  as 
great  a  boon  to  programmers  as  every¬ 
one  thinks.  Memory  has  been  overrat¬ 
ed  as  an  aid  to  the  developer.  You  don’t 
have  to  build  a  user  interface  or  do 
those  other  things  the  ROM  routines  do 
for  you,  so  you  have  more  of  the  RAM 
to  use  anyway.  128K  is  not  terribly 
limiting.  What’s  confusing  is  that  the 
Mac  memory  management  is  hard  to 
get  used  to.  That’s  really  where  the 
problems  lie,  not  in  the  amount  of 
memory.  The  larger  machine  may  in 
fact  mask  bugs  more  easily  than  the 
128K  machine.  With  the  128K,  the 
heap  gets  reorganized  fairly  often.  It’s 
easy  to  write  routines  that  depend  on 
something  being  relocatable  when  in 
fact  it’s  not.  Then  the  heap  gets  reorga¬ 
nized  and  things  get  moved  and  you 
see  an  erratic,  nonrepeatable  bug. 
With  512K  you  may  never  see  the  bug 
crop  up  in  beta  test. 

I  was  the  first  in  our  company  to  do 
development  on  the  Mac  and  I  saw  all 
the  others  going  through  the  learning 
process.  Nowhere  did  anybody  have  as 
much  trouble  as  in  memory  manage¬ 
ment.  Where  handles  get  allocated, 


Dr.  Dobb’s  Journal.  January  1 985 


124 

85 


heap  management.  Hardest-to-track 
bugs. 

DDJ\  The  predictions  are  that  Jack 
Tramiel  at  Atari  and  everyone  else  will 
soon  be  bringing  out  Maclike  machines, 
and  the  predictions  seem  likely  to  come 
true,  more  or  less.  What  do  you  think 
we  can  expect  from  such  machines? 
Evslin:  Those  who  are  going  for  the 
low-cost  market  will  opt  for  maximum 
compatibility  with  the  Mac;  those  sell¬ 
ing  functionality  will  go  for  merely  us¬ 
ing  the  conceptual  interface  and  will 
try  to  differentiate  themselves  in  terms 
of  functions.  Both  high  and  low  levels 
of  compatibility  will  emerge. 

The  Mac  itself  is  poised  to  penetrate 
two  new  markets:  people  who  would 
never  have  bought  a  computer  before 
and  the  professional  market.  The  for¬ 
mer,  for  sure,  as  more  software  is  devel¬ 
oped  that  is  fun.  We  will  see  not  so 
much  a  fight  with  IBM  for  market 
share  as  an  expansion  of  the  market. 
Professionals,  I’m  not  so  convinced 
about,  but  as  the  so-called  power  soft¬ 
ware  comes  out,  the  opportunity  will 
exist. 

[After  we  spoke,  Apple  leaked  its 
current  marketing  plans:  the  Christ¬ 
mas  “test  drive  a  Mac"  deal  and  the 
plan  to  go  after  corporate  middle 
managers,  leaning  on  the  new  Lotus 
product  for  the  Mac,  the  planned  Mac 
local-area  network,  a  laser  printer, 
and  a  telephone  integrated  into  the 
machine.  CEO  John  Sculley  claimed 
that  Apple  has  a  two-year  window  for 
getting  Macs  on  corporate  desktops 
and  said  that  the  plan  was  not  one  that 
puts  Apple  on  a  collision  course  with 
IBM.] 


DD| 


125 

86 


Dr.  Dobb's  Journal,  January  1985 


OF  INTEREST 


by  R.  P.  Sutherland 


Subliminal  Software 

New  Life  Institute  has  released  soft¬ 
ware  for  the  IBM  PC  and  compatibles 
designed  to  reprogram  the  user’s  hab¬ 
its,  behaviors,  and  personal  beliefs. 
The  program  entitled  Subliminal  Sug¬ 
gestions  and  Self  Hypnosis  Programs 
for  Your  Computer!  flashes  user-cho¬ 
sen  messages  as  a  super-high-speed 
subliminal  background  on  the  screen. 
The  subliminal  messages  can  be  used 
to  reprogram  the  user’s  subconscious. 
The  package  includes  separate  pro¬ 
grams  for  self-hypnosis  and  deep  re¬ 
laxation.  The  accompanying  documen¬ 
tation  contains  a  warning  about  pos¬ 
sible  misuses  of  subliminal  technology. 
The  “Subliminal  Suggestions  and  Self 
Hypnosis”  package  retails  for  $75.00. 
The  programs  are  written  in  C  with 
several  assembly  language  routines. 
Contact  New  Life  Institute,  P.O.  Box 
2390,  Santa  Cruz,  CA  95063  (408) 
429-1  122.  Reader  Service  No.  109. 


Babel 

The  Babel  that  towers  above  Silicon 
Valley  casts  a  shadow  around  the 
globe.  A  high-tech  lingua  franca  could 
speed  aspects  of  the  microcomputer 
revolution,  but  diversity  promotes  cre¬ 
ativity.  If,  by  joining  forces,  the  United 
States  and  Japan  could  produce  the 
man-like  machine  that  Turing  predict¬ 
ed  by  the  21st  century,  one  can’t  help 
but  wonder  if  the  present  confusion  is 
God’s  attempt  to  frustrate  our 
progress. 

I  attended  Gary  Kildall’s  PC  Faire 
presentation  in  San  Francisco  last 
year,  in  which  he  was  supposed  to 
prophesy  the  direction  of  the  micro¬ 
computer  industry  over  the  next  three 
years.  Instead,  he  traced  the  develop¬ 
ment  of  microprocessors,  memory,  and 
input/output  devices  during  the  last 


decade  and  limited  any  predictions  to 
one  year.  Within  a  year,  he  said,  pro¬ 
grammers  may  assume  that  the  stan¬ 
dard  business  computer  will  have  3 
megabytes  of  RAM,  a  CPU  in  the 
80286  category  or  a  32-bit  processor, 
and,  thanks  to  laser  disk  technology, 
enough  storage  to  contain  the  Encyclo¬ 
paedia  Britannica.  All  of  this  speed 
and  power  means  that  we  are  going  to 
be  able  to  get  a  lot  more  work  out  of 
the  microcomputer.  How?  Kildall  had 
the  solution:  a  copy  of  Concurrent 
DOS,  still  in  the  shrink-wrap,  which  he 
offered  to  sell  because,  he  said,  he 
needed  plane  fare  home.  Concurrent 
DOS,  like  TopView,  is  a  multitasking, 
windowed  program  to  enhance 
MSDOS.  Microsoft  Windows,  a  simi¬ 
lar  product,  has  been  delayed  until 
June. 

Kildall  seemed  most  enthusiastic 
about  Virtual  Device  Interface  (VDI) 
technology.  By  raising  the  functional¬ 
ity  of  all  devices  to  a  common  level, 
VDI  allows  one  to  create  programs  that 
are  not  at  the  mercy  of  technological 
leapfrogs.  The  VDI  presents  a  general 
interface  through  which  high-level 
programs  can  communicate  with  a 
wide  variety  of  hardware.  IBM,  Digital 
Research  Institute,  and  now  Ashton¬ 
Tate  are  licensing  GSS-Drivers  from 
Graphic  Software  Systems.  GSS-Driv¬ 
ers  is  an  implementation  of  the  Ameri¬ 
can  National  Standards  Institute’s 
proposed  VDI  programming  standard, 
joined  with  a  library  of  device  drivers, 
which  permits  input  and  output  device 
independence.  Graphic  Software  Sys¬ 
tems  is  located  at  25117  South  West 
Parkway,  Wilsonville,  OR  97070. 
Contact:  William  Merchant  (503) 
682-1606. 

Vertex  Systems  has  announced 
Apple-Turnover,  a  board  that  gives  IBM 
PCs  the  ability  to  read  and  write  Apple- 
Dos  3.3  and  Apple  CP/M  disks.  Vertex 
Systems  also  has  XenoCopy,  a  system 


of  programs  to  copy  files  between  disks 
of  different  computers.  I  wanted  to 
compare  it  to  microVersal,  a  similar 
product  that  we  have  been  using  with 
mixed  results,  but  Vertex  was  unable  to 
part  with  a  review  copy.  Apple- 
Turnover  is  priced  at  $279.50  and  Xen¬ 
oCopy  by  Fred  Cisin  is  $99.50  or 
$149.50  for  XenoCopy  Plus,  which  per¬ 
mits  writing  to  and  formatting  other 
disks.  Vertex  Systems,  Inc.,  6022  W. 
Pico  Blvd.,  Los  Angeles,  CA  90035 
(213)  938-0857.  Reader  Service  No.  105. 

CompuPro  users  may  now  run 
PCDOS  at  CompuPro  speed.  Comput¬ 
er  House  announces  PCPRO  version 
2.4.  PCPRO  is  IBM’s  PCDOS  version 
2.1  modified  to  operate  on  CompuPro 
letter  series  systems.  The  package  in¬ 
cludes  INTERDOS,  a  CP/M  to  MSDOS 
transfer  utility,  and  COFIGIO,  an  in¬ 
teractive  I/O  configurative  utility.  PC¬ 
PRO:  PCDOS  for  CompuPro  costs 
$395.00  from  Computer  House,  722  B 
Street,  San  Raphael,  CA  94901  (415) 
453-0865.  Reader  Service  No.  107. 


Hardware 

Logitech  has  applied  infrared  technol¬ 
ogy  to  the  mouse  and  produced  the 
first  cordless  mouse  as  a  result  of  cus¬ 
tom  development  work  for  Metaphor 
Computer  Systems.  Logitech  Inc.  is  lo¬ 
cated  at  805  Veterans  Blvd.,  Redwood 
City,  CA  94063  (415)  365-9852.  Read- 

er  Service  IMo.  111. 

CompuPro  has  introduced  a  high¬ 
speed,  low-power  static  RAM  board 
compatible  with  both  8-  and  16-bit  pro¬ 
cessors.  The  RAM  23  provides  up  to 
128K  of  static  RAM  and  operates  at  up 
to  12  MHz  with  8086/8088/80286 
CPUs.  The  RAM  23  has  a  suggested  re¬ 
tail  price  of  $400.00  for  the  64K  version 
and  $775.00  for  the  1 28K  version.  Con¬ 
tact  Jeff  Swartz,  CompuPro,  3506 
Breakwater  Court,  Hayward,  CA 


Dr.  Dobb's  Journal,  January  1985 


126 

87 


94545  (415)  786-0909.  Reader  Service 
No.  IIS. 

Morrow  has  begun  shipping  a  10- 
pound  battery-operated  portable  com¬ 
puter  that  operates  under  MSDOS  and 
uses  standard  5'/4-inch  floppy-disk 
drives.  A  640K  dual  drive  version  is 
available  for  $3,695.00.  Standard  fea¬ 
tures  include:  clock,  built-in  300  baud 
modem,  calculator,  and  word  proces¬ 
sor.  The  CPU  is  a  CMOS  80C86.  The 
Pivot  is  available  from  Morrow,  600 
McCormick,  San  Leandro,  CA  94577. 

Reader  Service  No.  117. 


Miscellany 

Musical  Software 

Tune  Smith/PC  is  the  first  software 
for  musical  composition  for  the  IBM 
PC.  Price:  $49.95  from  Blackhawk 
Data  Corporation,  307  N.  Michigan 


COMPUTEREYES  is  a  low-cost  video 
acquisition  system  for  the  Commodore 
64  series.  The  product  includes  ma¬ 
chine  language  image  capture  routines 
and  image  save-to-disk  capability. 
COMPUTEREYES  is  the  Commodore 
version  of  Digital  Vision’s  video  acqui- 


Avenue,  Chicago,  I L  60601  (312)236- 
8473.  Reader  Service  No.  119. 

Disk  Preventive  Maintenance 

Disk  P.M.  from  Digital  Pathways, 
Inc.,  performs  preventive  maintenance 
on  hard  and  floppy  disks  for  IBM  PCs 
and  compatibles.  Disk  P.M.  diagnoses 
problems,  automatically  condenses 
hard  or  floppy  disks,  restores  order,  re¬ 
builds  damaged  directories,  recovers 
damaged  files,  locks  out  faulty  areas, 
and  copies  system  information  to  disks 
that  refuse  to  boot.  Retail  price  is 
$49.95  from  Digital  Pathways,  Inc., 
1060  East  Meadow  Circle,  Palo  Alto, 
CA  94303.  Reader  Service  No. 121. 

Public  Domain  Help  Function 

This  software  package  provides  full 
on-screen  help  for  all  PCDOS  version 
2.0  and  2. 1  commands.  The  actual  help 
text  may  be  altered  by  the  user.  Copies 
are  available  for  $10.00  from  Chris 


sition  system  for  the  Apple  II  series. 
The  complete  system,  including  video 
camera,  is  priced  at  $349.95  from  Di¬ 
gital  Vision,  Inc.,  14  Oak  Street  Suite 
2,  Needham,  MA  02192  (617)  444- 
9040.  Reader  Service  No.  113. 


Bailey,  P.O.  Box  332,  Peterborough, 
Ontario,  Canada,  K9J  6Z3.  Reader  Ser¬ 
vice  No.  125. 

TRS-80  Screen  Dump 

A  high-resolution  screen  dump  utility 
for  TRS-80  Models  I,  II,  III,  and  4  al¬ 
lows  users  to  dump  the  contents  of  a 
video  screen,  text  and  graphics,  to  an 
Epson  or  Gemini  printer.  The  price  is 
$19.95,  available  from  Softbyte  Com¬ 
puting,  Box  217,  Wallingford,  CT 
06492  (203)  239-6923.  Reader  Service 
No.  123. 

Tools  for  Turbo  Pascal 

Programming  Tool  Kit  by  Paragon 
Courseware  is  a  set  of  utilities  for  Tur¬ 
bo  Pascal  programmers.  The  kit  in¬ 
cludes  a  Window  Package,  a  Function 
Evaluating  Package,  and  a  System  In¬ 
formation  Package.  The  Graphics 
Package  has  several  procedures  that 
draw  various  shapes  such  as  triangles, 
parallelograms,  other  polygons,  circles, 
and  ellipses.  The  Tool  Kit  retails  for 
$49.95  from  Paragon  Courseware, 
4954  Sun  Valley  Road,  Del  Mar,  CA 
92014  (619)  481-1477.  Reader  Service 
No.  127. 

Forth  Authors 

The  Forth  Interest  Group  announces 
the  formation  of  its  Author  Recogni¬ 
tion  Program,  which  offers  free  FIG 
membership  to  the  author  of  any 
Forth-related  article  published  in  a 
periodical.  FIG  hot  line:  (415)  962- 
8653. 

The  1985  Rochester  Forth  Confer¬ 
ence  will  be  held  June  12—15  at  the 
University  of  Rochester,  New  York. 
The  focus  of  the  Conference  will  be  on 
Software  Engineering  and  Software 
Management.  Papers  on  the  following 
topics  are  welcomed:  1 )  Software  En¬ 
gineering  and  Software  Management 
Practices;  2)  Forth  Applications;  and 
3)  Forth  Technology.  Submit  a  200- 
word  abstract  by  March  30  to  Law¬ 
rence  P.  Forsely,  Laboratory  for  Laser 
Energetics,  250  East  River  Road, 
Rochester,  NY  14623(716)  235-0168. 

DD| 

Reader  Ballot 

Vote  for  your  favorite  feature/article. 

Circle  Reader  Service  No.  1 98. 


127 


Dr.  Dobb’s  Journal.  January  1985 


TuuTTrrssueT 


SOFTWARE  TOOLS  FOR  ADVANCED  PROGRAMMER 

Dr.  Dobb’s  toura 

tt  100  February  198S  $2,9!> 


(l.SOCanadJ 


Festschrift  for  Doctor  Dobb 


Tiny  BASIC 
for  the  68000 


f 

) 


Logitech's  Modula-2 


/ 

Excerpts  fro 
Fire  in  the  Valle 


Dr.  Dobb’s  Journal 


Editorial 

Editor-in-Chief  Michael  Swaine 
Managing  Editor  Randy  Sutherland 
Assistant  Editor  Frank  DeRose 
Technical  Editor  Alex  Ragen 
Contributing  Editors  Robert  Blum, 

Dave  Cortesi, 

Ray  Duncan, 

Anthony  Skjellum, 
Michael  Wiesenberg 
Copy  Editors  Polly  Koch,  Cindy  Martin 
Typesetter  Jean  Aring 
Editorial  Intern  Mark  Johnson 
Production 
Design  /  Production 

Director  Delta  Penna 
Art  Director  Shelley  Rae  Doeden 
Production  Assistant  Alida  Hinton 
Cover  Tom  Upton 
Advertising 

Advertising  Director  Stephen  Friedman 
Advertising  Sales  Walter  Andrzejewski, 
Shawn  Horst 
Beth  Dudas 

Advertising  Coordinators  Alison  Milne, 

Lisa  Boudreau 

Circulation 
Circulation  and 

Promotions  Director  Beatrice  Blatteis 
Fulfillment  Manager  Stephanie  Barber 
Direct  Response 

Coordinator  Maureen  Snee 
Promotions  Coordinator  Jane  Sharninghouse 
Circulation  Assistant  Kathleen  Boyd 


M&T  Publishing,  Inc. 

Chairman  of  the  Board  Otmar  Weber 
Director  C.F.  von  Quad t 
President  Laird  Foshay 


February  1985 
Volume  10,  Issue  2 


CONTENTS 


In  This  Issue 

For  this  100th  issue  of  DDJ,  Mike  Swaine  interviewed  the  men  whose  names 
became  the  Doctor’s:  Dennis  Allison  and  Bob  Albrecht.  Dennis  is  in  “Software 
Designer”  and  Bob  is  in  “Festschrift.”  To  meet  Bob  and  Dennis  is  to  understand 
why  the  “temporary”  project  they  started  almost  a  decade  ago  is  still  gathering 
momentum.  Bob  and  Dennis  are  visionary,  and  powerful.  Suzanne  Rodriguez’ 
contribution  to  the  “Festchrift”  provides  an  interesting  glimpse  of  the  way  things 
were  at  People's  Computer  Company  under  Bob  and  Dennis’  leadership.  Ex¬ 
cerpts  from  Fire  in  the  Valley  provide  a  wider  context  for  what  was  happening. 
We  couldn’t  resist  dropping  in  Gordon  Brandly’s  “Tiny  BASIC  for  the  68000”  to 
link  our  roots  with  the  state  of  the  art. 

The  inclusion  of  Charles  Burton’s  “An  Enhanced  ADFGVX  Cipher  System” 
reflects  a  philosophy  that  has  continued  from  an  early  People's  Computer  Com¬ 
pany  poster:  “Use  computers  for  people,  not  against  them.”  The  power  to  encrypt 
one’s  communications,  once  the  domain  of  high  government  and  the  military,  has 
now  been  placed  in  the  hands  of  the  people.  Since  1976,  we  have  enjoyed  taking 
what  is  secret,  hidden,  expensive,  protected,  or  otherwise  inaccessible,  and  plac¬ 
ing  it  in  the  public  domain. 


Entire  contents  copyright  ®  1984  by  M&T  Publishing, 
Inc.  unless  otherwise  noted  on  specific  articles.  All 
rights  reserved. 

Dr.  Dobb’s  Journal  (USPS  307690)  is  published 
monthly  by  M&T  Publishing,  Inc.,  2464  Embarcadero 
Way,  Palo  Alto,  CA  94303,  (415)  424-0600.  Second 
class  postage  paid  at  Palo  Alto  and  at  additional  entry 
points. 

Address  correction  requested.  Postmaster:  Send  Form 
3579  to  Dr.  Dobb’s  Journal,  2464  Embarcadero 
Way,  Palo  Alto,  CA  94303.  ISSN  0278-6508 

Subscription  Rates:  $25  per  year  within  the  United 
States,  $44  for  first  class  to  Canada  and  Mexico,  $62 
for  airmail  to  other  countries.  Payment  must  be  in  U.S. 
Dollars,  drawn  on  a  U.S.  Bank. 

Contributing  Subscribers:  Christine  Bell,  W.D. 
Rausch,  DeWitt  S.  Brown,  Burks  A.  Smith,  Robert  C. 
Luckey,  Transdata  Corp.,  Mark  Ketter,  Friden  Mail¬ 
ing  Equipment,  Frank  Lawyer,  Rodney  Black,  Kenneth 
Drexler,  Real  Paquin,  Ed  Matin,  John  Saylor  Jr.,  Ted 
A.  Reuss  III,  InfoWorld,  Stan  Veit,  Western  Material 
Control,  S.P.  Kennedy,  John  Hatch,  Richard  Jorgen¬ 
sen,  John  Boak,  Bill  Spees,  R.B.  Sutton.  Lifetime 
Subscribers:  Michael  S.  Zick,  F.  Kirk. 

Foreign  Distributors:  ASCII  Publishing,  Inc.  (Ja¬ 
pan),  Computer  Services  (Australia),  Computer  Store 
(New  Zealand),  Computercollectief  (Nederland),  Ho- 
mecomputer  Vertriebs  GMBH  (West  Germany),  In¬ 
ternational  Presse  (West  Germany),  La  Nacelle  Book¬ 
store  (France),  McGill’s  News  Agency  PTY  LTD 
(Australia),  Progresco  (France). 


This  Months  Cover 

This  months  cover  was  created  by  Tom  Upton.  The  photograph  shows  bubbles 
with  sparks  reflecting  and  old  DDJ  cover. 

Next  Month 

The  March  issue  will  announce  the  winner(s)  of  the  AI  programming  competi¬ 
tion.  The  issue  is  shaping  up  to  be  quite  Prolog  oriented.  We  would  really  like  to 
work  up  a  public  domain  Prolog  for  micros.  Contributions?  In  the  “Realizable 
Fantasies”  column  next  month  you  will  read  of  a  proposed  project  to  write  a 
complete  public  domain  Unix,  called  GNU  (acronym  for  “GNU’s  Not  Unix”).  In 
the  same  vein,  the  author  of  the  grep.c  program  that  we  published  in  October 
1984,  Allen  Holub,  will  pick  up  where  Anthony  Skjellum  left  off  and  become  our 
new  C  columnist.  We  are  pleased  to  announce  that  the  C  column  will  now  run 
every  month. 


This  Month's  Referees 

Dave  Cortesi,  Resident  Intern 


John  Taber,  IBM 


People's  Computer  Company 

Dr.  Dobb’s  Journal  is  published  by  M&T  Publishing, 
Inc.  under  license  from  People’s  Computer  Company, 
2682  Bishop  Dr.,  Suite  107,  San  Ramon,  CA  94583,  a 
non-profit,  educational  corporation. 


4 


Dr  Dobb’s  Journal,  February  1985 


SOFTWARE  TOOLS  FOR  ADVANCED  PROGRAMMERS 


Dr.  Dobb’s  Journal 


ARTICLES 


Festschrift  for  Doctor  Dobb 

Contributions  by  Suzanne  Rodriguez,  Tom  Pittman 

and  Bob  Albrecht 

26 

Some  old  friends  flame  about  the  past,  present  and  future 
in  honor  of  the  Doctor’s  100th  issue  (Reader  Ballot  No.  192) 

Fire  in  the  Valley 

by  Michael  Swaine  and  Paul  Freiberg er 

32 

The  Homebrew  Computer  Club,  Processor  Technology, 
the  SI 00  bus  meetings  (Reader  Ballot  No.  193) 

Tiny  BASIC  for  the  68000 

by  Gordon  Brandly 

42 

Li  Chen  Wang’s  original  Palo  Alto  Tiny  BASIC,  now  in 
68000  code,  with  installation  instructions  and  a  bulletin 
board  number  that  carries  the  code  (Reader  Ballot  No.  194) 

An  Enhanced  ADFGVX  Cipher  System 

by  C.  E.  Burton 

48 

An  enhanced  version  of  the  ADFGVX  cipher  system, 
written  in  C,  wth  a  brief  history  of  the  original  German 

system  (Reader  Ballot  No.  1 95) 

More  dBASE  Tips  and  Techniques 

by  Gene  Head 

71 

A  program  FLIPIT  that  changes  the  command  file  extension 
.CM  D  to  .PRG  and  vice  versa  (Reader  Ballot  No.  1 99) 

COLUMNS 

Dr.  Dobb's  Clinic 

by  D.E.  Cortesi 

16 

Throughput  from  various  perspectives  (Reader  Ballot  No. 
190) 

C/Unix  Programmer's  Notebook 

by  Anthony  Skjellum 

22 

C  style,  comments  on  printfQ  (Reader  Ballot  No.  191) 

Software  Designer 

by  Michael  Swaine 

118 

Running  light  in  1 985  (Reader  Ballot  No.  1 96) 

CP/M  Exchange 

by  Bob  Blum 

96 

Buffered  disk  I /O  (Reader  Ballot  No.  1 97) 

DEPARTMENTS 

Editorial 

6 

Letters 

8 

Reviews 

74 

Logitech’s  Modula-2 

Books 

120 

Of  Interest 

124 

(Reader  Ballot  No.  1 98) 

Advertiser  Index 

128 

Dr.  Dobb’s  Journal,  February  1985 


5 

91 


.  EDITORIAL 


Before  they  put  me  on  the  payroll  here,  I  was  a  loyal  reader  of  DDJ.  This  is 
my  personal  thank-you  to  some  of  the  people  who  created  DDJ  over  the 
past  hundred  issues.  I’ve  left  out  many  people  through  oversight,  but  here 
are  well  over  a  hundred. 

Thank  you, 

Phyllis  Adams,  Bob  Albrecht,  Dennis  Allison,  Robin  Allison,  Walter  Andrze- 
jewski,  Julie  Anton,  Jean  Aring,  John  Arnold, 

Rick  Bakalinsky,  Stephanie  Barber,  Jeannie  Barroga,  Sam  Bassett,  Anatta 
Blackmarr,  Beatrice  Blatteis,  Robert  Blum,  Christine  Botelho,  Lisa  Boudreau, 
Kathleen  Boyd,  Sally  Brenton,  Bill  Bruneau, 

Ron  Cain,  Dave  Caulkins,  Ward  Christensen,  Maureen  Christine,  Peter  Clark, 
Dave  Cortesi,  Gavin  Cullen,  Carole  Cullenbine, 

Leah  Dansby,  Jim  Day,  Frank  DeRose,  Shelley  Rae  Doeden,  Steve  Dompier, 
Beth  Dudas,  Ray  Duncan, 

Gordon  Eubanks, 

Paula  Fairchild,  Fred  Fehlau,  Lee  Felsenstein,  Eugene  Fisher,  Laird  Foshay, 
Stephen  Friedman, 

Mike  Gabrielson,  Bill  Gale,  Gary  Gaugler,  Mag  Glick,  Greg  Gordon,  H.T.  Gor¬ 
don,  F.J.  Greeb,  Richard  Grigonis,  Ed  Gueble, 

Aleeca  Hamilton,  Craig  Harper,  David  C.  Harris,  Kim  Harris,  Hank  Harrison, 
Gene  Head,  Michael  Heatherman,  Matthew  Heiler,  Jim  Hendrix,  Andy  Hertz- 
feld,  Nancy  Heubach,  Alice  Hinton,  Susan  Hinton,  Tita  Hinton,  Willard  Hold¬ 
en,  Allen  Holub,  Shawn  Horst,  Alana  Hunter, 

Meredith  Ittner, 

Bob  Jacobsen,  John  S.  James,  Mark  Johnson,  Do-While  Jones, 

Laura  Kares,  Maria  Kent,  Gary  Kildall,  Art  Kleiner,  Polly  Koch,  Cynthia  Ko- 
sina,  Craig  LaGrow,  Jane  Laidley,  Carl  Landeau,  Fritzi  Lareau,  J.L.  Lawrence, 
Michael  Madaj,  Rosehips  Malloy,  Linda  Marohn,  Cindy  Martin,  Tom  Martin, 
Terri  Marty,  W.D.  Maurer,  Dwight  McCabe,  A1  McCahon,  Dennis  McGhie, 
Jane  McKean,  Lorraine  McLaughlin,  Mary  Jo  McPhee,  Laura  Mendell,  Ann 
Merchenberger,  Doug  Millison,  Alison  Milne,  Ann  Miya,  Michelle  Moonstone, 
Carl  Moser,  Erik  Mueller,  Andrea  Nasher, 

Theresa  O’Rourke,  Marlin  Ouverson, 

Sahnta  Pannuti,  Ronald  Parsons,  Janet  Payne,  Detta  Penna,  Giuliana  Peter, 
Tom  Pittman,  Terri  Pond,  Beverly  Pyle, 

Bill  Ragsdale,  Jef  Raskin,  Ed  Ream,  Pete  Roberts,  Beverly  Robinson,  Suzanne 
Rodriguez,  Betsy  Roeth,  Gloria  Romanoff,  Dan  Rosset,  Barbara  Ruzgerian, 
Barbara  Rymsa, 

Jonathan  Sachs,  Kent  Safford,  Carol  Sevilla,  Jane  Sharninghouse,  Anthony 
Skjellum,  Maureen  Snee,  John  Starkweather,  Susan  Strange,  Robert  Suding, 
Randy  Sutherland, 

Ari  Taylor,  Frank  Trujillo,  Marianne  Tryens,  Margaret  Tsai, 

Lichen  Wang,  Jim  Warren,  Judith  Wasserman,  Sara  Werry,  Ann  West,  Clifford 
West,  Charles  Wetherell,  Dick  Whipple,  Michael  Wiesenberg,  Renny  Wiggins, 
Tom  Williams,  Steve  Willoughby,  Marvin  Wizenread,  Donna  Lee  Wood,  Steve 
Wozniak, 

Greg  Yob 

and  Michael  Zick. 


oLoud 

Michael  Swaine 


6 

Q9 


Dr.  Dobb's  Journal,  February  1985 


Minimax  Exchange 
Algorithm 

Dear  Sir: 

I  was  very  interested  in  the  Minimax 
algorithm  presented  by  Steven  A.  Ru- 
zinsky  in  DDJ  No.  93  (July  1984).  He 
presented  several  approximations  that 
were  better  than  the  generally  pub¬ 
lished  series.  Seeing  this,  I  was  in¬ 
spired  to  program  a  form  of  the  ex¬ 
change  algorithm  and  compare  my 
results  with  his.  Given  the  work  that 
obviously  went  into  his  approach,  1  was 
surprised  that  even  his  results  could  be 
improved  upon.  In  particular,  for  sin 
(pi*x/2)  approximated  by  a  5-term 
series,  Ruzinsky  gave  a  maximum 
relative  error  of  5.31748E-09.  My  ex¬ 
change  algorithm  gave  a  maximum 
relative  error  of  5.31399E-09.  While 
this  difference  is  so  small  as  to  be  al¬ 
most  trivial,  I  believe  it  points  up  a  log¬ 
ical  error  in  Ruzinsky’s  approach. 

To  understand  this  error  one  must 
first  realize  that  for  an  Loo  approxima¬ 
tion  with  N  parameters,  the  approxi¬ 
mation  is  guaranteed  to  reach  its  maxi¬ 
mum  error  at  least  N  +  1  times. 
Ruzinsky’s  algorithm  tries  to  find  these 
N  +  1  or  more  places  by  giving  ever- 
increasing  weights  to  predetermined 
points  near  the  actual  maximum  error 
positions,  but,  of  course,  these  points 
are  virtually  never  in  the  right  positions 
for  a  true  Loo  approximation. 

The  exchange  method,  on  the  other 
hand,  is  directed  toward  finding  these 
N  +  1  points  and  building  an  approxi¬ 
mation  around  them.  When  it  works,  it 
works  best,  but  it  appears  to  be  far 
more  finicky  than  Ruzinsky’s 
algorithm. 

In  fact,  Ruzinsky  actually  underes¬ 
timates  the  maximum  error  of  his  ap¬ 
proximations  because  the  maximum 
error  of  his  approximations  does  not 
occur  at  his  lattice  points,  but  some¬ 
where  in  between. 


In  light  of  all  this  I  propose  the  fol¬ 
lowing  modification  of  Ruzinsky’s  al¬ 
gorithm:  after  the  Speedup  step,  which 
halves  the  number  of  lattice  points, 
add  new  lattice  points  midway  between 
each  of  the  remaining  points.  This  has 
the  effect  of  increasing  the  lattice  den¬ 
sity  near  the  maximum  error  and,  I  ex¬ 
pect,  will  give  the  correct  Loo 
approximation. 

A  few  technical  notes:  GW  BASIC  on 
the  Eagle  PC  that  I  used,  like  most  Mi¬ 
crosoft  BASIC  interpreters,  does  not 
support  double  precision  transcenden¬ 
tal,  so  my  first  order  of  business  was 
implementing  sin(p*x/2)  in  double  pre¬ 
cision.  This  is  not  too  bad  if  you  don’t 
care  much  about  speed.  Also  the  coeffi¬ 
cients  I  got  for  Figure  8,  page  94,  are: 

A(0) =  1.570796318447697 
A(l)  =  -0.6459637105998668 
A(2)  =  0.0796896789479709 
A(3)  =  —  4. 6737666 1266352E  — 03 
A(4)  =  1.514851 308552791 E  — 04 

I  also  tried  arctan(x)  and  again 
found  a  small  improvement  on  Ru¬ 
zinsky’s  result. 

Finally,  I  would  be  interested  in  how 
Mr.  Ruzinsky  linearized  his  various 
functions.  Perhaps  the  contest  he  men¬ 
tioned  (for  a  free  one-year  subscription 
to  DDJ)  could  be  ended. 

Sincerely, 

Allen  E.  Tracht 
12469  Cedar  Rd„  #1 1 
Cleveland  Heights,  OH 
44106 

Forth  Standards 

Dear  Editor: 

As  a  Forth  vendor,  I  must  take  issue 
with  your  September  1984  article  by 
Ray  Duncan  and  Martin  Tracy,  “The 
FVG  Standard  Floating-Point  Exten¬ 
sion.”  I  would  like  to  point  out  that  the 
document  discussed  in  the  article  was 


based  on  the  input  of  about  eight  ven¬ 
dors  (including  the  authors).  Most  of 
these  vendors  do  not  produce  floating¬ 
point  applications  systems.  In  fact, 
those  of  us  who  use  the  pre-existing 
Mountain  View  Press  Floating-Point 
Standard  were  quite  shocked  to  see 
this  material  in  print.  My  own  compa¬ 
ny  has  a  mature  floating-point  system 
with  utilities,  matrix  math,  and,FFTs, 
which  has  been  available  for  over  two 
years  and  was  purchased  by  Martin 
Tracy  of  Micromotion  about  a  year 
ago.  This  new  “standard”  bears  little 
resemblance  to  the  working  standard 
that  has  come  out  of  the  experience  of 
several  years  of  real  applications  pro¬ 
gramming  with  floating-point  Forth 
by  those  at  ForthKit,  Redshift  Limit¬ 
ed,  and  Mountain  View  Press. 

In  a  recent  editorial  [October  1984] 
you  commented  that  the  FVG  Stan¬ 
dard  showed  that  Forth  was  maturing 
and  was  no  longer  a  language  in  flux. 
This  conclusion  is  certainly  inaccurate. 

Micromotion  and  Creative  Solu¬ 
tions  (among  others)  were  invited  over 
a  year  ago  to  participate  in  a  formal 
standards  effort  sponsored  by  Moun¬ 
tain  View  Press.  They  declined  to  par¬ 
ticipate.  Through  a  great  deal  of  work, 
those  companies  that  did  contribute 
developed  a  solid,  mature  standard. 
The  programming  systems  based  on 
our  standard  have  received  a  great  deal 
of  use  in  laboratories  and  schools 
around  the  world;  we  consider  them  to 
have  passed  the  test  of  time. 

Something  also  needs  to  be  said 
about  the  use  of  separate  floating-point 
stacks.  The  FVG  document  claims  that 
applications  adhering  to  the  names  and 
functions  of  the  “standard”  will  be 
transportable  among  the  various  float¬ 
ing-point  implementations.  Unfortu¬ 
nately,  this  is  not  the  case.  The  para¬ 
graph  on  floating-point  stacks  says  that 
the  document  does  not  concern  itself 
with  the  issue  of  separate  stacks.  The 


Dr.  Dobb's  Journal.  February  1985 


8 

93 


document  states  that  on  systems  with  a 
separate  floating-point  stack  the  source 
or  destination  of  a  real  number  argu¬ 
ment  is  the  floating-point  stack.  These 
statements  seem  to  ignore  the  fact  that 
programs  written  for  a  separate  stack 
will  not  transport  to  a  system  that  uses 
the  parameter  stack  for  floating-point 
numbers  and  vice  versa. 

A  separate  floating-point  stack  is 
necessary  for  efficient  coding  of  com¬ 
plex  algorithms.  I  used  a  parameter 
stack-based  system  for  a  year  before 
switching  to  a  separate  floating-point 
stack  in  1981,  and  I  was  amazed  to  see 
all  my  code  simplify.  But  converting 
from  one  system  to  the  other  is  a  te¬ 
dious  process  and  involves  rethinking 
and  rewriting  all  the  code — this  can 
hardly  be  called  transportable!  The 
Mountain  View  Press  Standard  speci¬ 
fies  a  separate  stack  to  aid  transport¬ 
ability,  although  it  is  our  feeling  that 
floating-point  applications  cannot  be 
absolutely  portable,  due  to  the  differ¬ 
ences  in  hardware  and  software  num¬ 
ber  packages. 

I  feel  that  Ray  Duncan  has  per¬ 
formed  a  disservice  to  the  Forth  com¬ 
munity  by  publishing  this  confusing 
document  and  further  clouding  the 
floating-point  standards  issue. 
Sincerely, 

Charlie  Springer 
Redshift  Limited 
417  Forest  Ave. 

Palo  Alto,  CA  94301 

Forth  Compiler  Changes 

Dear  Editor, 

In  my  article  “A  Forth  Native-Code 
Cross  Compiler  for  the  MC68000” 
(September  84)  there  are  a  couple  of 
errors  that  should  be  corrected.  I  have 
also  done  some  additional  work  that 
should  make  the  compiler  more 
portable. 

On  page  71  (second  column,  first 
paragraph)  the  sentence: 

Each  time  2*  is  called,  the  code 
implementing  it  is  sorted  in  the 
host  dictionary,  and  the  dictionary 
pointer  is  updated. 

should  read: 

Each  time  2*  is  called,  the  code 
implementing  it  is  stored  in  the 


host  dictionary,  and  the  dictionary 
pointer  is  updated. 

On  page  75  under  “Final  Comments” 
the  sentence: 

This  word  should  reset  all  of  the 
compiler  variables  in  screen  9  to 
their  original  style  and  generate 
an  appropriate  header  to  permit 
the  operating  system  to  load  and 
execute  the  module. 

should  read: 

This  word  should  reset  all  of  the 
compiler  variables  in  screen  9  to 
their  original  values  and  generate 
an  appropriate  header  to  permit 
the  operating  system  to  load  and 
execute  the  module. 

I  have  used  the  compiler  on  a  Forth- 
83  system  (F83  model  by  Laxen  and 
Perry)  with  very  few  changes.  The  fol¬ 
lowing  is  a  list  of  the  changes  that  need 
to  be  made: 

1.  Change  the  variable  initialization 
in  screens  9  and  10  to  conform  to 
the  Forth-83  standard. 

2.  Change  the  definition  of  the  word 
HIGH-BYTE  on  screen  12  to  the 
following. 

:  HIGH-BYTE  FLIP  ;  (  FLIP  is  not 
Forth-83  standard,  for  F83  only.  ) 

3.  Change  the  word  <BUILDS  on 
screens  14  and  17  to  CREATE  or 
use  the  definition  of  <  BUILDS  giv¬ 
en  in  screen  44  line  10. 

4.  Delete  the  word  HERE  from  the 
definition  of  BYTES  on  screen  21. 

5.  Delete  the  word  ;S  from  screens  24, 
33,  and  43.  (Not  all  systems  sup¬ 
port  this  word  and  it  is  not 
required.) 

6.  Change  the  phrase  A  BYTES  to  0A 
BYTES  in  the  following  places  (the 
word  A  is  used  in  the  F83  model  to 
select  the  alternate  screen):  screen 
35  line  8,  screen  36  line  2,  screen  37 
line  6,  and  screen  42  line  7. 

7.  Change  all  of  the  uses  of  the  word 
ENDIF  to  THEN  or  use  the  defini¬ 
tion  of  ENDIF  given  in  screen  44 
line  2. 

The  following  is  a  better  implemen¬ 
tation  of  the  word  BYTES  that  appears 
in  screen  21.  This  word  now  saves  the 


9 


current  base  and  always  uses  HEX  no¬ 
tation.  Note  that  this  word  now  uses 
the  Forth-83  standard  definition  of 
WORD,  so  older  systems  will  have  to 
add  the  word  HERE  following  WORD 
just  as  in  the  original. 

:BYTES 

BASE  @  >R  HEX 
0  DO  BL  WORD  NUMBER  DROP 
C,  LOOP 
R>  BASE  ! ; 

The  following  is  one  possible  method 
of  implementing  the  word  M68OUT  in 
screen  1 8.  This  version  saves  the  output 
code  in  an  unused  portion  of  memory. 
This  block  of  memory  can  then  be  saved 
on  disk  or  transferred  in  some  other 
manner  to  the  68000  target  machine.  If 
you  happen  to  be  using  the  compiler  on 
a  68000  system,  you  could  execute  the 
code  directly  from  the  host  system. 

M68K  DEFINITIONS 

(  Pointer  to  current  location  in  output 
buffer  area  of  memory  ) 
VARIABLE  SM680UT 

(  Store  byte  in  output  buffer  area  of 
memory  ) 

:  M68OUT  (  b  -  ) 

SM680UT  @  C!  1  SM680UT  +  ! ; 

The  following  code  should  be  modi¬ 
fied  according  to  your  needs  and 
should  be  loaded  after  the  rest  of  the 
cross  compiler  code. 

(  Start  of  output  buffer  space  ) 

FORTH  DECIMAL 
HERE  10000  + 

M68K  DEFINITIONS 
CONSTANT  (SM680UT) 

(  Initialize  output  buffer  pointer ) 
(SM680UT)  SM680UT  FORTH  ! 

That  sums  up  the  changes  I  have 
made  to  the  compiler.  I  am  using  the 
68000  version  of  F83,  which  has  Mi¬ 
chael  Perry’s  assembler  built  in.  The  as¬ 
sembler  works  just  fine  in  conjunction 
with  my  cross  compiler  so  people  who 
want  an  assembler  built  into  the  com¬ 
piler  should  take  a  look  at  the  Septem¬ 
ber  1983  issue  of  Dr.  Dobb’s  Journal. 
Sincerely, 

Raymond  L.  Buvel 
Box  3071 


94 


Dr.  Dobb's  Journal,  February  1985 


Moscow,  ID  83843 

Debugging  Tool 

To  the  Editors  of  Dr.  Dobb’s  Journal, 
A  few  weeks  ago,  I  stumbled  onto  a  tool 
that  has  greatly  improved  my  debug¬ 
ging  time.  I  primarily  use  Turbo  Pascal 
and  Forth  in  software  development,  and 
when  a  program  crashes,  I  spend  much 
of  my  time  figuring  out  where  it 
crashed,  and  why.  I  have  evolved  the 
code  in  the  accompanying  source  listing 
[see  Listing,  page  12]  to  solve  these 
problems.  The  necessary  code  to  make 
an  error  trapping  route  is  included. 

The  basic  data  structure  is  a  stack  of 
strings  called  place.  The  stack  pointer 
is  named  place— plr,  and  the  stack 
depth  is  called  place— depth,  which  is  a 
constant. 

To  initialize  the  stack,  call  the  pro¬ 
cedure  starting.  At  every  level  of  invo¬ 
cation,  such  as  the  beginning  of  a  pro¬ 
cedure,  function,  or  loop,  call  entering 
with  the  name  of  the  place  you  are 
tracking.  Upon  exiting  the  procedure, 
function,  or  loop,  invoke  leaving.  At 
the  end  of  the  section  of  the  program 
you  are  tracking,  call  ending. 

Let’s  consider  the  error  handler. 
This  procedure  is  passed  a  string  of  any 
acceptable  length  by  its  calling  proce¬ 
dure,  which  is  assumed  to  be  an  error 
message.  After  displaying  the  error 
message,  which  may  be  optionally 
printed  on  a  printer,  the  place  stack 
can  be  dumped  either  to  the  screen,  the 
printer,  or  both.  This  indicates  to  any 
desired  level  of  precision  where  in  the 
program  things  went  wrong  and  the  in¬ 
vocation  path  as  to  how  the  program 
got  there.  It  is  then  possible  to  type 
control-C  and  abort  the  program. 

There  are  two  innate  errors  that 
these  procedures  can  trigger:  stack  un¬ 
derflow  and  stack  overflow.  The  first 
problem  is  caused  by  invoking  leaving 
more  places  than  entering.  This  prob¬ 
lem  is  detected  by  the  procedure  leav¬ 
ing,  and  the  place  stack  as  is  will  pro¬ 
vide  no  useful  information,  because  the 
place  stack  has  been  depleted  prema¬ 
turely.  I  find  that  the  most  common 
cause  for  this  is  placing  entering  before 
the  loop  starts  and  leaving  in  the  loop. 
For  example, 
begin 

entering(‘loop_test’); 

for  i:=  1  to  10  do 

Dr.  Dobb's  Journal,  February  1985 


begin 

do_some_stuff; 

lcaving(‘loop_test’); 

end: 

end; 

is  an  example  of  this  kind  of  mistake. 
The  place  array  still  has  the  name 
loop— test  in  its  first  cell.  By  setting  the 
place— ptr  to  place— depth,  the  dump¬ 
ing  of  the  place  stack  will  provide  vital 
clues  as  to  where  the  underflow 
occurred. 

The  second  problem,  stack  overflow, 
is  essentially  the  reverse  situation: 
there  were  more  invocations  of  enter¬ 
ing  than  leaving.  This  problem  can  be 
detected  two  different  ways,  either 
while  attempting  to  use  entering  or 
when  ending  has  been  invoked.  In  ei¬ 
ther  case,  the  place  stack  dump  will 
provide  useful  insights  into  what  has 
been  going  on.  When  ending  is  encoun¬ 
tered,  the  place  stack  should  be  empty; 
it  if  isn’t,  then  there  have  been  more 
invocations  of  entering  than  leaving, 
which  indicates  some  fundamental 
failure  of  structuring  in  the  program  or 
in  the  placement  of  the  entering  and 
leaving  invocations. 

Earle  Jennings 
Director  of  R&D 
Incremental  Computing 
Corp. 

P.O.  Box  2875 
Santa  Clara,  CA  95055 

DDJ 

(Listing  begins  on  next  page) 


11 

95 


Letters  Listing  (Text  begins  on  page  8) 


{************************************************** 

** 

**  Diagnostic:  tools  in  TURBO  PASCAL 

**  by  Earle  Jennings  10/21/84 

** 

***** ***************  **#*#*****#**■»****#*■»***#**#**} 

CONST 

name_l  ength-24 ;  1  length  of  names  stored  on  t.  h  e  place  stack} 

place_depth=50;  C  the  depth  of  the  place  stack  } 

TYPE 

name=str i ng  C name_l ength  3 ; 

{  name  is  a  string  of  name_l ength  chars  max} 
anystr  i  ng=str i  ng  1. 255 3  ; 

VAR 

pi ace_ptr : integer; 

place: arrayCO  . .  pi ace_depth 3  of  name; 

•C  This  is  the  place  stack  that  provides  our  travel  log} 

{****************************** *********************** 

**  right_pad(  Its_name,name_l ength  ) 

************ *** ***********  *******  *******  *  * ********** *} 

f  uncti  on  r  i  ght._pad  ( Its  ..name:  anystr  i  ng ;  name_l ength :  i  nteger  )  :  anystr  i  ng ; 
VAR  is  integer; 

temp : anystr i ng ; 
begin 

temp: =Its_name; 

if  length  (Its_name)< n a m e __  1  e n g t h  t h e n 

for  i  :  =1  ength  <  Its.  _na  me)  +1  to  name_l  ength  do  temp:  =conc  at.  (temp  ,  '  '); 

r  i  g  h  t  _  p  a  d :  =  t.  e  m  p ; 
end;  {  of  right_pad3- 

{*********  *  *****  -x  x  x  -x  x  x  x  x-  x  x  -x-  x  x-  x-  x-  x-  *  -x  x-  -x-  x  *  -x  *  x  x  *  x-  *  x  x  x  x-  x-  x 
**  dump_place_st.ack  (target:  text)  ;  10/20/84 

*********************** * * * * ********************* * * } 
procedure  dump_place_stack (VAR  target ; text )  ; 

VAR  i: integer; 
begin 

wr  i  tel  n  <  target. our  present  location  is; 
if  p  1  ace  ptr  >-- 1  then 

for  i:=place_ptr  downto  0  do 
begin 

write  (target ,  r  i  ght  ...pad  (pi  acef.  i  3  ,  name_l  ength  ; 

if  (  (  pi  ace_ptr--i  )  mod  3  )  =  0  then  wri  tel  n  (target )  ; 

end ; 

end;  C  of  dump_pl  ace._stack  } 

**  dump_places  10/20/84 

**************************************************} 
procedure  dump  places; 

VAR  i  t. :  integer  ; 
beg  i  n 

wri  tel  n  (' dump  place  stack  to:  l--crt,  2-lp,  3-both ,  else  skip'); 
readl n (it) ; 
case  it  of 

1:  dump_p 1 ace.stac k (con) ; 

2:  dump _p 1 ace_stac k ( 1 st ) ; 

3:  begin 

dump_pl ace_stack (con) ; 
dump_pl ace  stack ( 1 st ) ; 
end ; 

el  se 
end ; 


1 2  Dr.  Dobb's  Journal.  February  1 985 


96 


Letters  Listing  (Listing  Continued,  text  begins  on  page  8) 


end;  C  of  dump  _p  1  aces  } 

•C  ************************************************** 

**  error  (message)  1.1/3/84 

**************************************************} 
procedure  error  (message:  anystr  i.  ng )  ; 

VAR  y_or_n:char; 
begi  n 

writeln  (  'at*****************************************  '  )  • 
wri tel n (message) ; 

writeln ('Do  you  wish  error  message  output  to  printer?'); 
read 1 n ( y  or _n ) ; 
y_or_n:  =upcase  (y_or_ri )  ; 

if  y_or_n  ~  'Y'  then  wri tel n ( 1 st , message) ; 
dump  pi  aces; 

writeln ( ' - >>>>  hit  Ctrl-C  to  kill  program,  else  return  to  continue'); 

read In <y_or_n) ; 
end;  C  of  error  } 

•C  ******  ******** **************** ******************** 

**  enter r i ng ( i t )  11/3/84 

********************* *****************************} 
procedure  enterr i.  ng  (it:  name )  ; 
begi  n 

if  pi ace_ptr<pl ace_depth  then 
begin 

pi ace_ptr : ~pl ace_ptr+l ; 
pi  ace II  pi  ace...ptr  3  :  =i  t ; 
end 

else  error (place  stack  overflow  upon  enterring  "'+it+'H '); 
end;  •£  of  enterring  } 

•C  **************************  ************************ 

**  leaving  11/3/84 

*********************************************  *****}• 
procedure  leaving; 
beg  i  n 

if  p  1  ac e_p t r < 0  t. h en 
beg  i  n 

pi  ace _ptr : ~pl ace  depth; 
error ('place  stack  underflow'); 
end 

else  pi ace_ptr : =pl ace_ptr— 1 ; 
end;  ■£  of  leaving} 

■C  ******************  ******************************** 

**  starting  10/20/84 

**************************************************} 
procedure  starting; 
beg  i  n 

pi  ace_ptr : =-l ; 
enterr i ng ( ' main ' ) ; 
end;  C  of  starting  } 

•£***  *********************************************** 

**  ending  11/3/84 

**************************************************} 
procedure  ending; 

begin 

1 eavi ng ; 

if  pi ace_ptr< >- 1  then 
beg  i  n 

error  (  'place  stack  imbalances'); 
end ; 

end;  ■£  of  ending  } 

End  Listing 


Dr.  Dobb’s  Journal,  February  1985 


15 

97 


A  Random  Choice 

The  generators  of  pseudo-random 
numbers  are  algorithms  that  lie  on  the 
boundary  between  computer  science 
and  recreational  mathematics.  They’re 
as  little  tainted  by  real-world  consider¬ 
ations  as  we  could  want,  but  they  are 
endlessly  measurable,  improvable, 
discussable. 

These  thoughts  were  inspired  by  a 
recent  issue  of  PC  World ,  in  which  one 
Gary  Andrew  reported  an  unusually 
simple  algorithm  for  generating  ran¬ 
dom  integers.  Andrew  gives  credit  to 
Drs.  Wilson  Talley  and  Nicholas  Me¬ 


tropolis  for  the  design  of  the  algorithm. 
He  claims  it  stands  up  well  to  a  variety 
of  tests,  a  claim  we’ve  not  attempted  to 
verify. 

Andrew  gives  the  algorithm  in  BA¬ 
SIC;  we  recoded  it  in  C  by  way  of  un¬ 
derstanding  it.  Our  interpretation  ap¬ 
pears  in  the  Listing  (page  20).  It  looks 
to  us  as  if  a  version  that  produced  1 6- 
bit  numbers  would  be  most  effectively 
coded  in  assembly  language.  Then 
some  tricks  could  be  played  with  carry 
flags,  etc.,  to  avoid  having  to  carry 
long-integer  intermediate  results. 
Anyone  who  codes  one  up,  or  who  does 
any  statistical  tests  of  this  generator, 


be  sure  to  write. 

Skies  Still  Falling,  Ho-Hum 

A  couple  of  readers  had  comments  on 
our  November  review  of  Computers  In 
Crisis.  Charles  Martin  of  Durham, 
NC,  comments:  “The  same  issue — oh- 
migod  what  will  happen  to  our  Julian- 
date  routines  on  1  Jan  2000  was  cov¬ 
ered  rather  completely  (read:  thrashed 
half  to  death)  in  ComputerWorld  in 
about  1 975.  That  someone  would  write 
a  book  about  it  now  is  surprising;  that 
Petrocelli  would  publish  it  only  proves 
you  don’t  have  to  be  smart  to  be  an 
editor . . . 

Terry  Jackson  of  Lombard,  IL,  with 
tongue  firmly  implanted  in  cheek, 
wrote:  “The  issue  of  date  storage 
should  be  addressed  promptly.”  Why? 
Because  “it  is  well  known  to  anyone 
associated  with  large  DP  operations 
that  1 6  years  is  not  enough  for  them  to 
solve  this  problem.”  But  he  has  an  an¬ 
swer:  “The  obvious  solution  is  to  get 
the  whole  world  to  agree  to  write  the 
year  in  hexadecimal.  That  defers  the 
crisis  until  19FF  (2155  old  style),  thus 
allowing  time  to  rewrite  the  software, 
and  maybe  enough  time  to  debug.” 

Throughputting 

In  the  July  Clinic  we  wrote  about  the 
idea  of  “throughput”  and  suggested 
that  the  throughput  of  a  system’s  fun¬ 
damental  file-copy  utility  would  be  a 
useful  capsule  measurement  of  the  per¬ 
formance  of  that  configuration  of  soft¬ 
ware  and  hardware.  We  measured  the 
time  it  took  CP/M  Plus  and  PIP  to  copy 
files  of  different  sizes  on  three  differ¬ 
ent  hardware  configurations.  The 
numbers  seemed  to  fit  a  linear  model 
pretty  well. 

A  dozen  readers  took  the  time  to 
measure  their  own  systems’  copy  times 
and  report  them.  Their  names,  and  the 

Dr.  Dobb’ s  Journal,  February  1985 


Case -name 

Machine 

CPU 

DOS 

Drives  and  other  factors 

Cage 

North Star 

Z80  4MHZ 

CP/M  2.2a 

5-inch 

Castle-1 

NorthStar 

Z80  4MHZ 

N*D0S 

5-inch 

Castle-2 

North  Star 

Z80  4MHZ 

CP/M  2.2a 

5-inch 

Chamberlin- 1 

MTU- 140 

6502  1MHZ 

C0D0S 

8-inch,  5K  buffer 

Chamberlin-2 

MTU-140 

6502  1MHZ 

C0D0S 

8-inch,  32K  buffer 

Cortesi-1 

S-100 

Z80  4 MHZ 

CP/M  3.0 

8-inch  thinline 

Cortesi-2 

TRS-80  4P 

Z80  4 MHZ 

CP/M  3.0 

5-inch  thinline 

Cortesi-3 

Apple-ALS 

Z80  6MHZ 

CP/M  3.0 

Apple  5-inch 

Floyd -1 

PC 

8088 

PCD0S  2.0 

5-inch,  verify  off 

Floyd -2 

PC 

8088 

PCDOS  2.0 

5-inch,  verify  on 

Floyd-3 

PC 

8088 

PCDOS  2.0 

5-inch  to  Mdisk 

Floyd-4 

PC-XT 

8088 

PCDOS  2.0 

5-inch,  verify  off 

Floyd -5 

PC-XT 

8088 

PCDOS  2.0 

5-inch,  verify  on 

Floyd -6 

PC-XT 

8088 

PCDOS  2.0 

5-inch  to  Mdisk 

Gunn-1 

NorthStar 

Z80  4mhz 

CP/M  2.2 

5-inch  Tandon  100-2 

Gunn -2 

NorthStar 

Z80  4mhz 

CP/M  3-0 

5-inch  Tandon  100-2 

Gunn-3 

Zilog  MCZ-1 

Z80  2.5mhz 

CP/M  2.2 

8-inch  Shugart  800-2 

Hoffman-1 

S-100 

Z80  4 MHZ 

CP/M  2.2 

8-inch  single-density 

Hoffman-2 

S-100 

Z80  4 MHZ 

CP/M  2.2 

8-inch  double-density 

Hole-1 

CompuPro 

8088  8MHZ 

MP/M  8-16 

8-inch 

Hole -2 

CompuPro 

8088  8MHZ 

MP/M  8-16 

21MB  hard  disk 

Hole-3 

CompuPro 

8088  8MHZ 

MP/M  8-16 

Mdrive-H 

Johnson 

Morrow  MD-3 

Z80  4MHZ 

CP/M  2.2 

5-inch 

Prince 

n  .a . 

Z80  2 MHZ 

CP/M  2.2 

8-inch  single  density 

Sabin-1 

Altos  5 

Z80  4MHZ 

CP/M  2.2 

n.a. 

Sab  in -2 

TRS-80  III 

Z80  2MHZ 

CP/M  2.2 

5-inch 

Schemm-  1 

Digital  Grp 

n  .a . 

CP/M  2.2? 

8-inch 

Schemm-2 

Digital  Grp 

n.a. 

Disk-MON 

8-inch 

Schemm- 3 

IMS 

n.a. 

CP/M  2.2? 

8-inch 

Schemm-4 

NorthStar 

Z80  4 MHZ 

CP/M  2.2 

5-inch 

Schemm- Z1 

Z-1 10 

8086 

ZDOS 

5-inch 

Schemm- Z2 

Z-110 

8086 

CP/M-86 

5-inch 

Swearingen 

Big  Board 

Z80  4MHZ 

CP/M  2.2 

8-inch 

Table  1 

The  cases — reporters  and  configurations — analyzed  in  the 
throughput  study. 


16 

OR 


essential  details  of  their  hardware  and 
software  configurations,  are  listed  in 
Table  1  (page  16).  Most  cases  describe 
CP/M  systems,  but  we  received  a  few 
reports  on  NorthStar  DOS,  CODOS 
(what?),  and  PCDOS.  No  measure¬ 
ments  of  68K-based  systems,  Apple 
DOSses,  Triss-DOSses,  Commodore 
64s,  or  Ataris  were  reported.  Where  is 
everybody? 

Table  2  (below  right)  lists  the  copy 
times  reported  with  each  case  in  Table 
1.  As  shown,  the  numbers  appear  to  be 
precise  to  the  nearest  tenth  of  a  second, 
but  this  was  not  the  case.  Most  times 
were  taken  with  hand-operated  watch¬ 
es,  and  so  are  accurate  to  the  nearest 
0.2  second  at  best  (it  is  not  difficult  to 
get  repeatable  times  to  this  accuracy 
using  a  hand-operated  watch,  but  it  is 
difficult  to  be  any  better).  Some  were 
reported  only  to  the  nearest  sejod  and 
were  entered  with  a  fraction  digit  of 
zero.  And  there  may  be  a  few  transcrip¬ 
tion  errors,  since  some  numbers  were 
submitted  in  minuscule  handwriting  on 
reader  service  cards! 

Furthermore,  we  can’t  be  sure  that 
the  measurements  were  taken  in  a  con¬ 
sistent  way.  They  were  supposed  to  re¬ 
flect  the  time  to  copy  a  file  from  one 
disk  to  another,  exclusive  of  any  time 
needed  to  load  a  copy  program  (the 
built-in  COPY  command  of  MSDOS; 
multiple-copy  mode  of  CP/M  PIP). 
There  was  an  unstated  (but  obvious) 
assumption  that  all  buffered  data 
should  be  forced  to  disk  in  the  mea¬ 
sured  time.  We  suspect  that  some  sort 
of  buffering  phenomenon  is  affecting 
the  more  remarkable  ZDOS  and 
PCDOS  measurements. 

Throughput  Models 

Several  readers  made  thoughtful  com¬ 
ments  on  the  numbers.  Bruce  Johnson 
of  Lakewood,  CO,  had  this  to  say:  “The 
throughput  you  showed  in  the  table  is 
really  a  combination  of  two  factors. 
One  is  the  speed  at  which  PIP  transfers 
data-  which  I  would  call  throughput — 
and  the  other  is  a  theoretical  minimum 
time  to  transfer  a  zero-length  file, 
which  I  would  call  the  setup  time.  The 
setup  time  corresponds  to  the  Y-inter- 
cept  of  each  line  on  the  graph,  while  the 
throughput  corresponds  to  the  recipro¬ 
cal  of  the  slope  of  the  line.  I  would  sug¬ 
gest  as  a  measure  of  throughput  for  lin¬ 
ear  utility  programs: 

Dr.  Dobb's  Journal,  February  1985 


T  =  60/(T64  -  T4) 

where  T  =  throughput,  T64  =  sec¬ 
onds  to  transfer  a  64K  file,  and  T4  = 
seconds  to  transfer  a  4K  file.  This  for¬ 
mula  will  yield  a  useful  measure  of 
throughput  over  the  range  of  file  sizes 
of  most  interest  with  only  two 
measurements.” 

Johnson’s  ingenious  formula  does 
just  what  he  claims;  it  produces  num¬ 
bers  that  are  in  fair  agreement  with  a 
more  elaborate  model  to  be  discussed 
below. 

William  Hole,  of  Barrington,  RI, 
made  the  same  point,  that  the  timings 
we  published  and  those  he  took  on  his 
own  system  were  a  good  fit  to  a  linear 
model  with  a  fixed  startup  time.  He 
put  his  numbers  through  a  linear  re¬ 
gression  test  to  check  the  model  and 
got  good  agreement. 

CP/M  Measurements 

Because  Bill  Hole  seemed  to  know  his 


way  around  the  fundamental  tools  of 
statistics,  we  dumped  the  entire  con¬ 
tents  of  Table  2  on  him  and  asked  him 
to  test  them  for  conformity  to  the  fol¬ 
lowing  model: 

T  =  s  +  e  *  floor(  n/16)  +  n/  t 

where  T  is  the  reported  time  in  sec¬ 
onds,  s  is  a  setup  time,  e  is  the  time 
required  by  CP/M  for  opening  the  sub¬ 
sequent  “extent”  of  a  file,  n  is  the  num¬ 
ber  of  kilobytes  that  are  transferred, 
and  t  is  the  number  of  kilobytes  that 
can  be  transferred  in  one  second,  that 
is,  the  actual  “throughput”  of  the 
system. 

He  performed  this  analysis  for  us 
(and  many  thanks  for  the  time  it  must 
have  taken)  but  the  results  are  some¬ 
what  equivocal.  Table  3a  (page  18)  re¬ 
ports  the  analysis  for  the  CP/M-com- 
patible  systems.  The  first  group  of 
three  columns  presents  the  computed 
results  for  the  setup  time.  The  estimat- 


Ik 

2k 

4k 

8k 

16k 

24k 

32k 

48k 

64k 

128k 

Cage 

3.8 

4.8 

6.6 

10.5 

18.8 

-.- 

36.2 

53.2 

72.0 

151.1 

Castle-1 

5.5 

5.8 

7.9 

11.5 

20.0 

40.0 

78.0 

Castle-2 

6.0 

6.4 

7.2 

9.0 

16.6 

29.4 

56.7 

Chamberlin-1 

2.4 

2.2 

3.1 

3.8 

4.7 

6.4 

7.6 

10.6 

13.6 

23.4 

Chamberlin-2 

2.5 

2.5 

2.9 

3-6 

4.6 

5.6 

6.5 

6.5 

10.4 

18.5 

Cortesi-1 

3-4 

3.4 

3.4 

4.8 

5.8 

8.2 

9.6 

13.2 

18.0 

Cortesi-2 

3.2 

4.0 

5.0 

7.2 

11.0 

17.4 

20.6 

30.0 

39.5 

Cortesi-3 

4.0 

4.2 

5.0 

6.4 

9.2 

13.8 

17.4 

23.6 

32.5 

Floyd- 1 

2.3 

2.3 

2.8 

3.4 

4. 1 

5. 1 

5.8 

7.3 

12.5 

Floyd-2 

3.5 

3.5 

4.2 

4.8 

5.9 

7.4 

8.4 

10.6 

17.5 

Floyd -3 

1.3 

1.3 

1.7 

2.0 

2.4 

3.1 

3-3 

4. 1 

6.3 

Floyd-4 

4.3 

4.6 

4.7 

5.2 

5.3 

5.3 

5.4 

6.2 

7.5 

Floyd-5 

4.8 

4.9 

5.0 

5.6 

5.7 

5.9 

6.4 

7.3 

8.7 

Floyd-6 

3.1 

3.5 

3.5 

3.6 

3-9 

3.9 

4.0 

4.5 

5.4 

Gunn-1 

2.6 

2.6 

2.9 

3.8 

5.6 

7.6 

9.2 

14.2 

18.6 

Gunn -2 

3.5 

3.6 

4.1 

4.7 

6.7 

8.0 

9.7 

14.1 

17.9 

Gunn-3 

2.2 

2.4 

3.0 

4.2 

6.0 

8.9 

11.2 

17.3 

22.6 

Hoffman- 1 

4,0 

5.0 

6.0 

9.0 

16.0 

25.0 

31 .0 

42.0 

55.0 

Hoffman-2 

— 

2.5 

3.5 

4.0 

6.0 

10.0 

13.0 

16.5 

22.0 

Hole-1 

2.9 

3.1 

3.6 

3.9 

5.5 

7.7 

9.8 

13.5 

17.0 

33.5 

Hole-2 

2.  1 

2.2 

2.4 

3.5 

6.4 

6.9 

8.7 

13-3 

16.4 

30.8 

Hole-3 

0.6 

0.6 

0.7 

0.9 

1.3 

1.7 

2.1 

2.9 

3.8 

7.0 

Johnson 

3-6 

3.7 

4.7 

6.0 

9.6 

12.2 

17.1 

23.5 

31.7 

Prince 

-.- 

2.4 

2.6 

3.4 

5.6 

-.- 

11.5 

22.7 

Sab  in -1 

2.5 

3.5 

5.0 

7.0 

12.5 

17.0 

23-0 

48.0 

44.0 

Sab  in -2 

2.5 

3.0 

4.5 

6.0 

9.0 

1 1 .5 

17.0 

22.5 

31.0 

Schemm-  1 

-.- 

4.9 

5.2 

5.9 

8.5 

-.- 

13.8 

24.2 

Schemm-2 

-.- 

2.6 

2.6 

3.4 

3.7 

-.- 

5.8 

- .  - 

Schemm- 3 

-.- 

7.6 

8.0 

9.7 

13.8 

-.- 

23-3 

_ .  _ 

42.8 

Schemm-4 

-.- 

6.2 

7.2 

7.3 

9.4 

14.8 

22.9 

Schemm-ZI 

1.1 

0.9 

1.1 

1.3 

-.- 

1.6 

2.9 

Schemm-Z2 

3.1 

3.1 

3.2 

4.5 

-.- 

6.8 

12.0 

Swearingen 

3.4 

3.6 

4.2 

5.8 

8.2 

-.- 

15.9 

20.9 

28.3 

Table  2 

Reported  times,  in  seconds,  to  transfer  files  of  different  sizes. 
These  data  were  input  to  the  analysis  performed  by  W.  T.  Hole. 
The  two  italicized  entries  were  mistranscriptions  (see  text). 


17 

99 


ed  setup  time  appears  in  the  first  col¬ 
umn.  The  second  column  (headed  “s/ 
e”)  gives  the  standard  error  in  seconds; 
that  is,  the  estimated  setup  time,  plus 
or  minus  this  value,  should  encompass 
68%  of  the  observations. 

The  third  column,  headed  “prob,”  is 


a  confidence  rating.  It  is  the  probabili¬ 
ty  that  the  setup  value  should  equal 
zero.  If  it  exceeds  0.05,  the  setup  time 
is  probably  not  a  reasonable  part  of  the 
model.  The  eye  is  drawn  at  once  to  the 
third  line,  case  Sabin- 1,  the  only  case 
that  appears  not  to  conform  to  a  model 


Case 

-  Startup 

— 

-  Extent 

-  Throughput 

sec. 

s/e 

prob 

sec . 

s/e 

prob 

kb/sec 

s/e 

prob 

Cage 

3-13 

0.92 

0.014 

5.17 

1.58 

0.017 

1.15 

0.14 

C.0001 

Hoffman-1 

3.32 

0.75 

0.004 

-.07 

1.73 

0.967 

1.21 

0.14 

0.0001 

Sabin-1 

1.26 

3.23 

0.710 

-.72 

7.40 

0.924 

1.27 

1.10 

0.0747 

Castle-2 

4.67 

0.55 

0.001 

2.36 

1.23 

0. 126 

1.42 

0.14 

0.0003 

Cortesi-2 

3.06 

0.24 

0.000 

1 .50 

0.54 

0.033 

1.99 

0. 1 1 

0.0001 

Schemm-3 

6.36 

0.24 

0.000 

2.47 

0.48 

0.014 

2.20 

0.13 

0.0004 

Sab in -2 

2.08 

0.49 

0.005 

-.22 

1.13 

0.848 

2.21 

0.31 

0.0002 

Johnson 

2.76 

0.38 

0.000 

0.43 

0.88 

0.638 

2.36 

0.27 

0.0001 

Cortesi-3 

3.52 

0.29 

0.000 

1.86 

0.67 

0.032 

2.79 

0.28 

0.0001 

Swearingen 

2.93 

0.34 

0.000 

0.93 

0.77 

0.280 

2.86 

0.35 

0.0003 

Hoffman-2 

2. 17 

0.55 

0.011 

0.82 

1.12 

0.493 

3.64 

0.96 

0.0048 

Hole -2 

1.52 

0.26 

0.000 

1.72 

0.47 

0. 165 

3.73 

0.41 

0.0001 

Schemm-1 

4.14 

0.22 

0.000 

1 .00 

0.44 

0. 106 

3.74 

0.35 

0.0013 

Gunn-3 

1.88 

0. 19 

0.000 

1.41 

0.44 

0.019 

3-90 

0.37 

0.0001 

Prince 

1.70 

0.  18 

0.002 

1.82 

0.36 

0.015 

4.10 

0.34 

0.0010 

Schemm-4 

5.81 

0.46 

0.001 

0.59 

0.91 

0.559 

4. 12 

0.99 

0.0139 

Gunn -2 

3.14 

0.22 

0.000 

0.52 

0.50 

0.339 

4.93 

0.69 

0.0002 

Gunn-1 

2. 16 

0.21 

0.000 

1.22 

0.49 

0.047 

5.  10 

0.73 

0.0002 

Hole-1 

2.68 

0.  17 

0.000 

1.19 

0.31 

0.006 

5.75 

0.63 

0.0001 

Cortesi-1 

3.08 

0.21 

0.000 

1.35 

0.49 

0.033 

6. 11 

1.07 

0.0005 

Schemm-Z2 

2.70 

0.20 

0.000 

0.94 

0.39 

0.096 

9.96 

2.50 

0.0157 

Hole-3 

0.51 

0.01 

0.000 

0.04 

0.03 

0.258 

20.61 

0.80 

0.0001 

Table  3a 

Analysis  of  the  CP/M  (and  MP/M)  cases,  ranked  by  increasing 

throughput. 


Ca3e 

-  Startup 

— 

-  Extent 

-  Throughput 

sec. 

s/e 

prob 

sec . 

s/e 

prob 

kb/sec 

s/e 

prob 

Castle-1 

3-96 

0.32 

0.000 

3.24 

0.72 

0.010 

0.99 

0.03 

0.0001 

Chamberlin-1 

2. 17 

0.21 

0.000 

-.28 

0.37 

0.473 

5.45 

0.71 

0.0001 

Chamberlin-2 

2.50 

0.44 

0.000 

0.42 

0.78 

0.607 

10.03 

7.92 

0.0580 

Schemm-2 

2.43 

0.21 

0.007 

0.64 

0.62 

0.409 

11.80 

4.38 

0.0660 

Table  3b 

Analysis  of  8-bit  DOS's  other  than  CP/M.  These  cases  were  a  poor 
fit  to  the  hypothesized  model. 


Case 

-  Startup 

— 

-  Extent 

-  Throughput 

sec. 

s/e 

prob 

sec. 

s/e 

prob 

kb/seo 

s/e 

prob 

Floyd-2 

3.44 

0.67 

0.002 

1.80 

1.54 

0.286 

9.00 

19.73 

0.1959 

Floyd -1 

2.27 

0.54 

0.005 

1.26 

1.23 

0.345 

12.26 

36.28 

0.2291 

Floyd -3 

1.35 

0.20 

0.000 

0.34 

0.48 

0.500 

18.24 

13.81 

0.0594 

Floyd -5 

4.79 

0.  15 

0.000 

0.06 

0.35 

0.853 

18.72 

9.05 

0.0222 

Floyd -4 

4.47 

0.20 

0.000 

0.01 

0.47 

0.974 

24.50 

32.31 

0.1303 

Floyd-6 

3.28 

0.13 

0.000 

0.01 

0.30 

0.956 

35.08 

38.44 

0. 1043 

Schemm-ZI 

0.97 

0.12 

0.004 

0.33 

0.23 

0.252 

72.99 

515.24 

0.3383 

Table  3c 

Analysis  of  the  few  reported  PC-,  MS-,  or  Z-DOS  cases.  Not  only 
the  extent,  but  the  linear  model  is  rejected  by  these  numbers. 


that  includes  a  fixed  setup  time.  Why 
is  this?  Quite  possibly  because,  as  indi¬ 
cated  in  Table  2  by  an  italicized  entry, 
one  of  Joe  Sabin’s  numbers  was  mis¬ 
transcribed  (the  48K  copy  time  should 
be  34.0  seconds).  There  wasn’t  time  to 
rerun  the  analysis,  so  we  left  this  case 
in  place  as  an  interesting  exception. 
The  fact  that  the  only  case  not  to  fit 
the  model  contained  bad  data  seems  to 
strengthen  the  case  for  the  model. 

The  second  group  of  three  columns 
in  Table  3a  represents  the  estimate  for 
factor  “e,”  the  time  to  open  a  new  1 6K 
file  extent.  Again  the  first  column  is  an 
estimate  of  the  time,  the  second  is  the 
standard  error  —read  it  as  “plus  or  mi¬ 
nus  seconds”-  and  the  third  a  confi¬ 
dence  rating.  You'll  immediately  no¬ 
tice  that  the  second  line  exhibits  a 
remarkably  unconfident  probability  of 
0.967.  A  16K  extent  overhead  isn’t  ap¬ 
parent  in  the  numbers  for  this  case 
(which  were  transcribed  correctly). 
That’s  odd,  because  (as  we  can  see  in 
Table  1)  this  case  involved  CP/M  sin¬ 
gle-density  8-inch  disks.  Other  cases 
that  disagree  with  the  hypothesized  ex¬ 
tent  factor  are  easier  to  explain,  e.g., 
because  they  use  a  32K  extent  or  (in 
the  case  of  the  M-drive)  because  the 
extent  overhead  is  actually  near  zero. 

The  final  group  of  three  columns 
presents  the  estimated  throughput, 
with  the  same  interpretation  as  before. 
A  poor  confidence  rating  here  (one 
greater  than  0.05)  indicates  that  these 
data  do  not  fit  a  linear  model.  There  is 
one  case  in  Table  3a  for  which  this  is 
definitely  true  and  two  others  that  are 
suspect. 

But  by  and  large,  the  linear  model  of 
throughput  fits  very  well,  so  the 
throughput  value  (on  which  Table  3a 
is  sorted)  is  probably  a  fairly  good  esti¬ 
mate  for  these  CP/M-based  systems.  It 
shows  a  general  trend  of  increasing 
speed  from  5-inch  drives  to  8-inch  to 
M-drives,  with  Hole’s  CompuPro  M- 
drive  the  fastest.  Schemm’s  Z-100  (8- 
bit  CP/M,  but  with  I/O  handled  by  a 
16-bit  BIOS)  produced  a  remarkable 
time  for  a  5-inch  disk  drive.  We  sus¬ 
pect  this  measurement  was  affected  by 
some  sort  of  internal  buffering — notice 
that  it  conforms  less  well  to  the  linear 
model  than  most. 

Non-CP/M  Throughput 

Table  3b  (page  18)  contains  the  analy- 


18 


inn 


Dr.  Dubb’s  Journal,  February  1985 


sis  of  the  cases  that  involved  8-bit  sys¬ 
tems  other  than  CP/M.  Mostly  these 
deny  an  “extent”  hypothesis — not  un¬ 
reasonably,  since  that’s  a  CP/M  pecu¬ 
liarity — and  half  of  them  appear  to  be 
nonlinear.  One  of  these  is  due  to  a  tran¬ 
scription  error:  we  miscopied  Hal 
Chamberlin’s  48K  time  as  6.5  seconds 
when  it  should  have  been  8.3  seconds. 
With  that  change,  case  Chamberlin-2 
should  be  much  closer  to  linearity,  but 
time  didn't  permit  rerunning  the 
analysis. 

Table  3c  (page  18)  details  the  analy¬ 
sis  for  the  MS  DOS  (PCDOS,  ZDOS) 
cases.  We  expected  that  an  MSDOS 
copy  command  would  be  just  as  linear 
as  a  CP/M  PIP  operation.  That  appears 
not  to  be  so,  as  none  of  these  cases  con¬ 
forms  to  the  linear  model.  That  they 
don’t  support  the  “extent”  factor  is  no 
surprise;  there  are  no  “extents”  in 
iMSDOS.  But  that  they  don’t  appear 
linear  is  most  puzzling,  as  is  the  re¬ 
markable  speed  they  suggest.  (After 
all,  no  amount  of  software  cleverness 
will  make  a  5-inch  drive  rotate  or  seek 
any  more  quickly,  so  why  should  a  PC 
be  so  very  much  quicker  than  an  8-bit 
system  with  equivalent  drives?)  Can  a 
reader  suggest  what  might  be  going 


on?  Is  there  a  measurement  procedure 
for  MSDOS  that  will  produce  linear 
numbers? 

The  Johnson  Model 

We  present  Table  4  (below)  as  a  final 
view  of  the  data.  It  shows  all  the  cases 
that  support  a  linear  model,  ranked  by 
what  we  might  call  their  “Johnson 
Number” — the  figure  of  merit  pro¬ 
duced  by  Bruce  Johnson's  formula  dis¬ 
cussed  earlier.  There  is  a  fairly  close 
correspondence,  so  Johnson’s  rule  of 
thumb  is  useful  when  the  data  is  lin¬ 
ear.  But  since  it  presumes  linearity,  it 
should  only  be  applied  where  linearity 
is  known  to  exist. 

DDJ 

(Listing  begins  on  page  20) 

Reader  Ballot 

Vote  for  your  favorite  feature/article. 

Circle  Reader  Service  No.  1 90. 


4k 

64k 

Johnson 

Linear 

Case 

Time 

Time 

Number 

Model 

Castle-1 

7.9 

78.0 

0.86 

0.99 

Cage 

6.6 

72.0 

0.92 

1.15 

Castle -2 

7.2 

56.7 

1.21 

1.42 

Hoffman-1 

6.0 

55.0 

1.22 

1.21 

Schemm-3 

8.0 

42.8 

1.72 

2.20 

Cortesi-2 

5.0 

39.5 

1.74 

1.99 

Cortesi-3 

5.0 

32.5 

2. 18 

2.79 

Johnson 

4.7 

31.7 

2.22 

2.36 

Sab  in -2 

4.5 

31.0 

2.26 

2.21 

Swearingen 

4.2 

28.3 

2.49 

2.86 

Prince 

2.6 

22.7 

2.99 

4.10 

Gunn-3 

3.0 

22.6 

3.06 

3.90 

Schemm-1 

5.2 

24.2 

3. 16 

3.74 

Hoffman-2 

3.5 

22.0 

3.24 

3.64 

Gunn-1 

2.9 

18.6 

3.82 

5. 10 

Schemm-4 

7.2 

22.9 

3.82 

4.  12 

Cortesi-1 

3.4 

18.0 

4.11 

6.  1 1 

Hole-2 

2.4 

16.4 

4.29 

3.73 

Gunn-2 

4.1 

17.9 

4.35 

4.93 

Hole-1 

3.6 

17.0 

4.48 

5.75 

Chamberlin-1 

3.1 

13.6 

5.71 

5.45 

Schemm-Z2 

3.1 

12.0 

6.74 

9.96 

Hole -3 

0.7 

3.8 

19.35 

20.61 

Table  4 

The  cases  that  matched  a  linear  model,  ranked  by  Bruce  John¬ 
son's  rule-of-thumb  formula. 


Dr.  Dobh’s  Journal,  February  1985 


19 

101 


Dr.  Dobb's  Clinic  Listing  (Text  begins  on  page  16) 


/* 

Random  integer  algorithm  reported  by  Gary  Andrew  to 
the  column  of  PC  World,  December  1984,  with 

credit  to  Drs.  Wilson  Talley  and  Nicholas  Metropolis. 
Recoded  to  C  by  DEC. 

This  version  produces  15-bit  integers;  for  16-bit 
unsigned  integers  longs  must  be  used  for  intermediate 
computations . 

*/ 

//define  BITS  15  /*  how  many  bits  in  numbers  */ 

//define  MAXO  1  <<  BITS 
//define  MAX  (MAXO)  -  1 

//define  private  static 

//define  cardinal  unsigned  /*  is  this  Wirth-while?  */ 

private  cardinal  seedA  =  32749,  seedB  =  32633; 

int  rseed(a,b) 
cardinal  a,b; 

{  /*  seed  the  generator,  returning  first  number  of  sequence  */ 

a  &=  MAX;  /*  reducing  negative  signed  ints,  */ 

b  &=  MAX;  /*  ..etc.,  to  values  <=  MAX  */ 

a  *=  2;  if  (a>MAX)  a  -=  MAX; 

b  *=  2;  if  (b>MAX )  b  -=  MAX; 

seedA  =  a;  seedB  =  b; 
return  (b )  ; 

} 

private  cardinal  randO( ) 

{  /*  cycle  the  generator,  return  next  number  of  sequence  */ 

register  cardinal  c; 

c  =  seedA  +  seedB; 
if  (c>MAX )  c  -=  MAXO; 
c  *=  2;  if  (c>MAX)  c  -=  MAX; 
seedA  =  seedB;  seedB  =  c; 
return  (c); 

} 

cardinal  randi(n) 
cardinal  n; 

{  /*  return  a  random  number  in  1..n  */ 

register  cardinal  c; 

c  =  randO( ) ; 

return  (  1+  (c  %  n)  ) ; 


20 

in? 


Dr.  Dobb’ s  Journal.  February  1985 


Before  beginning  this  month’s  column, 
I  want  to  express  my  congratulations 
to  DDJ  on  its  100th  issue.  (My  first 
DDJ  article  appeared  a  little  more  than 
five  years  ago.)  I  hope  that  readers  will 
continue  to  make  their  excellent  con¬ 
tributions  to  keep  the  magazine  going 
during  the  next  few  years;  I  include 
both  members  of  the  “old  guard”  and 
new  readers  in  that  wish. 

This  month,  1  include  more  of  the 
reader  responses  received  over  the  past 
few  months  plus  some  ideas  about 
variable  formats  in  printf(  )  functions. 

A  C  Style  Reference 

Last  year,  we  had  a  long-running  dis¬ 
cussion  on  C  style.  Several  references 
were  cited  at  that  time.  Since  then.  I’ve 
run  into  another  paper  on  the  subject. 
While  the  formatting  is  different  from 
what  1  prefer,  I  think  the  paper  is  well 
worth  reader  attention.  The  article, 
called  “A  C  Style  Sheet,”  was  written 
by  Martin  Minow  of  Digital  Equip¬ 
ment  Corp.  The  article  lists  an  address, 
so  perhaps  this  is  the  best  way  to  ac¬ 
quire  a  copy: 

Martin  Minow 
Digital  Equipment  Corp. 

146  Main  St.,  MLO  3-3/U8 
Maynard,  MA  01754 
It  includes  a  list  of  references  from 
which  the  work  has  been  abstracted. 

Wish  List 

John  M.  Gamble  of  Batavia,  Ohio, 
writes: 

“I  am  very  glad  you  printed  this  col¬ 
umn  [August  1984]  (I  ranked  it  num¬ 
ber  one  for  the  month)  because  it  in¬ 
spired  me  to  jot  down  my  own  wish  list. 
It  is  only  one  wish  long,  but  it  is  some¬ 
thing  I  have  longed  for  for  some  time.” 

Mr.  Gamble’s  wish  is  for  a  way  to 
control  more  precisely  the  sizes  of  in¬ 
trinsic  data  types  in  C.  For  example, 


integers  can  have  different  sizes  on  dif¬ 
ferent  machines.  He  continues: 

“The  size  of  the  various  types  (int, 
char,  long,  etc.)  vary  too  much  from 
machine  to  machine.  However,  I  don’t 
think  that  forcing  a  size  to  a  type  (a  la 
Plum  Hall)  is  the  answer  (besides,  I 
hate  the  baby  words  Plum  uses  to  de¬ 
fine  them).  Instead,  I  think  that  anoth¬ 
er  ‘storage  class  specifier’  (see  The  C 
Programming  Language ,  p.  192)  like 
typedef  could  be  defined.  I’m  going  to 
call  it  ‘sizetype.’  Sizetype  would  be 
used  just  like  typedef,  but  what  is  de¬ 
clared  is  the  size  of  the  storage  class. 
For  example,  we  could  define  a  type 
small  this  way: 

sizetype  u8  small; 

The  letter  ‘u’  is  optional  and  stands  for 
‘unsigned.’  Thus,  a  variable  of  type 
small  is  unsigned  and  8  bits  long.  An¬ 
other  example  would  be: 

sizetype  16  hexsize; 

which  would  guarantee  that  variables 
of  type  hexsize  are  16  bits  long.” 

I  am  very  enthusiastic  about  Mr. 
Gamble’s  suggestion.  It  would  enhance 
portability  of  C  code.  Anyone  who  has 
moved  between  microcomputers  and 
minis  is  aware  that  significant  prob¬ 
lems  can  occur  when  moving  from  ma¬ 
chines  with  signed  or  unsigned  charac¬ 
ters.  Integer  length  differences  are  also 
annoying.  He  adds: 

“Use  of  the  sizetype  declaration 
would  guarantee  portability  between 
machines  (which  currently  is  a  heck  of 
a  problem).  It  also  means  that  only  one 
more  reserved  word  is  added,  instead 
of  the  many  which  would  be  needed  to 
define  a  type  for  every  conceivable  in¬ 
teger  length.  If  the  sizeof  operator 
could  be  altered  to  return  fractions,  we 
could  use  sizetype  to  define  bit  lengths 
that  are  not  multiples  of  eight.  On  the 


other  hand,  I  notice  that  the  sizeof  op¬ 
eration  on  a  bit-field  structure  is  not 
defined  in  The  C  Programming  Lan¬ 
guage ,  so  maybe  it  is  just  up  to  the  per¬ 
son  who  writes  the  compiler.” 

Another  August  Response 

Readers  may  have  followed  the  some¬ 
what  fervent  comments  made  by  Ger¬ 
ald  Evenden  in  the  August  1984  col¬ 
umn.  He  has  responded  to  my  remarks, 
and  these  remarks  deserve  further 
commentary.  To  summarize  the  previ¬ 
ous  material:  Mr.  Evenden  was  ex¬ 
tremely  displeased  by  my  comments 
about  C  because  he  felt  that  they  could 
convey  the  wrong  impressions  to  the 
uninitiated.  Furthermore,  he  felt  it  un¬ 
fair  for  me  to  discuss  C  I/O  libraries 
without  strongly  emphasizing  that  C 
and  its  libraries  are  completely  sepa¬ 
rate  concepts  (which  they  indeed  are). 
He  now  writes: 

“In  regard  to  your  response  to  my 
letter  in  the  August  issue  ...  I  feel  I 
must  expand  upon  some  of  my  earlier 
points  and  make  some  additional  com¬ 
ments.  In  addition,  please  excuse  the 
excesses  of  a  middle-age  curmudgeon. 
Scars  acquired  in  numerous  battles  of 
the  computer  wars  tend  to  create  a 
knee-jerk  reaction  when  I  sense  some 
potentially  deviant  and  dangerous 
thought  processes.  I  wanted  to  empha¬ 
size  that  the  compiler  and  the  support 
library  are  two  very  distinct  entities 
and  we  must  be  careful  to  maintain  the 
distinction.  When  I  talk  of  C,  I  am  re¬ 
ferring  to  the  compiler  .  .  .  when  I  talk 
about  the  C  library,  I  am  referring  to 
what  I  feel  is  currently  a  very  vague 
and  poorly  defined  item.” 

The  original  concept  of  a  C  library  is 
defined  in  The  C  Programming  Lan¬ 
guage.  Other  libraries  are  available  on 
Unix  systems,  but  these  vary  from  in¬ 
stallation  to  installation  and  from  ver¬ 
sion  to  version.  I  agree  with  Mr.  Even- 


Dr.  Dobb’s  Journal.  February  1985 


22 

103 


den  that  the  words  “C  library”  are 
currently  vague.  In  my  opinion,  we 
should  standardize  functions  that  are 
not  inherently  Unix-only  features.  For 
example,  we  should  include: 

(1)  “block”  input-output  (read,  write, 
and  relatives) 

(2)  stream  input-output  functions 
(e.g.,  fopen,  fwrite,  fputc) 

(3)  memory  allocation  functions  (cal- 
loc,  malloc, . . .) 

(4)  setjmp/longjmp  procedures 

(5)  alarm  (but  not  signal,  which  is 
Unix  dependent) 

(6)  exit 

(7)  scanf,  printf,  and  relatives 
Furthermore,  we  should  include  the 
Unix  math  library,  because  these  are 
fundamental  routines  (e.g.,  sin(  ), 
exp(  )).  Readers  may  wish  to  formu¬ 
late  an  exact  list  for  exclusion  and  in¬ 
clusion.  If  there  is  sufficient  response, 
we  could  propose  a  standard  for  the  C 
library  in  a  future  column. 

Mr.  Evenden  continues: 

“This  sensitivity  to  the  compiler- 
library  problem  is  caused  by  having  to 
deal  with  compilers  where  too  many 
features  that  should  have  been  relegat¬ 
ed  to  the  support  library  were  included 
as  part  of  the  compiler  [i.e.,  language 
definition].  For  example,  Fortran  gives 
us  READ,  WRITE,  and  a  few  other  in¬ 
put-output  support  operations,  which 
must  be  treated  by  the  compiler  as  spe¬ 
cial  operations  since  the  syntax  does  not 
match  normal  external  module  calls  (of 
course,  external  modules  are  involved, 
but  they  are  transparent  to  the  pro¬ 
grammer).  When  specialized  input-out- 
put  is  required,  which  can’t  be  handled 
by  these  statements,  all  sorts  of  contor¬ 
tions  are  done  by  the  programmer  to 
get  around  these  restrictions  . . .  typi¬ 
cally  these  gyrations  are  specific  to  the 
host  system.  In  addition,  many  manu¬ 
facturers  will  compound  a  bad  situation 
by  supplying  a  compiler  with  supple¬ 
mentary  functions  to  provide  access  to 
unique  features  of  their  system.  Good¬ 
bye  transportability!” 

Mr.  Evenden  is  one  hundred  percent 
correct.  Fortran’s  implicit  connection 
of  input-output  functions  to  the  lan¬ 
guage  is  a  terrible  failing.  Pascal  also 
suffers  from  this  malady,  even  though 
it  is  a  newer  structured  language. 
Evenden  continues  with  the  following 
remarks: 

“In  the  case  of  C,  the  compiler  writer 

23 

104 


doesn’t  have  to  go  out  of  his  way  to  han¬ 
dle  special  input-output  syntax,  and  the 
programmer  utilizing  the  typical  C  li¬ 
brary  can  go  to  basically  three  levels  of 
input-output  to  handle  his  problem: 

( 1 )  basic  block  ‘read’  -  ‘write’ 

(2)  buffered  (stream)  input-output 
with  the  getc(  ),  putc(  )  functions 

(3)  Fortran-like  scanf(  )  and  printf(  ) 
operations 

This  is  an  excellent  example  of  build¬ 
ing-block  code:  the  read-write  level  is 
the  lowest  level  and  is  the  only  place 
where  we  have  to  deal  with  the  host  ma¬ 
chine’s  operating  system;  each  succes¬ 
sive  level  uses  the  previous  level’s  en¬ 
tries.  The  applications  programmer  can 
thus  choose  the  starting  level  best  suited 
to  his  job  and  add  the  remaining  tiers  of 
code  to  perform  his  task.” 

Given  this  buildup  of  the  C  library, 
Mr.  Evenden  returns  to  the  reasons  for 
his  original  objections: 

“One  of  the  principal  fears  I  have  is 
that  if  we  get  to  talking  about  the  C 
compiler  and  a  standard  library  in  one 
breath,  we  will  find  some  well-meaning 
ignoramus  developing  a  C  compiler 
with  built-in  input-output  functions 
(or,  for  that  matter,  other  ‘special’  fea¬ 
tures).  In  this  situation,  our  input-out¬ 
put  is  engraved  in  stone,  and  we  will  be 
forced  back  into  the  same  situation  in¬ 
volved  in  Fortran  coding.  With  C,  we 
can  individually  or  collectively  trash 
the  input-output  part  of  the  library  in 
favor  of  some  new  software  and  still 
preserve  the  compiler  itself.  In  addi¬ 
tion,  the  old  software  is  still  good  as 
long  as  we  maintain  a  working  copy  of 
the  old  library.  We  often  cannot  do  this 
if  the  compiler  has  been  rewritten.  I 
would  much  rather  try  to  transport  a 
program  where  a  few  specialized  rou¬ 
tines  had  to  be  rewritten  than  .  .  .  deal 
with  compiler  variations.” 

To  summarize  the  principal  points 
Mr.  Evenden  makes  (and  with  which  I 
agree): 

( 1 )  Computer  languages  like  C  are  su¬ 
perior  because  they  segregate  their  li¬ 
brary  from  the  language  definition. 

(2)  Because  of  ( 1 ),  the  language  offers 
greater  maintainability,  even  through 
revisions  of  the  libraries,  because  we 
can  retain  old  libraries  more  readily 
than  whole  compiler  environments. 

(3)  Specialized  applications  can  com¬ 
pletely  ignore  the  standard  software  li¬ 
brary  without  any  loss  of  power. 


(4)  When  informing/teaching  people 
about  C,  we  must  emphasize  this 
unique  feature  to  ensure  that  it  is  re¬ 
tained  in  future  incarnations  of  the 
language.  We  hope  future  languages 
will  also  be  constructed  in  this  way. 

To  summarize  his  point,  Evenden 
states: 

“The  problem  of  C  libraries  and 
what  is  a  ‘standard’  C  function  is  not 
yet  resolved  and  needs  further  discus¬ 
sion.  Tight  binding  of  C  and  Unix  is 
unfortunate,  and  we  need  to  dissociate 
the  two  if  we  are  to  encourage  non- 
Unix  use  of  C  and  transportable  C 
software.  An  important  part  of  this  un¬ 
binding  is  specifying  a  viable  C  library 
which  can  be  installed  without  ambi¬ 
guity  and  omission  on  a  wide  range  of 
operating  systems.” 

This  is  similar  to  what  I  mentioned 
above.  I  encourage  a  reader  effort 
along  these  lines. 

Tongue  Biting 

In  his  previous  letter,  Mr.  Evenden  in¬ 
dicated  that  he  usually  “bit  his 
tongue”  instead  of  complaining  about 
C.  I  suspect  that  he  is  truly  concerned 
that  dissent  about  C  could  lead  to  its 
demise  as  an  important  language.  He 
addresses  this  point  as  follows: 

“My  ‘tongue  biting’  remark  related 
to  criticizing  the  C  language  was,  of 
course,  mostly  rhetoric.  But  most  of 
my  criticisms  are  minor  except  for  one: 
evaluation  of  all  floating-point  data  el¬ 
ements  in  double  precision.  When  two 
type  float  values  are  joined  by  a  binary 
operator,  I  fail  to  see  any  reason  why 
we  should  pay  the  costly  runtime  pre¬ 
mium  of  double-precision  evaluation! 
Of  course,  conversion  of  float  function 
arguments  to  double  is  equally  strange 
and  pointless.  I  suspect  that  floating¬ 
point  arithmetic  was  one  of  the  last 
features  added  to  the  language  and  got 
shortchanged  in  the  final  stages  of  de¬ 
velopment  of  C.  If  the  people  at  Bell 
Labs  have  any  excuse  for  this  peculiar 
handling  of  floating  point  by  C,  I'd 
sure  like  to  hear  it!  Hopefully,  some 
. .  .  readers  may  also  have  comments.” 

Mr.  Evenden  has  hit  on  an  impor¬ 
tant  point.  Not  only  is  the  conversion 
of  float  to  double  expensive,  program¬ 
ming  can  get  messy  when  dealing  with 
pointers  and  arrays.  For  example,  a 
program  that  needs  a  large  number  of 
floats  (in  an  array)  is  difficult  to  use 

Dr.  Dobb's  Journal.  February  1985 


with  a  function  that  expects  pointers  to 
double.  I  see  no  reason  for  using  arrays 
of  double-precision  numbers  for  many 
applications.  Yet,  to  simplify  program¬ 
ming  logic,  memory  must  often  be 
wasted  (by  a  factor  of  two)  for  float¬ 
ing-point  arrays.  On  systems  like  the 
8086/8087,  the  actual  cost  for  single/ 
double-precision  operations  is  the 
same,  but  conversions  and  other  effects 
are  still  cause  for  grief. 

Evenden  continues: 

“I  have  given  some  thought  to  what 
would  be  required  to  make  C’s  floating 
point  behave  in  a  more  traditional 
manner  and  have  come  to  the  conclu¬ 
sion  that  upward  compatibility  of  a 
new  compiler  might  not  be  possible  as 
far  as  floating-point  syntax  is  involved. 
Obviously,  the  ‘standard’  library  rou¬ 
tines  will  have  to  be  changed  (‘printf’ 
will  need  ‘%E’  and  ‘%e’)  .  . .  functions 
returning  floating-point  values  will 
have  to  be  in  two  precisions  (sqrt(  ) 
and  dsqrt(  ),  for  example).” 

Variable  Formats  in  printf(  ) 

Ed  like  to  change  gears  and  consider 
an  aspect  of  printff  )  format  strings. 
Consistent  with  the  previous  discus¬ 
sion,  I  remind  readers  that  this  is  a  dis¬ 
cussion  relevant  to  the  C  library  and 
not  the  compiler. 

A  typical  printf(  )  call  might  look  as 
follows: 

float  number; 

printf(“%7.3e\n”,  number); 

In  certain  cases,  we  might  wish  to  vary 
the  format  dynamically.  One  way  to 
accomplish  this  is  shown  in  the  Figure 
(page  25). 

The  format  string  in  the  sprintf(  ) 
presented  in  the  figure  is  “%%%sf.” 
The  first  two  percent  signs  place  a  sin¬ 
gle  output  percent  in  fmt_string.  The 
“%s”  causes  the  string  format  (con¬ 
taining  “7.3”)  to  go  into  fmt_string. 
Finally,  the  letter  “f”  is  interpreted  lit¬ 
erally  and  is  sent  to  fmt_string.  The 
point  of  giving  this  example  is  to  show 
how  cumbersome  the  operation  can  be. 
Now  I  want  to  pose  a  simple  solution  to 
the  problem. 

To  allow  variable  formats  as  part  of 
a  single  printf(  ),  we  need  a  way  to  in¬ 
dicate  an  indirection.  Then  printf(  ) 


float  number; 

char  fmt_string[  100];  /*  make  format  string  here*/ 

char  format]  10];  /*  format  contained  here  */ 

strcpy(format,"7.3");  /*  copy  a  specific  format  */ 

sprintf(fmt_string,"%%%sf", format);  /*  make  format  string  */ 
printf(fmt_string, number);  /*  print  number  in  format  */ 

Figure 


could  use  the  current  member  of  its  ar¬ 
gument  list  as  a  source  for  the  format. 
This  is  illustrated  as  follows: 

float  number; 

printf(“%&s\n”,“7.3f”,number); 

The  indirect  format  is  “&s,”  which 
tells  printf(  )  to  take  the  first  argument 
as  a  string  and  print  it  into  the  format 
string  before  proceeding.  Thus,  the  ul¬ 
timate  format  string  is  “%7.3f.”  An¬ 
other  possibility  would  be: 

float  number; 
float  format  =  7.3; 

printf(“%&f\n”, formal, number); 

which  demonstrates  the  range  of 
choices  allowed  by  indirect  formats. 
To  print  an  ampersand,  we  would  need 
the  following  sequence: 

printf(“%&&”); 

Why  would  these  be  useful?  Primar¬ 
ily,  they  allow  programs  to  readily 
adapt  to  data  variations.  This  could  al¬ 
low  greater  user  selectability  or,  if  ex¬ 
tended  to  scanf(  ),  greater  ability  to 
read  and  write  “foreign”  data  files. 

Conclusions  and  Comments 

In  this  column,  I  have  presented  more 
reader  feedback  and  some  brief  com¬ 
ments  about  printf(  ). 

The  “C/Unix  Programmer’s  Note¬ 
book”  was  started  in  September  1983.  I 
think  that  I  have  achieved  a  lot  in  writ¬ 
ing  the  column  during  this  last  year. 
Unfortunately,  other  responsibilities 
make  it  impossible  for  me  to  continue. 


Therefore,  I  wish  to  thank  those  readers 
who  have  responded  for  their  assis¬ 
tance,  ideas,  and  criticisms.  I  also  hope 
the  readership  will  provide  the  new  col¬ 
umnist  with  the  same  level  of  enthusias¬ 
tic  support  that  I  have  received.  Inten¬ 
tionally,  I  have  left  few  loose  ends  in  my 
discussion.  This  will  allow  the  next  col¬ 
umnist  to  develop  areas  of  discussion 
without  too  much  loss  of  continuity. 
With  a  new  columnist  comes  a  new  per¬ 
spective,  and  I  hope  that  more  Unix 
coverage  will  be  possible.  Certainly,  I 
don’t  expect  that  C  will  be  excluded  for 
the  benefit  of  Unix  but  rather  that  a 
balance  will  be  struck.  Perhaps  some¬ 
day  we  will  have  separate  “Notebooks” 
for  each  subject. 

Thank  you.  I  look  forward  to  enjoy¬ 
ing  the  next  one  hundred  issues  of  DDJ, 
and  I  hope  you  enjoy  them  too. 

An  Addendum: 

The  Future  of  C 

Concerning  future  upgrades/modifi¬ 
cations  of  C,  Evenden  writes: 

“Some  people  criticize  C  as  being  a 
‘Spartan’  language,  but  I  maintain 
that  this  Spartan  attribute  is  its  princi¬ 
pal  and  strongest  feature  ...  [C]  is  a 
real  programmer’s  language  providing 
an  excellent  tool  for  doing  everything 
from  real-time  processing,  to  writing 
other  compilers,  to  sophisticated  scien¬ 
tific  applications.  If  we  ever  make 
changes  to  C,  we  will  have  to  be  very 
careful  to  maintain  this  strong  feature 
of  the  language.” 

I  leave  this  quotation  as  my  parting 
remark. 

DD) 


Dr.  Dobb's  Journal.  February  1985 


25 

105 


Festschrift  for  Doctor  Dobb 


Contributions  by  Suzanne 
Rodriguez,  Tom  Pittman 
and  Bob  Albrecht  in 
celebration  of  the  100th 
issue  of  DDJ 


26 

inn 


Suzanne  Rodriguez  was  editor  of  DDJ 
from  January  1979  to  September 
1980.  It  was  Suzanne  who  introduced 
the  funky  colored  border  (designed  by 
Betsy  Roeth  and  Aleeca  Harrison)  and 
Suzanne  who  published  Ron  Cain's 
original  Small-C  compiler. 

Tom  Pittman  wrote  to  DDJ  in  March 
1976  to  announce  his  version  of  Tiny 
Basic  for  the  Motorola  6800  micro¬ 
processor.  Unlike  the  DDJ  version. 
Tom's  Basic  was  not  free  (“ software  is 
my  living,"  he  said).  The  price:  $5. 

Bob  Albrecht  is  the  "OB"  of  Dr.  Dobb. 
After  launching  (with  Dennis  Allison) 
DDJ,  he  went  off  to  launch  new  pro¬ 
jects.  He  is  presently  involved  in  a  cor¬ 
poration  dedicated  to  computers  and 
small  children,  fantasy  role-playing 
games,  and  tennis.  His  contribution 
here  launches  a  new  DDJ  department 
by  Bob  and  Mike  Swaine:  Realizable 
Fantasies. 


many  successful  organizations,  and 
even  a  few  publications,  got  them  going 
and  then  gone  on  to  pursue  new  ven¬ 
tures.  Allison’s  ventures  tend  to  take 
place  in  the  business  and  academic 
worlds;  Albrecht’s  almost  exclusively 
with  children  and  computer  learning. 

What  is  also  not  history  is  the  long 
list  of  people  whose  lives  have  been  in¬ 
fluenced,  at  a  formative  time,  by  one  or 
the  other — or  both  of  this  duo.  If 
you’ve  been  following  the  routine  rules 
and  regulations  of  the  world,  doing 
things  the  way  you’ve  heard  they’re 
supposed  to  be  done,  your  first  interac¬ 
tion  with  Allison  and/or  Albrecht  can 
leave  you  somewhat  dumbfounded. 
Prolonged  interaction  either  gets  you 
running  with  a  vengeance  back  toward 
the  world  of  rules,  or  it  changes  you. 

Take  me,  for  instance.  I  first  met  Al¬ 
lison  and  Albrecht  in  late  August  of 
1978,  when  they  interviewed  me  to  be- 


Who  is  Dr.  Dobb ?  Can  you  hack  on  a  Mac?  And  who 
are  the  hackers  of  tomorrow? 


The  Bob  and  Dennis  Show 
by  Suzanne  Rodriguez 

In  the  past  year  I’ve  read  more  than 
one  book  or  article  discussing  the 
founding  of  Dr.  Dobb's  Journal ,  and 
chances  are  that  you  have,  too.  It  all 
sounds  fairly  straightforward  on  the 
surface:  Bob  Albrecht  asked  Dennis 
Allison  to  write  a  2K  BASIC  that  could 
be  published  in  the  newspaper  Al¬ 
brecht  had  founded.  People’s  Comput¬ 
er  Company.  Allison  complied.  The 
rest  is  history. 

What’s  not  history — at  least  not 
yet  is  how  incredibly  prolific  and  cre¬ 
ative  Albrecht  and  Allison  are.  Togeth¬ 
er  or  individually,  they’ve  founded 


come  the  third  editor  of  Dr.  Dobb’s 
Journal.  (Jim  Warren,  DDJ' s  first  edi¬ 
tor,  had  gone  on  to  found  both  the 
Computer  Faire  and  the  newspaper 
that  would,  in  time,  become 
InfoWorld.  Warren’s  successor,  Tom 
Williams,  was  soon  leaving  DDJ  to  pur¬ 
sue  other  endeavors.)  At  the  time  I  was 
young  and  inexperienced  enough  to  be 
impressed  with  my  own  credentials, 
which  were  really  rather  slender:  I  had 
published  a  number  of  magazine  arti¬ 
cles;  I  had  been  a  technical  writer  at 
Zilog,  writing  their  first  piece  of  docu¬ 
mentation;  and  I  was  working  on  my 
Master’s  in  Journalism  at  Stanford. 
The  idea  of  being  editor  of  DDJ  was 
very  exciting:  I  could  combine  what  I'd 

Dr.  Dobb's  Journal,  February  1985 


be  learning  in  school  with  the  technical 
aspects  of  the  magazine. 

At  least  that’s  what  1  thought.  I 
imagined  that  I’d  be  hired  for  a 
straightforward,  rational,  logical  job, 
and  that  I'd  be  working  closely  with 
rational  and  logical  people,  applying 
well  thought-out  publishing  principles 
in  a  logical  and  rational  manner.  Boy, 
did  I  have  a  thing  or  two  to  learn! 

First  there  was  the  interview  with 
Allison.  He  greeted  me  in  his  antique- 
and-book-laden  home  and  proceeded 
to  question  me  very  thoroughly  about 
my  recent  trip  to  Greece.  We  sipped 
retsina  and  talked  for  an  hour  about 
Ios  and  Samos,  ouzo  and  feta,  sleeping 
on  beaches  and  hitching  rides  on  don¬ 
keys.  Finally  he  turned  to  me  and  said: 
“Well,  Suzie,  as  far  as  I’m  concerned, 
you’ve  got  the  job.” 

I  was  very  puzzled.  We  hadn’t  men¬ 
tioned  the  word  computer ,  and  the  top¬ 
ic  of  Dr.  Dobb’s  Journal  had  not  come 
up  at  all.  “But,”  I  stammered,  “don’t 
you  want  to  hear  about  my  back¬ 
ground?  I  mean.  I’ve  worked  with  .  . .” 

He  dismissed  my  words  with  an  im¬ 
patient  wave  of  his  hand,  a  gesture 
which  I  would  come  to  learn  was  char¬ 
acteristic.  “No,  no!  I  think  DDJ  will  be 
in  good  hands.  I  make  these  decisions 
based  on  character,  and  it  doesn’t  seem 
that  too  much  will  get  by  you.  You 
might  find  it  difficult  working  with  Al¬ 
brecht,  though  he  can  be  trying 
but  I  think  you  can  handle  him.” 

I  went  both  dazedly  and  apprehen¬ 
sively  off  to  my  interview  with  Al¬ 
brecht.  He  had  asked  me  to  join  him  in 
a  “piano  bar  safari.”  I  had  no  idea 
what  that  was,  but  I  was  soon  to  find 
out.  A  true  piano  bar  safari  consists  of 
going  from  one  piano  bar  to  another, 
drinking  beer  and  singing  in  each.  At 
our  initial  meeting,  however,  we  only 
hit  one  piano  bar.  I  managed  to  find 
him  in  the  dark  interior,  and  we  spent 
the  next  few  hours  (when  he  wasn’t 
singing)  talking  about  fantasy  role 
playing,  Greek  dancing  and  tennis. 
Once  more,  computers  and  Dr.  Dobb’s 
were  never  discussed.  When  it  was 
time  for  me  to  go,  Albrecht  said,  al¬ 
most  as  if  in  afterthought:  “Well,  if 
Dennis  says  you’re  ok,  that’s  good 
enough  for  me.” 

This  time  I  didn’t  even  bother  to  start 
flaunting  my  qualifications.  “Fine,*’  I 
said,  “I’m  looking  forward  to  working 


with  such  a  talented  bunch.” 

“Oh,  you’ll  learn  a  lot,  there’s  no 
doubt  about  that,”  said  Albrecht. 
“You  might  have  trouble  with  Allison, 
though  -he  can  be  difficult — but  I 
think  you  can  handle  him!” 

This  was  all  the  beginning  of  some¬ 
thing  great — my  two  years  at  DDJ.  Ev¬ 
ery  day  I  would  sit  in  my  classes  and 
learn  about  the  way  it's  supposed  to  be 
in  the  publishing  world.  And  then  I 
would  dash  to  People’s  Computer 
Company  and  experience  the  way  it 
really  is.  Or  at  least  the  way  it  was  in 
that  particular  place  and  at  that  par¬ 
ticular  time. 

While  the  day-to-day  decision  mak¬ 
ing  for  DDJ  was  in  my  hands,  larger 


decisions  for  instance,  should  we  go  to 
glossy  paper-  were  decided  by  the 
Board.  This  occasioned  marvelous  and 
stormy  battles,  the  best  of  which  were 
with  Bob  Albrecht.  In  the  end  he  would 
always  give  in  and  tell  me  he’d  wanted 
it  all  along.  “I  just  like  a  good  fight,”  he 
would  say.  This  would  usually  be  a  day 
in  which  I  had  taken  a  mid-term  in 
something  like  “Human  Resources  and 
Intra-Company  Dynamics”.  If  I’d  used 
my  real-life  experiences  with  Albrecht 
as  illustrative  examples,  I  would  have 
flunked  the  course. 

Dennis  Allison,  on  the  other  hand, 
never  fought;  he  just  told  you  exactly 
what  he  thought.  “I  think  that’s  a  fan¬ 
tastic  idea”  made  you  feel  just  great.  “I 


Dr.  Dobb's  Journal.  February  1985 


27 

107 


think  that’s  the  dumbest  idea  I  ever 
heard”  plunged  you  into  the  depths.  1 
kept  telling  Dennis  that  what  I  had 
learned  in  my  seminar  on  how  to  moti¬ 
vate  others  taught  me  that  he  was  going 
about  things  all  wrong.  In  response,  he 
said  that  was  the  dumbest  thing  he’d 
ever  heard. 

There  were  the  even  stormier  meet¬ 
ings  of  the  Board  of  Trustees  where  Al¬ 
lison  would  bait  Albrecht,  or  Albrecht 
would  bait  Allison,  and  one  or  the  other 
would  storm  out,  only  to  return  5  min¬ 
utes  later  with  a  bottle  of  champagne. 
We  would  all  toast  to  friendship,  and  1 
would  silently  ponder  the  term  paper  I 
had  just  finished  for  a  course  in  the  law 
school:  “Obligations  and  Duties  of 
Trustees  in  a  California  Non-Profit 
Corporation.”  What  would  the  profes¬ 
sor  think  of  the  scene  I’d  just  wit¬ 
nessed? 

I  devoted  most  of  my  attention  to  my 
studies  and  to  my  duties  at  Dr. 
Dobb’s — there  wasn’t  time  for  much 
else  but  I  always  managed  to  keep 
aware  of  what  Allison  and  Albrecht 
were  up  to.  (All  of  you  who  know  them 
are  probably  saying,  “How  could  she 
help  it?”)  They  were  always  trying  to 
tell  me  that  I’d  never  learn  from  a  book 
what  1  needed  to  know  about  life  and 
my  work,  and  about  getting  along  with 
people.  For  two  years  I  argued  with 
them,  together  and  individually,  telling 
them  I  just  didn’t  understand  the  hap¬ 
hazard,  unplanned,  chaotic  way  they 
went  about  doing  things.  They  kept 
telling  me  that  I  didn’t  know  it  yet,  but 
1  was  just  as  crazy  as  they  were. 

I  suppose  they  were  right.  In  the  end 
when  I’d  finished  my  schoolwork  and 
learned  everything  I  could  at  Dr. 
Dobb'  Journal ,  1  turned  down  each  of 
the  highly-paid  job  offers  that  came 
my  way.  I  moved  to  Sausalito  to  starve 
and  write  a  novel.  There  are  a  couple  of 
characters  in  it  who  remind  me  of  Alli¬ 
son  and  Albrecht;  they’re  quite  vivid, 
extremely  bohemian  and  very  strong. 
With  the  exception  of  the  female  pro¬ 
tagonist,  they  rather  steal  the  show. 

Mac  the  Hack 
by  Tom  Pittman 

I  recently  attended  a  “Hackers  Con¬ 
ference,”  where  one  of  the  subjects  of 
discussion  was  the  definition  of  hack¬ 
er.  There  was  no  consensus.  Why? 

28 


When  Apple  introduced  the  Macin¬ 
tosh,  I  got  excited  about  it  just  like  ev¬ 
erybody  else.  This  was  the  computer  I 
really  wanted!  At  the  Hackers  Confer¬ 
ence,  over  half  the  people  were  into  the 
Mac;  none  was  willing  to  admit  to 
spending  his  time  on  IBM.  Why? 

About  the  time  Jim  Warren  sold  the 
West  Coast  Computer  Faire ,  most  of 
us  had  noticed  that  the  fun  was  gone. 
Where  were  “the  good  old  days?”  Ste¬ 
ven  Levy’s  book  Hackers  chronicles 
“the  last  hacker.”  The  Homebrew 
Computer  Club  just  isn’t  what  it  used 
to  be.  Why? 

When  I  started  to  write  this,  I 
thought  I  had  answers  to  these  ques¬ 
tions,  a  sort  of  “unified  field  theory”  of 
computerism.  I’m  not  so  sure  any  more, 
but  at  least  I  can  share  some  ideas. 

A  hacker  is  an  artist,  and  computer 
artistry  is  not  distinguished  from  other 
art  forms  except  in  the  medium 
chosen. 

Just  as  paintings  range  from  great 
(e.g.  Rembrandt,  Dali)  to  professional 
(painted  houses  and  cars)  to  spray-can 
grafitti,  so  computer  artisty  ranges 
from  the  sublime  (e.g.  MacPaint, 
TeX)  to  the  professional  (1-2-3  and 
MSDOS)  to  the  so-called  414s.  Unlike 
painting,  sculpture,  music,  and  the 
like,  few  people  can  really  appreciate 
the  artistry  in  a  computer  product. 
Perhaps  that  will  change. 

It  is  the  nature  of  great  art  that  it 
compresses  a  great  deal  of  design,  in¬ 
tellectual  sweat  and  individual  person¬ 
ality  into  the  created  object.  There  are 
no  great  paintings  painted  by  a  com¬ 
mittee  of  artists.  Curiously,  it  seems  to 
matter  little  what  tools  the  artist  had, 
or  where  his  starting  point  was,  provid¬ 
ed  that  his  accomplishment  from  that 
point  exceeds  what  the  rest  of  us  could 
have  done.  Ansel  Adams  had  color  and 
motion  available,  but  he  chose  to  limit 
himself  to  black  and  white  stills— and 
what  art  he  created!  A  virtuoso  on  a 
violin  produces  art;  the  same  sound 
from  a  Moog  is  ho-hum. 

There  is  another  facet  to  computing 
that  deserves  exploration:  the  urge  to 
produce  results  that  are  impossible. 
There  is  a  feeling  of  accomplishment 
in  climbing  the  north  face  of  Halfdome 
for  the  first  time,  in  breaking  the  (“im¬ 
possible”)  four-minute  mile  or  the 
sound  barrier,  in  producing  a  tune 
from  a  computer  with  only  256  bytes 


of  RAM.  This  same  feeling  can  accom¬ 
pany  feats  that  are  impossible  only  be¬ 
cause  some  authority  said  “you  can’t” 
(or  must  not).  Thus,  the  urge  to  create 
finds  its  expression  in  the  hacker  who 
cracks  the  protection  scheme  in  com¬ 
mercial  software,  or  breaks  into  a  “se¬ 
cure”  computer  installation  with  his 
modem. 

Macintosh  is  a  computer  that  says, 
“you  can’t.”  It  is  promoted  as  a  com¬ 
puter  that  is  easy  to  use,  but  the  box  is 
sealed  with  screws  you  can’t  reach,  the 
software  (in  ROM)  can’t  be  changed, 
and  the  development  tools  can’t  be  ob¬ 
tained.  Although  I  don't  think  that  is 
why  computer  people  buy  it,  still  after 
you  get  over  the  realization  that  it  does 
not  do  all  they  seem  to  claim  for  it,  you 
are  left  with  a  lot  of  “impossible”  things 
to  challenge  the  aspiring  intellect,  a  lot 
of  opportunity  to  show  off  your  freedom 
from  such  oppressive  forces. 

The  computer  hacker,  like  any  real 
human  being,  wants  recognition.  I 
write  programs  for  a  living.  I  don’t 
know  if  that  qualifies  me  as  a  hacker, 
but  it  is  more  than  just  a  job:  it’s  fun. 
But  I  write  my  best  stuff  for  other  peo¬ 
ple,  or  at  least  to  help  me  do  things  for 
others.  I  never  seem  to  get  around  to 
doing  things  just  for  me. 

To  get  recognition  requires  that  the 
program  (or  hardware,  or  whatever) 
be  noticeably  outstanding.  It  must  be 
head-and-shoulders  above  the  compe¬ 
tition,  so  that  it  is  clear  how  good  it  is. 
If  you  start  with  limited  tools  you  can 
do  an  outstanding  job,  but  who  can 
tell?  Start  with  the  best  tools  (would 
you  believe  Mac?  Next  year,  anyway), 
and  then  your  Herculean  effort  will  be 
seen  for  what  it  is.  The  result:  recogni¬ 
tion,  praise,  glory. 

Computing  is  a  drug.  Some  people 
take  aspirin  to  cure  a  headache,  so  they 
can  get  on  with  what  they  are  trying  to 
do.  Others  pop  pills  just  for  the  imme¬ 
diate  effects.  Drugs  have  a  way  of 
growing  on  you.  Once  you  have  free- 
based  Unix,  it’s  pretty  hard  to  go  back 
to  snorting  CP/M.  Mac  gives  a  high 
something  like  Unix — it  would  be  way 
up  in  the  stratosphere  if  it  had  better 
software  tools,  like  Unix  already  has — 
but  Mac  costs  a  lot  less  than  Unix:  it’s 
affordable. 

Computing  is  a  religion.  Centuries 
ago,  in  the  1940’s,  the  founders  of  this 
religion  (Turing,  Von  Neumann,  etc.) 

Dr.  Dobb's  Journal.  February  1985 


i  no 


brought  salvation  to  the  human  race, 
entrusting  the  Holy  Truth  to  a  small 
band  of  devoted  disciples.  As  these 
saints  spread  the  Gospel,  great 
churches  grew  up  (IBM,  Univac)  with 
an  ecclesiastical  hierarchy  (we  call 
them  “priests”  even  today).  A  few  ana¬ 
baptists  (the  hackers  at  MIT)  nipped  at 
their  heels,  but  the  Reformation  hap¬ 
pened  in  Silicon  Valley,  led  by  Martin 
“8080”  Luther;  the  doctrine  of  univer¬ 
sal  priesthood  (all  believers  are  priests) 
was  once  again  gospel. 

Not  for  long.  Slowly  the  encrusta¬ 
tions  of  institutional  religion  again 
took  over.  Scientific  Enlightenment 
(bean  counters  in  three-piece  suits)  re¬ 
placed  the  faith  of  the  Reformation. 
But  there  is  hope!  John  Wesley  and 
Billy  Graham  sparked  revival  in  Chris¬ 
tendom;  Macintosh  is  doing  the  same 
for  Computerdom.  First  Jimmy,  then 
Ronnie;  now  Woz  for  President! 

The  urge  to  create,  the  delirious  feel¬ 
ing  of  power,  the  longing  for  praise: 
these  are  universal  human  drives,  dat¬ 
ing  back  to  the  first  rejection  of  author¬ 
ity  in  the  Garden  of  Eden.  The  first  va¬ 
porware  announcement  was,  “You 
shall  not  surely  die,  but  you  shall  be  like 
gods.”  Macintosh,  is  thy  name 
Serpent? 

Tiny  Hackers: 
a  Realizable  Fantasy 
by  Bob  Albrecht 
(with  Mike  Swaine) 

Ob  returns. 

I  first  used  the  expression  “realizable 
fantasies”  in  the  PCC  newsletter  back 
before  there  was  a  DDJ.  Starting  next 
issue  I’ll  be  back  in  the  pages  of  this 
magazine  doing  a  column  called  “Real¬ 
izable  Fantasies”  with  Mike  Swaine  be¬ 
cause  we  both  think  that  people  doing 
things  with  computers  today  need  a 
kick  in  the  imagination.  The  idea  of  the 
column  will  be  to  open  discussion  each 
month  on  some  project  that  any  fool 
can  see  is  a  good  idea,  but  that,  for 
whatever  reason,  nobody  is  carrying 
out.  A  public-domain  Unix,  an  open-ar¬ 
chitecture  Macclone,  things  like  that. 

This  Festschrift  paper  can  be  con¬ 
sidered  the  pilot  for  the  series,  because 
I  have  such  a  realizable  fantasy.  It  has 
a  name.  I  call  it  Tiny  Hackers,  and  like 
any  good  fantasy,  it  wears  a  different 

Dr.  Dobb's  Journal,  February  1985 


face  every  time  I  look  at  it.  Its  origin 
goes  back  to  before  DDJ,  back  to  the 
early  sixties  when  I  started  teaching 
kids  to  play  with  computers.  It  recently 
got  rekindled  by  listening  to  Brian 
Harvey,  at  the  Hackers’  Conference 
you’re  hearing  so  much  about  in  this 
issue,  talking  about  passing  on  knowl¬ 
edge  to  a  new  generation  of  hackers. 

I  have  been  working  with  kids  and 
computers  since  1962.  I  wasn’t  really 
having  a  good  time  in  life  until  I  began 
teaching  kids  about  computers.  I  now 
work  with  tiny  kids  three  to  six  years 
old,  helping  them  to  play  with  comput¬ 
ers,  although  most  of  them  are  not, 
strictly  speaking,  hackers. 

My  corporation,  Dragonquest , 
works  with  the  East  Menlo  Park  Boys’ 
Club.  Boys’  Clubs  are  an  ideal  place 
for  hackers  who  want  to  bring  wonder¬ 
ment  to  kids.  They  are  open  to  girls  as 
well,  and  there  are  also  Girls’  Clubs.  If 
kids  are  going  to  do  things  with  com¬ 
puters,  they’re  going  to  do  so  at  home 
or  at  places  like  the  clubs,  not  in  the 
schools,  which  are  the  last  place  you’re 
going  to  see  any  hacking.  The  stuff 
they’re  doing  in  schools  is  dreadful. 

Around  1981  we  started  Computer 
Kids,  of  which  Tim  Finger  is  the  direc¬ 
tor.  With  Computer  Kids,  we’re  bring¬ 
ing  such  things  as  programs  that  teach 
touch  typing  into  the  clubs.  Once 
somebody  gets  comfortable  with  using 
the  machine  to  store  and  print  words, 
it’s  only  a  short  step  to  seeing  the  possi¬ 
bility  of  putting  your  thoughts  into 
words  and  printing  many  copies  and 
distributing  them;  and  that’s  a  news¬ 
letter.  We’d  like  to  explore  the  possi¬ 
bility  of  kids  in  the  Black  community 
getting  into  self-publishing  and  using 
the  newsletter  to  find  out  what’s  going 
on  around  them. 

I  want  to  bring  tools  to  these  kids, 
powerful  tools.  Future  tools.  Impor¬ 
tant  tools.  Once  they  have  those  tools, 
some  of  them  are  going  to  become  the 
next  generation  of  hackers;  they  will  be 
the  kids  who  redefine  the  word  hacker. 

What  kind  of  tools  should  they 
have?  Is  there  a  Tiny  Hackers’  ma¬ 
chine?  Cheap,  powerful,  designed  so 
small  children  can  use  it  effectively 
and  artfully? 

If  you  dig  through  back  issues  of 
Rainbow,  a  magazine  for  users  of  the 
Radio  Shack  Color  Computer,  you  can 
find  a  wealth  of  tips  for  turning  the 


Color  Computer  into  a  system  with 
surprising  programming  power,  like  a 
cheap  128K  upgrade.  I  priced  out  a 
system:  $750  will  give  you  a  Color 
Computer  with  O/S-9,  a  Unix-like  op¬ 
erating  system.  There  is  a  Basic  com¬ 
piler  available,  a  Pascal  compiler  and  a 
C  compiler.  That’s  one  possibility  for  a 
Tiny  Hackers’  machine.  [Radio  Shack 
may  in  fact  bring  out  a  Color  Comput¬ 
er  in  1985  with  128K  RAM,  an  RGB 
monitor  and  O/S-9  in  ROM.  — Ed.  ] 

But  there  are  other  possibilities. 
Now  is  the  time  for  somebody  to  have 
the  courage  to  produce  a  home  com¬ 
puter  with  Logo  built  in.  With  sprites. 
Kids  don’t  give  a  damn  about  turtle 
graphics,  but  they  love  sprites.  And  a 
touch  pad  and  a  keyboard  with  keys  in 
alphabetical  order  so  the  kids  can  find 
the  letters.  The  Qwerty  layout  is  about 
as  easy  to  use  as  if  the  letters  were  dis¬ 
tributed  randomly.  Why  put  barriers 
like  that  in  kids’  way? 

It  would  be  great  to  see  kids  in  the 
clubs  building  Lee  Felsenstein’s  pro¬ 
posed  Hackers’  Macs  from  kits. 

So  that’s  the  realizable  fantasy: 
Tiny  Hackers.  It’s  a  confluence  of 
ideas  that  could  split  off  into  many 
streams:  projects,  columns,  newslet¬ 
ters.  One  of  the  forgotten  motivations 
behind  the  original  Tiny  BASIC  was 
that  it  be  a  language  for  tiny  people. 
Maybe  we  still  need  a  tiny  language.  If 
you  are  interested  in  the  next  genera¬ 
tion  of  hackers,  if  you  think  that  we 
need  home  machines  with  sprite  chips 
or  that  computers  for  kids  should  use 
random  number  generators  that  return 
integer  values,  consider  DDJ  a  forum 
for  bringing  the  issues  forward. 

I  am  starting  a  small,  personal  news¬ 
letter  called  Dragonsmoke,  and  I  sus¬ 
pect  that  some  of  these  issues  will  also 
come  up  there.  The  focus  will  be  on 
whatever  Dragonquest  is  up  to,  includ¬ 
ing  the  work  at  the  Boys’  Clubs.  If 
you’d  like  to  be  on  the  mailing  list, 
send  an  SASE  to  PO  Box  310,  Menlo 
Park  CA  94026  (that’s  the  original 
1976  DDJ  PO  box). 

But  the  thing  I'd  most  like  to  see  is 
for  some  of  DDJ' s  readers  to  do  some¬ 
thing  good  for  a  Boys’  Club  or  Girls’ 
Club.  Dig  out  those  old  Sols  and  Im- 
sais.  Better  yet,  give  time.  DDJ 


30 

inq 


FIRE  IN  THE 


There  was  a  strong  feeling  [at  the  Homebrew  Club/  that  we 
were  subversives.  We  were  subverting  the  way  the  giant  cor¬ 
porations  had  run  things.  We  were  upsetting  the  establish¬ 
ment,  forcing  our  mores  into  the  industry.  1  was  amazed 
that  we  could  continue  to  meet  without  people  arriving  with 
bayonets  to  arrest  the  lot  of  us. 

Keith  Britton 


by  Paul  Freiberger  and 
Michael  Swaine 


The  Homebrew  Computer  Club 

Felsenstein  and  Marsh  weren’t  there  during  Homebrew’s 
gestation.  Early  in  1975,  a  number  of  San  Francisco  Bay 
Area  counterculture  information  exchanges  existed  for  peo¬ 
ple  interested  in  computers.  Community  Memory  was  one. 
There  was  also  PCC  [People’s  Computer  Company]  and  the 
PCC  spin-off,  the  Community  Computer  Center.  In  addi¬ 
tion,  peace  activist  Fred  Moore  was  running  a  non-comput- 
erized  information  network  out  of  the  Whole  Earth  Truck 
Store  in  Menlo  Park,  matching  people  with  common  inter¬ 
ests  about  anything,  not  just  computers.  Moore  became  in¬ 
terested  in  computers  when  he  realized  he  needed  a  machine 
and  a  base  of  operations,  and  he  talked  to  Bob  Albrecht  at 
PCC  about  both.  Soon,  Moore  was  teaching  children  about 
computers  and  learning  about  them  himself. 

At  the  same  time,  Albrecht  had  been  looking  for  someone 
to  write  certain  assembly  language  programs  and  found  Gor¬ 
don  French,  a  mechanical  engineer  and  computer  hobbyist, 
who  then  supported  himself  building  slot  car  motors. 

When  the  Affair  story  appeared  in  Popular  Electronics , 
the  need  for  a  more  direct  information  exchange  became 
clear.  The  PCC  people  took  the  Affair  seriously  from  the 
beginning.  Keith  Britton,  a  demolition  consultant  and  PCC’s 
treasurer,  thought  its  arrival  foretold  the  eventual  demise  of 
the  computer  priesthood.  “All  of  us  were  champing  at  the  bit 


Strange  things  happen 
when  hackers  get  organized. 


Michael  Swaine,  Editor-in-Chief  of  DDJ 
Paul  Freiberger,  West  Coast  Editor  of  Popular  Computing 
From  Fire  in  the  Valley:  The  Making  of  the  Personal  Com¬ 
puter.  Copyright  2/3  1984  by  McGraw-Hill,  Inc.  Used  with 
the  permission  of  Osborne/McGraw-Hill. 


Wm 

Si 

Ipf , 

*  i. 

k  ir ' 

mug?* 

■f  V 

VALLEY 


Photo  1 

Gordon  French,  one  of  the  founding  members,  addressing  a  meeting  of  the  Homebrew  Computer  Club  in  1979 


to  get  an  Altair,”  French  recalls.  So  Fred  Moore  pulled  out 
his  list  of  the  computer  curious,  the  revolutionaries,  the 
techies,  and  the  educational  innovators,  and  sent  out  the  call. 
“Are  you  building  your  own  computer?  Terminal?  TV 
Typewriter?  I/O  device?  Or  some  other  digital  black  box? 
Or  are  you  buying  time  on  a  time-sharing  service?”  Moore’s 
flyer  asked.  "If  so,  you  might  like  to  come  to  a  gathering  of 
people  with  like-minded  interests.  Exchange  information, 
swap  ideas,  talk  shop,  help  work  on  a  project,  whatever.” 
The  announcement  tentatively  called  the  group  the  Amateur 
Computer  Users  Group  or  Homebrew  Computer  Club,  and 
it  met  on  March  5,  1975,  in  Gordon  French’s  garage. 

Felsenstein  read  about  the  upcoming  meeting  and  intend¬ 
ed  not  to  miss  it.  He  collared  Bob  Marsh  and  they  drove  in 
Felsenstein’s  pickup  truck  through  the  rain  across  the  Bay 
Bridge  to  the  peninsula  that  stretches  from  San  Francisco  in 
the  north  to  Silicon  Valley  in  the  south.  Gordon  French  lived 
in  suburban  Menlo  Park,  a  town  jogging  distance  from  Stan¬ 
ford  and  right  on  the  edge  of  Silicon  Valley. 

At  the  first  meeting,  Steve  Dompier  reported  on  his  visit  to 
Albuquerque.  M1TS  had  shipped  1500  Altairs  and  expected 

Dr.  Dobb’s  Journal,  February  1985 


to  ship  1 100  more  that  month.  The  company  was  staggering 
under  the  weight  of  the  orders  and  couldn’t  begin  to  fill 
them,  Dompier  said.  Bob  Albrecht  displayed  the  Altair  that 
PCC  had  received  that  week  PCC  was  just  behind  Harry 
Garland  and  Roger  Melen,  over  at  Stanford,  on  MITS’s 
list  and  passed  out  the  latest  issue  of  PCC. 

Dompier,  like  Marsh  and  Felsenstein,  had  driven  down 
from  Berkeley,  but  most  of  the  32  or  so  attendees  were  from 
the  San  Francisco  Peninsula.  Albrecht  and  Gordon  French, 
who  chaired  the  meeting,  and  Fred  Moore,  who  took  notes 
for  a  newsletter,  and  Bob  Reiling,  who  soon  took  over  that 
newsletter,  all  lived  in  Menlo  Park.  Many  other  people  had 
come  from  farther  south  -from  deeper  into  Silicon  Valley: 
Mountain  View,  Sunnyvale,  Cupertino,  San  Jose — people 
like  Allen  Baum,  Steve  Wozniak,  and  Tom  Pittman,  who 
described  himself  as  a  microcomputer  consultant,  perhaps 
the  first  in  the  world. 

As  the  meeting  concluded,  one  Homebrewer  held  up  an 
Intel  8008  chip,  and  asked  who  could  use  it,  and  gave  it 
away.  Many  people  present  that  night  sensed  the  opportuni¬ 
ty  in  this  Homebrew  spirit  and  in  Dompier’s  words.  One  of 

33 

111 


them  was  Bob  Marsh.  Marsh  went  immediately  to  see  Gary 
Ingram  about  forming  a  business  enterprise.  I’ve  got  a  ga¬ 
rage,  he  said.  It  seemed  enough.  They  decided  to  call  them¬ 
selves  Processor  Technology,  or  Proc  Tech.  Marsh  designed 
three  plug-in  circuit  boards  for  the  Altair:  two  I/O  boards 
and  a  memory  board.  They  looked  good,  he  thought.  He 
devised  a  flyer  announcing  Proc  Tech’s  products,  made  hun¬ 
dreds  of  copies  of  it  on  a  campus  photocopying  machine,  and 
took  300  of  them  to  distribute  at  the  third  meeting. 

By  this  time  the  club  was  flourishing.  Fred  Moore  was 
exchanging  newsletters  with  Hal  Singer,  who  put  out  the 
Micro-8  Newsletter  in  Southern  California  and  had  formed 
a  Micro-8  club  shortly  after  Homebrew  started.  Other  publi¬ 
cations  were  passed  around.  PCC  and  Hal  Chamberlain’s 
The  Computer  Hobbyist  attracted  special  interest.  A  Den¬ 
ver  organization  identifying  itself  as  a  provider  of  support  for 
Micro-8  and  TV  Typewriter  hobbyists  and  calling  itself  The 
Digital  Group  offered  subscriptions  to  its  newsletter.  The 
movement  was  getting  hard  to  keep  up  with.  Intel,  with  its 


The  next  week  the  first  order  arrived.  Garland  and  Melen 
were  seeking  Processor  Technology’s  cheapest  advertised 
product.  Their  request  was  written  on  the  stationery  of  their 
new  company,  Cromemco.  They  sent  no  check,  just  a  pur¬ 
chase  order  requesting  30  days  net  credit,  hardly  what 
Marsh  had  expected.  He  had  made  Proc  Tech  look  like  a  real 
company,  all  right. 

After  the  Cromemco  order,  many  others  followed,  most 
enclosing  cash.  Ingram  fronted  $360  for  an  advertisement  in 
Byte.  With  the  cash  streaming  in,  Marsh  and  Ingram  could 
afford  to  advertise  in  Popular  Electronics,  and  they  did, 
spending  $1000  for  a  one-sixth-page  ad.  They  incorporated, 
and  Ingram  was  made  president.  As  corporate  headquarters 
and  factory,  they  had  half  of  an  1 100-square-foot  garage,  no 
products,  no  schematics  for  proposed  products,  no  supplies, 
no  employees,  and  thousands  of  dollars  in  cash  orders.  It  was 
beginning  to  appear  that  they  had  some  work  ahead  of  them. 

Meanwhile,  Lee  Felsenstein  was  getting  more  involved 
with  Homebrew.  He  took  over  from  Gordon  French  as  the 


Photo  2 

Chuck  Grant  and 
Mark  Greenberg, 
founders  of  Ken¬ 
tucky  Fried  Com¬ 
puters  and  North 
Star  Computers, 
with  a  NorthStar 
Horizon 


4004,  8008,  and  8080  chips,  and  at  least  15  other  semicon¬ 
ductor  manufacturers  had  introduced  microprocessors  into 
the  market,  and  the  newly  formed  club  tried  to  keep  its  mem¬ 
bers  informed  about  them  all. 

The  third  Homebrew  meeting  drew  several  hundred  peo¬ 
ple,  too  many  for  Gordon  French’s  garage.  It  was  moved  to 
the  Coleman  mansion,  a  Victorian  building  serving  as  a 
schoolhouse.  There  Marsh  gave  a  brief  talk,  explaining  that 
he  was  selling  memory  and  I/O  boards  for  the  Altair.  He 
hoped  to  present  Proc  Tech  as  a  serious  company,  not  just 
the  fancy  of  an  unemployed  electronics  engineer  with  access 
to  a  copying  machine.  He  offered  a  20%  discount  for  cash 
prepayment.  To  his  disappointment,  no  one  came  to  talk  to 
him  at  or  after  the  meeting. 


master  of  ceremonies — he  refused  to  think  of  himself  as  a 
chairman.  The  meetings  were  now  held  in  the  auditorium  at 
the  Stanford  Linear  Accelerator  Center.  Over  the  years,  Fel¬ 
senstein  became  intimately  associated  with  the  club  and  fos¬ 
tered  its  anarchic  structure.  The  group  had  no  official  mem¬ 
bership,  no  dues,  and  was  open  to  everyone.  Its  newsletter, 
offered  free  after  a  nudge  from  Felsenstein,  became  a  point¬ 
er  to  information  sources  and  a  link  between  hobbyists.  As 
group  toastmaster,  Felsenstein  performed  with  a  curious 
kind  of  populist  showmanship.  As  one  attendee,  Chris  Es¬ 
pinosa,  said,  “People  call  him  the  Johnny  Carson  of  Home¬ 
brew,  but  he’s  more  than  that.  He  kept  order,  he  kept  things 
moving,  he  made  it  fun  to  go  to  the  meetings.  There  were  7  50 
people  in  that  room  at  one  time,  and  he  worked  it  like  a  rock 


34 


Dr.  Dobb's  Journal,  February  1985 


concert.  It’s  hard  to  describe,  but  to  see  him  work  a  crowd 
like  a  Baptist  preacher.  ...  He  was  great.” 

The  meetings  didn’t  follow  Robert’s  Rules  of  Order  with 
Felsenstein  running  them:  he  gave  them  their  own  special 
structure.  First  came  a  mapping  session,  during  which  Fel¬ 
senstein  recognized  people  who  briefly  proffered  their  inter¬ 
ests,  questions,  rumors,  or  plans.  Felsenstein  sometimes  had 
quick  answers  to  their  questions  or  witty  comments  on  their 
plans.  A  formal  presentation  followed,  generally  of  some¬ 
one’s  latest  invention.  Finally,  there  was  the  Random  Access 
session,  in  which  everyone  scrambled  around  the  auditorium 
to  meet  those  they  felt  had  interests  in  common  with  them.  It 
worked  brilliantly,  and  numerous  companies  were  formed.  A 
remarkable  amount  of  information  was  exchanged  at  those 
meetings,  and  much  information  had  to  be  exchanged;  they 
were  all  in  unfamiliar  territory. 

About  this  time,  a  San  Francisco  branch  of  Homebrew 
started.  It  held  its  first  meeting  at  the  Lawrence  Hall  of 
Science  in  Berkeley.  Although  it  was  called  the  San  Francis¬ 
co  branch,  Berkeley  was  a  logical  place  for  it  to  meet.  Uni¬ 
versities  were  becoming  hotbeds  of  self-taught  microcom¬ 
puter  expertise.  Professors  with  grant  money  now  found  it 
cost-effective  to  buy  minicomputers  rather  than  buy  time  on 
the  university  mainframe  computer,  which  was  invariably 
out  of  date  and  overworked.  DEC  was  selling  PDP-8  and 
PDP-1 1  minicomputers  to  professors  as  fast  as  it  could  build 
them.  They  were  particularly  popular  in  psychology  labs, 
where  they  were  used  for  experimenting  on  human  subjects, 
automating  rat  and  pigeon  labs,  and  analyzing  data.  The 
invasion  of  the  psych  lab  by  minicomputers  created  a  new 
kind  of  expert:  one  who  might  know  something  about  psy¬ 
chological  research,  but  who  was  more  clearly  a  hacker  and 
a  computer  nut — someone  to  figure  out  how  to  run  the  com¬ 
puter  and  make  it  do  what  professors,  who  were  generally 
ignorant  about  the  machine,  wanted. 

Howard  Fulmer  was  such  a  person.  Fulmer  worked  in  the 
Psychology  Department  at  UC  Berkeley  running  PDP-1  Is, 
selecting  minicomputers  for  professors  to  buy,  building  in¬ 
terfaces,  and  programming  experiments.  In  early  1975,  one 
of  Fulmer’s  professors  bought  an  Affair,  and  Fulmer  learned 
to  use  it.  Soon  after,  Fulmer  left  his  job  to  devote  more  time 
to  microcomputers. 

He  was  not  alone:  .the  Affair  raided  the  University  of  Cali¬ 
fornia  at  Berkeley.  George  Morrow,  a  graduate  student  in 
math,  worked  at  the  university’s  Center  for  Research  in 
Management  Science  with  two  other  students,  Chuck  Grant 
and  Mark  Greenberg.  They  were  trying  to  develop  a  lan¬ 
guage  to  use  with  a  microprocessor  in  computer-controlled 
research. 

Morrow,  Grant,  and  Greenberg  found  that  they  worked 
well  together.  All  three  were  perfectionists,  although  in  dif¬ 
ferent  ways.  Morrow,  thin,  prematurely  balding,  and  with  a 
twinkle  in  his  eye  and  an  irrepressible  wit,  seemed  always  to 
be  enjoying  himself,  perhaps  especially  when  he  was  hard  at 
work.  Grant  and  Greenberg  appeared  cut  from  a  darker 
cloth.  They  were  more  businesslike.  Although  Grant  and 
Greenberg  often  attended  Homebrew  meetings  and  profited 
from  the  free,  open  exchange  of  information,  they  never  con¬ 
sidered  themselves  part  of  the  hobbyist  community.  Techni¬ 
cally,  the  three  formed  a  good  team.  Morrow  knew  hard- 


Photo  3 

George  Morrow  in  a  pose  for  an  early  advertisement 


ware,  Grant  preferred  software,  and  Greenberg  was  at  home 
with  either. 

The  trio  considered  making  boards  for  the  Affair  or  even  a 
kit  computer  of  their  own.  They  knew  that  they  were  a  good 
design  team,  but  they  also  knew  they  lacked  sophistication  in 
marketing.  So  Morrow  sought  the  advice  of  Bill  Godbout. 
Middle-aged,  blunt,  opinionated,  with  a  paunch  that  he 
joked  about  and  an  airplane  that  he  flew  stunts  in,  Godbout 
was  the  electronics  distributor  whom  Bob  Marsh  had  tried  to 
interest  in  his  walnut  digital  clock  when  he  and  Felsenstein 
had  first  worked  in  the  garage.  Morrow  told  Godbout  about 
their  plans. 


Photo  4 

Steve  Dompier,  Bob  Marsh,  and  Lee  Felsenstein  at  a  meeting  of 
Processor  Technology  dealers  in  1979. 


Dr.  Dobb's  Journal,  February  1985 


35 


i  in 


Godbout  was  then  selling  chips  and  minicomputer  memo¬ 
ry  boards  by  mail,  and  Morrow  asked  if  he  intended  to  sell 
Altair  memory  boards.  Godbout  scoffed.  He  wouldn’t  so  dig¬ 
nify  the  product,  he  said.  Morrow  wondered  if  he  might  be 
interested  in  distributing  a  good  computer,  the  creation  of  a 
top-notch  design  team.  “You  guys?”  Godbout  asked.  He 
looked  Morrow  over.  Godbout  believed  he  was  good  at  sizing 
people  up,  and  Morrow  looked  all  right.  They  agreed  to  split 
profits  down  the  middle  and  shook  on  it.  No  written  con¬ 
tract,  Godbout  said.  Written  contracts  were  a  sign  of  mis¬ 
trust  and  an  invention  of  lawyers,  and  if  there  was  anybody 
Godbout  didn’t  trust,  it  was  a  lawyer. 

By  this  time  a  motley  group  of  engineers  and  revolutionar¬ 
ies  had  assembled  in  Silicon  Valley  in  the  infancy  of  a  bil- 
lion-dollar  industry:  irascible  Bill  Godbout,  who  suspected 
lawyers;  zx-Berkeley  Barb  technical  editor  and  current 
Homebrew  toastmaster  Lee  Felsenstein;  Bob  Albrecht,  who 
left  a  high-paying  career  to  teach  children  about  computers, 
who  smoked  cigars,  and  called  himself  “The  Dragon”;  Bob 
Marsh,  testing  his  own  abilities,  turning  his  love  for  electron¬ 
ics  into  a  garage  corporation;  and  Keith  Britton,  who  saw 
himself  and  the  other  Homebrewers  as  pivotal  in  “an  equiva¬ 
lent  of  the  industrial  revolution  but  profoundly  more  impor¬ 
tant  to  the  human  race.”  A  surprising  number  of  them  held 
political  views  that  would  have  shocked  the  local  Rotary 
Club,  and  almost  all  had  no  love  for  IBM  and  the  computer 
establishment.  But  they  and  others  like  them  were  pulling 
off  the  most  startling  entrepreneurial  achievement  in  recent 
times.  And  much  of  the  action  took  place  at  Homebrew. 

The  Homebrew  Computer  Club  was  not  merely  the 
spawning  ground  of  many  Silicon  Valley  microcomputer 
companies.  It  was  also  the  intellectual  nutrient  in  which  they 
first  swam.  Presidents  of  competing  companies  and  chief  en¬ 
gineers  would  gather  there  to  argue  design  philosophy  and 
announce  new  products.  Statements  made  at  Homebrew 
changed  the  directions  of  corporations.  Homebrew  was  a 
respected  critic  of  microcomputer  products.  The  Home- 
brewers  were  sharp,  and  could  spot  shoddy  merchandise  and 
items  that  were  difficult  to  maintain.  They  blew  the  whistle 
on  faulty  equipment  and  meted  out  praise  for  solid  engineer¬ 
ing  and  convivial  technologies.  Homebrewers  soon  developed 
the  power  to  make  or  break  new  companies.  In  part  due  to 
Lee  Felsenstein,  Homebrew  encouraged  the  conviction  that 
computers  should  be  used  for  and  not  against  people.  Home¬ 
brew  thrived  in  a  kind  of  joyous  anarchy,  but  the  club  was 
also  an  important  step  in  the  development  of  a  multi-billion 
dollar  industry.  Processor  Technology  was  one  of  the  chil¬ 
dren  of  Homebrew. 

The  first  part  of  the  meeting  we  were  involved  in  open  com¬ 
bat  with  Intel.  Intel  was  out  to  torpedo  any  standardization 
effort  on  the  S-100  bus. 

George  Morrow 

Home  Rule 

A  continuing  fear  in  the  developing  microcomputing  indus¬ 
try  was  that  “the  big  boys”  would  come  in  and  spoil  all  the 
fun.  Sometimes  “the  big  boys”  meant  IBM  and  the  other 
mainframe  computer  and  minicomputer  companies;  some¬ 
times  it  meant  Texas  Instruments,  Commodore,  and  the 


other  electronics  companies  that  had  waged  Pyrrhic  price- 
cutting  wars  in  the  calculator  industry;  especially  it  meant 
Texas  Instruments,  known  for  its  ruthless  price-cutting.  Lee 
Felsenstein  summarized  the  dread  of  the  hobbyist  entrepre¬ 
neurs:  “Anyone  but  TI!”  Intel  and  some  of  the  other  semi¬ 
conductor  companies,  although  well  situated  to  produce  mi¬ 
crocomputers  from  their  own  chips,  had  expressed 
reluctance  to  do  anything  that  could  be  construed  as  compet¬ 
ing  with  their  own  customers,  and  the  hobby-born  micro¬ 
computer  companies  had  developed  enough  clout  by  this 
time  to  be  taken  seriously  as  semiconductor  customers. 

In  December  of  1976,  Commodore  International,  an  elec¬ 
tronics  firm  with  a  lot  of  market  muscle,  leaked  information 
to  Electronic  Engineering  Times  about  a  new  product.  Com¬ 
modore,  the  story  went,  was  ready  to  release  a  machine  very 
much  like  a  low-cost  Sol.  Proc  Tech  was  just  shipping  the 
first  Sols  and  Marsh  was  thinking  about  the  company’s  next 
product,  a  new  version  of  the  Sol  with  an  integrated  key¬ 
board  and  64K  of  memory,  that  would  be  cheap  at  $1000.  It 
was,  unfortunately,  essentially  the  Commodore  machine. 

Convinced  that  Commodore  actually  had  the  computer  on 
the  launch  pad  and  that  Proc  Tech  could  never  compete  with 
it,  and  even  more  worried  by  news  that  National  Semicon¬ 
ductor  was  also  planning  a  microcomputer,  Marsh  scrapped 
the  project.  The  laws  of  battle  in  the  semiconductor  wars  five 
years  earlier  had  been  to  cut  prices  to  the  baseline  and  push 
the  technology  madly,  even  under  threat  of  corporate  extinc¬ 
tion.  Proc  Tech  couldn’t  compete  with  National,  especially 
in  mortal  combat.  But  in  fact  the  Commodore  machine 
would  not  appear  for  some  time,  and  the  National  computer 
never  materialized  at  all. 

Many  new  hobbyist-born  companies  were  starting  to  man¬ 
ufacture  microcomputer  products,  but  most  of  these  were 
turning  out  boards  for  the  Altair  or  IMSAI,  and  practically 
all  were  small  companies,  start-ups  like  Proc  Tech. 

Howard  Fulmer  began  such  a  firm  in  his  Oakland  base¬ 
ment.  After  reading  an  editorial  by  Ed  Roberts  in  Dave  Bun¬ 
nell’s  Computer  Notes  that  attacked  the  compatible  board 
companies  as  “parasites,”  he  considered  calling  his  own 
company  Symbiotic  Engineering  to  emphasize  his  concep¬ 
tion  of  the  proper  relationship  between  MITS’s  products  and 
his  own.  But  a  group  called  the  Symbionese  Liberation 
Army  was  making  a  name  for  itself  then,  and  he  wanted  to 
avoid  confusion.  He  called  his  company  Parasitic 
Engineering. 

George  Morrow  and  Howard  Fulmer  were  both  designing 
Altair-compatible  products,  and  in  the  spring  of  1977  decid¬ 
ed  to  build  a  computer  together.  Morrow  would  supply 
Fulmer  the  boards  he  had  designed  at  a  cheap  price,  and 
Fulmer  would  devise  the  remainder  of  the  computer.  Fulmer 
called  it  the  Equinox  100.  It  was  a  solid  design,  for  they  had 
listened  to  the  ideas  of  Bob  Mullen,  one  of  the  founders  of 
Diablo  Systems,  a  Silicon  Valley  disk-drive  manufacturer, 
and  of  Bill  Godbout  about  improving  the  S-100  bus. 

The  timing  of  the  machine’s  release  was  unfortunate 
though.  The  Equinox  was  an  8080  machine,  and  Technical 
Design  Labs  in  New  Jersey,  Garland  and  Melen’s  Cro- 
memco,  and  The  Digital  Group  in  Denver  were  all  known  to 
be  designing  computers  based  on  the  new  and  apparently 
better  Z80  chip.  Cromemco  had  already  produced  a  Z80 


36 


Dr.  Dobb’s  Journal,  February  1985 


centra]  processor  board,  and  hobbyists  were  dropping  it  into 
the  IMSAI  chassis  to  create  a  mongrel  Z80  machine. 

Marsh  wondered  if  Proc  Tech  shouldn’t  do  a  Z80  machine 
as  well.  But  it  seemed  irrational  to  dump  a  successful  design 
to  achieve  a  marginal  improvement  in  performance.  Besides, 
the  processor  mattered  much  less  than  the  software,  he  be¬ 
lieved.  The  software  made  the  computer  work,  and  that 
would  distinguish  one  machine  from  another. 

Proc  Tech  called  two  programmers,  Jerry  Kirk  and  Paul 
Greenfield  of  MicroTech  in  Sunnyvale,  who  had  produced 
high-level  language  compilers  for  minicomputers.  They  were 
hired  to  create  a  set  of  programmer’s  tools,  programs  that 
would  make  it  easier  to  write,  edit,  and  debug  other  pro¬ 
grams  on  the  Sol.  Ingram  developed  their  work  into  Soft¬ 
ware  Package  One. 

Ownership  of  software  was  an  inflammatory  issue  in  the 
Valley  and  elsewhere.  Proc  Tech  was  aggressively  pro-pira¬ 
cy,  and  its  hobbyist  founders  swapped  program  tapes  at  Ho¬ 
mebrew  meetings  along  with  everyone  else.  Gordon  French, 
who  after  helping  to  start  Homebrew  had  become  Proc 
Tech’s  General  Factotum  (his  official  title),  argued  for  an 
open  system,  that  is,  free  dissemination  of  software  code  and 
internal  workings  to  everyone.  He  wanted  outside  program¬ 
mers  and  peripheral  manufacturers  to  be  able  to  create  com¬ 
patible  products  and  expand  the  market.  At  that  time,  Ed 
Roberts  and  the  entire  mainframe  and  minicomputer  indus¬ 
try  held  the  opposite  view.  But  the  hobbyists  were  bringing 
their  own  values  into  their  industry.  An  open  architecture, 
the  publicly  known  physical  design  of  the  machine,  was  one 
emerging  ideal.  An  open  operating  system  was  another.  But 
at  Proc  Tech  the  idea  of  an  open  operating  system  was 
frowned  upon.  Marsh  and  Ingram  wanted  a  proprietary  op¬ 
erating  system. 

In  fact,  Proc  Tech  had  its  own  disk  operating  system  very 
early  on.  The  company  bought  PTDOS  from  its  author,  19- 
year-old  Bill  Levy,  who  developed  it  at  the  Lawrence  Hall  of 
Science  at  Berkeley.  Levy  modeled  PTDOS  after  Unix,  a 
mainframe/minicomputer  operating  system  in  use  at  UC 
Berkeley.  Marsh  thought  PTDOS  much  better  than  CP/M, 
but  PTDOS  was  slow  to  reach  the  market  because  of  “the 
drive  fiasco.” 

Disk  drives  posed  an  alluring  challenge  in  1976,  when  the 
Sol  was  released.  They  existed  and  were  used  heavily  in 
mainframe  and  minicomputers,  but  to  mount  a  disk  drive  on 
a  microcomputer  was  prohibitively  expensive.  Drives  typi¬ 
cally  cost  $3500.  So  Marsh  was  very  intrigued  when  George 
Comstock,  Bob  Mullen’s  partner  at  Diablo  Systems,  de¬ 
clared  at  Homebrew  one  night  that  he  wanted  to  develop  a 
disk  drive  for  microcomputers.  Comstock  thought  that  a 
drive,  complete  with  a  controller  board  and  software,  could 
be  sold  for  about  $1000. 

But  Diablo  was  not  then  involved  in  the  growing  micro¬ 
computer  industry,  and  Comstock  felt  a  disk-drive  system 
would  only  flail  around  without  close  consultation  with  mi¬ 
crocomputer  companies.  He  later  proposed  a  joint  effort  to 
Marsh.  Diablo  would  develop  the  drives,  the  physical  mecha¬ 
nisms  that  read  and  write  information  from  and  to  disks,  and 
Processor  Technology  would  write  the  software  and  develop 
an  S-100  board  to  control  the  drives.  He  also  proposed  that 
Proc  Tech  could  market  the  board  on  its  own. 

Dr.  Dobb's  Journal.  February  1985 


Disk  drives  were  so  clearly  destined  to  belong  in  any  seri¬ 
ous  microcomputer  system  that  engineers  were  already  vy¬ 
ing  to  develop  a  low-cost  drive  system  with  software  and  a 
controller  board.  Shugart’s  5'A-inch  drives  seemed  attrac¬ 
tive,  but  they  had  one  drawback.  IBM  had  been  using  8-inch 
drives  and  had  established  certain  standards  for  the  devices. 
There  were  no  standards  for  small  disk  drives  and  no  guaran¬ 
tees  that  disks  written  on  one  brand  of  machine  would  be 
readable  on  another.  North  Star  had  selected  the  Shugart 
drive  and  sold  it  for  under  $800.  Using  an  idea  of  Eugene 
Fisher’s  of  Lawrence  Livermore  Labs,  both  Morrow  and  San 
Francisco  engineer  Ben  Cooper  had  begun  developing  rela¬ 
tively  low-cost  8-inch  disk  drives.  Cooper  had  perhaps  the 
first  commercial  8-inch  disk  controller  for  microcomputers. 
Morrow,  shortly  thereafter,  had  the  first  one  available  for 
the  $1000  price  Comstock  was  aiming  for,  and  he  negotiated 
with  Digital  Research  and  Microsoft  for  an  operating  system 
(CP/M)  and  BASIC  to  distribute  free  with  the  drive  system. 
Both  Morrow  and  Cooper  continued  to  develop  significant 
disk  products,  and  Cooper  created  the  first  hard  disk  control¬ 
ler  for  microcomputers. 

But  at  Proc  Tech,  the  disk  drive  plans  were  crumbling. 
Diablo  encountered  trouble  with  the  drives  and  dropped  the 
project,  leaving  Proc  Tech  so  far  into  development  of  the 
controller  that  it  had  to  continue.  Marsh  and  Ingram  raised 
the  price  of  the  system  to  $1700  and  substituted  a  more 
expensive  drive  offered  by  Perscii.  The  price  was  too  high, 
and  Proc  Tech’s  drives  didn’t  always  work.  Customers  could 
find  better  deals  from  Cooper,  Morrow,  and  North  Star. 

Despite  such  problems,  Proc  Tech  seemed  to  be  thriving. 
The  executives  were  recycling  their  profits  into  the  company. 
(Lee  Felsenstein  was  investing  his  in  the  Community  Memo¬ 
ry  project.)  The  Proc  Tech  staff  in  Emeryville  now  num¬ 
bered  85,  not  counting  non-employee/consultant  Felsen¬ 
stein,  and  headquarters  was  growing  crowded.  That  year, 
1977,  Proc  Tech  moved  south  to  the  bedroom  community  of 
Pleasanton.  The  new  offices  boasted  a  spacious  executive 
suite  with  large  windows  looking  out  over  the  valley. 

But  there  was  competition.  As  1977  came  to  an  end,  Proc 
Tech  found  itself  in  a  more  serious  industry.  The  trading  of 
information,  the  shirt-sleeve  management,  the  flashes  of  ide¬ 
alism,  and  the  lack  of  detailed  planning  that  had  character¬ 
ized  the  industry  from  the  start  still  existed.  The  chief  users 
and  the  designers  and  company  presidents  were  still  hobby¬ 
ists  at  heart,  and  most  of  the  world  knew  nothing  of  the 
revolution  that  was  afoot.  But  new  companies  were  emerging 
like  mushrooms  overnight.  Among  the  computer  and  com¬ 
puter-related  companies  at  the  end  of  1977  were  Apple 
(which  some  insiders  thought  had  great  promise),  Exidy, 
IMSAI,  Digital  Microsystems,  Alpha  Micro  Systems,  Com¬ 
modore,  Midwest  Scientific,  GNAT,  Southwest  Technical 
Products,  MITS,  Technical  Design  Labs,  Vector  Graphic, 
Ithaca  Audio,  Heathkit,  Cromemco,  MOS  Technology, 
RCA,  TEI,  Ohio  Scientific,  The  Digital  Group,  Microma- 
tion.  Polymorphic  Systems,  Parasitic  Engineering,  Godbout 
Engineering,  Radio  Shack,  Dynabyte,  North  Star,  Morrow’s 
Microstuff,  and,  of  course.  Processor  Technology. 

Many  companies  were  located  in  the  Bay  Area  and  were 
associated  with  the  Homebrew  Club.  The  club  had  become 
large,  and  by  1977  tended  to  assemble  in  fairly  predictable 

38 

115 


groups.  In  front,  performing,  was  Lee  Felsenstein.  Bob 
Marsh  and  the  Proc  Tech  group  were  usually  assembled 
along  one  wall.  Steve  Wozniak  and  the  boys  from  Apple  and 
the  other  6502  processor  fans  sat  in  the  back.  Jim  Warren  of 
Dr.  Dobb's  sat  on  the  aisle  three  seats  from  the  back,  stage 
left,  ready  to  stand  during  the  mapping  session  and  do  his 
Core  Dump,  an  extemporaneous  outpouring  of  all  the  news 
and  rumors  he  had  heard.  The  front  row  always  had  Gordon 
French,  who  maintained  the  software  library,  and  Bob  Red¬ 
ing,  who  wrote  the  newsletter. 

In  December  1977,  Reiling  wrote,  “The  development  of 
special-interest  groups  has  probably  been  the  biggest  change 
during  the  past  year.  At  the  beginning  of  the  year  the  6800 
group  was  holding  regular  meetings.  At  the  end  of  1977,  the 
groups  include  not  only  the  6800  group,  but  also  the  F8 
Users,  North  Star  Users  Group,  Sol  Users  Society,  and  PET 
Users.”  At  that  time,  the  Homebrew  attendees  (the  club  did 
not  have  members)  included  key  people  from  Apple,  Cro- 
memco,  Commodore,  Computer  Faire,  Dr.  Dobb's,  Itty  Bit¬ 
ty  Computers,  M&R  Enterprises,  Mountain  Hardware, 
IBEX,  Mullen  Computer  Boards,  North  Star,  PCC,  Proc 
Tech,  and  the  Bay  Area  computer  stores.  The  most  signifi¬ 
cant  of  these  then  was  Proc  Tech.  Marsh  had,  to  some  ex¬ 
tent,  realized  his  dream.  The  company  seemed  to  be  doing 
very  well. 

And  in  December,  Reiling  could  report  optimistically, 
“The  IEEE  now  has  a  standards  group  to  sort  out  the  various 
hardware  and  software  standards.”  That  blithe  statement 
subsumed  a  wrangling  struggle  and  a  remarkable  achieve¬ 
ment  which  brought  new  legitimacy  to  the  industry.  The 
sorting  out  had  been  no  simple  matter. 

Bob  Stewart  was  a  consultant  in  optics  and  electronics  and 
a  member  of  the  Institute  for  Electrical  and  Electronics  En¬ 
gineers  (IEEE).  He  had  bought  an  Altair  and  had  become 
frustrated  with  it.  At  a  meeting  at  Diablo  Valley  College  to 
discuss  the  S- 1 00  bus  he  met  some  company  presidents:  Har¬ 
ry  Garland  of  Cromemco,  Howard  Fulmer  of  Parasitic  Engi¬ 
neering,  Ben  Cooper  of  Micromation,  and  George  Morrow 
of  what  he  was  then  calling  Thinkertoys.  Byte's  Carl 
Helmers  was  also  there.  The  idea  was  to  cure  the  obvious 
problems  of  the  bus  and  to  establish  common  standards,  so 
that  one  company’s  board  would  work  with  another’s.  Gar¬ 
land  explained  the  virtues  of  his  and  Melen’s  shielded  bus, 
but  Morrow  thought  he  had  a  better  approach.  No  immedi¬ 
ate  agreement  was  forthcoming.  Stewart  suggested  creating 
an  official  IEEE  standard  for  the  bus.  With  the  group’s  en¬ 
couragement,  he  petitioned  the  IEEE  to  form  a  microcom¬ 
puter  standards  subcommittee  of  the  computer  standards 
committee.  The  petition  succeeded,  and  the  group  became 
official. 

Roberts  was  invited  to  participate  in  the  microcomputer 
standards  subcommittee,  but  declined  to  send  a  representa¬ 
tive  or  even  to  respond  directly.  He  did  say  in  print  that  he 
felt  MITS  had  the  sole  right  to  define  the  bus.  The  subcom¬ 
mittee  ignored  him.  At  first,  the  meetings  involved  conten¬ 
tion  with  Intel,  which  fought  standardization.  Morrow  got 
the  impression  that  Intel  wanted  no  standards  unless  Intel 
was  setting  them.  But  when  the  subcommittee  decided  to 
formulate  standards  whether  Intel  liked  them  or  not,  Intel 
acquiesced.  This  was  outrageous  cheek.  A  bunch  of  hobby- 

40 


ists  turned  entrepreneurs  had  simply  ignored  the  biggest  mi¬ 
crocomputer  company  of  that  time  and  had  faced  the  lead¬ 
ing  chip  manufacturer  and  not  been  struck  by  lightning. 

In  spite  of  its  solidarity,  the  subcommittee  had  no  guaran¬ 
tee  that  it  could  really  create  standards.  The  subcommittee 
had  1 5  assertive,  opinionated  people  disputing  an  issue  about 
which  they  held  legitimate  and  conceivably  irresolvable  dif¬ 
ferences.  Each  of  the  members  had  a  product  that  would  be 
incompatible  with  anything  likely  to  be  proposed.  As  the 
meetings  went  on,  Roger  Melen  came  in  for  Cromemco.  Al¬ 
pha  Micro  was  represented.  Elwood  Douglas  appeared  for 
Proc  Tech  and  judged  the  standard  against  the  memory 
board  he  was  designing.  George  Millard  spoke  for  North 
Star.  Someone  arrived  from  IMSAI  to  read  its  formal  posi¬ 
tion,  which  resembled  Ed  Roberts’.  The  subcommittee  ig¬ 
nored  that  position  too.  Most  of  its  members  had  written 
IMSAI  off  as  a  place  where  training  in  est  mattered  more 
than  training  in  engineering. 

At  times  the  subcommittee  members  weren’t  too  fond  of 
each  other.  They  argued  for  hours,  with  no  one  yielding  an 
inch.  They  would  then  return  to  their  companies  and  discuss 
how  to  compromise  their  own  designs  to  achieve  a  standard. 
At  the  next  meeting,  they  would  find  themselves  closer  to 
agreement.  Little  by  little,  these  creative,  independent  peo¬ 
ple  subordinated  their  egos  and  short-term  economic  gains 
for  the  good  of  the  entire  microcomputer  field. 

The  committee  was  attempting  guerrilla  design.  In  main¬ 
frames  and  minicomputers,  the  bus  was  always  whatever  the 
bus  designer  said  it  was.  Although  the  IEEE  suggested  subtle 
variations  in  tolerance  during  the  process  of  formalizing  the 
company  bus  into  a  standard,  independent  committees  did 
not  assemble  to  redesign  the  whole  bus.  Timing  parameters 
and  other  features  were  dictated  by  the  companies.  IBM  and 
DEC  worked  this  way.  In  a  way  their  method  was  certainly 
easier  than  communal  design.  But  the  S-100  committee 
members  dug  into  the  Roberts  bus,  figured  out  how  it 
worked,  and  were  scrapping  it  in  favor  of  a  new,  independent 
bus  open  to  all.  This  was  a  populist  revolt  against  the  tyranny 
of  the  big  company,  with  MITS  hoisted  as  a  poor  but  ade¬ 
quate  symbol  of  the  big  company.  The  revolution  was 
succeeding. 

DD] 


Dr.  Dobb's  Journal.  February  1985 


11R 


Tiny  BASIC  for  the  68000 


Dr.  Dobb’s  Journal  was  created  by  BASIC  itself  is  ubiquitous  now  and  still 

Dennis  Allison  and  Bob  Albrecht  in  lacking  any  practical  standard  defini- 

1975  as  a  vehicle  for  getting  public-  tion;  no  longer  Tiny,  it  is  now  Better  or 

domain  versions  of  the  BASIC  pro-  Professional  or  True.  DDJ  publishes 

gramming  language  into  the  hands  of  little  BASIC  code  now,  but  over  the 

computer  enthusiasts.  Except  for  years  DDJ  has  continued  to  publish 

Gates  and  Allen’s  BASIC  for  the  MITS  programming  tools,  including  small 

Altair,  there  was  no  high-level  lan-  versions  of  the  C  and  Ada  languages, 
guage  available  for  the  new  micro-  But  memory  is  cheap  today;  does  it 

computers  in  1975,  only  arcane  as-  make  any  sense  to  write  about  Tiny  Ba- 

sembly  language,  so  Allison  and  sic  for  the  Motorola  68000  micropro- 

Albrecht,  interested  in  giving  kids  and  cessor?  The  answer,  we  think,  is  yes. 

tinkerers  and  as  many  people  as  possi-  Tiny  BASIC  was  never  intended  to  be  a 

ble  access  to  the  technology,  set  about  development  language,  but  it  is  a  pro- 
developing  a  microcomputer  BASIC  gramming  language,  and  it  is  tiny,  and 
and  publishing  reports  on  its  develop-  “cheap"  is  a  relative  term.  The  ques- 
ment  in  their  newsletter,  PCC.  Since  at  tion  is,  how  many  people  are  interested 

that  time  4K  of  memory  was  a  lot,  in  getting  into  the  68000  as  cheaply  as 

they  made  their  BASIC  Tiny.  possible?  For  them,  this. — Editor. 

This  3K  interpreter  beats  Applesoft 
on  the  Sieve  benchmark. 

Tiny  BASIC  was  more  popular  than  ■""*  emember  the  good  old  days? 
its  inventors  expected  and  they  soon  When  the  8080  microprocessor 

found  that  they  needed  a  separate  I  X  reigned  supreme,  8K  of  memory 

newsletter  just  to  publish  Tiny  BASIC  cost  an  arm  and  a  leg,  ah  yes  .  .  .  Well, 

developments.  Dick  Whipple  and  John  the  years  went  by,  microcomputers  got 

Arnold  of  Tyler,  Texas,  wrote  a  2.9 K  bigger,  software  grew  more  sophisti- 

Tiny  BASIC  Extended,  which  was  cated,  and  prices  went  up.  This  is  just 

published  in  octal  in  the  first  issue  of  fine,  of  course,  if  you  can  afford  the 

Dr.  Dobb’s  Journal  of  Tiny  BASICCed-  higher  prices.  The  less  fortunate 

isthenics  and  Orthodontia  (Running  among  us,  however,  must  build  or  buy 

Light  without  Overbyte),  in  January,  smaller  16-bit  “educational”  systems. 

1976.  Other  Tiny  BAS/Cs  followed.  This  is  fine  too — if  you  don’t  mind 

Well,  a  lot  followed,  in  fact.  Virtual-  having  hardly  any  software. 
ly  the  whole  personal  computer  revolu-  This  is  just  the  sort  of  situation  that 
tion  to  date:  the  demise  of  the  early  mi-  gave  rise  to  Dr.  Dobb’s  Journal  in  the 

crocomputer  companies  (MITS,  Imsai,  “good  old  days.”  The  solution  back 

Proc  Tech,  Sphere ),  Apple’s  rise  and  then  was  to  publish  a  tiny  BASIC  inter- 

Osborne’s  rise  and  Jail,  the  entries  of  preter  that  could  be  adapted  to  just 

Tandy  and  IBM,  the  irony  of  Atari,  ex-  about  any  8080  microcomputer  around. 

pensive  advertising  and  cheap  memory.  This  solution  worked  fabulously  and 

_  gave  many  a  hobby  computer  its  first 

taste  of  useful  software.  Well,  if  the  so- 
Gordon  Brandly,  R.  R.  2,  Fort  Sask.,  lution  worked  once,  why  not  again?  I 
AB,  Canada,  T8L  2N8.  therefore  decided  to  produce  a  tiny  BA- 


by  Gordon  Brandly 


Dr.  Dobb’s  Journal,  February  1985 


42 

117 


SIC  interpreter  for  the  relatively  small 
68000  systems  such  as  the  Motorola 
Education  Computer  Board,  the  EMS 
68000  board,  and  so  on. 

To  produce  this  BASIC,  I  took  one  of 
the  most  successful  8080  tiny  BASICS, 
Li  Chen  Wang’s  Palo  Alto  Tiny  BA¬ 
SIC  {Dr.  Dobb's  Journal ,  May  1976), 
and  translated  it  into  68000  code.  I 
then  added  a  few  features  and  opti¬ 
mized  the  code  a  little,  producing  a 
surprisingly  usable  interpreter. 

First,  I’ll  describe  the  differences 
between  my  interpreter,  Palo  Alto 
Tiny  BASIC,  and  the  ubiquitous  Mi¬ 
crosoft  BASICS.  I  then  will  describe 
how  you  can  install  this  software  on 
your  68000  system.  Finally,  I’ll  give 
my  evaluation  of  the  interpreter’s  pres¬ 
ent  performance  and  how  it  can  be  im¬ 
proved. 

Features 

Those  who  know  the  original  Palo  Alto 
Tiny  BASIC  (or  the  Sherry  Brothers’ 
version  on  CP/M  User’s  Group  Vol¬ 
ume  1 1 )  will  find  this  interpreter  very 
similar.  I  have  made  two  or  three 
changes  to  the  interpreter’s  syntax  to 
bring  it  closer  to  the  de facto  Microsoft 
standard.  The  colon  is  used  instead  of 
the  semicolon  to  separate  multiple 
statements  on  a  line.  The  inequality 
operator  (#)  has  been  changed  to  the 
more  standard  <  >  .  I  also  added  the 
PEEK,  POKE,  CALL,  BYE,  LOAD,  and 
SAVE  commands,  which  are  described 
later. 

Those  of  you  used  to  a  bigger  BA¬ 
SIC,  such  as  the  various  Microsoft  in¬ 
terpreters,  will  find  that  this  version 
works  almost  the  same  within  its  limi¬ 
tations.  Following  are  some  excerpts 
from  Li  Chen  Wang’s  original  docu¬ 
mentation,  mixed  with  descriptions  of 
my  extensions. 

The  Language 

Numbers 

In  this  version  of  Tiny  BASIC,  all  num¬ 
bers  are  32-bit  integers  and  must  be  in 
the  range  2,147,483,647  to 
—  2,147,483,648.  I  decided  to  use  32 
bits  so  that  the  PEEK  and  POKE  com¬ 
mands  could  access  the  entire  address 
range  of  the  68000.  This  slows  down 
arithmetic  operations  somewhat,  but 
sticking  to  16  bits  would  have  produced 
unnecessary  complications. 


Variables 

There  are  26  variables,  denoted  by  the 
letters  A  through  Z,  and  a  single  array 
@(I).  The  dimension  of  this  array  (i.e., 
the  range  of  value  of  the  index  I)  is  set 
automatically  to  make  use  of  all  the 
memory  space  that  is  left  unused  by  the 
program  (i.e.,  0  through  SIZE/4,  see 
the  SIZE  function  below).  All  variables 
and  array  elements  are  four  bytes  long. 

Functions 

There  are  four  functions: 

( 1 )  ABS(X)  gives  the  absolute  value  of 
X. 

(2)  RND(X)  gives  a  random  number 
between  1  and  X  (inclusive). 

(3)  SIZE  gives  the  number  of  bytes 
left  unused  by  the  program. 

(4)  PEEK(X)  gives  the  value  of  the 
byte  at  memory  location  X. 

Commands 

The  LET  command 

LET  A  =  234  -  5*6,  A  =  A/2, 

X  =  A  —  1 00,  @(X  +  9)  =  A-1 
will  set  the  variable  A  to  the  value  of 
the  expression  234  —  5*6  (or  204),  set 
the  variable  A  (again)  to  the  value  of 
the  expression  A/2  (or  102),  set  the 
variable  X  to  the  value  of  the  expres¬ 
sion  A— 100  (or  2),  and  then  set  the 
variable  @(  1 1 )  to  101  (where  1 1  is  the 
value  of  the  expression  X+9  and  101 
is  the  value  of  the  expression  A  —  1 ). 
The  PRINT  command 
PRINT  A*3+l, 

“abc  123  !@#”,  ‘cba  ' 
will  print  the  value  of  the  expression 
A*3+  1  (or  307),  the  string  of  charac¬ 
ters  abc  123  !@#  and  the  string  cba, 
and  then  a  CR-LF  (carriage  return  and 
line  feed).  Note  that  you  can  use  either 
single  or  double  quotes  to  quote 
strings,  but  pairs  must  match.  If  a 
comma  appears  at  the  end  of  the 
PRINT  command,  the  final  CR-LF  will 
not  be  printed.  Note  also  that  commas 
separate  adjacent  items  (most  other 
BASICS  use  the  semicolon  to  perform 
this  function). 

PRINT  A,  B,  #3,  C,  D,  E,  #10.  F.  G 
will  print  the  values  of  A  and  B  in  1 1 
spaces;  the  values  of  C,  D,  and  E  in  3 
spaces;  and  the  values  of  F  and  G  in  10 
spaces.  The  value  will  be  printed  in  full 
even  if  there  aren’t  enough  spaces 
specified  for  it. 

PRINT ’abc’,_,'xxx' 
will  print  the  string  abc,  a  CR  without 


43 


Dr.  Dobb  s  Journal.  February  1985 


a  LF,  and  then  the  string  xxx  (over  the 
abc),  followed  by  a  CR-LF. 

The  INPUT  command 
INPUT  A,  B 

will  cause  Tiny  BASIC  to  print  A:  and 
wait  to  read  in  an  expression  from  the 
console.  The  variable  A  will  be  set  to 
the  value  of  this  expression.  Then  B: 
will  be  printed  and  variable  B  set  to  the 
value  of  the  next  expression  entered. 
Note  that  you  can  enter  complete  ex¬ 
pressions  as  well  as  numbers.  This  en¬ 
ables  an  interesting  trick:  you  can  set 
the  variable  Y  to  an  unusual  value 
(e.g.,  9999)  and  use  it  to  get  the  answer 
to  a  yes-or-no  question,  such  as: 

10  Y  =  9999 

:  INPUT  ‘Are  you  sleepy?’A 
:  IF  A  =  Y  GOTO  100 
The  user  can  answer  the  question  with 
the  expression  Y,  which  puts  the  nu¬ 
meric  value  of  Y  into  the  A  variable. 
INPUT  ‘What  is  the  weight’A, 
“and  size”B 

is  the  same  as  the  first  INPUT  example 
except  that  the  prompt  A:  is  replaced 
by  “What  is  the  weight:”  and  the 
prompt  B:  is  replaced  by  “and  size:”. 
Again,  you  can  use  both  single  and 
double  quotes  as  long  as  they  match. 
INPUT  A,  ‘string’, 

“another  string”,  B 
with  the  strings  and  the  has  the 
same  effect  as  in  PRINT. 

The  POKE  command 
POKE  4000  + X,Y 

puts  the  value  produced  by  expression 
Y  into  the  byte  memory  location  speci¬ 
fied  by  the  expression  4000  +  X. 

The  CALL  command 
CALL  x 

will  call  a  machine  language  subrou¬ 
tine  at  the  address  specified  by  the  ex¬ 
pression  x.  All  of  the  CPU’s  registers 
except  the  stack  pointer  can  be  used  in 
the  subroutine. 

The  BYE  command  will  return  con¬ 
trol  to  the  resident  monitor  program  or 
operating  system. 

The  SAVE  command  will  save  your 
BASIC  program  on  the  storage  device 
you  provide.  Details  on  installing  this 
device  are  given  in  the  source  code.  As 
set  up  for  the  Educational  Computer 
Board,  this  command  will  send  the 
program  out  to  the  host  computer  in  an 
easily  stored  text  form.  This  isn’t,  how¬ 
ever,  human-readable  program  text 
because  the  line  numbers  are  repre¬ 
sented  in  hexadecimal. 

Dr.  Dobb's  Journal.  February  1985 


The  LOAD  command  will  delete  the 
program  in  memory  and  load  in  a  pro¬ 
gram  from  your  storage  device. 

Stopping  Program  Execution 

You  can  stop  the  execution  of  the  pro¬ 
gram  or  listing  of  the  program  by 
pressing  the  control-C  key  on  the  con¬ 
sole.  Additionally,  you  can  pause  in  a 
program  listing  by  pressing  control-S 
and  then  pressing  any  key  to  continue. 

Abbreviations  and  Blanks 

You  may  use  blanks  freely  within  a 
program  except  that  numbers,  com¬ 
mand  keywords,  and  function  names 
cannot  have  embedded  blanks. 

You  may  abbreviate  all  command 
keywords  and  function  names,  follow¬ 
ing  each  by  a  period.  For  instance,  P., 
PR.,  PRL,  and  PRIN.  all  stand  for 
PRINT.  You  may  also  omit  the  word 
“LET”  in  LET  commands.  The  shortest 
abbreviations  for  all  the  keywords  are 
given  in  the  Table  (page  46). 

Note  that,  in  some  cases,  the  same 
abbreviation  applies  to  different 
keywords.  The  interpreter  is  “smart” 
enough  to  identify  the  correct  keyword 
for  a  particular  situation.  For  instance, 
if  the  abbreviation  P.  appears  at  the  be¬ 
ginning  of  a  line,  it  can  only  mean 
PRINT.  In  a  statement  like  A  =  P.(8), 
the  P.  only  makes  sense  if  it  stands  for 
PEEK. 

Error  Reports 

There  are  only  three  error  conditions 
in  Tiny  BASIC.  The  line  containing  the 
error  is  printed  out  with  a  question 
mark  inserted  at  the  point  where  the 
error  is  detected. 

(1)  “What?”  indicates  an  error  in  a 
statement’s  syntax: 

What? 

260  LET  A  =  B  +  3, 

C  =  (3  +  4?.  X  =  4 

(2)  “How?”  means  that  the  statement 
in  question  is  syntactically  correct,  but 
for  some  reason  the  command  can’t  be 
carried  out: 

How? 

310  LET  A  =  B*C?  +  2 
where  B*C  is  larger  than 
2147483647 

How? 

380  GOTO  4 12? 

where  line  412  does  not  exist 

(3)  “Sorry.”  means  that  the  interpret¬ 
er  understands  the  statement  and 


knows  how  to  do  it  but  lacks  sufficient 
memory  to  accomplish  the  task. 

Error  Corrections 

If  you  notice  an  error  in  your  entry  be¬ 
fore  you  press  RETURN,  you  can  de¬ 
lete  characters  with  the  backspace 
(control-H)  key  or  delete  the  entire 
line  with  control-X.  To  delete  an  exist¬ 
ing  program  line,  just  type  the  line 
number  and  press  RETURN. 

Installation 

Now,  how  do  you  get  this  wonderful 
piece  of  software  running  on  your  com¬ 
puter?  Very  easily,  if  you  have  a  setup 
similar  to  mine.  Installation  on  other 
systems  should  also  be  fairly  easy  if 
you  have  access  to  a  68000  assembler 
of  some  kind. 

My  setup  is  a  Motorola  MEX- 
68KECB  Educational  Computer  Board 
(ECB)  connected  between  my  terminal 
and  my  CP/M  system.  The  source  code 
was  assembled  with  the  Quelo  version 
1.9  public  domain  68000  cross-assem¬ 
bler  for  CP/M.  (By  the  way,  if  you  use 
this  assembler,  you  will  get  36  “trim  1 6 
address”  errors,  which  is  normal.) 
Tiny  BASIC  is  then  loaded  into  the 
ECB  and  executed  at  the  cold  start  ad¬ 
dress  of  hex  900. 

BASIC  programs  are  saved  and  load¬ 
ed  by  setting  up  an  appropriate  CP/M 
command  before  using  SAVE  or  LOAD. 
For  example  (user  input  is  underlined): 

After  a  program  is  written, 
exit  to  the  monitor: 

>  BYE 

Enter  transparent  mode: 

TUTOR  l.x>  TM 
Issue  a  PIP  command  to  the  CP/M 
host: 

A>  PIP  PROGRAM. BAS  =  CON: 
Exit  transparent  mode  and  do  a 
BASIC  warm  start: 

TUTOR  l.x>  GO  904 
Do  the  actual  save: 

SAVE 

The  warm  start  is  an  entry  point  into 
the  interpreter  that  will  preserve  any 
program  you  may  have  already  entered. 

Program  LOADs  are  done  similarly, 
except  instead  of  PIP  you  must  run  a 
small  program  that  will  wait  to  receive 
a  carriage  return  before  sending  the 
program  to  the  ECB.  Here  is  a  sample 
program  in  Microsoft  BASIC: 

44 

119 


10  INPUT  “Program  to  send?”;F$ 
20  OPEN  ‘T\1,F$ 

30  INPUT  “Now  exit  Transparent 
Mode  and  do  a  LOAD.”;Z$ 

40  WHILE  NOT  EOF(  1  ):LINE  IN¬ 
PUT  #1  ,A$:PRINT  A$:WEND 

Admittedly,  this  way  of  LOADing 
and  SAVEing  is  a  fairly  complex  proce¬ 
dure,  but  it  allows  you  to  save  your 
programs  on  disk  while  keeping  the  in¬ 
terpreter  itself  small.  If  your  ECB  isn’t 
connected  to  another  computer,  you 
probably  could  change  the  AUXIN  and 
AUXOUT  subroutines  to  use  the  cas¬ 
sette  interface.  (I  haven’t  tried  it, 
though,  so  caveat  emptor!) 

For  other  68000  systems,  you  will 
have  to  modify  only  the  OUTC,  INC, 
AUXOUT,  AUXIN,  and  BYEBYE  rou¬ 
tines  at  the  end  of  the  interpreter  pro¬ 
gram.  In  addition,  you  must  put  the 
address  of  the  first  unavailable  memo¬ 
ry  location  above  BASIC  into  the  loca¬ 
tion  ENDMEM.  BASIC  programs  are 
SAVEd  in  a  form  that  can  be  stored  as 


A.  =  ABS 
C.  =  CALL 

F. =FOR 
GOS.=GOSUB 

G. =GOTO 
IF  =  IF 

l.=  INPUT 
L.  =  LIST 
LO.  =  LOAD 
N.  =  NEW 
N.  =  NEXT 
P.  =  PEEK 
PO.  =  POKE 
P.  =  PRINT 
REM  =  REMARK 
R.= RETURN 
R.  =  RND 

R.  =RUN 

S.  =  SAVE 
S.  =  SIZE 
S.  =  STEP 
S.=STOP 
TO  =  TO 

no  keyword  =  LET 

Table 


46 

120 


ASCII  text  and  read  back  quickly  by 
the  68000;  if  your  storage  device  can’t 
handle  the  present  format  or  if  you 
would  like  the  program  saved  in  a  hu¬ 
man-readable  form,  you  need  modify 
only  the  SAVE  and  LOAD  subroutines. 

One  warning:  I  wrote  the  DIRECT 
and  EXEC  routines  assuming  that  the 
interpreter  itself  would  be  somewhere 
in  the  first  64K  of  memory  ($0  to 
$FFFF).  If  you  move  it  above  64K,  you 
will  have  to  modify  the  EXEC  routine 
and  check  the  rest  of  the  code  carefully 
to  make  sure  the  addressing  modes  are 
correct. 

Evaluation 

I  am  quite  pleased  with  how  the  inter¬ 
preter  turned  out.  Even  though  I  added 
extra  error  checking,  lower-case  con¬ 
version,  and  more  commands  and  ex¬ 
tended  the  variable  size  to  32  bits,  the 
whole  thing  still  fits  inside  3K  of  mem¬ 
ory.  I  ran  the  Sieve  of  Eratosthenes 
benchmark  program  on  this  interpret¬ 
er  and  on  the  Sherry  Brother’s  CP/M 
tiny  BASIC  with  the  following  results: 

68000  at  4  MHz  Z80  at  4  MHz 
2670  seconds  3000  seconds 

Although  I  adjusted  the  results  for 
the  usual  10  iterations  of  the  basic  al¬ 
gorithm,  I  actually  ran  the  program 
only  for  one  iteration  to  keep  running 
times  within  a  practical  limit.  This  tiny 
BASIC  may  not  be  a  speed  demon,  but 
it  does  beat  Applesoft  and  PET  BASIC 
at  running  the  Sieve  benchmark.  I 
should  add  that  I  compressed  the  Sieve 
program  listing  to  the  maximum  for 
speed  considerations;  I  normally  use 
more  spaces  and  some  comments  so 
that  I  can  figure  out  later  what  the  pro¬ 
gram  was  supposed  to  do! 

Of  course,  many  improvements  can 
be  made  given  more  available  memory. 
My  Educational  Computer  Board  has 
32K  of  memory,  so  I  probably  will  add 
such  things  as  more  variables,  strings, 
and  keyword  tokenization.  The  last  is  a 
method  used  by  most  BASIC  interpret¬ 
ers  to  compress  keywords  such  as  LET 
and  PRINT  into  single  bytes.  This 
would  greatly  speed  up  the  interpreter 
while  using  less  memory  to  store  the 
BASIC  program. 

Availability 

By  the  time  you  read  this,  the  inter¬ 


preter  source  code  and  some  example 
programs  should  be  available  on  a  cou¬ 
ple  of  the  RCP/M  bulletin  board  sys¬ 
tems  in  my  area: 

Meadowlark  RCP/M  (403)  484- 
5981 

Edmonton  RCP/M  (403)  454-6093 

The  Edmonton  RCP/M  accepts  both 
300  and  1200  baud,  but  the  Meadow¬ 
lark  system  allows  access  to  its  CP/M 
area  only  at  1200  baud.  Both  systems 
run  24  hours  a  day. 

The  interpreter  source  code  is 
known  as  TBI68K.AQM,  which  is  a 
“squeezed”  text  file.  If  you  don’t  have 
a  MODEM7  type  program  and  a  way 
to  unsqueeze  this  file,  you  can  use 
these  systems’  LIST  command  to  list 
out  the  source  code  while  you  capture 
it  with  a  telecommunications  program. 

A  short  documentation  file, 
TBI68K.DQC,  and  some  sample  pro¬ 
grams,  TBIPROGS.LBR,  are  also  avail¬ 
able.  The  latter  is  a  CP/M  library  file, 
which  contains  several  programs.  You 
can  list  the  library’s  contents  with  the 
LDIR  command  and  extract  individual 
programs  using  either  the  systems’ 
XMODEM  or  LTYPE  commands.  The 
Quelo  cross-assembler  is  also  some¬ 
times  available  on  these  systems  under 
the  names  A68K.COM  and  A68K.DOC. 

Although  I’d  prefer  that  you  obtain 
the  source  code  from  one  of  the  above 
sources,  for  $20  I  can  provide  the  code 
in  the  following  forms:  8-inch  CP/M 
SSSD  diskette,  5-inch  Osborne  or  Ap¬ 
ple  CP/M  diskettes,  or  a  paper  listing. 

If  you  find  any  bugs  in  the  interpret¬ 
er  or  have  any  questions,  please  write 
to  me  or  contact  me  on  the  above  RCP/ 
M  system. 

DDJ 

Reader  Ballot 

Vote  for  your  favorite  feature/article. 

Circle  Reader  Service  No.  1 94. 


Dr.  Dobb’s  Journal,  February  1985 


In  another  article,  I  described  a  public  key  system  based  on 
the  RSA  algorithm.1  The  major  disadvantage  of  that  algo¬ 
rithm  was  that  it  was  not  very  fast,  either  in  key  generation 
or  in  encryption/decryption.  However,  if  you  need  the  secu¬ 
rity,  you  probably  should  use  the  RSA  cryptography  system 
or  the  DES  cryptosystem. 

Most  information,  however,  has  a  time  value  associated 
with  it.  Thus,  it  is  not  necessary  to  use  a  RSA  or  a  DES 
cryptography  system  on  all  messages.  For  example,  if  a  mili¬ 
tary  operation  were  going  to  be  mounted  within  a  month,  we 
could  encrypt  messages  relating  to  the  operation  using  any 
cryptographic  system  that  we  can  show  to  be  unbreakable 
over  that  period  of  time  (plus  some  “slop”  for  weather,  de¬ 
lays,  etc.). 

Private  key  systems  are  fast  and  can  be  difficult  to  break. 
The  cryptography  system  described  in  this  article  is  based  on 
a  method  developed  by  the  Germans  during  World  War  I. 
David  Kahn,  in  his  book  The  Code  Breakers  (MacMillan, 
1967),  indicated  that  this  field  cipher  system  was  probably 
the  toughest  then  developed.2  The  original  cipher  was 
termed  the  ADFGX  system,  and  it  took  Painvin,  a  French 
cryptoanalyst,  over  a  month  to  decode  the  first  batch  of  mes¬ 
sages  that  the  Germans  encrypted. 

The  ADFGX  system,  however,  has  the  same  problem  that 
the  DES  system  or  any  other  private  key  system  has:  the  key 
must  be  known  to  both  the  encryptor  and  the  decryptor. 
Muller-Schloer  has  suggested  one  way  around  this  problem.3 
Having  some  way  to  send  the  key  with  the  message  would 
eliminate  the  logistics  of  key  distribution.  The  question  be¬ 
comes:  How  can  we  do  that  without  giving  everything  away? 
The  answer  is  really  simple!  We  encrypt  the  message  with  a 
private  key  system.  Next  we  encrypt  the  private  key,  using  a 
public  key  system,  and  append  the  encrypted  form  of  the 
private  key  to  the  message. 

We  now  have  the  best  of  both  worlds.  The  private  key 
system  can  encrypt /decrypt  a  message  rapidly  (in  hardware 
or  in  software),  and  we  can  even  change  the  key  for  every 
message.  Because  the  private  key  is  short,  it  does  not  take 
long  to  encrypt/decrypt  it  using  a  public  key  system. 

This  article  describes  an  enhanced  version  of  the  ADFGX 
cipher  system:  the  cipher,  the  character  substitution  method, 
the  use  of  a  key  or  password  to  do  an  irregular  columnar 
transposition,  a  run-length  data  compression  method,  and 
some  of  the  housekeeping  functions  performed  in  the  encryp¬ 
tion/decryption  process.  The  substitution  method  contains  a 
“card”-shufffing  algorithm  and  a  random  number  generator. 
The  language  used  to  write  these  routines  is  C. 

Before  we  get  into  the  details  of  my  implementation,  we 
should  look  at  the  original  German  ADFGX  cipher  system  to 
get  a  feel  for  where  we  will  be  heading. 

The  ADFGX  Cipher 

The  ADFGX  cipher  was  named  after  the  letters  of  the  Morse 
code  used  in  the  transmission  of  the  encrypted  messages.  Be- 


An  Enhanced 
ADFGVX 
Cipher  System 

by  C.  E.  Burton 

Cryptography  is  too  important 
to  be  left  to  the  NS  A. 


Charles  E.  Burton,  13284  W.  Utah  Circle,  Denver,  CO 
80228. 


Dr.  Dobb's  Journal,  February  1985 


48 

121 


cause  these  particular  characters  are  quite  distinctive,  the 
Germans  felt  that,  even  if  noise  partially  garbled  the  message, 
these  characters  could  not  be  confused.  For  those  of  you  unfa¬ 
miliar  with  Morse  code,  it  represents  the  alphabet  by  a  series 
of  dots  and  dashes.  The  telegraph  system  sent  and  received 
messages  translated  into  Morse  code,  and  after  Marconi  de¬ 
veloped  the  radio,  Morse  code  was  used  over  the  air  waves. 
The  Morse  code  representation  of  the  cipher  letters  is: 

A 

D  - .  . 

F  . 

G  — . 

X  - . .  - 

With  these  letters,  the  Germans  could  represent  a  scrambled 
alphabet  in  a  5  X  5  square  matrix  (leaving  out  one  letter, 
say,  Q).  The  substitution  matrix  might  have  looked  like  this: 

A  D  F  G  X 


D/1  0/2  G/3  M/4  E/5 

~A  G  G  A  cT 

A  A  G  G  A 

F  A  F  A  D 

A  F  X  A  A 

F  D  F  A  F 

A  F  F 


A/6  T/7 

~A  F 

F  D 

X  G 

A  A 

A  G 


Message 
MEET 
M  E  A 
T  T  H  E 
ZOO 
A  T  T  E 
N 

Finally,  we  pull  the  columns  out  and  rearrange  them  as  rows 
in  the  previously  determined  order  (6/1  /5/3/4/2/7): 


A 

I) 

F 

G 

X 


() 

u 

T 

E 

F 


1) 

Y 
A 
.1 

V 


CMS 
X  L  H 
N  W  Z 
G  I  P 
B  R  K 


6  /  1  /  5  /  3  / 

AFXAA  /AAFAFA  /GADAF  /GGFXFF  / 

_ 1 _ L _ 2 _ / _ 7 _ 

GAAA  /GAAFDF  /FDGAG 


The  next  item  required  was  a  key,  which  is  used  to  do  a 
columnar  transposition.  We  assign  each  letter  in  the  key  a 
column  number.  Then  we  sort  the  key  alphabetically  by  char¬ 
acter  to  get  the  column  order  for  transmission.  Let  us  choose  a 
key:  DOG  MEAT.  Squeezing  out  the  blanks  and  alphabetical¬ 
ly  sorting  the  letters  of  the  key,  we  get  ADEGMOT.  The  sorted 
column  order  is  6/1/5/3/4/2/7: 

D  O  G  M  E  A  T 
12  3  4  5  6  7 

becomes 

ADEGMOT 
6  15  3  4  2  7 

Now,  we  need  a  message  to  see  how  all  of  this  works.  Let’s 
use  the  message,  “Meet  me  at  the  zoo  at  ten.”  After  remov¬ 
ing  all  of  the  spaces  and  punctuation,  we  have: 
MEETMEATTHEZOOATTEN 

Next  we  take  each  letter,  in  turn,  and  pass  it  through  the 
ADFGX  substitution  table  to  get  the  row  and  column  address 
(in  the  form  of  a  row/column  pair  using  the  letters  ADFGX). 
For  example,  the  letter  M  would  become  AG  (row  A,  col¬ 
umn  G): 

MEETMEATTHE 
AG  GA  GA  FA  AG  GA  FD  FA  FA  DX  GA 

ZOOATTEN 
FX  AA  AA  FD  FA  FA  GA  FF 

We  place  these  letter  pairs  in  a  table  that  has  columns  head¬ 
ed  by  the  key  letters  (DOG  MEAT).  The  key  transposition 
table  is  loaded  a  row  at  a  time: 


The  message  is  now  encrypted  and  ready  for  transmission 
(without  the  “/”s).  Note  that,  although  the  message  length 
has  doubled,  sending  the  encrypted  message  in  this  fashion 
gave  the  Germans  benefits  that  outweighed  this  disadvan¬ 
tage:  the  distinctive  nature  of  the  letters  transmitted.  Cer¬ 
tainly  they  could  have  run  the  transposed  message  back 
through  the  substitution  table  to  get  a  message  of  length 
equal  to  the  original: 

AF  XA  AA  AF  AF  AG  AD  AF  GG  FX 

CFOCCMDC1  Z 

FF  AG  AA  AG  AA  FD  FF  DG  AG 

NMOMOANLM 

But  this  would  mean  using  the  whole  alphabet  rather  than 
five  letters  and  increase  the  chance  of  error  introduction  at 
two  additional  stages  (one  at  the  sending  end  and  one  at  the 
receiving  end).  Remember,  there  were  no  computers  during 
World  War  I.  On  the  other  hand,  had  the  Germans  taken 
this  extra  step,  in  all  likelihood  it  would  have  taken  much 
longer  to  break  the  cipher. 

The  decryption  process  follows  the  above  steps  in  the  re¬ 
verse  order:  the  received  letters  are  put  into  the  key  table  in 
their  original  columns;  character  pairs  are  read  out  from  the 
rows;  and  these  pairs  are  used  in  the  substitution  table  to  get 
the  original  message.  Note  that  the  receiver  must  know  two 
pieces  of  information  to  decipher  the  message:  the  key  and 
the  substitution  table. 

The  letter  V  was  added  to  the  original  ADFGX  formula¬ 
tion  to  allow  all  26  letters  and  1 0  digits  to  be  used;  the  substi¬ 
tution  table  produced  by  the  ADFGVX  cryptosystem  was  a 
6X6  matrix.  Again  the  36  characters  were  put  into  the 
substitution  matrix  in  a  random  order.  The  same  procedure 
to  encrypt /decrypt  was  used. 


49 

122 


Dr.  Dobb  s  Journal,  February  1985 


Enhanced  ADFGVX 

I  have  enhanced  the  basic  ADFGVX  system  in  several  areas. 
First,  because  it  is  useful  to  be  able  to  encrypt/decrypt  text,  as 
well  as  data,  programs  or  anything  else,  we  must  be  able  to  deal 
with  byte-wide  data.  A  substitution  table  that  can  handle  char¬ 
acters  from  0  to  255  (00  to  FF  hex)  must  be  a  16  X  16  matrix. 

Second,  because  the  “messages”  will  be  longer  than  a  few 
“words,”  one  columnar  transposition  will  not  do;  we  must  block 
the  “message”  into  something  more  suitable.  Each  block  will 
be  passed  through  the  algorithm  for  encryption  and  decryption. 
Because  each  letter  of  the  message  will  be  expanded  to  two 
letters  by  the  substitution  step,  a  transposition  table  with  an 
even  number  of  characters  is  desirable  (but  not  mandatory).  To 
make  the  transposition  more  difficult  to  figure  out,  the  table 
must  not  end  on  an  even  square,  such  as  25  or  36.  That  way  the 
messages  are  not  blocked  into  a  form  that  provides  information 
about  the  ciphering  method.  Foster  has  shown  that  the  use  of 
incomplete  or  irregular  columnar  transposition  makes  the  deci¬ 
phering  task  much  harder.4 

Third,  some  simple  data  compression,  especially  for  text, 
would  reduce  storage  capacity  and  transmission  time.  We  also 
will  do  the  backward  substitution  operation  at  the  end  so  that 
the  message  size  does  not  double — we  have  a  computer  that 
should  not  make  the  mistakes  that  a  human  might. 

A  couple  of  additions  could  make  this  implementation  even 
more  difficult  to  attack  and  break.  First,  make  the  incomplete 
columnar  transposition  variable.  For  example,  the  previous 
key  transposition  table  had  seven  columns.  Say  that  we  need 
between  4  and  10  rows  in  the  transposition  table  before  trans¬ 
posing.  We  could  fill  the  transposition  table  with  an  even 
number  of  characters  from  the  substitution  operation  so  that 
28  (7  X  4)  to  70  (7  X  10)  of  these  characters  are  loaded  into 
the  transposition  table  before  each  column  transposition  oper¬ 
ation.  For  instance,  choose  the  repeating  character  count  se¬ 
quence:  46,  62,  30,  54,  and  38.  On  the  first  pass,  we  load  the 
first  23  (46  /  2)  row/column  pairs  into  the  transposition  ma¬ 
trix;  on  the  next  pass,  we  load  the  next  31  row/column  pairs; 
and  so  on,  cycling  through  the  five  character  count  sequences. 
The  main  problem  with  this  variability  option  is  that  it  in¬ 
volves  another  piece  of  information  that  both  the  sending  and 
receiving  parties  must  know. 

The  second  addition,  which  involves  a  double  irregular  col¬ 
umnar  transposition,  again  requires  that  another  piece  of  in¬ 
formation  be  available  to  both  parties.  To  apply  this  option, 
we  do  everything  as  usual,  using  one  key  to  do  the  first  trans¬ 
position,  then  we  use  a  second  key  to  form  a  second  transposi¬ 
tion  table  (just  as  we  did  with  the  first  key);  we  fill  the  second 
transposition  table  with  the  output  of  the  first  transposition 
table.  Next  we  perform  the  second  transposition,  and  finally 
we  do  the  backward  substitution  to  get  the  encrypted  mes¬ 
sage.  The  technique  of  double  transposition  has  been  used  as  a 
military  cipher  system  (“U.S.  Army  Transposition”)  because 
of  the  difficulty  in  deciphering  the  result.  Foster  indicated 
that  he  knew  of  no  method  to  crack  the  double  transposition 
technique  by  hand  or  by  microcomputer. 

Figures  1  and  2  (page  51)  present  the  structure  charts  for 
the  encryption  and  decryption  processes,  respectively.  Rath¬ 
er  than  have  each  of  the  boxes  represent  a  separate  routine,  I 
have  combined  many  of  these  routines  together.  Those  boxes 
with  an  asterisk  (*)  above  them  indicate  separate  modules; 

Dr.  Dobb’s  Journal,  February  1985 


their  names  are  given  below  the  box.  Those  boxes  without 
the  special  notation  appear  in  their  parent  box.  The  looping 
arrow  at  the  bottom  of  a  box  indicates  repetition,  and  the 
diamond  indicates  a  decision  (the  child  may  or  may  not  be 
called,  depending  on  the  outcome  of  the  decision). 

We  now  are  ready  to  discuss  the  various  procedures.  Even 
though  I  designed  the  software  in  a  top-down  fashion,  I  will 
introduce  the  modules  in  a  somewhat  bottom-up  fashion. 

Substitution  Matrix 

We  must  generate  a  16  X  16  matrix  (256-entry  table)  to  get 
our  character  substitutions.  Rather  than  use  characters 
(e.g.,  ADFGVX  or  equivalent)  to  represent  the  rows  or  col¬ 
umns,  we  will  use  the  row  and  column  numbers  (0  to  15).  We 
must  fill  the  matrix  positions  in  a  random  order.  A  simple 
card-shuffling  routine  can  be  used  to  do  this.  We  also  must 
overcome  one  minor  problem:  how  can  we  find  the  “card” 
value,  given  its  position  in  the  “deck,”  and  how  can  we  find 
the  position  in  the  “deck”  of  a  “card”  of  a  given  value.  We 
need  these  answers  to  perform  both  the  forward  and  back¬ 
ward  substitution  procedures. 

The  routines  that  handle  the  matrix  filling  are  shuffle(  )  and 
ran( ).  shuffle( )  takes  a  linear  array  (having  256  character 
positions)  and  fills  the  entries  in  linear  order.  Then  it  passes 
through  the  “deck”  and  swaps  the  current  “card”  with  another 
card  in  a  random  position  as  determined  by  a  call  to  ran( ). 

Once  the  “deck”  has  been  shuffled,  it  is  scanned  to  find  the 
position  of  each  “card”  within  it.  The  result  is  two  linear  arrays: 
position[I]  -  holds  the  position  of  card  I  within  the  deck 
value[J]  -  holds  the  value  of  the  Jth  card  in  the  deck 
Given  position  [  ],  we  can  find  the  row  and  column  position  of 
a  character  (or  perform  the  forward  substitution)  by: 
row  =  position[char]  /  16 
column  =  position[char]  %  16 
(i.e.,  position[char]  mod  16) 

Given  value[  ],  we  can  find  the  character  occupied  by  a  row 
and  column  (or  perform  the  backward  substitution)  by: 

char  =  value[  16  *  row  +  column] 

Note  that  we  are  using  a  linear  array  to  represent  a  matrix. 

ran(MAX)  returns  a  random  integer  between  0  and 
(MAX—  1).  We  can  alter  the  random  number  generation  se¬ 
quence  by  changing  the  initial  value  of  IY  to  be  greater  than  0 
and  less  than  2796203.  One  possibility  might  be  to  declare  IY 
as  a  global  variable  and  to  ask  for  a  seed  value  in  the  main 
program.  Remember,  however,  both  the  sender  and  the  re¬ 
ceiver  must  have  the  same  starting  seed  to  produce  identical 
substitution  tables.  For  the  same  reason,  the  substitution  table 
must  be  generated  before  any  other  use  is  made  of  ran(  ). 

Irregular  Columnar  Transposition 

From  the  ADFGX  example,  we  found  that  the  transposition 
involved  several  steps.  First,  given  a  password  or  key,  we 
must  sort  the  password  on  a  character-by-character  basis. 
This  job  is  performed  by  pwsort( ).  In  pwsort(  ),  I  have  limit¬ 
ed  the  length  of  the  password  to  values  greater  than  4  and 
less  than  1 1.  If  the  password  is  less  than  5,  the  routine  gives 
an  error  message  and  exits  to  the  operating  system.  If  the 
password  is  greater  than  10,  the  password  is  truncated  to  the 
first  10  characters.  You  can  alter  the  routine  to  use  other 
password  lengths.  Once  the  password  is  validated,  we  sort 

50 

123 


the  characters  individually  using  a  simple  bubble  sort. 

The  array  pwcolumnorder[  ]  holds  the  sorted  order  of  the 
transposition  columns.  This  sort  arranges  the  columns  in  de¬ 
scending  (largest  to  smallest)  order;  i.e.,  DOGMEAT  be¬ 
comes  TOMGEDA.  Variations  could  arrange  the  sorted  col¬ 
umns  in  ascending  order,  inside-out,  outside-in,  and  so  on,  as 
long  as  the  sender  and  receiver  agree.  pwcolumnorder[0] 
provides  the  first  column  and  pwcolumnorder[pwlen— 1  ] 
gives  the  last  column  to  extract. 

Second,  we  must  determine  the  size  of  the  transposition 
table.  This  is  done  in  the  main(  )  routine.  I  have  picked  a 
length  to  ensure  the  columns  are  irregular  by  using: 
blklen  =  pwlen  *  pwlen  +  pwlen  /  2 
blklen  +  =  odd(blklen)  ?  1  :  0 

(i.e.,  if  blklen  is  odd  then  increment  blklen  else  leave  it 
alone) 

This  makes  the  transposition  matrix  (pwlen  +  1)  rows  and 
pwlen  columns,  with  the  last  row  incomplete.  Note  that  I 
have  made  the  number  of  entries  even  so  that  row/column 
pairs  are  not  broken. 

Finally,  we  must  fill  the  transposition  matrix  and  perform 
the  transposition.  For  encryption,  we  must  enter  the  infor¬ 
mation  from  the  forward  substitution  matrix  into  the  trans¬ 
position  matrix  by  rows  and  in  the  substitution  row/column 
order.  Once  the  transposition  matrix  is  filled,  the  routine 
transpose(  )  is  called  to  pull  out  the  columns  in  sorted  order. 
These  new  row/column  pairs  are  passed  through  the  back¬ 
ward  substitution  and  then  saved  as  the  encrypted  message. 

For  decryption,  we  must  enter  the  information  from  the 
forward  substitution  matrix  into  the  transposition  matrix  by 
columns  and  in  the  sorted  column  order.  Once  the  transposi¬ 
tion  matrix  is  full,  the  dual  routine  untranspose(  )  is  called  to 
pull  out  the  rows  in  their  original  order.  These  new  row/ 
column  pairs  are  run  through  a  backward  substitution  and 
saved  as  the  plain  text  message. 

Actually,  the  transposition  matrix  is  filled  in  a  linear  or¬ 
der,  and  the  transpose(  )  and  the  untranspose(  )  routines 
take  care  of  obtaining  the  proper  ordering  of  the  rows  and 


columns.  The  transposition  matrix-filling  operation  pro¬ 
duces  the  message  blocking  that  1  mentioned  previously. 

One  minor  problem  with  this  operation  is  the  handling  of 
messages  that  are  not  an  even  multiple  of  blklen  (the  usual 
case).  A  simple  solution  is  to  pad  the  end  of  the  message  with 
random  characters.  However,  to  make  sure  that  the  decryp¬ 
tion  returns  the  original  message  without  the  padding,  we 
must  arrange  for  the  length  of  the  original  plain  text  mes¬ 
sage  to  be  found  and  saved  at  the  beginning  of  the  encrypted 
file.  I  have  done  this  by  using  a  union  called  filelen.  It  is  used 
identically  to  an  “equivalence”  in  Fortran.  Because  the  C 
library  routine  putc(  )  handles  only  bytes,  I  built  the  union  to 
hold  a  long  integer  (the  file  length)  and  an  equivalent-sized 
character  array.  This  construction  allowed  me  to  load/save 
the  file  length  from/to  a  file  in  a  byte-at-a-time  mode. 

Data  Compression/ Expansion 

A  useful  program  is  available  on  most  CP/M-based  bulletin 
board  systems.  Called  SQ/USQ  (squeezer),  it  allows  files  to 
be  compressed  and  expanded  so  that  you  can  save  disk  space. 
Richard  Greenlaw  wrote  the  software,  which  uses  two  types 
of  compression:  run  length  compression  and  Huffman  cod¬ 
ing.  The  achievable  compression  ranges  from  3%  to  70%, 
depending  on  the  makeup  of  the  file.  I  have  borrowed  and 
modified  the  run  length  compression  routine  to  perform  my 
data  compression.  The  expansion  routine  that  I  used  is  mod¬ 
eled  after  the  compression  routine.  (Although  Greenlaw’s 
USQ  program  has  a  more  efficient  expansion  routine,  I  chose 
to  do  my  expansion  differently.) 

What  makes  these  compression/expansion  routines  inter¬ 
esting  is  that  they  are  good  examples  of  state  machine  rou¬ 
tines.  Figures  3a  and  3b  (below)  show  the  state  diagrams  for 
the  compress!  )  and  expand!  )  routines,  respectively.  These 
routines  show  how  the  switch-case  construct  can  be  used  to 
advantage  in  representing  the  states.  In  the  figures,  the  bub¬ 
bles  represent  the  states  and  the  arrows  represent  the  state 
transition  conditions.  Similar  directed  graphs  are  used  in 
electrical  engineering  to  represent  logic  circuit  operations. 


51 


-1  0/1 


Dr.  Dobb's  Journal.  February  1985 


Make  Plain  Text 
File  Name 


Modify  File 
Name  Extension 


Save  Plain  Text 
File  Length 


Get  a  Random 
Number 


f  Routine  Name 


Compress 
Plain  Text 


COMPRES S(  ) 


Get  Character 
Position  in  the 
Randomized 
Lookup  Table 


Transpose  Row  and 
Column  Positions 
using  Key 

TRANSPOSE {  ) 


Get  New  Char 
Valua  from 
Randomized 
Lookup  Table 


Save  Encrypted 
Characters 


Figure  1 

Encrypt  Structure  Chart 


Modify  File 
Name  Extension 


Get  Plain  Text 
File  Length 


Decrypt  an 
Enciphered 
Block 


*  Routine  Name 


Get  a  Block  of 
Enciphered 
Text 


Get  Character 
Position  in  the 
Randomized 
Lookup  Table 


Transpose  Row  and 
Column 

Positions  using  Key 

UNTRANSPOSE(  ) 


Get  New  Char 
Value  from 
Randomized 
Lookup  Table 


Figure  2 

Decrypt  Structure  Chart 


Dr.  Dobb’s  Journal,  February  1985 


52 

125 


Software  dealing  with  control  mechanisms  also  uses  state 
diagrams.  Comparing  the  state  diagrams  with  the  routines 
provides  an  easy  understanding  of  the  code. 

Basically,  the  run  length  compression  routine  looks  at  the 
character  stream.  If  three  or  more  adjacent  characters  are  the 
same,  the  routine  counts  the  like  characters  and  substitutes  a 
three-byte  string  that  incudes  the  character,  a  special  character 
indicating  compression  has  occurred,  and  the  character  repeat 
count;  i.e.,  <char>  SYN  <count>.  To  handle  the  circum¬ 
stance  where  the  special  character  occurs  in  the  character 
stream,  the  routine  generates  the  special  character  and  a  char¬ 
acter  count  of  zero;  i.e.,  SYN  <0>.  Finally,  if  an  end  of  file 
(EOF)  occurs,  the  routine  responds  with  an  EOF  thereafter. 
The  expansion  operation  essentially  reverses  the  process. 

Housekeeping  Operations 

One  of  the  nice  features  of  C  is  that  it  allows  you  to  read 
information  from  the  command  line  that  calls  the  program 
into  execution.  I  have  used  this  capability  in  both  the  encryp¬ 
tion  and  the  decryption  programs.  With  either  program,  you 
specify  the  execution  program  name,  followed  by  the  plain 
text  filename,  followed  by  the  password  or  key.  Most  of  the 
early  housekeeping  involves  picking  up  the  filename  and 
password. 

Once  the  plain  text  filename  is  retrieved,  the  encrypted 
filename  is  generated  from  the  plain  text  filename  by  modi¬ 
fying  the  filename  extension.  If  the  plain  text  filename  ex¬ 
tension  has  at  least  one  character,  a  Y  is  substituted  for  the 
second  letter  of  the  extension.  If  there  is  no  extension,  an 
extension  of  .YYY  is  appended  to  the  filename.  For  example, 
if  the  plain  text  filename  is  MESSAGE.TXT,  then  the  en¬ 
crypted  filename  becomes  MESSAGE. TYT. 

The  encryption  program  checks  for  the  existence  of  the 
plain  text  source  file,  and  the  decryption  program  checks  for 
the  existence  of  the  encrypted  source  file.  If  the  source  file 
cannot  be  found  and  opened  for  reading,  the  programs  exit  to 
the  operating  system  after  generating  an  error  message. 
Similarly,  if  the  target  file  is  opened  for  writing  and  a  prob¬ 
lem  occurs,  a  message  is  generated  and  an  exit  takes  place. 
After  taking  care  of  the  files,  the  password  is  retrieved  and  is 
checked  using  pwsort(  ),  as  already  described. 

As  indicated  previously,  the  length  of  the  plain  text  file 
must  be  found.  The  encryption  process  determines  the  plain 
text  file  length  using  the  fseek(  )  and  ftell(  )  functions  found 
in  the  C  library.  It  saves  the  length  at  the  head  of  the  en¬ 
crypted  file.  The  decryption  process  reads  the  file  length 
from  the  encrypted  file  and  uses  the  value  to  determine  when 
the  end  of  the  plain  text  file  has  occurred,  exclusive  of  the 
padding  characters. 

Once  these  steps  are  complete,  the  encryption/decryption 
process  proceeds  as  already  explained.  The  arrays  BUF1[  ] 
and  BUF2[  ]  hold  the  row/column  pairs  that  result  from  the 
substitution  process.  These  arrays  contain  a  block  of  mes¬ 
sage,  the  length  of  which  is  twice  the  length  of  the  plain  text 
or  encrypted  text  block.  BUF1  [  ]  holds  the  forward  substitu¬ 
tion  pairs;  BUF2[  ]  contains  the  result  of  the  transposition 
that  is  ready  for  submission  to  the  backward  substitution. 
The  encryption  program  uses  these  arrays  in  the  main(  ) 
routine,  and  the  decryption  program  uses  them  in  the 
getcnxt(  )  routine.  Note  that  these  arrays  are  large  enough 

54 

1  oc 


for  the  largest  password  used  in  the  columnar  transposition 
(plus  a  little  bit). 

After  the  encryption/decryption  process  is  complete,  you 
no  longer  need  to  keep  the  source  file,  so  it  is  erased.  When 
you  are  debugging  these  programs,  you  will  probably  want  to 
inhibit  the  unlink(  )  function  that  performs  this  file  deletion. 

One  final  operation  is  performed  in  the  encryption  program 
prior  to  source  file  deletion.  Because  certain  programs  can 
“unerase”  or  look  at  a  disk  on  a  sector-by-sector  basis,  and  we 
do  not  want  to  leave  any  traces  of  the  original  plain  text  file, 
we  must  overwrite  the  entire  plain  text  file  with  something.  I 
have  chosen  to  use  the  character  F6  hex,  but  any  byte  charac¬ 
ter  will  do.  Again  you  will  probably  want  to  inhibit  this  opera¬ 
tion  while  debugging.  (There  is  no  need  to  overwrite  the  en¬ 
crypted  file,  since  it  is  “gibberish”  anyway.) 

Example 

Figure  4a  (page  55)  is  the  original  plain  text  file  that  I  en¬ 
crypted  and  subsequently  decrypted.  I  have  shown  the  en¬ 
crypted  text  file  (Figure  4b,  page  55)  and  the  decrypted  text 
file  (Figure  4c,  page  56)  in  both  hex  and  ASCII  representa¬ 
tions;  this  is  essentially  the  same  representation  used  by  the 
D  command  under  DDT.  Note  that  the  encrypted  file’s  first 
four  bytes  provide  the  length  of  the  original  plain  text  file.  It 
is  after  that  point  (from  the  fifth  byte  to  the  end  of  the  file) 
that  the  encrypted  text  of  the  original  plain  text  file  occurs. 
The  plain  text  file  has  several  repeated  character  sequences 
to  demonstrate  data  compression. 

To  duplicate  my  results,  let’s  call  the  plain  text  file 
ADFGVX.TXT  and  use  the  password  GERMANS.  To  encrypt 
ADFGVX.TXT,  you  type  in  the  command  line: 

ENCRYPT  adfgvx.txt  germans 
This  produces  the  encrypted  file  ADFGVX.TYT  and  destroys 
ADFGVX.TXT.  To  decrypt  the  encrypted  file,  you  type  the 
command  line: 

DECRYPT  adfgvx.txt  germans 
This  produces  the  decrypted  file  ADFGVX.TXT  and  erases 
ADFGVX.TYT.  You  enter  the  command  lines  at  the  operat¬ 
ing  system  prompt;  e.g.,  at  the  >A  under  CP/M.  You  must 
compile,  assemble,  and  link  the  programs  so  that  the  encryp¬ 
tion  program  has  a  name  of  ENCRYPT.COM  and  the  de¬ 
cryption  program  has  the  name  DECRYPT.COM. 

Note  that  this  implementation  does  not  remove  spaces, 
punctuation,  and  so  on.  Because  the  substitution  table  covers 
the  entire  byte-wide  character  set,  what  you  put  in  will  come 
out  at  the  other  end  unaltered.  You  do  not  have  to  squeeze 
together  then  break  up  the  text,  as  we  had  to  do  with  the 
original  German  cipher.  In  addition,  you  can  encrypt/de¬ 
crypt  any  file. 

Conclusion 

Although  this  cipher  is  not  as  secure  as  a  RSA  or  a  DES 
cryptosystem,  its  character  set  scrambling  makes  it  indistin¬ 
guishable  from  these  other  methods.  According  to  Kahn, 
this  cipher  is  not  easily  broken.  The  advantages  are  that  the 
cipher  is  simple  to  implement  and  fast  in  execution.  The 
major  disadvantage  is  that  both  the  sender  and  the  receiver 
must  know  the  key  or  password  and  the  substitution  table 
ordering.  As  I  pointed  out,  you  can  overcome  this  difficulty 
by  using  a  public  key  system  to  encrypt  the  key  and  table 

Dr.  Dobb’s  Journal,  February  1985 


First  of  all,  let's  generate  a  string  of  repeated  characters: 

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX 

YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY 

These  sequences  will  be  converted  by  the  data  compression 
routine  to  the  following  three  character  representation: 

<x>  SYN  <52>  =78  16  34  (in  hex) 

<Y>  SYN  <52>  =  59  16  34  (in  hex) 

Notice  that  we  are  not  restricted  to  upper  case  letters  and 
digits  only.  This  implementation  does  not  have  to  remove 
spaces  and  punctuation.  Thus,  all  ASCII  characters  (0  to 
127)  can  be  used,  as  well  as  the  byte  values  from  128  to  255. 

The  encrypted  file  will  not  have  a  one-to-one  correspondence 
between  these  characters  and  those  produced  by  the  encryption 
process,  i.e.  letter  frequency  analysis  will  not  help.  This 
result  is  due  to  the  transposition,  which  may  replace  an  "a" 
in  one  case  by  a  "Y"  and  in  another  case  by  a  etc. 

Figure  4a 

Plain  Text  to  be  encrypted.  File  Name  is  ADFGVX.TXT. 


80 

04 

00 

00 

56 

FA 

BO 

CD 

B7 

AC 

8D 

EB 

32 

6C 

67 

59 

. 

a 

.  .  V. 

. 21  gY 

F7 

9E 

20 

9E 

4B 

AO 

8D 

26 

20 

25 

BO 

75 

5C 

83 

6F 

60 

a 

a 

•  K. 

.  &  7. .  u  \ .  o  ' 

09 

AC 

28 

46 

El 

32 

26 

B9 

FE 

OB 

82 

5C 

5C 

6D 

OB 

20 

. 

. 

( F . 2& . . . . \ \m . 

9E 

20 

OB 

2C 

7C 

65 

92 

9E 

3E 

F9 

16 

F6 

OB 

3D 

54 

54 

. 

-  !.  !  e 

=TT 

B4 

79 

D7 

E8 

9D 

E6 

C2 

2B 

77 

27 

20 

41 

5E 

2C 

AE 

7A 

a 

y 

.  .  ,  a 

.  +W  ’  A'',.  2 

C2 

F2 

OA 

65 

F7 

A7 

C2 

31 

31 

B3 

D2 

20 

54 

F2 

AC 

8D 

. 

.  e.  a 

.  11. .  T. .  . 

A8 

53 

89 

F7 

F3 

OD 

54 

C6 

CD 

33 

31 

OC 

31 

25 

8D 

8D 

a 

s 

a  e  a  e 

T.  .31. 17..  . 

37 

FB 

53 

31 

CF 

48 

20 

00 

65 

18 

AO 

31 

57 

83 

DF 

OB 

7 

a 

Sl.H 

. e. . 1W. . . 

81 

20 

AA 

20 

7A 

FB 

AB 

BD 

OB 

FA 

5C 

AO 

55 

62 

3E 

68 

a 

.  z  . 

.  .  .  .  \  .  Ub  >h 

20 

65 

90 

82 

C2 

FB 

5C 

56 

44 

OB 

35 

C4 

20 

52 

89 

31 

e 

a  .  ,  s 

WD.5.  R.  1 

9E 

65 

6E 

65 

46 

4A 

00 

35 

57 

57 

EB 

ED 

5F 

CF 

FF 

6D 

a 

eneFJ 

. 5WW. .  ..m 

20 

F7 

F7 

OB 

8D 

5D 

BD 

87 

87 

B 1 

25 

B4 

91 

42 

AC 

5F 

a 

.  .  .  1 

_ 7..  -B. 

BA 

35 

E2 

2C 

86 

15 

OB 

OB 

FF 

BD 

FB 

62 

E4 

57 

BD 

2C 

a 

5 

. b.W. , 

8D 

E8 

8D 

9E 

5E 

6B 

CA 

04 

C2 

34 

F7 

25 

D2 

42 

6D 

55 

a 

. 

.  . 

...4.7..  BmU 

AC 

91 

E8 

3D 

62 

38 

08 

OD 

31 

57 

00 

33 

OE 

79 

92 

BD 

a 

, 

.  =b8 

.  .  1W. 3. y. . 

29 

Cl 

6D 

B7 

E6 

FB 

C2 

AC 

90 

FB 

OO 

53 

67 

90 

12 

CD 

) 

a 

m.  a  a 

a  a  a  a  a  Sg  .  .  . 

0E 

04 

58 

D2 

OB 

20 

FF 

4F 

CA 

36 

20 

CF 

F7 

25 

04 

AC 

a 

a 

X.  . 

.0.6  .  .  7..  . 

6D 

39 

BB 

91 

86 

90 

FB 

38 

79 

20 

31 

57 

20 

C2 

09 

79 

m9 

a  .  a  a 

. 8y  1W  . . y 

18 

BD 

81 

40 

25 

31 

C2 

52 

65 

AB 

D2 

9E 

AA 

53 

65 

C6 

a 

. 

.  87.1 

. Re. . . . Se. 

5F 

90 

71 

F7 

77 

OD 

8E 

57 

AC 

33 

5C 

6D 

57 

F7 

40 

05 

. 

q.  w. 

.  W.  3\mW. 8. 

20 

84 

FB 

71 

BD 

41 

9E 

ED 

65 

FB 

20 

9E 

BD 

9E 

62 

40 

a 

.  q.  A 

. . e.  . . . b@ 

7F 

AB 

IB 

FF 

35 

F7 

49 

BD 

OB 

AE 

ED 

6D 

3C 

62 

65 

AA 

'•w 

. 

..5. 

I .  .  . . m<be. 

20 

3E 

20 

CD 

C2 

FB 

38 

E4 

44 

OB 

9D 

DD 

20 

31 

8D 

FB 

S' 

.  a  a 

8. D.  .  .  1 .  . 

OB 

6C 

AA 

48 

35 

C7 

93 

23 

25 

25 

09 

C9 

CB 

63 

CD 

B7 

a 

1 

.  H5. 

a  $/•/!«  a  a  C  a  a 

CD 

8B 

AO 

8D 

70 

69 

09 

82 

OE 

3C 

23 

A5 

6F 

20 

ED 

ED 

a 

a 

.  .  pi 

...<#. O  . . 

20 

DF 

OB 

32 

OB 

9E 

31 

FF 

74 

35 

FB 

3F 

20 

61 

8D 

C7 

. 

.  2.  . 

1 . t5. ?  a. . 

OC 

6D 

9E 

31 

E2 

D2 

58 

6C 

8D 

43 

FB 

FB 

8D 

16 

32 

40 

a 

m 

.  1 .  . 

XI  .C _ 28 

8D 

31 

65 

61 

C2 

5C 

85 

C7 

70 

29 

FA 

20 

90 

BD 

CF 

8D 

e 

i 

ea.  \ 

aap)e  aaaa 

2C 

BO 

C6 

OD 

8D 

65 

69 

25 

E2 

44 

EA 

63 

20 

8B 

OF 

31 

n 

a 

a  a  -  e 

i  7. .  D .  c  .  .  1 

20 

A2 

A6 

OB 

48 

A4 

OE 

8B 

C2 

41 

20 

AC 

A8 

7A 

C6 

21 

a 

.  .H. 

.  .  .  A  .  .  z  .  ! 

57 

9D 

57 

52 

84 

69 

6D 

80 

OB 

20 

20 

63 

75 

8D 

87 

BD 

w 

a 

WR.  i 

Dae  CUa  a  a 

F8 

49 

09 

OB 

C6 

20 

20 

31 

BD 

D2 

C9 

44 

68 

AO 

20 

CD 

. 

I 

a  a  a 

1 .  . . Dh .  . 

55 

CE 

5C 

31 

C7 

31 

CF 

20 

92 

1C 

F7 

F8 

20 

40 

75 

D3 

U 

a 

\1  .  1 

. @u. 

7A 

20 

20 

5C 

65 

C4 

00 

AO 

3C 

F7 

20 

65 

20 

EC 

75 

26 

2 

\e. 

.  .  < .  e  . u& 

44 

6E 

OC 

31 

57 

18 

86 

17 

25 

20 

FB 

9D 

62 

8D 

6F 

OD 

Dn 

.  1W. 

a  a  /m  aabaOs 

62 

20 

5A 

C2 

74 

2B 

53 

AC 

20 

3F 

17 

A5 

25 

AO 

F2 

62 

b 

Z.t+S.  ? .  .  7. .  .  b 

34 

OB 

97 

15 

62 

F7 

20 

9C 

00 

20 

9D 

C6 

20 

CC 

00 

CD 

4 

a 

.  .  b. 

56 

65 

E4 

E7 

6E 

40 

7A 

4A 

OC 

FF 

65 

04 

68 

FA 

2B 

1C 

Ve 

a  a  n@ 

Z  J.  a€S.ha+B 

75 

6F 

57 

57 

73 

00 

EO 

FB 

5C 

CD 

FA 

IF 

C6 

18 

8D 

OE 

uoWWs. 

.  .  \ . 

F7 

87 

87 

OC 

20 

65 

9D 

9F 

C2 

EO 

31 

2B 

20 

20 

F8 

1A 

a 

. 

a  a  e 

a  *  ,  a  1  f*  aa 

Figure  4b 

Encrypted  Text  using  the  Plain  Text  in  Figure  4a. 
The  first  four  bytes  (80  04  OO  00)  define  the  Plain 
Text  file  length  (00  OO  04  80)  in  bytes.  Usage: 
ENCRYPT  ADFGVX.TXT  GERMANS 


Dr.  Dobb’ s  Journal,  February  1985 


55 

127 


20 

20 

20 

20 

20 

20 

20 

61 

6C 

6C 

2C 

20 

72 

61 

74 

65 

20 

61 

20 

72 

65 

70 

65 

61 

74 

65 

72 

73 

3A 

OD 

20 

20 

20 

20 

20 

20 

78 

78 

78 

78 

78 

78 

78 

78 

78 

78 

78 

78 

78 

78 

78 

78 

78 

78 

20 

20 

20 

20 

20 

20 

59 

59 

59 

59 

59 

59 

59 

59 

59 

59 

59 

59 

59 

59 

59 

59 

59 

59 

0A 

0D 

0A 

20 

20 

20 

65 

20 

20 

73 

65 

71 

6C 

6C 

20 

62 

65 

20 

62 

79 

20 

74 

68 

65 

6D 

70 

72 

65 

73 

73 

20 

20 

20 

20 

20 

72 

74 

68 

65 

20 

66 

6F 

72 

65 

65 

20 

63 

68 

70 

72 

65 

73 

65 

6E 

0A 

20 

20 

20 

20 

20 

78 

3E 

20 

53 

59 

4E 

20 

31 

36 

20 

33 

34 

0D 

0A 

20 

20 

20 

20 

3C 

59 

3E 

20 

53 

59 

39 

20 

31 

36 

20 

33 

29 

0D 

0A 

0D 

OA 

20 

74 

69 

63 

65 

20 

20 

65 

20 

6E 

6F 

74 

20 

20 

74 

6F 

20 

75 

70 

65 

74 

74 

65 

72 

73 

20 

20 

20 

20 

20 

20 

6E 

6C 

79 

2E 

20 

20 

65 

6D 

65 

6E 

74 

61 

6E 

6F 

74 

20 

68 

61 

6D 

6F 

76 

65 

20 

OD 

73 

70 

61 

63 

65 

73 

74 

75 

61 

74 

69 

6F 

20 

20 

61 

6C 

6C 

20 

61 

63 

74 

65 

72 

73 

20 

20 

20 

20 

20 

20 

6E 

20 

62 

65 

20 

75 

6C 

6C 

20 

61 

73 

20 

61 

6C 

75 

65 

73 

20 

6F 

20 

32 

35 

35 

2E 

20 

20 

20 

54 

68 

65 

64 

20 

66 

69 

6C 

65 

68 

61 

76 

65 

20 

61 

65 

20 

63 

6F 

72 

72 

20 

0D 

0A 

20 

20 

20 

65 

65 

6E 

20 

74 

68 

74 

65 

72 

73 

20 

61 

72 

6F 

64 

75 

63 

65 

6E 

63 

72 

79 

70 

74 

20 

20 

20 

20 

20 

70 

2E 

65 

2E 

20 

6C 

65 

65 

6E 

63 

79 

20 

61 

6C 

6C 

20 

6E 

6F 

74 

69 

73 

20 

0D 

OA 

20 

73 

75 

6C 

74 

20 

69 

68 

65 

20 

74 

72 

61 

2C 

20 

20 

77 

68 

69 

6C 

61 

63 

65 

20 

61 

20 

20 

20 

20 

20 

20 

73 

65 

20 

62 

79 

20 

69 

6E 

20 

61 

6E 

6F 

62 

1A 

79 

20 

61 

20 

22 

20 

20 

46 

69 

72 

6C 

65 

74 

27 

73 

20 

73 

74 

72 

69 

74 

65 

64 

20 

63 

OA 

OD 

OA 

20 

20 

20 

78 

78 

78 

78 

78 

78 

78 

78 

78 

78 

78 

78 

78 

78 

78 

78 

78 

78 

78 

20 

20 

20 

20 

20 

59 

59 

59 

59 

59 

59 

59 

59 

59 

59 

59 

59 

59 

59 

59 

20 

20 

20 

20 

20 

75 

65 

6E 

63 

65 

63 

6F 

6E 

76 

65 

20 

20 

64 

61 

74 

69 

6F 

6E 

20 

OD 

6F 

75 

74 

69 

6E 

6C 

6C 

6F 

77 

69 

61 

72 

61 

63 

74 

74 

61 

74 

69 

6F 

20 

20 

20 

20 

20 

20 

3C 

35 

32 

3E 

20 

20 

28 

69 

6E 

20 

20 

20 

20 

20 

4E 

20 

3C 

35 

32 

34 

20 

20 

28 

69 

20 

20 

20 

20 

20 

74 

68 

61 

74 

20 

72 

65 

73 

74 

72 

70 

65 

72 

20 

63 

20 

20 

61 

6E 

64 

20 

64 

69 

67 

69 

20 

54 

68 

69 

73 

74 

69 

6F 

6E 

20 

76 

65 

20 

20 

74 

OA 

20 

20 

20 

20 

20 

20 

61 

6E 

64 

6E 

2E 

20 

20 

20 

41 

53 

43 

49 

49 

20 

28 

30 

20 

20 

20 

20 

20 

31 

32 

73 

65 

64 

2C 

20 

74 

68 

65 

20 

62 

66 

72 

6F 

6D 

20 

OD 

OA 

OD 

OA 

20 

20 

20 

65 

6E 

63 

20 

77 

69 

6C 

6C 

20 

6F 

6E 

65 

2D 

65 

73 

70 

6F 

6E 

20 

20 

20 

20 

20 

65 

73 

65 

20 

63 

6E 

64 

20 

74 

68 

64 

20 

62 

79 

20 

69 

6F 

6E 

20 

OD 

72 

6F 

63 

65 

73 

74 

74 

65 

72 

20 

6E 

61 

6C 

79 

73 

20 

68 

65 

6C 

70 

20 

20 

20 

20 

20 

73 

20 

64 

75 

65 

6E 

73 

70 

6F 

73 

63 

68 

20 

6D 

61 

6E 

20 

22 

61 

22 

20 

69 

6E 

20 

6F 

61 

20 

22 

59 

22 

74 

68 

65 

72 

20 

2A 

22 

2C 

20 

65 

73 

74 

20 

6F 

66 

20 

67 

65 

6E 

65 

6E 

67 

20 

6F 

66 

68 

61 

72 

61 

63 

20 

20 

20 

20 

20 

78 

78 

78 

78 

78 

78 

78 

78 

78 

78 

78 

7B 

78 

78 

78 

OD 

OA 

20 

20 

20 

59 

59 

59 

59 

59 

59 

59 

59 

59 

59 

59 

59 

59 

59 

59 

59 

59 

59 

59 

OD 

20 

54 

68 

65 

73 

73 

20 

20 

77 

69 

72 

74 

65 

64 

20 

61 

20 

20 

63 

6F 

OA 

20 

20 

20 

20 

65 

20 

74 

6F 

20 

6E 

67 

20 

74 

68 

65 

72 

20 

72 

65 

6E 

3A 

OD 

OA 

OD 

20 

20 

20 

20 

3C 

20 

3D 

20 

37 

38 

20 

68 

65 

78 

29 

20 

20 

20 

20 

20 

3E 

20 

3D 

20 

35 

6E 

20 

68 

65 

78 

20 

20 

20 

4E 

6F 

77 

65 

20 

61 

72 

69 

63 

74 

65 

64 

61 

73 

65 

20 

6C 

20 

OD 

OA 

20 

20 

74 

73 

20 

20 

6F 

20 

69 

6D 

70 

6C 

64 

6F 

(£> 

73 

20 

6F 

20 

20 

72 

65 

20 

20 

20 

20 

20 

20 

70 

75 

6E 

63 

54 

68 

75 

73 

2C 

20 

63 

68 

61 

72 

74 

6F 

20 

OD 

OA 

37 

29 

20 

63 

61 

61 

73 

20 

77 

65 

79 

74 

65 

20 

76 

31 

32 

38 

20 

74 

20 

20 

20 

20 

20 

72 

79 

70 

74 

65 

20 

6E 

6F 

74 

20 

74 

6F 

2D 

6F 

6E 

64 

65 

6E 

63 

65 

20 

62 

65 

74 

77 

68 

61 

72 

61 

63 

6F 

73 

65 

20 

70 

74 

68 

65 

20 

65 

OA 

20 

20 

20 

20 

73 

2C 

20 

20 

69 

66 

72 

65 

71 

75 

69 

73 

20 

77 

69 

2E 

20 

20 

54 

68 

20 

20 

20 

72 

65 

20 

74 

6F 

20 

74 

69 

74 

69 

6F 

6E 

79 

20 

72 

65 

70 

20 

OD 

OA 

20 

20 

6E 

65 

20 

63 

61 

20 

61 

6E 

64 

20 

63 

61 

73 

65 

20 

74 

63 

2E 

OD 

OA 

First  of 
all,  let’s  gene 
rate  a  string  of 
repeated  charac 
ters: .... 

xxxxxxxxx 
xxxxxxxxxxxxxxxx 
xxxxxxxxxxxxxxxx 
xxxxxxxxxxx. . 

YYYYY 

YYYYYYYYYYYYYYYY 

YYYYYYYYYYYYYYYY 

YYYYYYYYYYYYYYY. 

. . .  Thes 

e  sequences  wi 
1 1  be  converted 
by  the  data  co 
mpression  . . 

routine  to 
the  following  th 
ree  character  re 
presentation: . . . 

< 

x>  SYN  <52>  =  78 
16  34  (in  hex) 

<Y>  SYN  <52>  -  5 
9  16  34  (in  hex 
) . . . .  No 

tice  that  we  ar 
e  not  restricted 
to  upper  case  1 
etters  and  . . 

digits  o 
nly.  This  impl 
ementation  does 
not  have  to  re 
move  .  . 

spaces  and  punc 
tuation.  Thus, 
all  ASCII  char 
acters  (0  to  . . 

127)  ca 
n  be  used,  as  we 
11  as  the  byte  v 
glues  from  128  t 

o  255 . 

The  encrypte 
d  file  will  not 
have  a  one-to-on 
e  correspondence 
. .  betw 

een  these  charac 
ters  and  those  p 
roduced  by  the  e 
ncryption  . . 

process,  i 
■e.  letter  frequ 
ency  analysis  wi 
11  not  help.  Th 
is  . .  re 

suit  is  due  to  t 
he  transposition 
,  which  may  rep 
1  ace  an  "a"  .  . 

in  one  ca 
se  by  a  "Y“  and 
in  another  case 
by  a  “ *" ,  etc. . . 


Figure  4c 

Decrypted  Text  using  the  Encrypted  Text  in  Figure  4b. 
Usage:  DECRYPT  ADFGVX.TXT  GERMANS 


56 


Dr.  Dobb’s  Journal,  February  1985 


then  appending  this  information  to  the  encrypted  message. 

In  everyday  use,  this  cipher  is  probably  adequate.  You  can 
make  it  more  secure  by  using  the  additions  that  I  have  suggest¬ 
ed.  Remember,  however,  that  it  all  depends  on  the  time  value  of 
the  information  that  you  are  securing.  If  you  want  to  be  sure 
that  unauthorized  persons  cannot  decipher  your  information 
for  decades,  you  should  use  a  RSA  or  a  DES  cryptosystem.  For 
shorter  periods,  this  enhanced  ADFGVX  cipher  should  be  ef¬ 
fective:  until  the  discovery  of  the  DES  and  RSA  methods,  it  was 
claimed  to  be  one  of  the  “best”  ciphers  around. 

References 

1  C.  E.  Burton,  “RSA:  A  Public  Key  Cryptography  System, 


Parts  1  and  2,”  DDJ ,  March  and  April  1984. 

2  D.  Kahn,  The  Code  Breakers ,  Macmillan  Book  Co.,  1967. 

3  C.  Muller-Schloer,  “A  Microprocessor-based  Crypto¬ 
processor,”  IEEE  MICRO ,  vol.  3,  no.  5,  October  1983,  pp. 
5-15. 

4  C.  C.  Foster,  Cryptoanalysis  for  Microcomputers,  Hayden 
Book  Co.,  1982. 

DD) 

(Listings  begin  on  next  page) 


1  would  like  to  thank  John  K.  Taber,  the  reviewer,  for  sug¬ 
gesting  three  improvements  to  this  cipher  system:  a  better 
random  number  generator,  a  more  thorough  data  compres¬ 
sion  technique,  and  an  alternate  padding  method. 

Mr.  Taber  first  pointed  out  that  my  pseudo-random  num¬ 
ber  generator  has  a  fairly  small  period  (about  2,796,203)  as 
pseudo-random  number  generators  go.  The  short  period 
makes  possible  a  concerted  attack  on  the  cipher.  Because 
there  are  256!  ways  to  fill  the  16X16  array,  and  I  am  using 
only  a  small  fraction  of  them  (based  on  the  period),  he  sug¬ 
gested  a  random  number  with  a  period  of  2  **  64,  or  possibly 

2  **  128,  as  being  safe  for  the  forseeable  future.  Note,  how¬ 
ever,  that  the  ran(  )  function  is  a  stand-alone  module  and 
can  be  replaced  by  a  random  number  generator  of  the  user’s 
choice,  as  long  as  the  interface  remains  unchanged.  The 
reader  might  be  interested  in  knowing  that  Knuth  has  devot¬ 
ed  an  entire  chapter  to  random  numbers,  their  generation, 
and  their  testing  in  his  book  on  seminumerical  algorithms. 

Other  ways  around  this  problem  might  be  to  use  truly 
random  physical  phenomena  to  generate  the  random  num¬ 
bers.  For  example,  the  Allies  during  World  War  II  used  the 
X-System:  a  mercury  vapor  rectifier  tube  that  generated 
wideband  thermal  noise.  The  output  was  sampled  into  six 
levels  of  equal  probabilities  at  a  20  msec  rate  using  nonuni¬ 
form  quantization.  These  random  numbers,  recorded  onto 
plastic  disks  similar  to  phonograph  records,  were  used  to 
scramble  voice  messages.  (See  D.  Kahn,  “Cryptology  and 
the  Origins  of  Spread  Spectrum,”  IEEE  Spectrum,  vol.  21, 
no.  9,  September  1984,  p.  74.) 

But  remember,  you  have  to  contend  with  the  problem  of 
key  distribution:  sending  these  random  numbers,  as  well  as 
the  irrregular  transposition  key,  to  the  receiver.  The  value  of 
the  message  and  its  life  time  will  dictate  the  extremes  to 
which  you  must  go  to  protect  your  data. 

Mr.  Taber  next  suggested  that  better  data  compression 
schemes  exist.  Because  I  used  this  cipher  in  particular  to 
encrypt  data  files  that  consisted  of  numerous  repeated  char¬ 
acter  strings,  the  run  length  code  suited  my  purposes.  As  I 
pointed  out  in  the  article,  Mr.  Greenlaw  has  an  excellent, 
public  domain  data  compression  package  (SQ/USQ).  Read¬ 
ers  interested  in  Huffman  compression  should  obtain  a  copy 
of  Mr.  Greenlaw’s  source  code  from  your  local  RCP/M  bul¬ 
letin  board  system.  You  could  certainly  use  it  to  enhance  my 
compression  scheme. 


One  reason  that  I  did  not  use  the  Huffman  algorithm  was 
because  you  have  to  scan  the  text  twice  to  obtain  the  com¬ 
pression.  You  use  the  first  scan  to  obtain  the  frequency  infor¬ 
mation  and  to  assign  the  compression  codes.  The  second  scan 
compresses  the  data  using  the  compression  codes  and  saves 
the  result  on  disk.  To  uncompress  the  data,  you  must  retain 
the  character/compression  code  substitution  pairs  (or  en¬ 
crypt  the  compression  codes  along  with  the  compressed 
data).  If  the  codes  are  stored  in  a  known  position,  they  could 
be  used  to  break  the  cipher.  On  the  other  hand,  because  these 
Huffman  characters  are  variable  length  codes,  they  might 
make  an  attack  on  the  cipher  more  difficult.  Anyway,  I  did 
not  have  the  need  to  go  to  such  lengths. 

Finally,  Mr.  Taber  suggested  an  alternate  padding  meth¬ 
od.  He  recommended  the  use  of  an  array  at  least  as  long  as 
the  padding  needed,  say,  256  bytes.  The  pad  array  is  filled 
with  some  known  pattern  (byte  by  byte).  Then  each  byte  of 
the  clear  text  is  read  and  exclusive  or’ed  with  the  next  ele¬ 
ment  of  the  pad  array  in  a  circular  fashion;  i.e.,  when  the 
256th  byte  is  used,  the  process  starts  over  with  the  first  byte. 
When  padding  is  required,  the  pad  bytes  are  drawn  from  the 
pad  array.  This  method,  or  another  comparable  method, 
avoids  the  weakness  of  having  a  known  range  of  bytes  or 
known  bytes  in  the  final  block. 

I  do  not  believe  that  most  people  will  have  to  make  any  of 
these  modifications;  the  cipher  (as  written)  should  be  safe 
enough  for  most  uses.  For  example,  MSDOS/PCDOS,  unlike 
MP/M-II  and  CP/M-3,  does  not  allow  for  making  files  read¬ 
only.  Thus,  users  could  use  this  cipher  to  encrypt  those  sensi¬ 
tive  personnel  files  contained  on  a  PC/XT  that  is  used  by 
several  employees. 

With  respect  to  irregular  transposition,  Mr.  Taber  pointed 
out  that  C.  A.  Deavours,  editor  of  Cryptologia,  has  written  a 
BASIC  program  to  help  determine  the  cipher  “breakpoints” 
(i.e.,  the  possible  tops  and  bottoms  for  given  irregular  trans¬ 
position  dimensions).  The  program  uses  an  “odds  in  favor” 
approach  from  statistics  to  measure  when  the  right  break¬ 
points  are  determined.  Deavours  claims  that  he  can  break 
the  cipher  within  five  to  ten  minutes  using  this  technique — 
starting  from  scratch.  (Deavours’  article  describing  the  tech¬ 
nique,  along  with  the  program,  appears  in  Cryptologia,  vol. 
5,  no.  4,  October  1981,  pp.  247-251.)  However,  when  you 
combine  the  irregular  transposition  cipher  with  the  en¬ 
hanced  ADFGVX  cipher,  the  job  of  breaking  the  code  be¬ 
comes  orders  of  magnitude  more  difficult! 


Dr.  Dobb’s  Journal,  February  1985 


57 

129 


ADFGVX  Cipher  System  (Text  begins  on  page  48) 
Listing  One 


/*######################################################################## 


###  ### 

###  Copyright  1983,  Charles  E.  Burton,  Denver,  Colorado  ### 

###  ### 

###  All  rights  reserved.  Permission  granted  to  use  this  software  for  ### 

###  personal,  non-commercial  purposes  only.  ### 

###  ### 


########################################################################*/ 

/« 

*  PROGRAM  NAME:  SHUFFLE. C 

*  PURPOSE:  Card  shuffling  routine  — 

*  Shuffles  a  "deck"  of  cards.  The  number  of  cards  is  NOCARDS. 

*  Random  number  routine  — 

*  Generates  a  random  interger 

* 

*  LANGUAGE:  C 

*  AUTHOR:  CEB 

*  USAGE:  SHUFFLE (POSITION, VALUE) 

*  POSITION  CIO  —  returns  as  the  position  of  Card  #1  in  the  Deck. 

*  VALUE  CJ3  —  returns  as  the  value  of  the  Jth  Card  in  the  Deck. 

*  RAN (MAX) 

*  MAX  —  integer  specifying  the  maximum  random  integer  that  can 

*  be  returned. 

*  ARRAYS  USED:  POSIT I ONCNOC ARDS] , VALUE! NOCARDS 3 

*  EXTERNALS: 

*  UPDATE  HISTORY:  INITIAL  RELEASE  —  11/25/83  CEB 
*/ 

/*  Card  shuffling  routine  *■/ 
shuff le(posit ion, value) 

char  position!];  /*  POSITION  CN0CARDS3  on  entry  */ 
char  val ueC  3  >  /*  VALUE  CN0CARDS3  on  entry  */ 

{ 

char  temp; 
int  i , j  > 

for  (i=0;  i  <  NOCARDS;  i++>  /*  initialize  CARDS  values  */ 
val  ue  Cd  3  =  i  ; 

for  ( i =0;  i  <  NOCARDS;  i++)  /*  shuffle  */ 

! 

j  =  ran (NOCARDS) ;  /*  swap  index  */ 
temp  =  value  Ii3;  /*  swap  CARDS  values  */ 
value  C i 3  =  value  Cj3> 
value  Cj3  =  temp; 

> 

for  (i=0;  i  <  NOCARDS;  i++>  /*  get  positions  of  cards  */ 
position  C  value  Ci3  3  =  ij 

} 

/*************************************************************************/ 

/* 

Random  number  generator  — 
adapted  from  the  FORTRAN  version 

in  "Software  Manual  for  the  Elementary  Functions" 
by  W.J.  Cody,  Jr  and  William  Waite. 

*/ 

ran (max  > 

int  max! 

C 

static  long  int  iy  =  100001; 
iy  *=  125; 

iy  -=  (iy/2796203)  *  2796203; 
return  ((int)  (max  *  iy  /  2796203)); 

> 


58 

130 


End  Listing  One 

I  Listing  Two  begins  on  page  60) 
Dr.  Dobb’s  Journal,  February  1985 


ADFGVX  Encryption  (Listing  Continued,  text  begins  on  page  48) 
Listing  Two 


/*#######################################################################* 
###  ### 


###  Copyright  1983,  Charles  E.  Burton,  Denver,  Colorado  ### 
###  ### 
###  All  rights  reserved.  Permission  granted  to  use  this  software  for  ### 
###  personal,  non-commercial  purposes  only.  ### 

###  ### 


########################################################################*/ 


/* 

*  PROGRAM  NAME:  PWSORT.C 

*  PURPOSE:  Password  sorting  routine  — 

*  Sorts  password  in  largest  to  smallest  character  ordeer.  Used  to 

*  specify  the  column  order  to  pull  out  columns  i n 'TRANSPOSE ( )  and 

*  UNTRANSPOSE ( ) . 

* 

*  LANGUAGE:  C 

*  author:  CEB 

*  USAGE:  PWSORT (PASSWORD) 

*  PASSWORD  —  character  array  containing  the  encryption/ 

*  decryption  key. 

* 

*  ARRAYS  USED:  PASSWORD C  3 

*  EXTERNALS: 

*  UPDATE  HISTORY:  INITIAL  RELEASE  —  11/25/83  CEB 
*/ 


#define  min(x,y>  (<>:)  <  (y)  ?  (x)  :  <  y  >  ) 


pwsort (password) 

char  password!]; 


i nt  i , j , temp ; 

pwlen  =  strl en (password ) ;  /*  get  length  of  PASSWORD  string  */ 
if  (pwlen  <  MINPWLEN)  /*  PASSWORD  too  short  */ 

pr  i  ntf  ( " \n***  Password:  ‘/.s  is  too  short  (  <  5  characters  )  ***\n", 
password ) 5 
ex i t  (0)  ; 

pwlen  =  mi n (pwl en , MAXPWLEN) ;  /*  truncate  PASSWORD  if  necessary  */ 
for  (i  =0;  i  <  pwlen;  i++)  /*  initialize  Password  Column  Order  */ 
pwcol umnor der C i 3  =  i; 

for  (i  =0;  i  <  pwlen  -  1;  i++)  /*  sort  Password  by  Column  Order  */ 
for  (j  =  i  +  1;  j  <  pwlen;  j++) 

if  (password Cpwcol umnorder C i 3 ]  <  password Cpwcol umnorder Cj 3 3 ) 

/*  largest  to  smallest  character  order  */ 

i 

temp  =  pwcol umnorder C i 3 ; 

pwcol umnorder C i 3  =  pwcol umnorder C j 3 ; 

pwcol umnorder Cj 3  =  temp; 

3 

} 


Listing  Three 


End  Listing  Two 


/*#### ft################################*################################## 


###  ### 

###  Copyright  1983,  Charles  E.  Burton,  Denver,  Colorado  ### 

###  ### 

###  All  rights  reserved.  Permission  granted  to  use  this  software  for  ### 

###  personal,  non— commerci al  purposes  only.  ### 

###  ### 


########################################################################*/ 


/* 

*  PROGRAM  NAME:  ENCRYPT.  C 

*  PURPOSE:  Encryption  using  the  ADFGVX  Cipher  — 

*  re.  C.C.  Foster,  "Cryptoanalysis  for  Microcomputers, "  Hayden  Book 

*  Co.  (Rochelle  Park,  NJ ) ,  p.  222. 


Dr.  Dobb's  Journal,  February  1985 


60 

131 


* 

* 


*  LANGUAGE:  C 

*  AUTHOR:  CEB 

*  USAGE:  ENCRYPT  <f i lename)  <password> 

*  <filename>  —  File  Name  to  be  encrypted 

*  <password>  —  5  to  10  character  key  to  be  used  -for 

* 


encryption 


*  ARRAYS  USED:  F I LENAME C  20] , ENCFNAMEC  20] , PASSWORD C 11], BUF 1 [ 1 10] ,  BUF2C 110], 

*  F'OS I T I ON  C  NOCARDS ] , VALUE  C  NOCARDS ] , PWCOLUMNORDER C  MAXPWLEN ] 

*  EXTERNALS:  F'WSORT  ( )  ,  SHUFFLE (> ,  RAN  ( ) 

*  UPDATE  HISTORY:  INITIAL  RELEASE  —  11/25/83  CEB 
*/ 


♦include  "stdio.h" 

♦define  NOCARDS  256 

#def i ne  NO  0 
#def i ne  YES  1 

main(argc,argv> 

char  *argvC]j 
int  argci 

C 

static  char  f  i  1 enameC20] , encfnameC20] , password C 1 1 ], *str ; 
static  char  c ,  buf  1  C  1 10]  ,  buf  2C  1 10]  ,  posi ti on  C NOCARDS]  ,  val ue[ NOCARDS]  ; 
int  i  , f nl en , pwl en, bl kl en , idx , 
done  =  NO; 

long  int  f tel  1 O , dummy; 
un  i  on 

long  int  xlong; 

char  xbytelsizeof (dummy) ] ; 

}  f i 1  el en ; 

FILE  *f open (), *f pin, *f pout; 

if  (argc  !=  3) 

{ 

printf ("\nUsage:  ENCRYPT  <filename>  (password >\n" ) ! 

ex  i  t  ( 0 )  ; 

} 

for  (i=0,  idx=sizeof (filename) ,  str=argvC 1 ] ;  /*  get  file  name  to  use  */ 

*str  !=  'NO'  &!■<  i  <  14;  i++,  str++) 
i 

f i 1 enameC i ]=encf nameC i 3=*str ;  /*  get  file  name  */ 

if  (*str  ==’.')  /*  start  of  <ext>  found?  */ 

idx=i+l;  /*  get  index  to  start  of  <ext>  */ 

3- 

f ilenameCi ]=encf name! i ]=' NO' ;  /*  terminate  file  name  */ 

fnlen=i;  /*  get  length  of  file  name  */ 

switch  (fnlen-idx)  /*  based  on  length  of  <ext>  */ 

case  1: 

/*  only  one  character  in  <ext>  */ 

encf nameCidx+2]=' NO’ ;  /*  move  EOS  over  one  character  */ 

fnlen++;  /*  account  for  added  character  */ 
case  2: 
case  3: 

/*  two  or  three  characters  in  <ext>  */ 

encf name! idx+1 ]=’ Y’ ;  /*  make  2nd  character  of  <ext>  a  'Y'  */ 

break ; 
def  aul t : 

if  (idx  ==  fnlen)  /*  no  <ext>,  but  exists  */ 

strcpy (encf name+f nl en , " YYY" ) ;  /*  add  <ext>  */ 

else  if  (idx  ==  si zeof (f i 1  enamel )  /*  no  and  no  <ext>?  */ 

strcpy (encf name+ (fnl en++) YYY" ) ;  /*  add  <ext>  */ 
else  /*  invalid  file  name  */ 

( 

printf  (  "Nn***  Bad  (filename/:  */.s  ***Nn"  ,  f  i  1  ename)  ; 
exit (0) 5 

} 

fnlen  +=  3;  /*  account  for  added  ,,yyy"  */ 

break ; 

> 

for  ( i =0 ,  str=argvC2];  *str  !=  'NO'  &&  i  <  sizeof (password) -1 ;  i++) 
password C  i  ]=* (str++) ;  /*  get  Password  */ 

password C i ]=’ NO’ ;  /*  terminate  Password  */ 

pwlen=i;  /*  get  length  of  Password  */ 


61 

132 


(Continued  on  next  page) 
Dr.  Dobb's  Journal.  February  1985 


ADFGVX  Cipher  System  (Listing  Continued,  text  begins  on  page  48) 

Listing  Three 


blklen=pwlen*pwlen+pwlen/2;  /*  get  length  of  block  to  read/write  -for 

■file  I/O  to  get  irregular  columns  -for 
password  */ 

if  (blklen  !=  2* (bl kl en/2) )  /*  BLKLEN  odd?  *7 

blklen++;  /*  make  it  even  */ 

if  ( (fpin=fopen <f i lename, "r") )  ==  NULL)  /*  cannot  open  input  file?  */ 

printf  ("\n***  Cannot  open  7.s  ***\n  "  ,  f  i  1  enamel  ; 
e>:  it (0) 5 


} 

if  ( <fpout=f open (encf name, "w" ) )  ==  NULL)  /*  cannot  open  output  file?  */ 
{ 

pr  i  ntf  <  " \n***  Cannot  open  7.S  ***\n"  ,  encf  name)  ; 
f cl ose (f pin) j  /*  close  input  file  */ 
e>:i  t  <0)  5 


> 

f seek <f pin, 0L, 2) ;  /*  find  end  of  file  */ 

f  i  1  el  en  .  x  1  ong=f  tel  1  (f  pi  n)  ;  /*  get  length  of  plain  text  file  */ 

fclose(f pin) !  /*  close  the  input  file  */ 

fpin=f open (f i lename, "r" >  ;  /*  re-open  the  input  file  */ 

for  <i=0s  i  <  si zeof (f i 1  el en. x long) ;  i++> 

putc <f ilelen.xbyteli ],fpout) ;  /*  save  file  length  at  beginning  of 

output  file  */ 

shuf f le(position, value) ;  /*  randomize  cipher  character  set  */ 

pwsort (password) ;  /*  get  transposition  for  row/column  pairs  */ 

do  /*  encrypt  plain  text  file  */ 
f 

for  <i=05  i  <  blklen;  i++)  /*  read  a  block  of  plain  text  */ 


if 


(  (c=compress (f pi n ) )  ==  EOF)  /*  Compress  text,  EOF  found?  */ 

< 

done=YES;  /■*  indicate  last  pass  of  plain  text  */ 
if  <i)  /*  at  least  one  value  in  BUF 1  */ 

for  <  ;  i  <  blklen;  i++)  /*  fill  in  rest  of  BUF1  */ 


c=r an (NQCARDS) ;  /*  generate  a  random  char  */ 

buflCi++3-positionCc]/16; 

/*  char  row  position  */ 
buf  1  C  i  3=posi  ti  on  Cc  37.16; 

/*  char  column  position  */ 


> 


break ; 


/*  fill  BUF1  */ 

buf 1 C i ++3=posi ti on Cc ] / 1 6;  /*  char  row  position  */ 

buf  1  C  i  l=posi  t  i  on  Cc  17. 16;  /*  char  column  position  */ 

} 

if  <i)  /*  BUF 1  full?  */ 

{ 

transpose (buf 1 , buf 2, bl kl en ) ;  /*  encrypt  using  Password  */ 

for  (i=0;  i  <  blklen;  i  +=  2)  /*  write  a  block  of  encrypted 

text  */ 


J 


putc (value! 16*buf2ii 3+buf2Ci+133,  f pout )  ; 


} 

while  (done  ==  NO); 
f cl ose ( f p i n ) ;  /*  close  files  */ 

f cl ose ( f pout ) ; 

if  ( (f pout=f open ( f i 1 ename, "r + " ) )  ==  NULL)  /*  cannot  open  output  file?  */ 

C 


pr  i  ntf  (  " \n***  Cannot  open  7.s  ***\n  ",  f  i  1  ename)  ; 
ex i t  (0)  ; 

> 


else  /*  overwrite  all  records  so  that  they  cannot  be  recovered  */ 

C 

while  (f i 1  el en. x 1 ong — )  /*  Null  out  plain  text  file  */ 

putc (0xF6, f pout) ;  /*  use  formatting  data  character  to  null  */ 

f cl ose (f pout ) ;  /*  close  the  file  */ 

unlink (filename) ;  /*  erase  plain  text  file  */ 

printf ( “NnEncryption  completedNn" ) ; 


(Continued  on  page  64) 


Dr.  Dobb’s  Journal,  February  1985 


62 

133 


(Listing  Continued,  text  begins  on  page  48) 

Listing  Three 


/**■**•*  ***********************  *****************  *«^**  *********■*****.#..*.*#***.#.*  / 

/*  Multiple  Character  Compression.  Encodes  repeated  characters.  The  Stream  is 
byte  -for  byte  pass-through  except  that  SYN  is  encoded  as  SYN/O  and  repeated 
byte  values  are  encoded  as  Byte/SYN/Count ,  where  Count  >  ='3.  This  routine 
is  a  state  machine  r epresentati on  and  is  modeled  after  the  GETNCR ( ) 
■function  in  SQ.C  (squeezer)  by  Richard  Greenlaw. 

*/ 

#define  NOHISTORY  0  /*  do  not  consider  previous  input  */ 

#define  SENTCHAR  1  /*  LASTCHAR  sent,  no  lookahead  yet  */ 

#define  SENDNEWC  2  /*  NEWCHAR  sent,  previous  sequence  done  */ 

#def i ne  SENDCNT  3  /*  NEWCHAR  sent,  SYN  sent,  send  COUNT  next  */ 

ttdefine  SYN  0X16  /*  duplicate  character  indicator  */ 

compress (stream) 

FILE  *stream; 

static  char  state  =  NOHISTORY;  /*  states  */ 

static  int  count;  /*  Count  of  consecutive  identical  characters  */ 
static  int  1 astchar , nextchar ; 

switch  (state) 

C 

case  NOHISTORY: 

/*  no  relevant  history  */ 
state  =  SENTCHAR; 

return  (lastchar  =  getc (stream) ) ; 
break ; 

case  SENTCHAR: 

/*  LASTCHAR  is  sent,  need  to  lookahead  */ 
switch  (lastchar) 

case  SYN: 

/*  actual  SYN  character  found  */ 
state  =  NOHISTORY; 
return  (0) ; 
break ; 
case  EOF: 

/*  end  of  file  found  */ 
return  (lastchar); 
break ; 

def aul t : 

/*  any  other  character  found  */ 

for  (count  =  l;  (nextchar  =  getc  (stream)  )  ==  lastchar  t*t-. 
count  <  255;  count++)  /*  count  like  characters  */ 

; 

switch  (count) 
f 

case  1: 

/*  one  character  found  */ 
return  (lastchar  =  nextchar); 
break ; 
case  2: 

/*  two  characters  found  */ 
state  =  SENDNEWC; 
return  (lastchar); 
break ; 
def  aul t : 

/*  three  or  more  characters  found  */ 
state  =  SENDCNT; 
return  (SYN) ; 
break; 

> 

break; 

case  SENDNEWC: 

/*  previous  sequence  complete,  send  NEWCHAR  */ 

state  =  SENTCHAR; 

return  (lastchar  =  nextchar); 

break ; 

case  SENDCNT: 


64 

134 


Dr.  Dobb  s  Journal,  February  1985 


/*  sent  SVN  -for  repeat  sequence,  send  COUNT  */ 
state  =  SENDNEWC; 
return  (count); 
break ; 
def  au  1 1 : 

print-f  ("\n***  Bad  STATE  in  COMPRESS  ( )  ***\n")( 

ex it(0) j 

break! 


> 

/*************************************************************************/ 

/*  Transpose  via  Columns  based  on  Password.  This  routine  takes  a  string  and 
transposes  the  characters  to  column  order  based  on  a  password.  The  password 
length  is  the  width  of  the  table  and  the  password  has  been  sorted  to 
specify  the  column  order  to  pull  out  the  columns.  */ 

#define  MAXF'WLEN  10 
#def  i  ne  MINF'WLEN  5 

static  int  pwl  en  ,  pwcol  umnorder  CMAXF'WLENl ; 

#i ncl ude  "PWSORT.C" 


transpose (from, to , 1 en ) 
char  fromC3,toCl; 
int  lenj 


int  i , j  ,  k ; 


l  = 

for 


> 


o; 

(j  =  0;  j  <  pwlen;  j++)  /*  transpose  in  column  order  */ 
for  (k  =  pwcol umnorder [j ] ;  k  <  1 en ;  k  +=  pwlen) 

/*  transpose  characters  */ 

toCi++l  =  fromlkl; 


ttinclude  "SHUFFLE. C" 


End  Listing  Three 


Listing  Four 


/*######################################################################## 


###  ### 

###  Copyright  1983,  Charles  E.  Burton,  Denver,  Colorado  ### 

###  ### 

###  All  rights  reserved.  Permission  granted  to  use  this  software  for  ### 

###  personal ,  non— commer c i al  purposes  only.  ### 

###  ### 


########################################################################*/ 


/* 

*  F'ROGRAM  NAME:  DECRYPT.  C 

*  PURPOSE:  Decryption  using  the  ADFGVX  Cipher  — 

*  re.  C.C.  Foster,  "Cryptoanalysis  for  Microcomputers,"  Hayden  Book 

*  Co.  (Rochelle  Park,  NJ ) ,  p.  222. 

* 

* 

*  LANGUAGE:  C 

*  AUTHOR:  CEB 

*  USAGE:  DECRYPT  <filename>  (password) 

*  (filename)  —  File  Name  to  be  decrypted 

*  (password)  —  5  to  10  character  key  to  be  used  for  decryption 

* 

*  ARRAYS  USED:  F I LENAME  C  20  3 , ENCFN AME  C  20 1 , PASSWORD  till, BUF 111101, BUF2  C 110], 

*  P0SITI0NCN0CARDS1, VALUE  C  N0CARDS 1 .  F'WCOLUMNORDER C  MAX  PWLEN 1 

*  EXTERNALS:  PWSORT ( ) ,  SHUFFLE ( ) 

*  UPDATE  HISTORY:  INITIAL  RELEASE  —  11/25/83  CEB 
*/ 


♦include  "stdio.h" 

♦define  NOCARDS  256 

♦define  NO  0 
♦define  YES  1 


(Continued  on  next  page ) 


Dr.  Dobb’s  Journal,  February  1985 


65 

135 


ADFGVX  Cipher  System  (Listing  Continued,  text  begins  on  page  48) 

Listing  Four 


static  char  password C 1 1 3, posi ti on CN0CARDS3 , val ue[N0CARDS3 ; 
static  int  blklen; 

main (ar gc , argv) 

char  *argvt3; 
int  argcj 

< 

static  char  f  i  1  enameC203  ,  dect nameC203  ,  *str ,  c ; 
int  i  , f n 1 en , pwl en , i dx ; 
long  int  dummy; 
uni  on 

long  int  xlong; 

char  xbytelsi zeof (dummy > 3 ; 

}  t  i  1  el en ; 

FILE  *f open (), *f pi n , *f pout ; 

if  (argc  !=  3) 

{ 

pr i ntf ( “ \nUsage:  DECRYPT  <  f  i  1  ename >  (password  >\n  " )  ; 
ex i t  (0)  ; 

> 

•for  <i=0,  i dx=si zeof <f i 1 ename) ,  str=argvC13;  /*  get  file  name  to  use  */ 
*str  !=  J\0’  ?<8<  i  <  14;  i++,  str++) 

f i 1 enameC i 3=decf name! i 3=*str ;  /*  get  file  name  */ 

if  (#str  ==  ’ . ’ )  /*  start  of  <ext>  found?  */ 

idx=i+l;  /*  get  index  to  start  of  <ext>  */ 

> 

J 

f  i  1  enameC i 3=decf name! i ]  =  ’ \0’ ;  /*  terminate  file  name  */ 

fnlen=i;  /*  get  length  of  file  name  */ 

switch  (fnlen-idx)  /*  based  on  length  of  <ext>  */ 

{ 

case  1 : 

/*  only  one  character  in  <ext>  */ 

decf  nameC  i  dx+2J  =  :' \0' ;  /*  move  EOS  over  one  character  */ 

fnlen++;  /*  account  for  added  character  */ 
case  2: 
case  3: 

/*  two  or  three  characters  in  <ext>  */ 

decf name! i dx+ 1 3=’ Y’ ;  /*  make  2nd  character  of  <ext>  a  ”Y’  */ 

break ; 
def aul t : 

if  (idx  ==  fnlen)  /*  no  <ext>,  but  ’ . '  exists  */ 

strcpy (decf name+f nl en , " YYY" ) ;  /*  add  <ext>  */ 

else  if  (idx  ==  sizeof (f i lename) )  /*  no  and  no  <ext>?  */ 

strcpy (decf name+ (fnl en++) YYY" ) ;  /*  add  <ext>  */ 

else  /*  invalid  file  name  */ 

C 

pr  i  ntf  (  " \n***  Bad  <filename>:  7.5  ***\n",f  ilename)  ! 
exit (0) ; 

> 

fnlen  +=  3;  /*  account  for  added  "YYY"  */ 

break ; 

> 

for  (i=0,  str=argvC23;  *str  !=  ’  \0r  ?<?<  i  <  si  zeof  (password ) -1 ;  i++) 
password C  i  D=* (str++) ;  /*  get  Password  */ 

password! i 3=’ \0' ;  /*  terminate  Password  */ 

pwlen=i;  /*  get  length  of  Password  */ 

bl kl en=pwl en*pwl en+pwl en/2;  /*  get  length  of  block  to  read/write  for  file 

I/O  to  get  irregular  columns  for  both 
passwords  */ 

if  (blklen  !=  2* (bl klen/2) )  /*  BLKLEN  odd?  */ 

blklen++;  /*  make  it  even  */ 

if  ( (f pi n=f open (decf name, "r" ) )  ==  NULL)  /*  cannot  open  input  file?  */ 

I 

pr i ntf ( " \n***  Cannot  open  Xs  ***\n ", decf name)  ; 
ex i t  (O)  ; 

3 

if  ( (fpout=fopen (filename, "w") )  ==  NULL)  /*  cannot  open  output  file?  */ 

{ 

pr  i  ntf  <  " \n***  Cannot  open  7.s  ***\n",f  ilename)  ! 


66 


(Continued  on  page  68) 
Dr.  Dobb's  Journal,  February  1 985 


ADFGVX  Cipher  System  (Listing  Continued,  text  begins  on  page  48) 

Listing  Four 


fclose(fpin) ;  /*  close  input  file  */ 

ex  i  t  (0)  ; 

> 

for  <i=0j  i  <  si zeof (f i 1  el en . x 1 ong ) ;  i++) 

f i 1  el en . xbyteC i D=getc (f pi n ) ;  /*  get  original  file  length  */ 

shuff le(position, value) ;  /*  randomize  cipher  character  set  */ 

pwsort (password ) j  /*  get  transposition  for  row/column  pairs  */ 
while  <  (  ( c=ex  pand  <  f  p  i  n  )  >  !=  EOF)  8<&  (f i 1  el en . x 1 ong — )) 

/*  decrypt  S<  expand  the  file  */ 
putc (c , f pout ) 5  /*  write  plain  text  */ 

f c 1 ose ( f pi n > ;  /*  close  files  */ 

f cl  ose (f pout ) 5 

pr i ntf ( " \n Decrypt i on  compl eted\n" ) ; 

> 

/**********  *#*#****#******■**•»•+ *************************  ******************#**/ 

/*  Multiple  Character  Expansion.  Decodes  compressed  characters.  The  Stream 
is  byte  for  byte  pass-through  except  that  SYN/0  is  decoded  as  SYN  and 
Byte/SYN/Count  is  decoded  as  Byte  repeated  Count  times.  This  routine 
is  a  state  machine  representation  and  is  modeled  after  the  GETNCR < ) 
function  in  SQ.C  (squeezer)  by  Richard  Greenlaw. 


#define  NOHISTORY  0  /*  do  not  consider  previous  input  */ 

#define  SENTCHAR  1  /*  LASTCHAR  sent,  no  lookahead  yet  */ 

#def i ne  SENDRPT  2  /*  LASTCHAR  sent,  SYN  found,  found  COUNT,  repeat 

LASTCHAR  */ 

#define  SYN  0X16  /*  duplicate  character  indicator  */ 

expand (stream) 

FILE  *streami 


static  char  state  =  NOHISTORY;  /*  states  */ 

static  int  count;  /*  Count  of  consecutive  identical  characters  */ 
static  int  1 astchar , nex tchar ; 

switch  (state) 

C 

case  NOHISTORY: 

/■*  no  relevant  history  */ 

switch  (lastchar  =  getcnxt (stream) ) 

case  SYN: 

/*  must  be  a  SYN  character,  check  */ 
switch  (getcnxt (stream) ) 

{ 

case  0: 

/*  SYN  character  found  */ 
return  (lastchar); 
break ; 

def  aul t : 

/*  bad  SYN  character  found  */ 
printf  ("\n***  Bad  SYN  in  EXPANDO  **#\n">; 
ex i t  (0)  ; 
break ; 

} 

break; 
def  aul t : 

/*  another  character  found  */ 
state  =  SENTCHAR; 
return  ( 1 astchar ) ; 
break ; 

> 

break ; 

case  SENTCHAR: 

/*  LASTCHAR  is  sent,  need  to  lookahead  */ 
switch  (lastchar) 

C 

case  EOF: 

/*  end  of  file  found  */ 
return  (lastchar); 


Dr.  Dobb's  Journal,  February  1985 


68 

137 


Listing  Four 


break; 

de-fault : 

/*  any  other  character  found  */ 
switch  (nextchar  =  getcnxt  (stream)  ) 

case  SYN: 

/*  repeating  characters  found  */ 
switch  (count  =  getcnxt (stream) > 

C 

case  0: 

/*  SYN  character  found  */ 
state  =  NOHISTORY; 
return  (nextchar); 
break ; 
def  aul t : 

/*  actual  COUNT  found  */ 
state  =  SENDRPT; 

count  -=  2;  /*  adjust  for  2  sent 
characters  */ 
return  (lastchar); 
break ; 

> 

break ; 
def  aul t : 

/*  another  character  found  */ 
return  (lastchar  =  nextchar) ; 
break; 


break ; 


case  SENDRPT: 

/*  repeat  sequence,  send  LASTCHAR  COUNT  times  */ 
if  (count  >  1) 

C 

/*  keep  sending  LASTCHAR  */ 
count  — ; 

return  (lastchar); 

> 

else  if  (count  ==  1) 

C 

/*  final  LASTCHAR  */ 
state  =  NOHISTORY; 
return  (lastchar); 

> 

el  se 

r 

\ 

/*  problem  with  COUNT  */ 

printf ( "\n***  Bad  COUNT  in  EXPANDO  ***\n">; 
ex i t  (0)  ; 

break ; 
def  aul t : 

printf ("\n***  Bad  STATE  in  EXPANDO  ***\nM); 
ex  i  t  <  0 )  ; 
break. ; 

} 

> 

/****•»**#•»■»************#************•»***•»*■«■**#*****#■»#  it*******************  / 

/*  Get  next  character  from  input  stream.  Must  read  a  block  of  data, 
untranspose  it  and  distribute  the  block  a  character  at  a  time.  */ 

getcnxt (stream) 

FILE  ‘stream; 

C 

static  char  buf 1 C 1 103 , buf 2C 1 103 , c ; 
i  nt  i  ; 

static  int  done  =  NO, 

bufidx  =  sizeof (buf 1 ) ; 

if  ((bufidx  >=  blklen)  (done  ==  NO)) 


( Continued  on  next  page) 


69 

138 


Dr.  Dobb's  Journal.  February  1985 


ADFGVX  Cipher  System  (Listing  Continued,  text  begins  on  page  48) 

Listing  Four 


/*  need  to  read  more  characters  into  BUF1?  ■*/ 

r 

\ 

■for  <i=05  i  <  blklen;  i++)  /*  read  a  block  of  encrypted  text  */ 

i  f  ( (c=getc (stream) )  ==  EOF)  /*  EOF  -found?  */ 

C 

done=YES!  /*  indicate  last  pass  of  plain  text  */ 
break  j 

J 

/*  fill  BUF 1  */ 

buf 1 Ci++3=positionCc3/16;  /*  char  row  position  */ 

buf 1 C i 3=posi tionCc ]X1 6!  /*  char  column  position  */ 

■». 

J 

if  (done  ==  NO)  /*  last  Block  meaningless,  ignore  it?  */ 

■C 

untranspose (buf 1 , buf 2, bl kl en ) 5  /*  decrypt  with  Password  */ 

bufidx=05  /*  reset  Block  Index  to  start  of  BUF1  */ 

j 

y 

if  (bufidx  <  blklen)  /*  characters  still  available?  */ 

< 

bufidx  +=  2;  /*  point  to  next  row/column  pair  */ 

return  (value! 16*buf  2Cbuf i dx— 2]+buf 2Cbuf i dx-1 ] 3 ) i 

/*  return  a  decrypted  character  */ 

J 

else  if  (done  ==  YES)  /*  no  more  characters  available  EOF?  */ 
return  (EOF);  /*  indicate  done  */ 
else  /*  character  stream  problem  */ 

C 

pr i ntf  (  " \n***  Synchronization  error  in  GETCNXTO  ***An"); 
ex i t  ( 0 )  j 
> 

} 


/****************************** ************** **************** *****•»***#*■»*/ 

/*  Untranspose  via  Columns  based  on  Password.  This  routine  takes  a  string  and 
transposes  the  characters  to  row  order  based  on  a  password.  The  password 
length  is  the  width  of  the  table  and  the  password  has  been  sorted  to 
specify  the  column  order  to  put  back  the  columns  before  pulling  out  rows.  */ 

ttdefine  MAXF'WLEN  10 
^define  MINPWLEN  5 

static  int  pwl  en ,  pwcol  umnorder  CMAXF'WLENl ; 

#inc 1 ude  "PWSQRT.C" 

un transpose (from, to, 1 en ) 
char  fromn,totli 
int  leni 


int  i , j , k ; 


l  = 
f  or 


> 


05 

=  0;  j  <  pwlen;  j++)  /*  transpose  in  column  order  */ 

for  (k  =  pwcol umnor der C j ] j  k  <  len;  k  +=  pwlen) 

/*  transpose  characters  */ 

toCk]  =  fromti++3; 


((include  "SHUFFLE.  C" 


End  Listings 


Dr.  Dobb's  Journal,  February  1985 


70 

139 


More  dBASE  Tips  and  Techniques 


by  Gene  Head 


I  wrote  a  dBASE  II  command  file  But  wait.  Now  I  want  to  go  the  other 
called  DB-SQZ.CMD  that  will  direction!  Make  the  IBM  dBASE  II 

squeeze  and  tokenize  any  command  program  look  for  .CMD  instead  of 

file  just  as  DBCODE  does  in  Ashton-  .PRG  command  files.  Furthermore,  I 
Tate’s  RUNTIME  package.  My  cus-  need  each  direction  for  several  versions 
tomers  who  already  owned  dBASE  II  (2.4,2.41,2.41  Z80)  of  dBASE  II.  This 
could  run  these  “scrambled”  com-  could  get  confusing, 
mand  files  and  not  have  to  purchase  Finally,  I  came  up  with  the  idea  of 
RUNTIME.  (DB-SQZ.CMD  may  ap-  writing  the  program  in  the  listing 
pear  in  a  future  column  or  call  me  for  a  (page  72)  called  MAKEFLIP.XXX  that 
modem  download.)  will  “write”  a  customized  version  of 

Unfortunately,  every  customer  that  FLIP.IT  for  any  version  of  dBASE  II  al- 

owned  dBASE  II  also  owned  an  IBM  most  automatically!  Now,  I  only  need 

PC.  In  their  wisdom  Ashton-Tate  to  have  a  copy  of  MAKEFLIP.XXX 

made  the  default  command  file  exten-  when  I  go  to  work  in  any  dBASE  II 

sion  different  for  the  MSDOS/IBM  environment. 

version  of  dBASE  II.  CP/M  command  On  any  new  installation  I  execute 
files  are  of  the  type  .CMD  and  IBM  MAKEFLIP.XXX  one  time  and  it  gen- 

command  files  are  .PRG.  So  I  have  this  erates  FLIP.IT  for  whatever  machine  it 

wonderful  program  that  protects  my  is  on.  Now,  whenever  I  need  to  change 

command  files  from  tampering,  but  I  the  command  file  default  extension  I 

can’t  use  it  unless  I  change  every  file  type  .DO  FLIP.IT. 

type  from  .CMD  to  .PRG  on  every  disk!  MAKEFLIP.XXX  is  not  only  a  very 
There  should  be  some  way  to  keep  useful  utility,  but  you  should  get  some 

the  filenames  as  they  are  and  make  good  insight  into  how  to  write  code 

How  to  write  dBASE  code 
that  writes  dBASE  code. 

dBASE  II  change  what  it  looks  for  that  writes  code.  This  could  be  espe- 

when  it  meets  a  DO  FILENAME.  daily  helpful  in  custom  installations. 

The  best  solution  was  to  make  Before  I  get  calls  and  letters  telling 
dBASE  II  look  for  the  file  type  I  told  it  me  that  my  search  routine  is  too  loose 

to  look  for;  that  is,  have  my  CP/M  and  could  find  a  bogus  patch,  let  me 

dBASE  II  look  for  .PRG  instead  of  say  that  I  already  know  that.  However, 

.CMD  command  files.  I  have  tried  MAKEFLIP.XXX  on  sever- 

This  is  easy  enough  because  the  file  al  types  of  micros  and  most  versions  of 

type  is  stored  as  plain  ASCII  text  and  dBASE  II  with  complete  success.  The 

easily  patched  with  the  POKE  com-  chances  of  finding  an  invalid  patch  lo- 

mand.  I  wrote  a  simple  command  file  cation  are  too  small  to  justify  addition- 

called  FLIP.IT  that  changed  the  de-  al  code.  Besides,  you  will  know  right 

fault  file  type  using  the  POKE  away  if  you  found  a  bad  patch  area.  It 

function.  won’t  work!  ddj 

(Listing  begins  on  next  page) 

Gene  Head,  Head  Quarters,  2860  NW 
Skyline,  Corvallis,  OR  97330. 


71 

140 


Dr.  Dobb’s  Journal,  February  1985 


dBASE  Listing  (Text  begins  on  page  70) 


Another  dBASE  II  goody  fr-om: 

Head  Quarters 
Gene  Head 

2860  NW  Skyline  Drive 
Corvallis,  Oregon  97330 
(503)  758-0279 

For  non-commercial  use  only.  If  you  use  this  utility 
to  help  you  develop  any  software  that  earns  you  money 
(even  in  your  own  business)  send  me  $5.00. 

It's  a  fair  price  to  pay  and  worth  a  clear  conscience. 

♦  a******************************* 


Program  name  — >  MAKEFLIP.XXX 

(DO  NOT  change  the  name  of  this  command  file) 

(Execute  it  as  .DO  MAKEFLIP.XXX  or  it  may  not  work) 

*  *  *  This  program  generates  the  command  file  FLIP. IT  *  *  * 

You  only  need  to  run  this  program  ONCE  to  create  FLIP. IT. 

After  you  create  FLIP. IT  put  a  copy  on  your  dBASE  II  disk. 

Then  when  you  wish  to  change  default  command  file  extension 
type  .DO  FLIP. IT 

Examplei 

To  run  CP/M  command  files  defaulted  to  .CMD  under  MS-DOS  simply 
type  .DO  FLIP. IT.  To  change  back  to  the  . PRG  default,  type  the 
same  thing  again,  .DO  FLIP. IT. 

Likewise,  to  run  MS-DOS  command  files  defaulted  to  .PRG  under 
CP/M,  type  .DO  FLIP. IT.  To  change  back  to  .CMD  default,  again 
type  .DO  FLIP. IT. 


SET  TALK  OFP 

*  This  range  works  well  for  dBASE  II  versions  2.4, 

*  2.41  and  Z-80  2.41.  For  other  versions  they  may  have  to  change. 
STORE  '12300'  TO  LOW 

STORE  '12500'  TO  HIGH 


*  First  get  the  range  of  program  RAM  to  search 

*  for  the  default  extension 

STORE  'Y'  TO  CHOICE 
DO  WHILE  CHOICE  =  'Y' 

STORE  T  TO  RANGE 

ERASE 

DO  WHILE  RANGE 

@  12,10  SAY  'Enter  START  of  search  area.  Suggest  — >  '; 
GET  LOW  PICTURE  '99999' 

@  14,10  SAY  '  Enter  END  of  search  area.  Suggest  — >  'j 
GET  HIGH  PICTURE  '99999' 

READ 


STORE  VAL ( LOW)  TO  MLOW 
STORE  VAL (HIGH)  TO  MHIGH 

IF  MLOW  >=  MHIGH  .OR.  MHIGH  >  65000  .OR.  MLOW  <  100 

@  20,10  SAY  'SEARCH  AREA  OUT  OF  RANGE.  TRY  AGAIN  .  .  .' 
ELSE 

STORE  F  TO  RANGE 

ENDIP  MLOW  >-  MHIGH  .OR.  MHIGH  >  65000  .OR.  MLOW  <  100 


Dr.  Dobb's  Journal,  February  1985 


72 

141 


ENDDO  WHILE  RANGE 


ERASE 

*  This  is  not  the  most  accurate  testing  but  the  chances  are 

*  very  high  that  it  will  only  find  what  we  are  looking  for. 

@  12,10  SAY  'SEARCHING' 

STORE  P  TO  POOND 

DO  WHILE  (.NOT.  POUND)  .AND.  MLOW  <  MHIGH 

IP  PEEK (MLOW)  <>  67  .AND.  PEEK (MLOW)  <>  80 
STORE  MLOW+1  TO  MLOW 
LOOP 
ELSE 

IP  PEEK (MLOW+1)  <>  77  .AND.  PEEK (MLOW)  <>  82 
STORE  MLOW+1  TO  MLOW 
LOOP 
ELSE 

IP  PEEK (MLOW+2)  <>  68  .AND.  PEEK (MLOW)  <>  71 
STORE  MLOW+1  TO  MLOW 
LOOP 
ELSE 

STORE  T  TO  FOUND 
LOOP 

ENDIF  PEEK (MLOW+2)  <>  68  .AND.  PEEK (MLOW)  <>  71 
ENDIF  PEEK (MLOW+1)  <>  77  .AND.  PEEK (MLOW)  <>  82 
ENDIP  PEEK (MLOW)  <>  67- .AND.  PEEK (MLOW)  <>  80 

ENDDO  WHILE  (.NOT.  FOUND)  .AND.  MLOW  <  MHIGH 

*  If  we  found  our  patch  area  create  a  command  file  called  FLIP. IT 
IP  FOUND 

@  12,10  SAY  'CREATING  COMMAND  PILE  — >  FLIP. IT  ' 

STORE  STR  ( MLOW ,  5 )  TO  PATCH 
SET  ALTERNATE  TO  FLIP. IT 
SET  ALTERNATE  ON 
SET  CONSOLE  OFF 
7 

7  [*] 

7 
7 
7 
7 
7 
7 
7 
7 
7 
7 
7 
7 
7 
7 
7 
7 
7 
7 
7 
7 
7 
7 


FLIP. IT] 

FLIP. IT  WILL  FLIP  COMMAND  FILE  EXTENSION  . CMD  < — >  . PRG] 

FLIP. IT  IS  DESIGNED  FOR  THOSE  WHO  DEVELOP  PROGRAMS  USING  CP/M] 
dBASE  II  TO  RUN  UNDER  MS-DOS  dBASE  II  AND  GET  TIRED  CHANGING] 
THE  COMMAND  FILE  EXTENSIONS  FROM  .CMD  TO  .PRG  AND  VICE-VERSAI] 

FLIP. IT  CAUSES  YOUR  CP/M  DBASE  II  TO  ACCEPT  .PRG  AS  DEFAULT] 
COMMAND  FILE  EXTENSION.  IT  CAN  ALSO  LET  YOUR  MS-DOS  dBASE  II] 
ACCEPT  .CMD  AS  THE  DEFAULT  COMMAND  FILE  EXTENSION.] 

IN  EITHER  CASE  JUST  TYPE  .DO  FLIP. IT] 


IF  PEEK(]+PATCH+[)  =  67] 

POKE  ] +PATCH+ [ ,  80,  82,  71] 

7  'DEFAULT  COMMAND  EXTENSION  — >  .PRG'] 

ELSE] 

IF  PEEK ( ] +PATCH+ [ )  =  80] 

POKE  ] +PATCH+ [ ,  67,  77,  68] 

7  'DEFAULT  COMMAND  EXTENSION  — >  .CMD'] 

ELSE] 

7  'ERROR  CHANGING  DEFAULT  EXTENSION'] 

7  [ENDIF] 

7 

SET  ALTERNATE  OFF 
SET  ALTERNATE  TO 
SET  CONSOLE  ON 

STORE  'N'  TO  CHOICE 

ELSE 

@  12,10  SAY  'CAN  NOT  LOCATE  PATCH  AREA  WITHIN; 
THE  SPECIFIED  RANGE.' 

@  14,10  SAY  'INCREASE  THE  SEARCH  RANGE.; 

TRY  AGAIN?  (Y/N)  — >  '  GET  CHOICE 
READ 


ENDIF  FOUND 

ENDDO  while  CHOICE  -  'Y' 


ERASE 


*  end  of  source  code  for  MAKEFLIP.XXX  * 


73 

142 


(End  Listing) 

Dr.  Dobb's  Journal,  February  1985 


Modula-2/86,  Version  1 .04 
Company:  Logitech,  805  Veterans 
Blvd.,  Redwood  City,  CA 
94086 

Operating  System:  MSDOS  and 
CP/M  86 

Price:  $495.00  (Discounts  are 
available  for  educational  insti¬ 
tutions  and  user  groups.  —  Ed.) 
Circle  Reader  Service  No.  135 
Reviewed  by  Michael  Schmidt 

There  has  been  much  to-do  about  Mo¬ 
dula-2  recently,  with  the  theme  that 
Modula-2  is  the  successor  to  Pascal. 
But  how  does  a  new  Modula-2  compil¬ 
er  compare  with  an  extended  Pascal 
compiler  that’s  been  around  a  while?  I 
was  curious  to  find  out. 

I  pitted  the  Logitech  Modula-2/86 
compiler,  version  1.04  (not  a  public  re¬ 
lease),  against  the  Microsoft  MS-Pas- 
cal  compiler,  version  3.13.  MS-Pascal 
is  a  highly  extended  version  of  the  Pas¬ 
cal  language,  designed  to  facilitate  sys¬ 
tems  programming.  Many  of  the  ex¬ 
tensions  of  MS-Pascal  correspond  to 
standard  features  of  Modula-2. 

All  tests  were  performed  on  an  8 
MHz  8086  Seattle  Computer  Products 
S-100  system,  running  MSDOS  2.0. 
The  system  was  equipped  with  224K  of 
memory,  two  Qume  DT-8  floppy  disk 
drives  that  store  1 .2Mb  each,  and  a  Ze¬ 
nith  Z19  terminal.  The  system  was  not 
equipped  with  an  8087  coprocessor. 

Documentation 

The  Logitech  manual  represents  what 
I  would  call  a  “user’s  guide,”  with  cer¬ 
tain  parts  of  a  “reference  manual”  in 
numerous  appendices.  With  this  orga¬ 
nization,  two-thirds  of  the  documenta¬ 
tion  is  in  appendices!  The  preface  re¬ 
fers  the  reader  to  the  book 
Programming  In  Modula-2  by  N. 
Wirth  for  a  definition  of  the  Modula-2 
language.  However,  implementation 
specifics  and  peculiarities  of  the  SYS¬ 


TEM  and  library  modules  in  the  Logi¬ 
tech  implementation  are  not  described, 
of  course,  in  Wirth’s  book,  and  Logi¬ 
tech  reduces  them  to  the  lowly  status 
of  appendices. 

What  do  I  have  against  appendices? 
The  problem  lies  not  with  appendices 
per  se  but  with  the  authors,  who  use 
them  here  as  an  excuse  to  supply  sub¬ 
standard  documentation.  Gone  are  any 
requirements  that  material  be  present¬ 
ed  in  a  logical  order  or  that  coherent 
English  appear  on  each  page  or  that  a 
70-page  section  show  subdivisions  with 
page  numbers  in  the  table  of  contents. 

The  largest  appendix  consists  of  the 
definition  modules  of  the  Logitech  li¬ 
brary,  with  comments.  Unfortunately, 
these  comments  are  not  always 
enough!  For  example,  the  rather  com¬ 
plex  library  module  FileSystem  seems 
to  defy  comprehension,  at  least  mine. 
A  similar  file  system  appears  as  one  of 
two  alternate  designs  (one  for  RT-1 1, 
one  for  Lilith)  in  Wirth’s  book  but 
with  little  supporting  text — again  just 
a  definition  module.  It  should  be  obvi¬ 
ous  to  anyone  struggling  through  Mo¬ 
dula-2  definition  modules  that  they  are 
not  self-documenting  and  require  a  lit¬ 
tle  English  explanation. 

Part  of  the  difficulty  I  had  under¬ 
standing  these  library  modules  result 
ed  from  undefined  terminology  bor¬ 
rowed  from  an  alien  (non-MSDOS)  en¬ 
vironment.  For  instance,  what  the  heck 
is  a  “medium”?  And  how  am  I  sup¬ 
posed  to  know  that  I  should  precede  a 
filename  with  “DK:”  to  select  the  de¬ 
fault  drive?  These  peculiarities  are  bad 
enough  by  themselves,  but  they  be¬ 
come  downright  unmanageable  when 
they  are  not  documented. 

The  “user’s  guide”  portions  of  the 
Logitech  documentation  are  well  writ¬ 
ten  and  complete,  with  the  following 
exceptions.  A  discussion  of  the  runtime 
support  (RTS)  was  missing  alltogether. 
This  program  resides  in  the  file 


M2. EXE,  and  handles  program  load¬ 
ing,  overlays,  coroutine  transfers,  and 
runtime  errors.  It  produces  a  number 
of  runtime  error  messages,  which  are 
not  listed  anywhere  in  the  manual. 
Also,  the  section  on  linking  overlays 
was  not  sufficiently  descriptive. 

The  MS-Pascal  documentation  is 
more  hefty.  It  consists  of  both  a  user’s 
manual  and  a  separate,  much  larger, 
language  reference  manual.  All  the  in¬ 
formation  I  have  ever  needed  to  know 
about  MS-Pascal  is  there  somewhere! 
The  mere  bulk  of  these  manuals  would 
tend  to  overwhelm  the  first-time  user, 
but  actually,  the  organization  is  logi¬ 
cal,  and  once  you  have  read  through 
the  manuals  a  few  times,  you  can  re¬ 
trieve  information  quickly. 

Neither  set  of  manuals  serves  as  an 
introduction  to  the  language  that  its 
compiler  implements.  However,  the 
MS-Pascal  documentation  is  logically 
complete  by  itself,  whereas  the  Logi¬ 
tech  documentation  requires  Pro¬ 
gramming  In  Modula-2  for  a  formal 
definition  of  Modula-2.  The  saving 
grace  of  the  Logitech  documentation  is 
Logitech’s  technical  support.  Whenev¬ 
er  I  reached  an  impasse,  a  quick  phone 
call  on  the  Logitech  ‘hotline’  produced 
my  answer.  With  Microsoft,  I  am  not 
even  aware  of  a  number  I  can  call. 

Installation 

The  MSDOS  version  of  the  Logitech 
compiler  is  distributed  only  on  three 
5‘/4-inch  double-sided  floppies,  using  9- 
sector/track  formatting.  Logitech  was 
not  able  to  satisfy  my  request  for  an  8- 
inch  distribution  disk.  I  was  able  to  add 
a  minifloppy  drive  to  my  system  (most 
MSDOS  systems  use  the  minifloppies 
anyhow),  but  for  a  minority  of  MSDOS 
systems  this  will  present  a  problem. 

The  Logitech  manual  instructs  the 
user  to  place  the  command 
device  =  ANSI 

in  the  CONFIG.SYS  file.  This  is  re- 


L 


Dr.  Dobb's  Journal,  February  1985 


74 

143 


quired  only  for  systems  with  non-ANSI 
console  devices.  Because  my  Z19  is  set 
to  ANSI  mode,  no  special  driver  is 
needed.  Only  the  Logitech  debugger 
generates  ANSI  escape  sequences;  you 
can  operate  both  the  compiler  and 
linker  without  an  ANSI  console  device. 

The  rest  of  the  installation  depends 
on  whether  you  are  using  floppies  or  a 
Winchester.  Actually,  all  that  matters 
is  that  you  have  a  mass-storage  device 
that  can  hold  about  1Mb,  which  is 
what  a  Modula-2  system  disk  requires. 
My  8-inch  floppies  were  adequate.  If 
you  have  a  system  with  only  360K  5 14- 
inch  floppies,  you  will  need  three  dis¬ 
kettes  in  addition  to  the  one  holding 
your  source  code:  a  system  disk  with  an 
editor  on  it,  a  compiler  disk,  and  a  link¬ 
er/debugger  disk.  Good  luck! 

If  you  have  adequate  mass  storage, 
you  are  instructed  to  place  the  follow¬ 
ing  commands  in  your  AUTOEXEC¬ 
.BAT  file: 

SET  M2LIB  =  \M2LIB\SYM 

SET  M2LNK  =  \M2LIB\LNK 

SET  M2REF  =  \M2LIB\REF 

SET  M2MAP  =  \M2LIB\MAP 
I  preceded  each  of  the  above  symbols 
with  a  drive  designator  (M2LIB  = 
A:\M2LIB\SYM)  so  that  I  did  not  have 
to  set  my  default  drive  to  the  Modula 
system  disk.  The  files  on  the  distribu¬ 
tion  disks  are  copied  to  the  above  sub¬ 
directories  or  to  the  subdirectory 
\M2LOD  for  the  executable  files.  Un¬ 
fortunately,  no  corresponding  symbol 
for  this  latter  subdirectory  exists  (the 
command  SET  M2LOD  =  A:\M2LOD 
does  nothing),  and  if  the  default  drive 
does  not  contain  the  Modula  system 
disk,  the  complete  path  name  of  the  ex¬ 
ecutable  file  must  be  given.  In  addi¬ 
tion,  the  RTS  in  the  file  M2.EXE  must 
be  copied  into  a  subdirectory  selected 
by  the  MSDOS  PATH  command  (in  my 
directory  structure  this  was  A:\BIN). 

Two  example  programs  are  provided 
on  the  distribution  disk.  When  I  first 
tried  to  compile  them,  I  received  an  er¬ 
ror  message  “cannot  load.  . .”  during 
linking.  What  disturbed  me  was  that 
this  message  was  not  in  the  manual.  I 
suspected  a  lack  of  RAM  and  on  closer 
examination  discerned  that  the  man¬ 
ual  requires  192K  of  program  RAM, 
which  does  not  include  the  roughly 
32K  required  by  MSDOS  2.0.  I  up¬ 
graded  my  system  to  256K  and  had  no 
further  troubles.  To  avoid  such  confu- 

75 

1AA 


sion,  Logitech  should  admit  to  a  re¬ 
quirement  of  256K  of  system  RAM. 

The  Logitech  distribution  is  config¬ 
ured  for  an  IBM  PC.  The  source  code 
for  several  modules,  which  might  need 
modification  for  a  different  environ¬ 
ment,  the  object  files  for  the  compiler, 
linker,  and  debugger  themselves,  and 
the  assembler  source  code  of  the  RTS 
modules  are  standard  distribution!  A 
rather  cryptic  comment  in  an  appendix 
states  that  the  user  should  study  the 
source  code  for  these  and  make 
changes  as  required. 

Despite  my  different  hardware  con¬ 
figuration,  I  found  that  none  of  the  li¬ 
brary  modules  had  to  be  recompiled. 
However,  the  process/interrupt  mod¬ 
ule  of  the  RTS  proved  to  be  incompati¬ 
ble  with  my  system.  It  assumes  a  single 
8259A  interrupt  controller,  addressed 
at  I /O  ports  20  and  2 1 H,  as  is  the  case 
in  the  IBM  PC.  My  system  has  multiple 
cascaded  8259As  and  nothing  at  I/O 
ports  20  and  21 H!  This  module  plays 
with  the  interrupt  controller  for  the 
TRANSFER  and  IOTRANSFER  opera¬ 
tions  to  implement  an  obscure  feature 
of  Modula-2  that  allows  numeric  “pri¬ 
ority”  module  parameters  to  turn  mod¬ 
ules  into  “monitors,”  as  in  Concurrent 
Pascal.  Logitech  interprets  module 
priorities  as  corresponding  to  different 
interrupt  masks  for  a  single  interrupt 
controller.  Luckily,  the  compiler,  link¬ 
er,  and  debugger  do  not  seem  to  use 
prioritized  modules  or  TRANSFER 
statements  and  thus  can  be  run  with¬ 
out  modification  of  M2. EXE.  My  sys¬ 
tem  would  require  nontrivial  modifica¬ 
tion  of  this  assembler  module  to  get 
these  Modula-2  features  working. 

The  installation  of  the  MS-Pascal 
compiler  is  more  straightforward.  All 
that  is  required  is  to  copy  the  three 
passes  of  the  compiler,  one  of  the  li¬ 
brary  files,  and  the  special  “LARGE” 
version  of  MS-LINK  to  a  system  disk. 
These  files  can  be  combined  on  a  single 
Pascal  system  disk  complete  with  an 
editor — even  a  360K  5!4-inch  floppy. 
The  MS-Pascal  compiler  (and  linker!) 
require  less  than  160K  total  system 
RAM  to  operate. 

Compiling  and  Linking 

Definition  and  implementation  mod¬ 
ules  are  compiled  separately  by  the  Lo¬ 
gitech  compiler.  The  output  from  com¬ 
piling  a  definition  module  is  accessed 


automatically  when  compiling  an  im¬ 
plementation  module.  You  can  invoke 
all  four  passes  of  the  Logitech  compil¬ 
er  entirely  from  the  command  line  with 
the  command: 

M2  COMP  <filename> 

The  overlay  mechanism  of  the  RTS 
loads  each  pass  of  the  compiler  into 
memory  separately.  For  this  to  work, 
however,  the  default  drive  when  the 
compiler  is  invoked  must  be  the  drive 
that  contains  the  directory  \M2LOD. 

To  get  around  this  and  to  automatically 
invoke  some  of  the  compiler  switches,  I 
created  the  following  command  proce¬ 
dure  (MOD. BAT)  to  invoke  the 
compiler: 

M2  A:\M2LOD\COMP 
%1  /R-/S-/T-/E 

Similarly,  I  created  a  command  proce¬ 
dure  MLNK.BAT  to  invoke  the  linker: 

M2  A:\M2LOD\LINK  %1 

The  compiler  and  linker  use  the  first 
eight  characters  of  a  module  name  to 
search  for  compiled  definition  modules 
and  object  modules.  This  has  the  ad¬ 
vantage  of  automating  the  search  for 
library  modules. 

As  is  apparent  from  the  compile  and 
link  times  shown  in  the  Figure  (page 
76),  the  Logitech  compiler  and  linker 
are  slow.  A  further  annoyance  stems 
from  the  Logitech  compiler’s  organi¬ 
zation  into  four  passes,  the  first  three 
of  which  can  generate  syntax-related 
error  messages.  Whether  the  compila¬ 
tion  is  aborted  on  an  error  after  either 
of  the  first  two  passes  or  not  can  be 
configured  by  the  user.  This  situation 
is  made  worse  by  the  compiler’s  lack  of 
certain  types  of  error  recovery.  For  in¬ 
stance,  the  compiler  is  case  sensitive: 
all  keywords  are  in  upper  case.  It  is 
very  easy  to  forget  and  write  “to”  in¬ 
stead  of  “TO”  in  a  FOR  loop.  Even 
though  it  should  be  an  easy  matter  for 
the  compiler  to  flag  this  as  a  warning 
and  continue,  such  errors  are  treated 
as  fatal. 

The  MS-Pascal  compiler  can  also  be 
invoked  entirely  from  the  command 
line.  However,  each  pass  of  the  compil¬ 
er  must  be  invoked  separately;  no  com¬ 
mand  procedure  is  provided  which 
automates  this  process.  I  have  written 
such  a  command  procedure  (PAS¬ 
SAT)  to  invoke  the  required  passes  of 
the  compiler: 

PAS1  %1,%1„%1; 

IF  ERRORLEVEL  0  DEL  %1.LST 

Dr.  Dobb’s  Journal.  February  1985 


IF  ERRORLEVEL  4 
GOTO  FINISHED 
PAS2 

FINISHED 

This  command  procedure  would 
need  enhancement  to  use  PAS3,  which 
provides  a  pseudo-assembly  language 
listing  of  the  code  generated.  In  order 
to  link  multiple  modules  using  Micro- 
Soft’s  LINK  utility,  the  following  com¬ 
mand  format  offers  the  greatest  flexi¬ 
bility  (the  /NO  parameter  suppresses 
the  automatic  search  for  the  Pascal  li¬ 
brary  on  the  default  path): 

LINK/NO  @program 
A  “program”  file  must  be  created  for 
each  program  you  are  developing.  It 
would  contain  the  following  entries: 
program + 
module  1  + 
module2  + 
moduleN 
program 
program 
A:\LIB\PASCAL 

Having  to  maintain  this  additional  file 
can  sometimes  be  a  nuisance. 

Although  the  MS-Pascal  compiler  is 
not  blindingly  fast,  it  is  certainly  toler¬ 
able — over  twice  as  fast  as  the  Logi¬ 
tech  compiler.  The  MS-Pascal  compil¬ 
er  is  organized  as  three  passes,  only  the 
first  of  which  generates  syntax-related 
error  messages.  The  error  recovery  of 
the  compiler  is  adequate,  and  some 
common  syntax  errors  are  automati¬ 
cally  corrected,  generating  only  warn¬ 
ings.  However,  some  simple  types  of 
errors,  such  as  a  missing  END,  will  cas¬ 
cade  and  cause  an  avalanche  of  mean¬ 


ingless  error  messages. 

The  MS-Pascal  compilation  unit 
that  most  closely  resembles  the  Mo¬ 
dula-2  definition  and  implementation 
modules  is  the  “unit”  concept  bor¬ 
rowed  from  UCSD  Pascal.  A  unit  also 
consists  of  a  definition  part  and  an  im¬ 
plementation  part.  However,  the  defi¬ 
nition  portion  of  an  MS-Pascal  unit  is 
not  itself  compiled;  it  is  included  as 
part  of  the  source  of  its  implementa¬ 
tion,  as  well  as  any  compilation  units 
importing  it,  usually  by  means  of  the 
INCLUDE  directive.  This  allows  for 
“version”  errors,  resulting  in  changes 
in  such  definition  parts. 

Benchmarks 

I  decided  to  use  the  system  clock  to 
time  my  benchmarks.  Although  both 
Logitech  Modula-2  and  MS-Pascal 
provide  library  functions,  I  didn’t  care 
for  the  format  of  either.  Instead  I  de¬ 
cided  to  write  a  GetTime  procedure 
that  would  perform  an  MSDOS  func¬ 
tion  call.  I  immediately  ran  into  prob¬ 
lems  with  MS-Pascal. 

MS-Pascal  provides  a  library  func¬ 
tion  (DOSXQQ)  to  invoke  MSDOS 
functions  and  two  global  variables 
(CRCXQQ  and  CRDXQQ)  to  pass  pa¬ 
rameters  through  the  CX  and  DX  reg¬ 
isters.  This  function  does  not  work 
properly:  the  two  global  variables  are 
not  updated  from  the  CX  and  DX  reg¬ 
isters  following  a  MSDOS  call.  Fur¬ 
thermore,  even  if  this  mechanism 
worked  as  advertised,  it  still  would  not 
be  powerful  enough  to  handle  all 
MSDOS  functions.  The  registers  ac¬ 


cessed  by  various  MSDOS  functions  in¬ 
clude  the  AX,  BX,  CX,  DX,  SI,  DI, 
DS,  and  ES  registers!  I  wrote  a  general 
purpose  MSDOS  interface  procedure 
for  MS-Pascal  in  assembly  language 
(Listing  One,  page  78)  which  allows 
access  to  all  these  registers,  and  seems 
to  do  the  job  quite  nicely.  Logitech,  on 
the  other  hand,  supplies  a  much  more 
versatile  library  function,  DOSCALL, 
which  gives  full  access  to  all  MSDOS 
functions  and  worked  properly  on  all 
my  test  cases. 

I  wrote  a  general  purpose  clock 
module  in  both  Modula-2  and  MS- 
Pascal  (Listing  Two,  page  80,  and 
Listing  Three,  page  82).  It  reflects  the 
capabilities  of  MSDOS  by  providing  a 
function  GetTime,  which  returns  the 
time  through  a  record.  I  then  wrote  a 
simple  stopwatch  module  (Listing 
Four,  page  84,  and  Listing  Five,  page 
86)  which  provides  two  procedures, 
Start  and  Stop.  This  module  allowed 
me  to  measure  the  benchmarks  to 
within  a  hundredth  of  a  second. 

I  ran  three  benchmarks:  the  ever- 
popular  Sieve  of  Eratosthenes  (Listing 
Six,  page  86  and  Listing  Seven,  page 
88),  the  Fibonacci  number  benchmark 
(Listing  Eight,  page  88,  and  Listing 
Nine,  page  90),  and  a  skeletal  text  fil¬ 
ter  program  (Listing  Ten,  page  92,  and 
Listing  Eleven,  page  93)  that  does 
nothing  more  than  copy  files.  Both  of 
these  compilers  produced  very  fast, 
highly  optimized  code  (see  the  Fig¬ 
ure).  The  code  generator  of  the  MS- 
Pascal  compiler  performed  better  on 
the  Sieve  benchmark,  but  not  by  a 


benchmark 
&  compiler 

|  compile 
time 
|  (sec) 

link 

time 

(sec) 

code 

size 

(bytes) 

linked 

size 

(bytes) 

execute 

time 
(  sec) 

1.  Sieve  of  Eratosthenes 
Logitech  Modula-2/86 

82 

243 

21474 

5.25 

Microsoft  MS-Pascal 

26 

161 

19518 

4.87 

2.  Fibonacci  Numbers 

Logitech  Modula-2/ 86 

1 

81 

68 

141 

21362 

17.67 

Microsoft  MS-Pascal 

I  25 

37 

107 

19470 

23.18 

3.  Text  Filter 

Logitech  Modula-2/86 

I 

1  87 

69 

391 

21694 

7.83 

Microsoft  MS-Pascal 

27 

38 

353 

19742 

18.32 

Figure 

Benchmark  Results. 

All  tests  performed  on  8MHz  8086,  all  run-time  error-checking  disabled. 


Dr.  Dobb’s  Journal,  February  1985 


76 

145 


wide  margin.  The  Logitech  compiler 
won  the  other  two  benchmarks  by  a 
somewhat  wider  margin.  However, 
both  of  these  languages  produced 
bulky  executable  files  by  the  time  the 
linkers  were  through  with  them. 

One  thing  that  is  not  apparent  from 
the  benchmarks  is  that  the  Logitech 
linker  produces  very  small  executable 
files  when  none  or  only  a  few  of  the 
library  modules  are  imported.  The  li¬ 
brary  module  InOut  (which  most  pro¬ 
grams  access)  causes  large  executable 
files  because  it  pulls  in  the  FileSystem 
module;  if  no  file  support  is  needed,  the 
Terminal  module  produces  small  exe¬ 
cutable  files.  This  feature  would  be  of 
value  for  writing  certain  types  of  utili¬ 
ty  programs,  such  as  a  program  to  con¬ 
tinuously  display  the  time. 

Miscellaneous 

Logitech  supplies  a  post  mortem  sym¬ 
bolic  debugger  with  the  compiler  pack¬ 
age.  This  program  allows  the  user  to 
examine  the  procedure-calling  se¬ 
quence,  last  statement  executed,  and 
value  of  all  data  items  following  a  ‘core 
dump.’  Such  a  core  dump  is  performed 
automatically  by  the  RTS  following  a 
runtime  error  or  a  ctrl-C  typed  by  the 
user  during  execution.  This  provides 
significantly  more  diagnostic  informa¬ 
tion  than  the  simple  error  message  pro¬ 
duced  by  MS-Pascal  on  a  runtime  er¬ 
ror.  However,  it  is  not  as  powerful  as 
an  interactive  runtime  debugger;  such 
a  product  has  been  announced  by  Lo¬ 


gitech,  however. 

Both  Logitech  and  Microsoft 
should  be  commended  for  the  large 
number  of  useful  library  modules  sup¬ 
plied  with  their  compilers.  Despite 
quirks  in  both  libraries,  these  give  the 
programmer  a  tremendous  headstart 
in  developing  an  application  program. 

Both  of  these  compilers  provide  for 
floating-point  support,  either  using  an 
8087  coprocessor  or  with  an  8087  emu¬ 
lation  library.  I  did  not  perform  a 
benchmark  on  the  emulation  library, 
because  anyone  who  is  truly  interested 
in  fast  floating  point  would  be  using  an 
8087,  anyway. 

The  Logitech  compiler  does  not  sup¬ 
port  a  32-bit  integer  type,  as  does  MS- 
Pascal.  This  is  unfortunate  because  a 
16-bit  integer  is  too  small  for  numer¬ 
ous  applications  (random  number  gen¬ 
erators,  for  instance).  This  problem 
will  undoubtedly  be  resolved  in  a  fu¬ 
ture  release,  as  recent  revisions  to  the 
Modula-2  language  by  N.  Wirth  in¬ 
clude  LONGCARD  and  LONGINT 
data  types. 

My  final  complaint  about  the  Logi¬ 
tech  compiler,  which  I  have  already 
touched  on,  is  that  when  a  ctrl-C  is 
typed  during  compilation  or  linking, 
about  15  seconds  elapse  and  a  large 
MEMORY.PMD  file  is  created  for  the 
debugger  in  the  default  directory.  This 
can  be  changed  only  by  reassembling 
the  RTS  after  changing  one  of  the  pa¬ 
rameters  in  the  source  code.  Of  course, 
post  mortem  debugging  of  application 


programs  requires  a  version  the  RTS 
with  this  feature  left  intact.  I  would 
suggest  that  Logitech  distribute  two 
executable  versions  of  the  RTS. 

Conclusion 

The  Logitech  Modula-2/86  package  is 
clearly  a  winner.  It  provides  a  com¬ 
plete,  relatively  mature  Modula-2  en¬ 
vironment  for  the  8086  that  is  competi¬ 
tive  with  the  best  8086  compilers 
available.  In  fact,  a  comparison  of  its 
speed  on  my  system  with  published 
benchmarks  on  the  Lilith  shows  them 
to  be  almost  even  in  execution  time. 
Perhaps  most  importantly,  Logitech 
provides  the  kind  of  technical  support 
necessary  for  a  professional  software 
development  package. 

On  the  other  hand,  MS-Pascal  is 
still  very  much  a  viable  systems  lan¬ 
guage.  It  would  certainly  be  preferable 
on  a  small  system:  it  requires  96K  less 
RAM  and  can  perform  adequately  on  a 
system  equipped  only  with  5 '/4-inch 
floppies.  In  addition,  its  faster  compile 
and  link  times  would  be  appreciated  on 
the  much  slower  IBM  PC. 

I  suppose  the  acid  test  is  this:  Which 
of  these  compilers  will  I  be  using  from 
now  on?  This  brings  us  back  to  the  be¬ 
ginning  of  the  article.  Modula-2  really 
is  the  successor  to  Pascal,  and  the  Lo¬ 
gitech  compiler  is  a  thoroughly  usable 
implementation.  I  have  made  the 
switch! 

DDJ 

(Listings  begins  on  next  page) 


77 

146 


Dr,  Dobb's  Journal,  February  1985 


Reviews  (Text  begins  on  page  74) 

Listing  One 


INTERFACE;  UNIT  SYSTEM  ( 

Registers , 

DOS CALL) ; 

TYPE 

Registers  =  RECORD 
ax,  bx,  cx,  dx:  WORD; 
si,  di:  WORD; 
ds,  es :  WORD; 

END; 

PROCEDURE  DOSCALL  (VAR  r:  Registers); 

{ 

loads  8086  registers  from  r 

invokes  MS-DOS 

loads  r  from  8086  registers 

} 


END;  {SYSTEM} 


NAME 

SYSTEM 

PUBLIC 

DOSCALL 

RGSTRS 

STRUC 

rax 

DW 

? 

rbx 

DW 

? 

rex 

DW 

? 

rdx 

DW 

? 

rsi 

DW 

? 

rdi 

DW 

? 

rds 

DW 

? 

res 

DW 

? 

RGSTRS 

ENDS 

DOSCODE 

SEGMENT 

ASSUME 

CS:  DOSCODE 

DOSCALL 

PROC 

FAR 

PUSH 

BP 

;  save 

calling  frame  pointer 

MOV 

BP, 

SP 

;  get 

doscall  frame  pointer 

PUSH 

DS 

;  save 

calling  data  segment 

MOV 

BP, 

6[BP] 

;  get 

rgstrs  pointer 

;  load  8086  registers  from  structure 


MOV 

AX, 

[ BP] .rax 

MOV 

BX, 

[ BP] .rbx 

MOV 

CX, 

[BP] .rex 

MOV 

DX, 

[ BP] .rdx 

MOV 

SI, 

[BP]  .rsi 

MOV 

DI, 

[BP] .rdi 

MOV 

DS, 

[ BP] .rds 

MOV 

ES, 

[ BP] .res 

(Continued  on  page  80) 


Dr.  Dobb's  Journal,  February  1985 


78 

147 


IVCVIC  WS  (Listing  Continued,  text  begins  on  page  74) 

Listing  One 


INT  21 H  ;  call  MS-DOS 

;  load  structure  from  8086  registers 
MOV  [BP]. rax,  AX 

MOV  [Bpj.rbx,  BX 

MOV  [BP]. rex,  CX 

MOV  [BP].rdx,  DX 

MOV  [ BP] .rsi  ,  SI 

MOV  [BPj.rdi,  DI 

MOV  [BP].rds,  DS 

MOV  [BP]. res,  ES 

POP  DS  ;  restore  calling  data  segment 

POP  BP  ;  restore  calling  frame  pointer 

RET  2  ;  pop  structure  pointer  during  return 

DOSCALL  ENDP 

D0SC0DE  ENDS 

END  End  Listing  One 

Listing  Two 


DEFINITION  MODULE  Clck; 

EXPORT  QUALIFIED 
Time , 

Date , 

SetTime , 

SetDate , 

Get Time , 

GetDate ; 

TYPE 

Time  =  RECORD 
hour  :  [0 .  .23]  ; 
minute :  [0 .  .59 ]  ; 
second :  [0  . . 59 ]  ; 
hundredth:  [ 0 . . 99 ] ; 

END; 

Date  =  RECORD 

year  :  [ 1980 . .2099] ; 
month :  [ 1 . . 12] ; 
day:  [  1 .  .31 ] ; 

END; 

PROCEDURE  SetTime  (VAR  t:  Time); 

PROCEDURE  SetDate  (VAR  d:  Date); 

PROCEDURE  Get  Time  (VAR  t:  Time); 

PROCEDURE  GetDate  (VAR  d:  Date); 


END  Clck. 

IMPLEMENTATION  MODULE  Clck; 

FROM  SYSTEM  IMPORT 
DOSCALL; 

( : *********************************** ) 
PROCEDURE  SetTime  (VAR  t:  Time); 

VAR 

al:  [0 . .255]  ; 
cx,  dx:  CARDINAL; 

BEGIN 

WITH  t  DO 

cx  :=  256*hour  +  minute; 
dx  :=  256* second  +  hundredth; 

DOSCALL  ( 2DH,  cx ,  dx ,  al); 

END; 

END  SetTime; 

(  *****************************  -k-kk-k-k-k} 

PROCEDURE  SetDate  (VAR  d:  Date); 

VAR 

al:  [0 . .255]  ; 
cx,  dx:  CARDINAL; 

( Continued  on  page  82) 


80 


Dr.  Dobb's  Journal,  February  1985 


i  A  Q 


Reviews  (Listing  Continued,  text  begins  on  page  74) 

Listing  Two 


BEGIN 

WITH  d  DO 
cx  :=  year; 

dx  :=  256*month  +  day; 

DOSCALL  ( 2BH,  cx ,  dx ,  al); 

END; 

END  SetDate; 

(*4^**** *********************** *****) 

PROCEDURE  GetTime  (VAR  t:  Time); 

VAR 

cx ,  dx:  CARDINAL; 

BEGIN 

WITH  t  DO 

DOSCALL  (2CH,  cx ,  dx) ; 
hour  :=  cx  DIV  256; 
minute  :=  cx  MOD  256; 
second  :=  dx  DIV  256; 
hundredth  :=  dx  MOD  256; 

END; 

END  GetTime; 

(******  a****************************) 

PROCEDURE  GetDate  (VAR  d:  Date); 

VAR 

cx,  dx:  CARDINAL; 

BEGIN 

WITH  d  DO 

DOSCALL  (2 AH,  cx ,  dx) ; 
year  :=  cx; 
month  :=  dx  DIV  256; 
day  :=  dx  MOD  256; 

END; 

END  GetDate; 

END  Clck.  End  Listing  Two 


Listing  Three 

INTERFACE;  UNIT  CLOCK  ( 
Time , 

Date , 

Set Time , 

SetDate , 

GetTime , 

GetDate) ; 

TYPE 

Time  =  RECORD 
hour:  0..23; 

Dr.  Dobb’s  Journal,  February  1985 


minute:  0..59; 
second:  0..59; 
hundredth:  0..99; 

END; 

Date  =  RECORD 

year:  1980.. 2099; 
month:  1..12; 
day:  1..31; 

END; 

PROCEDURE  SetTime  (VAR  t:  Time); 

PROCEDURE  SetDate  (VAR  d:  Date); 

PROCEDURE  GetTime  (VAR  t:  Time); 

PROCEDURE  GetDate  (VAR  d:  Date); 

END;  {Clock} 

IMPLEMENTATION  OF  CLOCK; 

USES  SYSTEM; 

{ft**********************************} 

PROCEDURE  SetTime; 

VAR 

r:  Registers; 

BEGIN 

WITH  t  DO  WITH  r  DO  BEGIN 
ax  :=  //2DOO; 

cx  :=  BYWORD  (hour,  minute); 
dx  :=  BYWORD  (second,  hundredth); 
DOSCALL  (r); 

END; 

END;  {SetTime} 

{ft**********************************} 

PROCEDURE  SetDate; 

VAR 

r:  Registers; 

BEGIN 

WITH  d  DO  WITH  r  DO  BEGIN 
ax  :=  //2 BOO ; 
cx  :=  year; 

dx  :=  BYWORD  (month,  day); 

DOSCALL  (r); 

END; 

END;  {SetDate} 

(Continued  on  page  84) 

82 

149 


ilCVIGWS  (Listing  Continued,  text  begins  on  page  74) 

Listing  Three 


^***********************************j 


PROCEDURE  Get Time; 
VAR 

r:  Registers; 


BEGIN 

WITH  t  DO  WITH  r  DO  BEGIN 
ax  :=  //2C00; 

DOSCALL  (r); 
hour  :=  HIBYTE  (cx)  ; 
minute  :=  LOBYTE  (cx); 
second  :=  HIBYTE  (dx); 
hundredth  :=  LOBYTE  (dx) ; 

END; 

END;  {Get  Time} 

{*******  **********************  ******} 

PROCEDURE  GetDate; 


VAR 

r:  Registers; 

BEGIN 

WITH  d  DO  WITH  r  DO  BEGIN 
ax  :=  //2A00; 

DOSCALL  (r); 

year  :=  cx; 

month  :=  HIBYTE  (dx)  ; 

day  :=  LOBYTE  (dx); 

END; 

END;  {GetDate} 

END.  {Clock}  End  Listing  Three 


Listing  Four 


DEFINITION  MODULE  StopWatch; 

EXPORT  QUALIFIED 
Start , 

Stop; 

PROCEDURE  Start; 

PROCEDURE  Stop; 

END  StopWatch. 


IMPLEMENTATION  MODULE  StopWatch; 

FROM  InOut  IMPORT 
WriteString , 

WriteCard , 

WriteLn; 

FROM  Clck  IMPORT 
Time , 

Get Time ; 

VAR 

tl ,  t2:  Time; 

(a*****************************) 

PROCEDURE  Start; 

BEGIN 

Get  Time  (tl); 

END  Start; 

(★a****************************) 

PROCEDURE  Stop; 

( ********************* A********) 

PROCEDURE  DisplayTime  (t:  Time); 

BEGIN 

WITH  t  DO 

WriteCard(hour ,  8); 

WriteCard(minute ,  8); 

WriteCard( second ,  8); 
WriteCard(hundredth,  8); 

WriteLn; 

END; 

END  DisplayTime; 

( ******  ************************ ) 

BEGIN 

GetTime  (t2); 

WriteString( ' start :  ');  DisplayTime(tl) ; 
WriteString( ' stop  :  ');  DisplayTime( t2) ; 

END  Stop; 

END  StopWatch. 


End  Listing  Four 


(Listing  five  begins  on  page  86) 


84 


Dr.  Dobb's  Journal,  February  1985 


Reviews  (Listing  Continued,  text  begins  on  page  74) 

Listing  Five 


INTERFACE;  UNIT  StopWatch  ( 

Start , 

Stop)  ; 

PROCEDURE  Start; 

PROCEDURE  Stop; 

END;  {StopWatch} 

IMPLEMENTATION  OF  StopWatch; 

USES  Clock; 

VAR 

tl ,  t2 :  Time; 

(******************************} 

PROCEDURE  Start; 

BEGIN 

GetTime  (tl); 

END;  {Start} 

{******************************} 

PROCEDURE  Stop; 

{****************************** } 

PROCEDURE  Display Time  (t:  time); 

BEGIN 

WITH  t  DO  BEGIN 

WriteLn( hour ,  minute , second , hundredth) ; 
END; 

END;  { DisplayTime} 

{******************************} 

BEGIN 

GetTime  ( 1 2 ) ; 

Write( ' start :  ');  DisplayTime( tl) ; 

Write( ' stop  :  ');  DisplayTime( t2) ; 

END;  {Stop} 

END.  {StopWatch} 

End  Listing  Five 


Listing  Six 

MODULE  Benchmarkl; 

(*  Sieve  of  Eratosthenes  *) 

FROM  StopWatch  IMPORT 
Start , 

Stop; 

CONST 

iterations  =  10; 

VAR 

i:  INTEGER; 

( ************************** ******************  ) 
PROCEDURE  Sieve; 

CONST 

size  =  8190; 

VAR 

i,  prime,  k,  count:  INTEGER; 
flags:  ARRAY[0. .size]  OF  BOOLEAN; 

BEGIN 

count  :=  0; 

FOR  i  :=  0  TO  size  DO 
flags[ i]  :=  TRUE; 

END; 

FOR  i  :=  0  TO  size  DO 
IF  flags [i]  THEN 
prime  :=  i  +  i  +  3; 
k  :=  i  +  prime; 

WHILE  k  <=  size  DO 
flags [k]  :=  FALSE; 

INC  (k,  prime); 

END; 

INC  (count); 

END; 

END; 

END  Sieve; 

(a*******************************************) 


BEGIN 

Start; 

FOR  i  :=  1  TO  iterations  DO 
Sieve ; 

END; 

Stop; 

END  Benchmarkl . 

End  Listing  Six 

(Listing  seven  begins  on  page  88) 


Dr.  Dobb's  Journal.  February  1985 


86 

151 


Reviews  (Listing  Continued, 

Listing  Seven b's'"s  °" pase  74> 

PROGRAM  Benchmark 1  (INPUT,  OUTPUT); 

{  Sieve  of  Eratosthenes  } 

USES  StopWatch; 

CONST 

iterations  =  10; 

{  number  of  times  benchmark  executed  } 
VAR 

i:  INTEGER; 

I**************************************} 
PROCEDURE  Sieve; 

CONST 

size  =  8190; 

VAR 

i,  prime,  k,  count:  INTEGER; 
flags:  ARRAY  [0..size]  OF  BOOLEAN; 

BEGIN 

count  :=  0; 

FOR  i  :=  0  TO  size  DO  BEGIN 
flags [i]  :=  TRUE; 

END; 

FOR  i  :=  0  TO  size  DO  BEGIN 
IF  flags [ i ]  THEN  BEGIN 
prime  :=  i  +  i  +  3; 
k  :=  i  +  prime; 

WHILE  k  <=  size  DO  BEGIN 
flags [ k]  :=  FALSE; 
k  :=  k  +  prime; 

END; 

count  :=  count  +  1; 

END; 

END; 

END;  {Sieve} 

I*************************************} 

BEGIN 

Start; 

FOR  i  :=  1  TO  iterations  DO  BEGIN 
Sieve ; 

END; 

Stop; 

END.  {  Benchmarkl }  End  Listing  Seven 

Listing  Eight 

MODULE  Benchmark2; 

(*  Fibonacci  series  *) 

FROM  StopWatch  IMPORT 
Start , 

Stop; 

88 

is? 


CONST 

iterations  =  10; 

(*  number  of  times  benchmark  executed*) 
length  =  24;  (*  length  of  series  *) 

VAR 

i:  INTEGER; 
useless  :  CARDINAL; 

****************************************** ) 
PROCEDURE  Fibonacci  (n:  CARDINAL):  CARDINAL; 
BEGIN 

IF  n  >  2  THEN 

RETURN  Fibonacci(n-l)  +  Fibonacci(n-2) ; 
ELSE 

RETURN  1; 

END; 

END  Fibonacci; 

(^kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk  kkkkk  ^ 

BEGIN 

Start; 

FOR  i  :=  1  TO  iterations  DO 

useless  :=  Fibonacci  (length); 

END; 

Stop; 

END  Benchmark2.  End  Listing  Eight 


Listing  Nine 

PROGRAM  Benchmark2  (INPUT,  OUTPUT); 

{  Fibonacci  Series  } 

USES  StopWatch; 

CONST 

iterations  -  10; 

{  number  of  times  benchmark  executed  } 

length  -  24;  {  length  of  series  computed  } 

VAR 

i:  INTEGER; 

{*****************************************} 

FUNCTION  Fibonacci  (n:  WORD):  WORD; 

BEGIN 

IF  n  >  2  THEN  BEGIN 

Fibonacci  :=  Fibonacci(n-l)  + 

Fibonacci(n-2) ; 

END  ELSE  BEGIN 
Fibonacci  :«  1; 

END; 

END;  {Fibonacci}  (Listing  Eight  begins  on  page  90) 
Dr.  Dobb's  Journal,  February  1985 


ft  Cr  VIC  VV5  (Listing  Continued,  text  begins  on  page  74) 

Listing  Nine 


{  A  *******  **********  **********************************  *  A  A  4c  4c  ** 

BEGIN 

Start; 

FOR  i  1  TO  iterations  DO  BEGIN 
EVAL  (Fibonacci  (length)); 

END; 

Stop; 

END.  { Benchmark2}  End  Listing  Nine 


Listing  Ten 

MODULE  Benchmark3 ; 

(*  text  filter  *) 

FROM  Stopwatch  IMPORT 
Start , 

Stop; 

FROM  FileSystem  IMPORT 
Response , 

File, 

Lookup, 

Reset , 

Close , 

Read  Byte , 

WriteByte ; 

FROM  InOut  IMPORT 
WriteString , 

WriteLn; 

CONST 

iterations  =  10; 

FileSize  =  1000; 

VAR 

i:  INTEGER; 

(*****************************************) 
PROCEDURE  Init; 

VAR 

i:  INTEGER; 
f 1 :  File ; 

BEGIN 

Lookup  (fl,  ' DK: f ilel .doc'  ,  TRUE); 

FOR  i  :=  1  TO  FileSize  DO 
WriteByte  (f 1 ,  'a' ) ; 

END; 

Close  (fl); 

END  Init; 


Dr.  Dobb's  Journal.  February  1985 


90 

153 


( a**************************************** ) 
PROCEDURE  Filter; 

VAR 

fl,  f 2 :  File; 
c:  CHAR; 

BEGIN 

Lookup  (fl,  ' DK:filel .doc' ,  FALSE); 

IF  NOT  ( f 1  .res  =  done)  THEN 

WriteString  ('filel.doc  not  found'); 
WriteLn; 

END; 

Lookup  ( f 2 ,  'DKrfile2.doc' ,  TRUE); 

LOOP 

Read Byte  (fl,  c) ; 

IF  fl.eof  THEN 
EXIT; 

END; 

(*  process  c  *) 

WriteByte  ( f 2 ,  c)  ; 

END; 

Close  (fl); 

Close  ( f 2 ) ; 

END  Filter; 

(*****************************************) 

BEGIN 

Init; 

Start ; 

FOR  i  :=  1  TO  iterations  DO 
Filter; 

END; 

Stop; 

END  Benchmark3. 

End  Listing  Ten 


Listing  Eleven 


PROGRAM  Benchmarks  (INPUT,  OUTPUT); 

{  text  filter  } 

USES  StopWatch; 

CONST 

Iterations  =  10; 

File  Size  =  1000; 

VAR 

i:  INTEGER; 

|************ *********  **************} 
PROCEDURE  Init; 


VAR 

i:  INTEGER; 
f 1 :  file  of  BYTE; 

BEGIN 

ASSIGN  (fl ,  'filel.doc') ; 

REWRITE  (fl); 

FOR  i  :=  1  TO  FileSize  DO  BEGIN 
WRITE  (fl ,  ORD('a')) ; 

END; 

CLOSE  (fl); 

END;  {Init} 

|***********************************J 

PROCEDURE  Filter; 

VAR 

fl,  f 2 :  file  of  BYTE; 
c:  BYTE; 

BEGIN 

ASSIGN  (fl,  'filel.doc'); 

ASSIGN  ( f2 ,  'file2.doc'); 

RESET  (fl); 

REWRITE  ( f 2) ; 

WHILE  NOT  EOF  (fl)  DO  BEGIN 
READ  (fl ,  c); 

{  process  c  } 

WRITE  ( f 2 ,  c); 

END; 

CLOSE  (fl); 

CLOSE  ( f 2) ; 

END;  {Filter} 

****************** &&*&&&*&******} 

BEGIN 

Init; 

Start; 

FOR  i  :=  1  TO  iterations  DO  BEGIN 
Filter; 

END; 

Stop; 

END.  {Benchmarks} 

End  Listings 


93 


Dr.  Dobb's  Journal.  February  1985 


THE  SOFTWARE  DESIGNER 


by  Michael  Swaine 

When  this  magazine  was  first  published 
in  January  1976,  its  title  was  Dr. 
Dobb’s  Journal  of  Tiny  BASIC  Calis¬ 
thenics  and  Orthodontia ,  with  the  sub¬ 
title  “Running  Light  Without  Over¬ 
byte”  (a  phrase  that  one  reader  recently 
asked  us  to  bring  back).  The  first  issue 
was  filled  with  very  tight  code  (and  dis¬ 
cussions  of  code)  designed  to  fit  in  the 
limited  memory  of  the  first  microcom¬ 
puters.  Hence  “running  light”;  hence 
“without  overbyte”:  it  made  sense  in 
1976  to  squeeze  every  last  byte  out  of 
your  system.  But  now  it’s  nine  years 
and  ninety-nine  issues  of  DDJ  later,  and 
the  memory  space  of  the  typical  micro¬ 
computer  has  increased  a  thousandfold. 
Does  it  make  any  sense  to  talk  about 
running  light  in  1985? 

To  get  an  answer  to  that  question,  I 
called  Dennis  Allison,  the  paradigmat¬ 
ic  light  runner,  in  Geneva. 

Dennis  is  the  “Do”  of  “Dobb.” 
When  and  he  and  Bob  Albrecht  of 
People’s  Computer  Company 
launched  this  magazine  as  a  three-is¬ 
sue  newsletter,  they  gave  the  job  of 
naming  their  creation  to  Rick  Baka¬ 
linsky,  who  was  then  PCC s  entire  pro¬ 
duction  department.  Rick  somehow 
got  the  idea  that  Dennis  was  “Don” 
and  combined  “Don”  and  “Bob”  to 
produce  “Dobb”  (doubling  the  “b”  to 
make  it  more  namelike). 

But  Dennis  is  not  just  Do;  among 
other  things  ( many  other  things),  he  is 
a  visiting  lecturer  in  Geneva  this 
month  and  next  month  will  be  doing 
something  or  other  in  Crete.  Among 
the  memorable  things  Dennis  has  done 
for  DDJ  over  the  years  is  a  column  on 
algorithms  he  wrote  during  the  editori¬ 
al  tenure  of  Suzanne  Rodriguez 
(whose  reminiscence  about  Bob  and 
Dennis  appears  in  the  “Festschrift  for 
Dr.  Dobb”  elsewhere  in  this  issue). 

Dennis  readily  agreed  that  the  world 
of  microcomputer  programming  had 
changed  radically  since  the  early  days. 

Dr.  Dobb's  Journal.  February  1985 


“It’s  different  now  that  memory  is 
free,”  he  told  me.  “When  DDJ  started, 
the  Altair  had  256  bytes  of  memory. 
That’s  a  far  cry  from  today,  when  for  a 
reasonable  sum  of  money  you  can  get 
64K  of  memory,  and  many  machines 
have  half  a  megabyte.  That  changes 
the  whole  scale  of  things.” 

Did  that  mean  that  the  urge  to  cre¬ 
ate  the  great  hack  was  no  longer  there? 
In  Hackers ,  Steven  Levy  wrote  about 
“the  last  hacker”;  was  he  right  in  his 
assessment  that  commercial  consider¬ 
ations  had  nudged  out  this  motivation? 

“It’s  still  there,”  Dennis  said. 
“There’s  a  certain  pride  in  making 
code  as  tight  and  as  efficient  as  possi¬ 
ble.  I  write  compilers  and  I  take  plea¬ 
sure  in  coding  as  efficiently  as  I  can  in 
writing  [parts  of  the]  compiler.  But  of¬ 
ten  it  is  done  as  an  aesthetic  exercise 
more  than  anything  else.  If  you  look  at 
Don  Knuth’s  code,  you’ll  find  that  it’s 
elegant  and  well-organized — and  effi¬ 
cient  in  this  same  sense.” 

But  Knuth  is  an  academician  and  an 
artist  in  his  work.  He  recently  got  into 
typefont  design  for  purely  aesthetic 
reasons.  Is  tight  coding  professionally 
important,  or  is  it  just  pretty? 

“It’s  still  important  to  use  code  effi¬ 
ciently.  The  one  thing  that’s  virtually 
guaranteed  is  that  you  won’t  have 
enough  cycles.  You’ll  never  have 
enough  memory,  because  expectations 
[can]  rise  faster  than  costs  [can]  drop.” 

Was  it,  then,  a  question  of  where 
you  invested  your  hacking  energy? 

“Yes.  The  rule  is  that  4%  of  the  code 
does  50%  of  the  work,  and  the  place 
where  you  need  to  squeeze  code  and 
get  the  number  of  instructions  down  is 
in  that  4%.” 

Tightening  the  critical  algorithms 
till  they  squeak,  rather  than  scroung¬ 
ing  space  for  one  more  cell  in  the 
spreadsheet? 

Dennis  agreed,  and  then  went  on  to 
talk  about  something  near  and  dear  to 


the  hearts  of  DDJ  readers  for  nine 
years:  the  virtue  of  publishing  program 
listings. 

“ DDJ  is  important  as  a  journal  be¬ 
cause  of  the  code.  It  was  started  to  pub¬ 
lish  code  and  it  has  always  published 
code.  It  publishes  more  code  than  any 
other  magazine.  And  it’s  still  my  belief 
that  people  [programmers]  learn  from 
reading  other  people’s  code.” 

What  they  learn,  of  course,  is  how  to 
program  well:  they  pick  up  tricks,  in¬ 
sights,  algorithms,  all  of  which  are  par¬ 
ticularly  well  expressed  when  present¬ 
ed  in  the  form  in  which  they  will 
ultimately  be  realized:  as  code.  We 
hope  to  go  right  on  publishing  useful 
and  educational  code,  including  both 
programs  significant  in  themselves  and 
bits  of  code  that  demonstrate  some  ex¬ 
emplary  algorithm  or  insight. 

Toward  the  latter  end,  we  will  be  re¬ 
introducing  next  month  a  column  on  al¬ 
gorithms,  written  by  none  other  than 
Dennis  Allison.  The  tentative  title  is 
“Allison  on  Algorithms.”  Well,  it's 
descriptive. 

With  Bob  Albrecht’s  column  on 
“Realizable  Fantasies,”  previewed  in 
the  “Festschrift  for  Dr.  Dobb”  else¬ 
where  in  this  issue,  the  founders  have 
returned  to  DDJ.  ddj 


96 

155 


Buffered  Disk  I/O 

The  attempt  to  predict  the  length  of  a 
mechanical  operation  is  easy  when  the 
system  involves  only  a  single  device. 
The  level  of  complication  increases  dra¬ 
matically,  however,  when  the  system 
comprises  two  or  more  dependent  de¬ 
vices.  It  simplifies  the  matter  to  realize 
that  total  system  performance  will  be 
no  greater  than  that  of  the  slowest  part. 

A  personal  computer  is  a  perfect  ex¬ 
ample.  The  electronic  portion  of  the 
computer  performs  its  task  at  blinding 
speeds,  but  read  a  record  from  disk,  and 
you  quickly  find  the  slowest  part  of  the 
computer.  A  few  techniques  can  mini¬ 
mize  the  mechanical  delays  encoun¬ 
tered  when  transferring  data  to  a  disk 
drive.  The  most  common  examples  are 
logical  sector  buffering  and  physical 
sector  skewing.  Both  are  typically  a 
standard  part  of  the  operating  system. 

Further  improvements  are  more  dif¬ 
ficult  to  achieve.  Most  require  addition¬ 
al  or  improved  hardware,  which  is  im¬ 
practical  in  many  cases.  This  places  the 
burden  of  any  further  improvement  di¬ 
rectly  on  the  application  program. 

To  increase  the  throughput  of  the 
CP/M,  we  can  use  any  extra  memory  to 
buffer  as  much  data  as  is  practical.  This 
manner  of  speeding  things  up  circum¬ 
vents  one  of  CP/M’s  problem  areas  and 
puts  all  the  system’s  resources  to  use — 
probably  for  the  first  time. 

The  CP/M  problem  has  to  do  with 
the  number  of  I/O  operations  neces¬ 
sary  to  write  a  record  to  disk.  This  was 
of  no  concern  when  disks  were  recorded 
in  single-density  format.  But  as  im¬ 
proved  hardware  enabled  higher  bit 
rates,  reliably  larger  sector  sizes  saw  al¬ 
most  immediate  use.  This  change  al¬ 
lowed  the  total  storage  capacity  of  the 
media  to  double.  There  was,  however,  a 
price  to  be  paid  for  this  improvement. 

CP/M  uses  a  single  I/O  buffer  to 
transfer  data  between  memory  and  the 
disk  drive.  A  1:1  logical-to-physical 


EXECUTION  TIME  VS  NUMBER  OF  BUFFERS 
READING  A  FILE  OF  1000  RECORDS 


HUMBER  OF  MEMORY  BUFFERS 

Figure  1 


EXECUTION  TIME  VS  NUMBER  OF  BUFFERS 
READING  A  FILE  OF  1000  RECORDS 
AND  WRITI  NG  A  FILE  OF  THE  SAME  SIZE 


NUMBER  OF  MEMORY  BUFFERS 

Figure  2 


98 

1 5fi 


Dr.  Dobb's  Journal,  February  1985 


sector  size  relationship  ensures  trans¬ 
fer  without  buffer  conflict.  Changing 
the  relationship  to  4:1,  however,  can 
require  up  to  three  times  as  much  disk 
I/O  to  transfer  one  logical  sector  of 
data.  For  a  more  complete  explanation 
of  buffer  conflicts,  refer  to  the  Decem¬ 
ber  column. 

Putting  memory-resident  data  buff¬ 
ers  to  work  speeding  data  through  your 
machine  can  be  as  simple  as  specifying 
a  parameter.  When  writing  CBASIC  as 
a  Master’s  project,  Gordon  Eubanks 
made  provision  for  the  programmer  to 
indicate  how  many  data  buffers  to  al¬ 
locate.  The  last  parameter  of  the 
OPEN  statement  (following  the  BUFF 
keyword)  specifies  how  many  record 
areas  to  set  aside. 

To  plot  the  effect  of  using  a  varying 
number  of  data  buffers  against  pro¬ 
gram  execution  time,  I  wrote  two  sim¬ 
ple  benchmark  programs.  The  first 
(Listing  One,  below)  requests  how 
many  data  buffers  to  allocate,  opens  an 
input  file,  and  executes  1000  sequen¬ 
tial  reads.  Total  program  execution 
time  is  plotted  in  Figure  1  (page  98). 
The  system  I  used  to  run  the  bench¬ 
mark  utilizes  a  physical  buffer  size  of 
5 1 2  bytes,  which  represents  a  logical- 
to-physical  sector  ratio  of  4:1. 

As  you  can  see  from  Figure  1,  there 
was  no  runtime  improvement  when 
from  one  to  four  buffers  were  allocat¬ 
ed.  When  six  buffers  were  used,  how¬ 
ever,  runtime  decreased  markedly.  An 
even  larger  decrease  resulted  when 
eight  or  more  buffers  were  allocated. 

The  reason  for  the  runtime  improve¬ 
ment  is  that  the  physical  sector  buffer 
contains  four  logical  sectors.  In  opera¬ 


tion,  the  first  program  read  request 
fills  the  physical  sector  buffer  with 
four  logical  sectors.  Each  subsequent 
read  request  is  satisfied  directly  from 
the  memory  buffer  until  it  must  be  re¬ 
plenished  from  disk.  When  the  data 
buffer  exceeds  the  size  of  one  physical 
sector,  the  possibility  of  reading  two 
sectors  in  a  single  revolution  of  the  disk 
is  very  good.  Allocating  six  or  more 
buffers  reduced  the  program  runtime 
by  nearly  50% — obviously  an  improve¬ 
ment  well  worth  pursuing. 

Runtime  improvements  come  even 
more  quickly  when  a  program  progres¬ 
sively  reads  from  one  file  and  writes  to 
another.  To  plot  this  condition,  I  wrote 
a  second  benchmark  program  (Listing 
2,  page  100).  Its  results  are  contained 
in  Figure  2  (page  98).  The  effect  of 
multiple  data  buffers  on  this  scenario 
is  even  more  dramatic. 

The  reason  for  the  sudden  decrease 
in  runtime  is  the  single  I/O  buffer 
maintained  by  CP/M.  When  only  a  sin¬ 
gle  record  buffer  is  chosen  for  the 
benchmark,  most  of  the  program’s 
runtime  is  consumed  by  buffer  mainte¬ 
nance.  To  understand  better  why  this 
function  consumes  so  much  time,  let’s 
trace  CP/M’s  data  flow. 

The  first  record  read  requires  that 
the  system  perform  a  physical  sector 
read  from  disk.  The  next  I/O  opera¬ 
tion  is  a  write.  Because  CP/M’s  buffer 
contains  only  data  from  a  previous 
read,  you  can  overwrite  it  without  re¬ 
gard  to  destroying  data.  The  next  oper¬ 
ation  is  another  read.  But  before  the 
selected  physical  sector  can  be  brought 
in  from  the  disk,  the  current  CP/M 
buffer  must  be  written  onto  disk;  oth¬ 


erwise,  the  data  it  contains  will  be 
overwritten.  The  next  operation  is  an¬ 
other  write,  but  first  the  physical  sec¬ 
tor  written  onto  disk  during  the  last 
write  operation  must  be  read  back  into 
memory  for  updating. 

From  this  study  of  a  typical  data 
flow,  we  realize  that  each  write  opera¬ 
tion  includes  a  pre-read  of  the  physical 
sector  on  all  but  the  first  logical  sector 
of  each  physical  sector.  Also  included 
in  each  read  is  a  physical  sector  read  to 
refresh  CP/M’s  I/O  buffer.  Data  buff¬ 
ering  of  only  a  single  record  in  this  sit¬ 
uation  can  account  for  an  I /O  reduc¬ 
tion  of  at  least  50%. 

To  conclude  this  discussion,  I  want  to 
share  an  assembly  language  routine 
(Listing  Three,  page  102)  that  also  per¬ 
forms  the  data  buffering  function. 
SFXIO  is  a  part  of  SYSLIB3  now  avail¬ 
able  in  the  public  domain  and  from 
Echelon,  Inc.  One  particularly  interest¬ 
ing  concept  used  in  this  routine  is  the 
transfer  of  information  between  the  ap¬ 
plication  program  and  the  subroutine 
by  use  of  a  control  block.  This  method 
is  especially  effective  in  trimming  the 
development  cycle  because  it  clusters 
all  the  relevant  control  information  into 
a  fixed  area  of  memory. 

I  will  leave  any  further  discussion  of 
this  subroutine  to  you.  If  you  have  any 
questions  about  this  subroutine,  please 
write  me  in  care  of  this  column.  If  you 
want  to  see  about  getting  SYSLIB3  on 
disk,  you  can  reach  Echelon  at  the  fol¬ 
lowing  address:  101  First  Street,  Los 
Altos,  CA  94022  (415)  948-3820. 

DD) 


CP/M  Exchange 

Listing  One 


REM 

REM  THIS  PROGRAM  READS  A  FILE  OF  1000  RECORDS  WITH  VARYING  NUMBERS 

REM  OF  MEMORY  BUFFERS  FOR  THE  PURPOSE  OF  SPEED  COMPARISONS 

REM 

100.0 

STRING  DRIVE, \ 

FILE. NAME, \ 

INPUT. DATA 

INTEGER  RECORD. SIZE, \ 

FILE. NUMBER, \ 

RECORD. COUNT,  \ 

LOOP. COUNTER,  \ 

NO. OF. BUFFERS 

(Continued  on  next  page ) 


Dr.  Dobb's  Journal,  February  1 985 


99 

157 


CP/M  Exchange 

Listing  One 


(Listing  Continued,  text  begins  on  page  98) 


DRIVE  =  "C:" 

FILE. NAME  =  "SPEED . DAT" 
RECORD. SIZE  =  128 
FILE. NUMBER  =  1 
RECORD. COUNT  =  1000 
NO. OF. BUFFERS  =  1 


200.0 

INPUT  "Enter  number  of  input  buffers  to  use:  ";  NO . OF . BUFFERS 
OPEN  DRIVE  +  FILE.NAME\ 

RECL  RECORD. SIZE\ 

AS  FILE. NUMBER\ 

BUFF  NO. OF. BUFFERS 


300.0 

FOR  LOOP. COUNTER  =  1  TO  RECORD. COUNT 

READ  #FILE. NUMBER;  INPUT. DATA 

NEXT  LOOP. COUNTER 

CLOSE  FILE. NUMBER 

END 


1  -  51 

2  -  51  R  S 

4  -  51  U  E 

M  B  6  -  46  N  C 

E  U  8  -  38  T  0 

M  F  10  -  35  IN 

OF  20  -  35  M  D 
RE  40  -  32  E  S 
Y  R  60  -  31 

S  80  -  30 

100  -  30 


LISTING  1  -  READ  ONLY  SPEED  TEST 


End  Listing  One 

Listing  Two 

REM 

REM  THIS  PROGRAM  READS  AND  WRITES  A  FILE  OF  1000  RECORDS  WITH  VARYING 

REM  NUMBERS  OF  MEMORY  BUFFERS  FOR  THE  PURPOSE  OF  SPEED  COMPARISONS 

REM 

100.0 

STRING  DRIVE, \ 

INPUT. FILE. NAME, \ 

OUTPUT. FILE. NAME, \ 

INPUT. DATA 

INTEGER  RECORD. SIZE, \ 

INPUT. FILE. NUMBER, \ 

OUTPUT. FILE. NUMBER, \ 

RECORD. COUNT, \ 

LOOP. COUNTER, \ 

NO. OF. BUFFERS 

DRIVE  =  "C:" 

INPUT. FILE. NAME  =  "SPEED . DAT" 

OUTPUT. FILE. NAME  =  "OUTSPEED.DAT" 

RECORD. SIZE  =  128 
INPUT. FILE. NUMBER  =  1 
OUTPUT. FILE. NUMBER  =  2 
RECORD. COUNT  =  1000 
NO. OF. BUFFERS  =  1 

200.0 

INPUT  "Enter  number  of  input  buffers  to  use:  ";  NO . OF . BUFFERS 
OPEN  DRIVE  +  INPUT. FILE. NAME\ 

RECL  RECORD. S I ZE\ 

AS  INPUT. FILE. NUMBER\ 

BUFF  NO. OF. BUFFERS 
CREATE  DRIVE  +  OUTPUT . FI LE . NAME\ 

RECL  RECORD. SIZE\ 

100  Dr.  Dobb's  Journal,  February  1985 

IRfi 


I Continued  on  page  102 ) 


CP/M  Exchange  (Listing  Continued,  text  begins  on  page  98) 
Listing  Two 


AS  OUTPUT. FILE. NUMBER\ 
BUFF  NO. OF. BUFFERS 


300.0 

FOR  LOOP. COUNTER  =  1  TO  RECORD. COUNT 
READ  #INPUT. FILE. NUMBER;  INPUT. DATA 
PRINT  #OUTPUT. FILE. NUMBER;  INPUT. DATA 
NEXT  LOOP. COUNTER 

CLOSE  INPUT. FILE. NUMBER,  OUTPUT . FI LE . NUMBER 
END 


1 

520 

2 

269 

M 

B 

4 

113 

R 

S 

E 

U 

6 

138 

U 

E 

M 

F 

8 

117 

N 

C 

0 

F 

10 

123 

T 

0 

R 

E 

20 

94 

I 

N 

Y 

R 

40 

80 

M 

D 

S 

60 

73 

E 

S 

80 

68 

100 

69 

LISTING  2  -  READ  AND  WRITE  SPEED  TEST 


Listing  Three 


End  Listing  Two 


SYSLIB  Module  Name:  SFXIO 
Author:  Richard  Conn 

SYSLIB  Version  Number:  3.0 
Module  Version  Number:  1.0 
Module  Entry  Points: 

FX$GET  F  X$  PUT 

SFXIO  provides  a  group  of  routines  which  can  perform  byte-oriented 
file  I/O  with  a  user-defined  buffer  size.  All  of  these  routines  work  with 
an  I/O  Control  Block  which  is  structured  as  follows: 


Byte 

0 

1 

2 

4 

6 

8 


Length  (Bytes) 
1 

1 

2 

2 

2 

36 


Function 

Number  of  128-byte  pages  in 

working  buffer  (set  by  user) 

End  of  File  Flag  (set  and  used 
by  SFXIO) 

Byte  Counter  (set  and  used  by  SFXIO) 
Next  Byte  Pointer  (set  and  used  by 
SFXIO) 

Address  of  Working  Buffer  (set  by  user) 
FCB  of  File  (FN  and  FT  Fields  set  by 
user,  rest  set  by  SFXIO) 


The  following  DB/DS  structure  can  be  used  in  the  calling  program 
to  implement  the  I/O  Control  Block: 


IOCTL : 

DB 

8 

Use  8  128-byte  pages  (IK) 

DS 

1 

Filled  by  SFXIO 

DS 

2 

Filled  by  SFXIO 

DS 

2 

Filled  by  SFXIO 

DW 

WORKBF 

Address  of  Working  Buffer 

IOCFCB: 

DB 

0 

Current  Disk  (Inited  by  SFXIO) 

DB 

'MYFILE 

1  ;  File  Name 

DB 

’TXT  ' 

;  File  Type 

DS 

24 

Fill  Out  36  Bytes 

WORKBF : 

DS 

1024 

Working  Buffer 

All  uses  of 

the  routines 

contain  the  address  of  IOCTL  in 

Note  that  if  you  use  a  buffer  for  input,  DO  NOT  use  it  for  output  also! 


(Continued  on  page  106) 


Dr.  Dobb’s  Journal,  February  1 985 


102 

159 


CP/M  Exchange  (Listing  Continued,  text  begins  on  page  98) 

Listing  Three 


EXTERNAL  SYSLIB  REFERENCES 

EXT  F$DELETE , F$MAKE , F$OPEN , F SC LOSE 

EXT  FXI SOPEN , FXO$OPEN , FXI SCLOS E , FXO$CLOS E 

EXT  FSREAD , F  $WRI TE 

EXT  INITFCB 

EXT  HMO  VB , SHFTRH , SHFTLH 


CONSTANTS 


TBUFF 

EQU 

80H  ;  DMA  BUFFER 

CTRLZ 

EQU 

'Z'-'e1  ;  *  Z 

;  MACRO  ROUTINES  FOR  FXIO 
/ 

PUTRG  MACRO 

PUSH  B 

PUSH  D 

PUSH  H 

ENDM 

GETRG  MACRO 

POP  H 

POP  D 

POP  B 

ENDM 


ENTRY  POINT  TO  READ  NEXT  BUFFER  FULL  FROM  DISK 


FXIO0 : 

LDA 

EOF 

/ 

CHECK  FOR  EOF 

ORA 

A 

t 

ABORT  IF  ALREADY  AT  EOF 

RNZ 

/ 

NZ  IS  ERROR  CODE 

LHLD 

FCB 

t 

PT  TO  FCB 

XCHG 

f 

...  IN  DE 

LDA 

BCNT 

f 

GET  BLOCK  COUNT 

MOV 

B,  A 

LHLD 

BUFADR 

• 

GET  ADDRESS  OF  BUFFER 

FXI01 : 

PUSH 

H 

i 

SAVE  BUFFER  PTR 

LHLD 

FCB 

i 

PT  TO  FCB 

XCHG 

i 

...  IN  DE 

CALL 

F$READ 

i 

READ  NEXT  BLOCK  (SECTOR) 

POP 

H 

i 

GET  PTR  TO  BUFFER 

JNZ 

FXI02 

LX  I 

D, TBUFF 

» 

COPY  INTO  MEMORY  BUFFER 

PUSH 

XCHG 

B 

t 

SAVE  COUNTER 

MVI 

B,  128 

CALL 

HMOVB 

XCHG 

POP 

B 

DCR 

B 

i 

COUNT  DOWN 

JNZ 

FXI01 

MVI 

A ,  0 

i 

SET  NO  EOF 

JMP 

FXI03 

FXI02 : 

MVI 

A, 0FFH 

i 

SET  EOF 

FXI03 : 

STA 

EOF 

t 

SET  EOF  FLAG 

LHLD 

BUFADR 

t 

PT  TO  FIRST  BYTE  AS  NEXT  BYTE 

SHLD 

BYTENXT 

LDA 

BCNT 

i 

SET  BLOCK  COUNT 

SUB 

B 

/ 

ADJUST 

MOV 

H  ,  A 

! 

RESULT  IN  HL 

MVI 

L ,  0 

106 


icn 


( Continued  on  page  109) 
Dr.  Dobb's  Journal.  February  1985 


CP/M  Exchange  (Listing  Continued,  text  begins  on  page  98) 

Listing  Three 


CALL 

SHFTRH 

;  SET  COUNT 

SHLD 

BYTECNT 

;  SET  BYTE  COUNT 

XRA 

A 

;  SET  NO  ERROR 

RET 

;  ENTRY  POINT 

TO  FLUSH 

BUFFER  TO  DISK  AND  SET  UP  FOR  NEXT 

FXOO0: 

LD  A 

BCNT 

;  GET  NUMBER  OF  BLOCKS 

MOV 

B,  A 

;  ...  IN  B 

LHLD 

BYTECNT 

;  NUMBER  OF  BYTES  YET  TO  GO 

CALL 

SHFTLH 

;  SHIFT  INTO  H 

MOV 

A ,  B 

;  COMPUTE  NUMBER  TO  WRITE 

SUB 

H 

MOV 

B,  A 

;  COUNT  IN  B 

LHLD 

BUFADR 

;  PT  TO  FIRST  BYTE 

FX001 : 

MOV 

A ,  B 

;  CHECK  FOR  DONE 

ORA 

A 

;  0=DONE 

JZ 

FX002 

DCR 

B 

;  COUNT  DOWN 

PUSH 

B 

;  SAVE  COUNT 

LXI 

D , TBUFF 

;  PT  TO  WRITE  BUFFER 

M  VI 

B,  128 

;  WRITE  128  BYTES 

CALL 

HMOVB 

;  COPY  INTO  BUFFER 

XCHG 

LHLD 

FCB 

;  PT  TO  FCB 

XCHG 

CALL 

F$WRITE 

;  WRITE  TO  DISK  NEXT  BLOCK  (SECTOR) 

POP 

B 

;  GET  COUNT 

JZ 

FX001 

XRA 

A 

;  SET  ERROR  CODE 

RET 

;  ENTRY  POINT 

TO  INIT  BUFFERS  FOR  NEXT  WRITE 

FX002 : 

LHLD 

BUFADR 

;  SET  NEXT  BYTE 

SHLD 

BYTENXT 

XRA 

A 

;  SET  NO  EOF 

STA 

EOF 

LDA 

BCNT 

;  SET  BLOCK  COUNT 

MOV 

H,  A 

;  ...  IN  HL 

MVI 

L,0 

CALL 

SHFTRH 

;  SHIFT  RIGHT  ONE  BIT 

SHLD 

BYTECNT 

;  SET  BYTE  COUNT 

MVI 

A, 0FFH 

;  NO  ERROR 

ORA 

A 

RET 

GET  NEXT  BYTE  FROM  BUFFER/FILE 


FX$GET : 


FXGETl : 


ON  INPUT,  DE  PTS 

ON  OUTPUT,  A=CH A 

PUTRG 

CALL 

PUTADR 

LHLD 

BYTECNT 

MOV 

A  ,  H 

ORA 

L 

JNZ 

FXGETl 

CALL 

FXIO0 

JNZ 

ERRET 

LHLD 

BYTENXT 

MOV 

A  ,  M 

STA 

BYTE 

INX 

H 

SHLD 

BYTENXT 

LHLD 

BYTECNT 

DCX 

H 

Z  FLAG  SET  IF  PAST  EOF 


SAVE  REGS 
PUT  ADDRESSES 
GET  REMAINING  COUNT 
NO  MORE  BYTES? 


READ  NEXT  BUFFER  FULL 


PT  TO  NEXT  BYTE 
GET  IT 
SAVE  IT 
PT  TO  NEXT 
SET  PTR 
COUNT  DOWN 


Dr.  Dobb  s  Journal,  February  1985 


I Continued  on  page  112) 


109 

161 


CP/M  Exchange  (Listing  Continued,  text  begins  on  page  98) 


Listing 

Three 

SHLD 

BYTECNT 

;  SET 

COUNT 

OKRETI : 

CALL 

GETADR 

;  GET 

ADDRESSES 

GETRG 
MV  I 

A, 0FFH 

;  SET 

NZ  FLAG 

ORA 

A 

;  NO 

ERROR  RETURN 

LDA 

BYTE 

;  GET 

BYTE 

RET 

PUT  NEXT  BYTE  INTO  BUFFER/FILE 

ON  INPUT,  A=CHAR  AND  DE  PTS  TO  IOCB 


r 

! 

ON  OUTPUT,  Z  FLAG 

SET  IF  WRITE  ERROR 

r 

FX$PUT: 

PUTRG 

/ 

SAVE  REGS 

STA 

BYTE 

/ 

SAVE  BYTE  TO  OUTPUT 

CALL 

PUTADR 

/ 

PUT  ADDRESSES 

LHLD 

BYTECNT 

/ 

CHECK  TO  SEE  IF  ANY  ROOM  LEFT 

MOV 

A,  H 

ORA 

L 

JNZ 

FXPUT1 

CALL 

FXOO0 

i 

FLUSH  BUFFER  AND  RESTART 

JZ 

ERRET 

i 

ERROR 

FXPUT1: 

LHLD 

BYTENXT 

i 

GET  PTR  TO  NEXT  BYTE 

LDA 

BYTE 

i 

GET  NEXT  BYTE 

MOV 

M,  A 

i 

STORE  IT 

I  NX 

H 

! 

PT  TO  NEXT 

SHLD 

BYTENXT 

LHLD 

BYTECNT 

i 

COUNT  DOWN 

DCX 

H 

SHLD 

BYTECNT 

JMP 

OKRETI 

f 

OK  RETURN  WITH  BYTE  AND  ADDRESSES 

•  ROUTINE  TO 

PUT  ADDRESS 

BUFFERS  FOR  LATER  USE 

! 

PUTADR: 

XCHG 

i 

PT  TO  BUFFER  WITH  HL 

SHLD 

BUFFER 

i 

PUT  BUFFER  ADDRESS 

MOV 

A ,  M 

/ 

GET  BLOCK  COUNT 

STA 

BCNT 

I  NX 

H 

MOV 

A ,  M 

J 

GET  EOF  FLAG 

STA 

EOF 

I  NX 

H 

MOV 

E,M 

/ 

GET  LOW  COUNT 

I  NX 

H 

MOV 

D,M 

XCHG 

SHLD 

BYTECNT 

/ 

PUT  BYTE  COUNT 

XCHG 

I  NX 

H 

MOV 

E ,  M 

i 

GET  LOW  COUNT 

I  NX 

H 

MOV 

D  ,  M 

XCHG 

SHLD 

BYTENXT 

) 

PUT  NEXT  BYTE  PTR 

XCHG 

I  NX 

H 

MOV 

E ,  M 

i 

GET  LOW  COUNT 

I  NX 

H 

MOV 

D ,  M 

XCHG 

SHLD 

BUFADR 

$ 

PUT  BUFFER  ADDRESS  PTR 

XCHG 

I  NX 

H 

SHLD 

FCB 

/ 

ADDRESS  OF  FCB 

RET 

ROUTINE  TO  GET  ADDRESS  BUFFERS  BACK  FOR  CALLING  ROUTINE 


GETADR: 


(Continued  on  page  119) 


112 


Dr.  Dobb’s  Journal,  February  1985 


ICO 


CP/M  Exchange  (Listing  Continued,  text  begins  on  page  98) 
Listing  Three 


LHLD 

BUFFER 

! 

PT  TO  BUFFER  ADDRESS 

I  NX 

H 

/ 

SKIP  BLOCK  COUNT 

LDA 

EOF 

! 

SET  EOF  FLAG 

MOV 

M,A 

XCHG 

LHLD 

BYTECNT 

/ 

SET  BYTE  COUNT 

XCHG 

I  NX 

H 

MOV 

M,  E 

f 

PUT  LOW  COUNT 

I  NX 

H 

MOV 

M ,  D 

XCHG 

LHLD 

BYTENXT 

1 

SET  NEXT  BYTE  PTR 

XCHG 

INX 

H 

MOV 

M,E 

! 

PUT  LOW  COUNT 

INX 

H 

MOV 

M,  D 

RET 

BUFFERS 


BYTE: 

DS 

1 

INPUT  BYTE 

BUFFER: 

DS 

2 

STARTING  ADDRESS  OF  I/O 

CONTROL 

BLOCK 

;  THE 

FOLLOWING  MIRRORS 

THE  STRUCTURE  OF 

THE  I/O  CONTROL 

BLOCK 

BCNT : 

DS 

1 

NUMBER 

OF  BLOCKS 

EOF: 

DS 

1 

EOF  FLAG  ( 0=NOT  AT  EOF, 

0FFH=AT 

EOF) 

BYTECNT 

:  DS 

2 

NUMBER 

OF  BYTES  TO  GO  YET 

BYTENXT 

:  DS 

2 

ADDRESS 

OF  NEXT  BYTE  TO 

PUT/GET 

BUFADR: 

DS 

2 

ADDRESS 

OF  WORKING  BUFFER 

FCB : 

DS 

2 

ADDRESS 

OF  FCB 

END 


End  Listings 


(Continued  from  page  121) 
welded  together.  It  can  support  over  a 
hundred  pounds.  The  80  column 
width  costs  $  1 9.95  and  the  132  col¬ 
umn  width  costs  $24.95.  Contact  Za- 
vie  Sokoloff,  484  Lakepark  Avenue, 
Suite  1 86,  Oakland,  CA  94610  (415) 
53  1  -0302  Reader  Service  No.  133. 


BASIC 

Morgan  Computing  Company  has  re¬ 
leased  version  two  of  Professional  BA¬ 
SIC  and  lowered  the  price  from 
$345.00  to  $99.00.  Professional  BA¬ 
SIC  requires  an  IBM  PC  or  AT.  The 
new  version  includes  windows  into 
program  execution  and  provides  mem¬ 
ory  access  of  640K.  A  $49.00  en¬ 
hancement  package  provides  8087  and 
80287  support.  Contact  Morgan 
Computing  Company  at  10400  N. 
Central  Expressway,  Suite  210,  Dal¬ 
las,  TX  75231  (214)  739-5895  Reader 

Service  No.  1 1 7.  DD) 


Dr.  Dobb's  Journal,  February  1985 


119 

163 


Diana,  An  Intermediate  Lan¬ 
guage  for  Ada 
Edited  by  G.  Goos,  W.  A.  Wulf, 

A.  Evans  Jr.,  and  K.  J.  Butler 
Published  by  Springer- Verlag 
$1 1 .00,  201  pages 
Reviewed  by  John  R.  Johnson 

Most  computer  language  compilers  in 
recent  years  have  used  a  concept  called 
“common  back  end”  in  their  implemen¬ 
tation.  We  normally  think  of  a  compiler 
as  a  program  that  accepts  a  file  of  state¬ 
ments  in  some  language  as  input  and 
provides  as  output  a  file  of  machine  lan¬ 
guage  codes  that  will  implement  the  in¬ 
tent  of  the  input  file  on  a  specific  com¬ 
puter.  Many  compilers  were  created  in 
just  this  way.  However,  significant  ad¬ 
vantages  can  be  gained  by  writing  com¬ 
pilers  for  higher-level  languages  to  pro¬ 
vide  their  output  in  a  more  generic 
lower-level  language  for  easy  imple¬ 
mentation  on  a  variety  of  machines. 
The  two  major  advantages  to  this  ap¬ 
proach  are  portability  and  flexibility. 

Portability  can  be  gained  by  using  a 
low-level  language  as  an  intermediate 
or  “common  back  end”  language.  My 
high-level  compiler  itself,  which  repre¬ 
sents  a  serious  programming  effort, 
can  be  written  in  a  high-level  language 
or  even  in  the  intermediate  language  it 
produces.  This  simple  intermediate 
language  then  can  be  implemented  on 
a  variety  of  machines  to  create  a  fam¬ 
ily  of  high-level  compilers  for  many 
different  computers.  A  large  part  of 
the  work  of  high-level  language  imple¬ 
mentation  has  to  be  done  only  once.  A 
good  example  of  this  approach  is  the  p- 
code  intermediate  language  used  in  a 
number  of  implementations  of  the  Pas¬ 
cal  language. 

Flexibility  is  gained  by  using  the 
same  intermediate  language  for  a 
number  of  different  high-level  lan¬ 
guage  compilers.  It  becomes  a  relative¬ 
ly  easy  task  to  combine  modules  or 


routines  written  in  different  source 
languages  into  one  program.  A  good 
example  of  this  is  the  Microsoft  family 
of  high-level  language  compilers:  they 
all  compile  to  a  common  relocatable 
assembly  language,  and  the  modules 
are  linked  with  a  common  linker/load¬ 
er  to  produce  executable  code. 

To  make  use  of  this  powerful  tech¬ 
nique,  you  need  an  intermediate  lan¬ 
guage  that  is  both  easily  implemented 
and  capable  of  supporting  all  of  the 
constructs  of  the  high-level  language. 
Because  Ada  contains  a  number  of  fea¬ 
tures  in  its  specification  that  are 
straight  from  computer  science  re¬ 
search  projects,  a  suitable  intermedi¬ 
ate  language  for  Ada  did  not  exist. 

Diana,  An  Intermediate  Language 
for  Ada  not  only  documents  such  a 
language,  but  it  provides  an  intermedi¬ 
ate  language  usable  for  virtually  any 
modern  high-level  language  such  as 
Modula  II  or  Pascal.  The  book  is  in¬ 
tended  as  both  an  introduction  to  Di¬ 
ana  and  a  language  reference  manual 
for  Diana. 

This  book  is  part  of  a  larger  series, 
“Lecture  Notes  in  Computer  Science,” 
published  by  Springer-Verlag.  The 
books  in  this  series  that  I  have  seen  are 
intended  primarily  for  graduate  stu¬ 
dents  in  computer  science.  This  book  is 
not  an  exception. 

It  is  a  clear  and  concise  definition  and 
description  of  a  language  that  is  a  pow¬ 
erful  tool  in  modern  high-level  compiler 
construction.  That  the  book  is  obviously 
written  for  experienced  compiler  writ¬ 
ers  is  not  a  criticism:  by  its  nature,  it 
would  be  of  only  limited  usefulness  to 
anyone  not  currently  engaged  in  de¬ 
signing  a  high-level  language  compiler. 

The  book  is  well  conceived  and  pre¬ 
sented,  and  I  would  suggest  that  any¬ 
one  considering  writing  a  compiler  for 
the  Ada  language  buy  it.  It  should  also 
be  in  the  library  of  any  modern  sys¬ 
tems  programmer.  If  your  program¬ 


ming  interests  do  not  extend  to  compil¬ 
er  design,  however,  Diana  would  most 
likely  fail  to  repay  the  considerable  ef¬ 
fort  required  to  read  and  understand  it. 

Ada,  An  Advanced 
Introduction  Including 
Reference  Manual  for  the 
Ada  Programming  Language 
by  Narain  Gehani 
Published  by  Prentice-Hall,  Inc. 
$19.95,291  pages 
Reviewed  by  John  R.  Johnson 

The  Ada  programming  language  has 
stirred  up  a  great  deal  of  interest  and 
controversy  in  recent  years.  As  a  re¬ 
sult,  many  books  have  appeared  on  the 
market  to  expand  upon  Ada.  This  is 
the  best  one  I  have  seen. 

The  book  is  written  for  the  experi¬ 
enced  programmer,  preferably  some¬ 
one  who  has  had  some  exposure  to  one 
of  the  ALGOL-derived  languages  such 
as  Pascal  or  PL/I.  Gehani  assumes  the 
reader  is  familiar  with  standard  pro¬ 
gramming  concepts  such  as  type  defi¬ 
nitions  and  control  structures. 

The  first  portion  of  the  book  is  de¬ 
voted  to  a  thorough  but  brief  descrip¬ 
tion  of  the  correct  Ada  syntax  and 
form  for  all  programming  constructs 
not  unique  to  Ada.  Gehani  covers  the 
Ada  character  set,  type  and  function 
declarations,  and  subtypes  with  ranges 
and  limits.  The  definitions  are  written 
in  the  concise  format  normally  used  for 
language  specification  and  documen¬ 
tation.  Any  experienced  programmer 
should  have  no  difficulty  following  the 
material.  Gehani  has  illustrated  each 
situation  with  several  examples  of  cor¬ 
rect  Ada  code. 

He  then  spends  the  bulk  of  the  book 
thoroughly  discussing  several  features 
of  Ada  that  are  shared  with  few,  if  any, 
common  languages.  There  is  an  excel¬ 
lent  discussion  of  the  Ada  “package” 


120 

164 


Dr.  Dobb’s  Journal,  February  1985 


concept  for  information  hiding  and 
data  encapsulation;  this  is  the  basic  fa¬ 
cility  or  modularity  in  Ada  programs. 
The  contents  of  any  “package”  may  be 
altered  at  will  as  long  as  the  imports 
and  exports  from  the  package  remain 
consistent  with  the  package  specifica¬ 
tion.  Ada  provides  for  separate  compil¬ 
ation  of  packages.  Gehani  uses  many 
examples  to  illustrate  the  specific  syn¬ 
tax  associated  with  the  concept. 

One  of  Ada’s  unusual  features  is  the 
provision  for  concurrency  of  portions 
of  a  program.  The  author  has  given  us 
a  clear  and  concise  look  at  the  prob¬ 
lems  of  concurrency.  Ada  has  a  num¬ 
ber  of  features  to  provide  coroutines, 
concurrent  actions,  and  data  passing 
between  independent  concurrent  tasks 
and  procedures  within  a  program. 
Once  again  we  have  numerous  exam¬ 
ples  to  show  how  to  handle  handshak¬ 
ing  between  asynchronous  indepen¬ 
dent  processes  using  correct  Ada 
syntax. 

Ada  provides  a  unique  and  complete 
system  for  exception  handling  within 
programs.  Specific  exception  handling 
routines  may  be  added  at  the  end  of 
any  block  to  process  any  exceptions 
that  may  arise  anywhere  in  the  block. 
When  an  exception  occurs,  control  is 
transferred  to  the  exception  handler. 
When  the  exception  handler  is  fin¬ 
ished,  the  block  where  the  exception 
handler  resides  is  terminated  and  con¬ 
trol  passes  back  to  the  enclosing  block. 
If  the  exception  handler  is  omitted, 
control  passes  back  to  the  exception 
handler  in  the  enclosing  block.  This 
concept  is  presented  clearly  with  some 
excellent  examples. 

The  generic  facilities  of  Ada  are  an 
interesting  variant  of  the  standard 
function  library  concept  used  in  C  and 
other  separate-module  compilation  lan¬ 
guages.  A  generic  package  may  be  “in¬ 
stantiated”  by  a  declaration  in  a  new 
package  with  appropriate  parameters; 
the  result  is  a  unique  and  specific  appli¬ 
cation  of  a  generic  package,  such  as  a 
sort  routine.  This  unusual  approach  is 
presented  with  some  useful  examples. 

The  balance  of  the  first  part  of  the 
book  is  devoted  to  a  discussion  of  the 
separate  compilation  features  and 
those  features  that  may  be  implemen¬ 
tation-dependent.  Here,  also,  Gehani 
has  used  precise  examples. 

For  all  of  the  examples  in  the  book. 

Dr.  Dobb's  Journal,  February  1985 


the  author  has  used  stepwise  refine¬ 
ment  to  work  from  a  general  problem 
statement  to  a  set  of  specific  routines 
that  implement  the  solution  in  Ada.  It 
is  interesting  to  follow  the  refinement 
process:  all  of  the  steps  are  included. 
The  book  would  be  worth  buying  just  to 
trace  this  stepwise  refinement  process. 

The  second  half  of  the  book  is  the 
complete  reference  manual  for  the  Ada 
programming  language — reprinted 
from  the  ANSI/MIL-STD-1815  A.  This 
is  the  current  official  definition  of  the 
Ada  language.  Considerably  less  read¬ 
able  than  the  first  part  of  the  book,  it  is 
indispensable  for  anyone  who  wishes  to 
attempt  an  implementation  of  the  Ada 
language. 

The  book  includes  a  comprehensive 
bibliography  for  anyone  interested  in 
additional  reading  on  the  topics  cov¬ 
ered.  The  two  halves  of  the  book  are 
indexed  separately  for  reference  pur¬ 
poses.  The  indexing  is  well  done. 

This  book  will  quickly  become  dog¬ 
eared  on  the  desk  of  any  programmer 
working  in  the  Ada  language.  When  we 
are  forced  to  begin  using  Ada  in  our 
shop,  I  am  going  to  insist  that  every 
programmer  be  provided  with  his  or  her 
own  copy.  It  is  an  essential  tool  for  the 
new  or  experienced  Ada  programmer. 

New  Books 

Hackers 

Steven  Levy 

Anchor  Press/Doubleday,  Garden 
City,  New  York,  1984 
$17.95,  458  pages 
ISBN  0-385-19195-2 
Steven  Levy  has  taken  on  the  exegesis 
of  the  Hacker  Ethic.  In  breathless, 
Right  Stuff  prose,  he  follows  the  fol¬ 
lowers  of  the  hacking  dream  from  the 
basements  of  MIT  through  the  garages 
of  Silicon  Valley  right  up  the  slopes  of 
the  Sierra,  where  game  programmers 
are  treated  like  rock  stars.  Steven,  did 
you  retain  the  film  rights? 

How  to  Copyright  Software 

M.J.  S alone 

Nolo  Press,  Berkeley,  California, 
1984 

$21.95,  256  pages 
ISBN  0-917316-7 

It  is  perhaps  only  to  be  expected  that  a 
256-page  book  explaining  how  to  copy¬ 
right  software  would  cost  more  than 
twice  as  much  as  a  475-page  book  tell¬ 
ing  how  to  get  it  free. 


The  Free  Software  Catalog 
and  Directory 

Robert  A.  Froehlich 
Crown  Publishers,  Inc.,  New  York 
New  York,  1984 
$9.95,  475  pages 
ISBN  0-517-55448-8 
Cross-referenced  directories  for  the 
CPMUG  and  ACJ-NJ’s  SIG/M  public 
domain  software  libraries,  plus  lists  of 
bulletin  boards  and  users’  groups 
throughout  the  U.S. 

Digital  Deli 

Steve  Ditlea,  Ed. 

Workman  Publishing  Co.,  New  York, 
New  York,  1984 
$12.95,  382  pages 
ISBN  0-89480-591-6 
No  way  to  summarize  this  book:  it  con¬ 
tains  a  short  piece  (all  the  pieces  in  this 
book  are  short)  on  how  to  use  a  word 
processor,  a  quarter-page  PR  photo  of 
IBM  CEO  John  Opel,  arguments  for 
the  Eniac  being  an  Aquarius  and  for 
the  Mac  being  short  skis  for  the  mind, 
cartoons,  computer  art,  quizzes,  and 
memoirs.  The  title  is  not  original,  but 
it  fits. 

The  Netweaver's 
Sourcebook 

Dean  Gengle 

Addison-Wesley  Publishing  Compa¬ 
ny.  Menlo  Park,  California,  1 984 
$14.95,  326  pages 
ISBN  0-201-05208-3 
How  refreshing  to  hear  that  “it’s  still 
perfectly  socially  acceptable  to  hate 
machines  arbitrarily  and  capriciously, 
especially  answering  machines.”  This 
book  takes  a  remarkably  elevated  per¬ 
spective  on  networks  and  communica¬ 
tions. 

The  Whole  Earth  Software 
Catalog 

Stewart  Brand,  Ed. 

Quantum  Press/Doubleday,  Garden 
City,  New  York,  1984 
$17.50,  208  pages 
ISBN  0-385-19166-9 
The  Brand  hand  is  as  evident  here  as  in 
Whole  Earth  non-software  catalogs, 
and  admirers  of  wit,  irreverence,  con¬ 
troversy,  and — um,  wholearthness  will 
be  glad  of  that.  Dr.  Dobb  contributed 
to  this  collection  of  recommended 
programs. 

( Con  tinued  on  page  119) 

121 

165 


OF  INTEREST 


by  R.P.  Sutherland 


Disk  Memory 

The  notion  of  having  the  Library  of 
Congress  in  the  palm  of  your  hand  is 
coming  closer  to  reality  every  day.  A 
4%-inch  compact  disk  ROM  with  550 
megabytes  of  storage  capacity  on  one 
side  has  been  developed  by  Nippon 
Columbia  of  Kawasaki,  Japan.  Con¬ 
tact  Robert  Heiblim,  Denon  America, 
Inc.,  27  Law  Drive,  Fairfield,  NJ 
07006  (201)  575-78  1  0  Reader  Service 

No.  111. 

Hewlett-Packard  has  entered  the 
OEM  market  with  a  3'/2-inch  10  mega¬ 
byte  disk  drive.  The  HP  97501A  Sc¬ 
inch  micro-Winchester  is  priced  at 
$400.00  each  in  quantities  of  10,000. 
For  more  information,  write:  Inquiries 
Manager,  Hewlett-Packard  Compa¬ 
ny,  1020  N.E.  Circle  Boulevard,  Cor¬ 
vallis,  OR  97330  Reader  Service  No.  113. 

A  diagnostic  diskette  for  the  IBM 
PC,  Apple  II,  TRS-80,  and  Commo¬ 
dore  64  is  available  from  Dymek  Cor¬ 


poration.  The  disk  is  called  RID  (Re¬ 
cording  Interchange  Diagnostic),  and 
it  performs  seven  tests  on  disk  drives: 
drive  speed,  radial  position,  hysteresis, 
write  function,  erase  crosstalk,  mini¬ 
mum  signal-to-noise,  and  clamping. 
The  disk  is  easy  to  use,  costs  $34.95, 
and  is  available  from  Dymek  Corpora¬ 
tion,  1851  Zanker  Road,  San  Jose,  CA 
95112  (408)  947-8700  Reader  Service 
No.  115. 


Utilities 

A  high-resolution  graphics  system  for 
Turbo  Pascal  has  been  released  by 
Borland  International.  Turbo  Graphix 
Toolbox  provides  procedures  to  create 
the  contents  of  windows  and  to  allow 
copying  from  window  to  window  as 
well  as  scrolling  of  windows  horizon¬ 
tally  and  vertically.  There  are  also 
procedures  that  provide  RAM  storage 
of  screen  images  and  so  permit  anima¬ 


124 


tion  in  real  time  application,  up  to  500 
images  per  second.  (See  photo  below.) 
Turbo  Graphix  Toolbox  comes  with 
complete  commented  source  code  on 
disk  for  $49.95  from  Borland  Interna¬ 
tional,  4113  Scotts  Valley  Drive, 
Scotts  Valley,  CA  95066  (408)  438- 
8400  Reader  Service  No.  123. 

Stellation  Two  has  disk  buffering 
software  for  the  IBM  PC.  Invisible 
Optimizer  loads  automatically  in  the 
unused  memory  space  of  any  expan¬ 
sion  board  for  the  IBM  PC.  It  works  by 
keeping  track  of  all  disk  activity  and 
copying  the  most  needed  data  into 
system  memory.  Applications  then  ob¬ 
tain  the  most  needed  data  from  RAM 
instead  of  from  the  disk  drive.  Invisible 
Optimizer  is  available  for  $69.00  from 
Stellation  Two  Inc.,  26  W.  Mission  St., 
P.O.  Box  2342,  Santa  Barbara,  CA 
93  1  20  (  805  )  569-3  1  32  Reader  Service 
No.  125. 


68000  Development 

PC-68K  is  a  new  product  from  Lan¬ 
guage  Resources  that  allows  68000 
development  on  an  IBM  PC-XT  or  PC- 
AT.  PC-68 K  provides  a  symbolic  de¬ 
bugger,  linker/locator,  Motorola  com¬ 
patible  macro  assembler,  and  an  IEEE 
floating  point  package.  Pascal  and  C 
compilers  are  available  as  options. 

The  PC-68K  plug-in  board  has  an  8 
MHz  68000  CPU,  a  memory  manage¬ 
ment  subsystem,  and  256K  of  RAM 
for  68000  family  development,  which 
can  also  be  used  as  expansion  memory 
by  PCDOS  programs.  The  basic  PC- 
68K  package  costs  $2995.00.  Lan¬ 
guage  Resources  Inc.,  4885  Riverbend 
Road,  Boulder,  CO  80301  (303)  449- 
8087  Reader  Service  No.  119. 

A  Modula-2  development  system 
for  the  Pinnacle  microcomputer  has 
been  introduced.  Interesting  details 
include  a  Modula-2  native  code  com- 

Dr.  Dobb's  Journal,  Feburary  1985 


4  CC 


piler  and  a  12  MHz  68000.  The  pack¬ 
age  is  available  for  $3995.00  from 
Pinnacle  Systems,  10410  Markison 
Road,  Dallas,  TX  75238  (214)  340- 

4941  Reader  Service  No.  1 21 . 


2010 

Arthur  C.  Clarke’s  2001:  A  Space 
Odyssey  was  written,  filmed,  and  pub¬ 
lished  before  1969,  when  Neil  Arm¬ 
strong  took  that  giant  leap.  In  the 
preface  to  2010:  Odyssey  Two ,  in  ref¬ 
erence  to  2001 ,  Clarke  mentions  “un¬ 
canny  instances  of  nature  imitating 
art.”  In  2001 ,  the  novel,  the  spaceship 
Discovery  “slingshots”  its  way  by  Ju¬ 
piter  to  reach  the  Saturnian  moons. 
The  Voyager  space  probes  in  1979 
used  the  same  technique.  When  Stan- 


Arlhur  C.  Clarke,  one  of  the  world's  best  known  and  most  respected  authors  of  science 
fiction  and  science  fact  literature,  poses  before  one  of  the  Kaypro  II  computers  used  to 
relay  information  between  the  production  office  and  Clarke’s  Sri  Lanka  home. 


ley  Kubrick  set  the  third  confronta¬ 
tion  between  Man  and  Monolith 
among  the  moons  of  Jupiter,  Io,  Euro- 
pa,  Ganymede,  and  Callisto  were  just 
points  of  light  in  our  telescopes.  The 
discoveries  of  the  Voyagers  have  since 
allowed  us  to  gaze  upon  the  surfaces 
of  these  fantastic  moons.  The  astro¬ 
nauts  of  Apollo  8  had  already  seen  the 
film  when,  in  1968,  they  became  the 
first  men  to  see  the  other  side  of  the 
moon.  They  were  tempted  to  report 
the  discovery  of  a  large  black  mono¬ 
lith!  More  recently,  the  crew  of  Sky- 
lab  televised  their  discovery  that  they 


could  run  around  the  interior  in  much 
the  same  way  in  which  Frank  Poole, 
in  the  film,  runs  around  the  circular 
track  of  the  giant  centrifuge. 

Pasadena’s  Jet  Propulsion  Labora¬ 
tory  (JPL)  claims  that  the  vision  of 
Jupiter  in  the  film  2010  is  better  than 
its  own.  The  visual  effects  supervisor 
fed  the  raw  JPL  data  from  the  Voyag¬ 
er  fly-by  mission  into  a  Cray.  The 
Voyager  data,  processed  and  correlat¬ 
ed  with  information  about  cloud  vor¬ 
texes,  produced  a  detailed  moving  im¬ 
age  of  Jupiter. 

I  look  forward  to  viewing  the  resur¬ 
rection  of  HAL.  (By  the  way,  in  the 
novel  there  is  a  character  who  flatly 


Dr.  Dobb  s  Journal.  February  1985 


125 

167 


denies  the  correspondence  of  HAL  to 
IBM.)  One  Hitchcockian  detail:  Ar¬ 
thur  Clarke  appears  as  a  pigeon-feed¬ 
ing  wino  on  a  park  bench  outside  the 
White  House. 


Telecommunications 

What  do  you  need  2400  baud  for? 
Well,  Arthur  Clarke  could  have 
halved  his  telephone  bills  across  thir¬ 
teen  and  a  half  time  zones  when  he 
sent  his  film  script  drafts  from  Sri 
Lanka  to  his  screenplay  writer  in  the 
United  States.  Both  Hayes  and  U.S. 
Robotics  have  introduced  2400  baud 
modems.  The  Hayes  costs  $899.00 
and  the  U.S.  Robotics  costs  $895.00. 
Hayes  is  at  5923  Peachtree  Industrial 
Blvd.,  Norcross,  GA  30092  Reader  Ser¬ 
vice  No.  ioi,  and  U.S.  Robotics  is  at 
1 123  W.  Washington  Blvd.,  Chicago, 
IL  60607  Reader  Service  No.  103.  Where 
do  we  go  from  here-  9600  baud? 

If  you  can’t  justify  $900.00  for  a 
2400  baud  modem,  consider  what 
$200.00  will  do.  Business  Computer 
Network  is  offering  a  1200  baud  mo¬ 
dem  with  SuperScout  software  for 
$200.00.  Business  Computer  Network, 
Inc.,  is  located  at  1000  College  View 
Drive,  Riverton,  WY  82501  (800)  446- 
6255  Reader  Service  No.  105.  In  the  Same 
spirit,  Artisoft  Inc.  has  introduced 
communications  software  for  $49.95. 
Envoy  is  a  menu-driven  telecommuni¬ 
cations  program  with  autodial  capabil¬ 


ities  and  XMODEM  transfer  protocols. 
Contact  Artisoft,  Inc.,  P.O.  Box 
41436,  Tucson,  AZ  85717  (602)  327- 
4305  Reader  Service  No.  107. 

A  newly  patented  device  called 
Shuttle  Communicator  allows  users  to 
download  software  from  an  AM  or  FM 
station  with  a  radio  interfaced  to  the 
computer!  Shuttle  Communicator  costs 
$70.00.  (See  photo  below.)  For  addi¬ 
tional  information,  contact  Robert 
Hardwick,  The  Microperipheral  Cor¬ 
poration,  2565  152nd  Ave.  N.E.,  Red¬ 
mond,  WA  98052  (206)  881-7544 

Reader  Service  No.  109. 


Miscellany 

Stars  and  Planets  on  the  Apple  II 

The  Observatory  is  a  program  that 
turns  your  Apple  II  into  an  electronic 
celestial  sphere  containing  stars,  star 
clusters,  galaxies,  and  nebulae.  Hal¬ 
ley’s  Comet  and  all  of  the  planets  are 
included.  Users  have  available  nine 
levels  of  magnification.  The  author, 
Gary  Lassiter,  claims  that  the  pro¬ 
gram  is  accurate  for  any  place  on  the 
earth  for  any  moment  of  time  in  a 
10,000  year  span.  The  Observatory 
sells  for  $125.00  from  Lightspeed 
Software,  2124  Kittredge,  Suite  185, 
Berkeley,  CA  94704  (415)  486-1 165 

Reader  Service  No.  127. 


Macintosh  Disk  Backup 

COPY  II  MAC  makes  backups  of  pro¬ 


tected  Macintosh  software.  The  pack¬ 
age  includes  utilities  to  lock/unlock, 
protect/unprotect,  and  make  files  visi¬ 
ble/invisible.  COPY  11  MAC  is  avail¬ 
able  for  $39.95  from  Central  Point 
Software,  Inc.,  9700  SW  Capitol 
Highway,  Suite  100,  Portland,  OR 
97219  (503)  244-5782  Reader  Service 

No.  129. 


Micromouse  Contest 

For  those  who  think  that  they  can 
build  a  small  self-contained  robot  able 
to  navigate  quickly  a  complicated 
maze,  a  contest  will  be  held  by  the 
IEEE  Computer  Society  at  the  Micro¬ 
processor  Forum.  The  Microprocessor 
Forum  will  be  held  March  31  to  April 
4,  1985,  at  Bally’s  Park  Place  Hotel, 
Atlantic  City,  NJ.  The  Micromouse 
Contest  aims  to  find  a  robot  able  to 
negotiate  a  maze  in  the  shortest  peri¬ 
od  of  time.  The  robot  cannot  use  an 
energy  source  employing  a  combus¬ 
tion  engine,  cannot  leave  part  of  its 
body  behind  while  running  the  maze, 
cannot  jump  over,  climb,  scratch, 
damage,  or  destroy  the  walls  that 
constitute  the  maze,  and  cannot  be 
longer  or  wider  than  25  cm.  It  also 
cannot  be  controlled  by  radio  or  wire. 
A  microcomputer  must  be  incorporat¬ 
ed  into  the  design  to  control  the  sen¬ 
sors  and  drive  motors,  to  memorize 
the  progress  of  the  mouse  through  the 
maze,  and  to  calculate  the  shortest 
path  to  the  destination.  For  more  in¬ 
formation  about  the  contest,  write  to: 
Micromouse  Contest,  IEEE  Computer 
Society,  P.O.  Box  639,  Silver  Spring, 
MD  20901. 

Micro  Cornucopia  Gets  Sol  Libes 

This  month,  Sol  Libes,  founder  of 
Microsystems ,  will  begin  a  regular 
column  in  Micro  Cornucopia.  The 
column  will  cover  the  latest  public 
domain  software  releases.  Micro  Cor¬ 
nucopia  is  the  “Single  Board  Systems 
Journal”  that  supports  the  Kaypro, 
the  Xerox  820,  and  the  Big  Boards. 
Micro  Cornucopia  is  located  at  P.O. 
Box  223,  Bend,  OR  97709 


Printer  Pedestal 

Zavie  Enterprises  has  a  desktop  print¬ 
er  stand  of  a  useful  design.  The  Print¬ 
er  Pedestal  comes  in  dark  brown  and 
is  made  of  'A -inch  metal  rods  arc- 

(  continued  on  page  119  ) 
Dr.  Dobb  s  Journal.  February  1985 


126 


IfiR 


3rCLI/\L  rKULUU  IWUC 


OFTWARE  TOOLS  F 


larch  1985 


How  to  Program 


/VI  Competition 
Winner 


New  Column 
rhe  C  Chest 


\  Proposed 

Public-Domain 

"Unix" 


2  1 

Dr.  Dobb’s  Journal 


Editorial 

Edltor-iiv-Clikf  Michael  Swaine 

Editor  Randy  Sutherland 
Managing  Editor  Frank  DeRose 
Technical  Editor  Alex  Ragen 
Editorial  Assistant  Sara  Noah 
Contributing  Editors  Robert  Blum, 

Dave  Cortesi, 

Ray  Duncan. 

Allen  Holub 

Copy  Editor  Polly  Koch 
Typesetter  Jean  Aring 
Production 
Design/Production 

Director  Delta  Penna 
Art  Director  Shelley  Rae  Doeden 
Production  Assistant  Alida  Hinton 

Cover  Claudia  Majewski 
Advertising 

Advertising  Director  Stephen  Friedman 
Advertising  Sales  Walter  Andrzejewski, 
Shawn  Horst. 

Beth  Dudas 

DDJ  Classifieds  Alex  Williams 
Advertising  Coordinators  Alison  Milne. 

Lisa  Boudreau 

Circulation 

Fulfillment  Manager  Stephanie  Barber 
Direct  Response 

Coordinator  Maureen  Snee 
Promotions  Coordinator  Jane  Sharninghouse 
Circulation  Assistant  Kathleen  Boyd 

Administration 
Finance  Manager  Sandra  Dunie 
Business  Manager  Betty  Tricketl 
Accounting  Coordinator  Mayda  Lopez -Quintana 
Accounts  Payable  Denise  Ciannini 
Billing  Coordinator  Laura  Di  Lazzaro 


- - - 

M&T  Publishing,  Inc. 

Chairman  of  the  Board  Otmar  Weber 
Director  C.F.  von  Quad t 
President  Laird  Foshay 

f  ■  ' 

:  !:  - t - - - 

Entire  contents  copyright  e  1985  by  M&T  Publishing. 
Inc.  unless  otherwise  noted  on  specific  articles.  All 
rights  reserved. 

Dr.  Dobb’s  Journal  (USPS  307690)  is  published 
monthly  by  M&T  Publishing.  Inc.,  2464  Embarcadero 
Way,  Palo  Alto,  CA  94303,  (415)  424-0600.  Second 
;  class  postage  paid  at  Palo  Alto  and  at  additional  entry 
:  points. 

Address  correction  requested.  Postmaster:  Send  Form 
3579  to  Dr.  Dobb’s  Journal,  2464  Embarcadero 
Way,  Palo  Alto.  CA  94303.  ISSN  0278-6508 

Subscription  Rates:  $25  per  year  within  the  United 
States.  $44  for  first  class  to  Canada  and  Mexico,  $62 
for  airmail  to  other  countries.  Payment  must  be  in  US. 
Dollars,  drawn  on  a  U.S.  Bank. 

Foreign  Distributors:  ASCII  Publishing,  Inc.  (Ja¬ 
pan),  Computer  Services  (Australia),  Computer  Store 
(New  Zealand).  Computercolleclief  (Nederland), 
Homecomputer  Vertriebs  GMBH  (West  Germany), 
International  Presse  (West  Germany),  La  Nacelle 
Bookstore  (France).  McGill's  News  Agency  PTY  LTD 
(Australia),  Progresco( France). 


People's  Computer  Company 

Dr.  Dobb’s  Journal  is  published  by  M&T  Publishing, 
Inc.  under  license  from  People's  Computer  Company, 
2682  Bishop  Dr.,  Suite  107,  San  Ramon.  CA  94583.  a 
non-profit,  educational  corporation. 


f; 

I 


March  1985 
Volume  10,  Issue  3 


CONTENTS 


Prolog 

A  year  ago  when  we  were  still  being  published  by  the  old  People’s  Computer 
Company  in  Menlo  Park,  I  asked  Mike  Swaine  if  Artificial  Intelligence  was,  by 
definition,  beyond  the  scope  of  microcomputers.  What  resulted  from  that  Sun¬ 
day  afternoon  brainstorming  session  was  the  AI  programming  competition  and 
this  special  issue. 

As  the  programs  and  manuscripts  rolled  in  we  found  ourselves  building  the 
issue  around  Prolog  and  we  wished  for  a  public  domain  tiny-Prolog  to  publish. 
We  haven’t  found  that  yet,  but  we  did  get  some  very  good  Prolog  pieces.  Future 
issues  of  DDJ  will  include  responses  from  Lisp  people. 

Speaking  of  future  issues:  June  is  the  special  telecommunications  issue,  as  well 
as  the  copy  deadline  for  the  annual  algorithms  issue  which  will  run  in  September. 
The  annual  Forth  issue  which  usually  runs  in  September  will  appear  in  October. 
Please  submit  Forth  material  by  the  Fourth  of  July. 


Reader  Ballot  Results 

Despite  the  popularity  of  our  December  1984  focus  on  Unix,  the  article  voted  best 
in  the  Unix  issue  was  not  a  Unix  piece.  Henry  Seymour’s  “An  Introduction  to 
Parsing”  received  twice  as  many  “best  article”  votes  as  the  second  most  popular 
article,  Alan  Walworth’s  “Varieties  of  Unix.”  By  the  way,  may  we  assume  that 
because  nobody  challenged  the  Unix  family  tree  on  page  35,  we  got  it  right? 


Mac  Erratum 

In  the  January  Fatten  Your  Mac  article,  there  is  a  discrepancy  between  the  text 
on  page  20  and  Figure  1  on  page  23.  The  figure  correctly  shows  pin  8  soldered, 
but  the  text  refers  to  pin  7  of  the  memory  select  IC.  Readers  who  have  called  to 
point  out  the  error  inform  us  that  the  error  in  the  text  is  obvious  to  anyone  who 
works  through  and  understands  the  article  and  the  figures.  We  hope  so. 


This  Month's  Referees 

Dave  Cortesi,  Resident  Intern 
Neil  Jacobstein,  Teknowledge 
Scott  Mace,  Free-lance  Computer  Writer 
Paul  Reinholdtsen,  Stanford  University 
Pekka  Sinervo,  Stanford  Linear  Accelerator 


Randy  Sutherland 
Editor 


4 

170 


Dr.  Dobb' s  Journal,  March  1985 


SOFTWARE  TOOLS  FOR  ADVANCED  PROGRAMMERS 


Dr.Dobb’s  Journal 


ARTICLES 


Programming  in  Logic 

by  John  Malpas 

36 

The  development  of  logic  programming  from  Frege  to 

ICOT;  features  and  applications  of  Prolog;  implementations 
of  Prolog.  (Reader  Ballot  No.  1 92) 

Tour  of  Prolog 

by  D.  E.  Cortesi 

44 

A  description  of  Prolog’s  style  from  a  programmer’s  point 
of  view.  (Reader  Ballot  No.  193) 

Tax  Advisor:  A  Prolog  Program  Analyzing  Tax 

Issues 

by  Dean  Schlobohm 

64 

The  winner  of  the  AI  programming  competition:  this 
Prolog  program  assists  attorneys  in  analyzing  the  tax  rules 
in  Section  318  (a)  and  explains  its  questions  and 
conclusions.  (Reader  Ballot  No.  194) 

COLUMNS 

C  Chest 

by  Allen  Holub 

10 

This  new  column  takes  over  where  the  “C/Unix 
Programmer’s  Notebook”  left  off;  this  month,  root 
module  modifications  for  the  Aztec  CII  compiler.  (Reader 
Ballot  No.  1 90) 

Realizable  Fantasies 

by  Richard  Stallman 

30 

Richard  Stallman  outlines  a  proposal  for  GNU,  a  public 
domain  Unix-like  operating  system.  (Reader  Ballot  No.  191) 

1  6- Bit  Software  Toolbox 

by  Ray  Duncan 

96 

A  Z-8001  CPU  for  the  S-100  bus;  Mac  notes;  a  fix  for  the 
80286  BOUNDS  instruction;  DOS  3.0  differences  and 
bugs;  8086  assembly  language  subroutines.  (Reader  Ballot 
No.  195) 

CP/M  Exchange 

by  Bob  Blum 

108 

An  RSX  to  correct  the  handling  of  random  read  errors 
under  CP/M  Plus;  a  PIP  patch  for  CP/M  Plus.  (Reader 
Ballot  No.  196) 

DEPARTMENTS 

Editorial 

6 

Letters 

8 

Reviews 

116 

Excalibur  Technologies’  SAVVY  PC,  Version  4.0. 

Of  Interest 

122 

(Reader  Ballot  No.  1  97) 

DDJ  Classifieds 

126 

Advertiser  Index 

128 

Dr.  Dobb's  Journal,  March  1985 


5 

171 


EDITORIAL 


In  one  of  my  favorite  novels  the  very  existence  of  a  certain  island  seems  to  depend 
on  the  direction  from  which  you  approach  it:  sort  of  a  human-scale  quantum 
uncertainty  effect.  Early  in  the  novel  a  philosopher  of  science  and  a  rather  un¬ 
imaginative  psychologist  discuss  the  island  over  lunch. 

“The  island  doesn’t  bother  you,  does  it,  Dr.  Blank,”  the  philosopher  of  science 
says  around  a  bite  of  toast,  “because  you  look  at  it  one  side  at  a  time.” 

One  side  at  a  time.  Well,  yes.  That’s  a  fundamental  human  limitation:  we  can 
only  see  one  side  of  anything  at  a  time.  It  comes  of  having  our  eyes  set  so  close 
together.  But  that  limitation  shouldn’t  keep  us  from  paddling  around  the  island 
to  discover  how  things  look  from  the  other  side. 

I  recently  spoke  before  several  hundred  engineers  at  a  major  electronics  research 
center.  Roughly  half  of  them  had  within  the  past  year  been  deeply  involved  in  a 
project  that,  to  their  chagrin,  the  company  ultimately  decided  would  not  become  a 
product.  The  company  had  probably  acted  in  its  own  best  economic  interest  in 
putting  the  kibosh  on  the  project,  although  some  of  the  engineers  seemed  to  think 
the  decision  might  have  been  a  mistake.  If  the  decision  was  not  in  the  company’s 
own  self-interest,  then  of  course  it  was  a  mistake.  Those  are  the  alternatives. 

Well,  maybe  in  that  case.  But  sometimes  companies — and  people  -deliberate¬ 
ly  act  contrary  to  their  own  perceived  economic  self-interest.  It’s  generally  as¬ 
sumed  that,  other  things  being  equal,  some  money  is  better  than  no  money,  and 
we  expect  others  to  choose  some  over  none  whenever  they  perceive  that  they  have 
such  a  choice.  Corporate  strategies  are  built  on  such  an  assumption  about  com¬ 
petitors’  motives.  But  the  assumption  is  not  always  warranted  in  fact. 

Say,  for  example,  that  a  bright  programmer,  convinced  that  software  should 
be  free,  sets  out  to  develop,  with  a  little  help  from  his  friends,  a  Unix-compatible 
operating  system  and  subsequently  distributes  it  free  to  anyone  who  wants  it. 
That’s  not  a  hypothetical  example;  it’s  exactly  what  Richard  Stallman  is  propos¬ 
ing  in  his  “GNU  Manifesto,”  this  month’s  Realizable  Fantasy. 

Say  you’re  one  of  the  hordes  of  people  who  have  an  economic  interest  in  the 
success  of  Unix.  If  Stallman’s  fantasy  is  realized,  you  will  soon  face  a  competitor 
who  is  giving  away  his  product,  a  strategy  that  some  might  think  gives  him  a 
competitive  edge.  What  do  you  do? 

The  fantasized  confrontation  between  Unix  and  GNU  could  occur  in  any  part 
of  the  software  market.  The  number  of  programmers  who  could  write  a  word 
processor  or  spreadsheet  program  better  than  many  currently  on  the  market 
grows  daily.  Large,  complex  programs  are  not  exempt  from  such  competition:  as 
Stallman  points  out,  modularizing  the  task  solves  the  mere  size  problem,  and  a 
little  reverse  engineering  unravels  much  complexity. 

The  fact  is,  both  commercial  software  ( pace  Richard  Stallman)  and  free  soft¬ 
ware — written  and  distributed  for  fun  or  for  pride  of  accomplishment  or  for  philo¬ 
sophical  reasons — are  here  to  stay.  You  can’t  keep  people  from  writing  programs. 
But  free  software  isn’t  going  to  destroy  the  market  for  commercial  software;  pro¬ 
grams  are  too  complex  to  be  evaluated  strictly  on  the  basis  of  price. 

On  the  other  hand,  smart  commercial  software  developers  will  keep  themselves 
aware  of  what  free  software  exists,  and  not  borrow  trouble  by  trying  to  compete 
with  free  products.  If  they  are  really  smart,  they  will  also  paddle  around  to  the 
other  side  and  imagine  what  free  software  might  be  written. 

What  is  Dr.  Dobb's  view  on  the  issue?  We’re  in  favor  of  good  software,  free 
and  commercial,  and  have  been  promoting  both  kinds  since  1976.  I  think  we  see 
both  sides. 

But  not  at  the  same  time. 

'-JfltlLoJ Sus& 

Michael  Swaine 


6 

1 79 


Dr.  Dobb's  Journal.  March  1985 


Fantasies 

Dear  DDJ: 

I  read  with  interest  your  piece,  “A  Re¬ 
alizable  Fantasy,”  in  the  November 
[1984]  issue.  Clearly,  a  product  such 
as  you  describe  would  be  potentially 
very  useful  as  a  productivity  tool.  A 
close  approximation  to  such  a  tool  may 
already  exist! 

Recently,  on  behalf  of  a  client,  we 
did  a  brief  evaluation  of  a  package 
called  the  Excelerator  from  Index 
Technology  Corporation.  The  prod¬ 
uct — software  +  hardware — does  ex¬ 
ist  and  seems  to  function  pretty  much 
as  described  in  the  brochure.  It’s  a  very 
powerful  utility  and  at  least  it’s  a  start 
in  the  direction  you  outlined. 

Not  surprisingly,  however,  our  cli¬ 
ent  balked  at  the  $9,500  price  tag 
nearly  twice  what  his  XT  cost!  I  agree 
that  in  a  micro  environment  this  is  an 
impossible  price,  but  there  is  no  reason 
to  suppose  that  such  a  capability  can’t 
be  built  and  offered  at  substantially 
less.  I  hope  someone  does  it  soon. 
Sincerely, 

R.  A.  Langevin 
7621  Fontaine  St. 

Potomac,  MD  20854 

Dear  DDJ: 

Some  humble  fantasies:  extra  CP/M 
BDOS  calls  for  full-screen  I/O,  multi¬ 
character  key  input  (function  keys) 
and  a  utility  that  translates  a  submit 
file  into  a  .COM  (or  .CMD)  file. 

Nelson  Richardson 
740  West  End  Avenue  #21 
New  York,  NY  10025 

BU  Bits 

Dear  DDJ: 

Ian  Ashdown’s  BU  program  published 
in  the  January  [1985,  #99]  DDJ  is  a 
fine  contribution  to  the  growing  list  of 
public  domain  programs  that  can  per¬ 


form  incremental  backup  given  correct 
operation  of  the  CP/M  “archive  bit” 
(t3‘).  As  the  author  of  one  of  the  best 
known  commercial  programs  for  per¬ 
forming  the  same  function,  1  feel  I 
should  comment  on  a  serious  factual 
error  in  Ian’s  article.  He  states:  “. .  .  if 
you  use  a  disk  utility  to  set  t3-prime  to 
true  in  the  file’s  directory  entry,  you 
will  find  that  the  BDOS  resets  it  to  false 
(zero)  whenever  the  directory  entry  is 
changed.  Since  this  means  that  the  file 
has  been  opened,  written  to,  and  closed 
(or  else  renamed)  by  the  BDOS,  31- 
prime  is  apparently  an  undocumented 
attribute  bit  that  indicates  when  a  file 
has  been  updated.”  I’m  not  sure  where 
this  idea  got  started,  but  for  CP/M  2.2 
it  just  ain’t  so.  (Ian  Ashdown  is  not  the 
only  notable  to  make  this  mistake:  it 
also  occurs  on  page  222  of  David  E. 
Cortesi’s  generally  outstanding  book 
Inside  CP/M.)  If  you  don’t  believe  me, 
spend  a  few  minutes  with  BU,  set  t3’ 
on  a  file,  change  the  file  with  a  stan¬ 
dard  CP/M  utility,  and  just  see  if  the 
bit  is  reset. 

Alas,  while  t3'  is  a  functional  ar¬ 
chive  bit  under  CP/M  3.0  and  MPM, 
under  CP/M  2.2  it  acts  just  like  any 
other  attribute  bit.  There  is,  in  fact,  no 
code  whatsoever  in  BDOS  to  reset  this 
bit  automatically  when  a  file  is 
changed,  as  those  who  have  tried  to  im¬ 
plement  Kelly  Smith’s  original  AR¬ 
CHIVE. ASM  program  know  full  well. 
The  BU  program  is  almost  useless  un¬ 
der  2.2  unless  BDOS  is  patched  to  in¬ 
stall  suppport  for  the  archive  bit.  Our 
own  product,  Qbax,  will  function  with¬ 
out  patching  for  programs  such  as 
word  processors  and  compilers  that 
change  files  by  rewriting  them  from 
scratch,  but  even  our  product  requires 
a  BDOS  patch  to  catch  random  access 
updates  in  place.  Such  patches  are  ex¬ 
tremely  tricky,  and  while  public  do¬ 
main  software  is  wonderful  it  is  also 
nice  to  have  a  company  to  rely  on  for 


support  when  the  patch  doesn’t  work. 
Some  of  the  versions  of  the  BDOS 
patch  for  ARCHIVE. ASM  that  have 
appeared  in  various  places  have  had 
some  serious  bugs. 

Ian  should  have  gotten  a  tip-off 
from  his  own  comments:  “Speaking  of 
file  copy  utilities,  a  few  are  available 
that,  under  certain  circumstances,  will 
write  a  file  without  resetting  its  Ar¬ 
chive  attribute.  One  of  these,  oddly 
enough,  is  DRI’s  own  PIP.COM.”  Not 
odd,  just  normal  for  CP/M  2.2!  Vanilla 
PIP  is  not  a  rogue  elephant  that  some¬ 
how  bypasses  BDOS  (like  so  many  pro¬ 
grams  that  run  under  that  other  oper¬ 
ating  system,  which  shall  remain 
nameless!)  In  fact  PIP  is  written  in  a 
high-level  language  and  uses  orthodox 
BDOS  calls.  PIP  fails  to  reset  the  ar¬ 
chive  bit  because  the  facility  to  do  so 
automatically  in  BDOS  doesn’t  exist. 
The  few  programs  available  that  do  re¬ 
set  the  archive  bit  do  it  as  the  result  of 
explicit  code  placed  there  by  the  effort 
of  their  authors. 

Best, 

Jim  Rosenberg 
President 
Amanuensis,  Inc. 

R.D.  #1,  Box  236 
Grindstore,  PA  15442 
(412) 785-2806 

Acknowledgments 

Dear  DDJ: 

I  want  to  set  the  record  straight  on 
three  items  that  have  arisen  recently 
concerning  authorship  and  propriety 
of  certain  copyrighted  material. 

Firstly,  in  the  January  1985  issue  of 
DDJ,  page  67,  Walt  Piotrowski  incor¬ 
rectly  gives  me  credit  for  the  original 
MBOOT  program  published  in  DDJ  Oc¬ 
tober  1982  page  47.  As  the  source  list¬ 
ing  of  M  BOOT  in  that  article  clearly  in¬ 
dicates,  Keith  Petersen,  W8SDZ,  is  the 
(Continued  on  page  12) 


Dr.  Dobb’s  Journal.  March  1985 


8 

173 


C  CHEST 


Pipes ,  Wild-Card  Expansion ,  and  Quoted  Arguments 


by  Allen  Holub 


Hi.  The  C  column  is  changing  hands 
this  month.  So,  before  leaping  into  the 
thick  of  things,  I  want  to  talk  a  little 
about  how  I’d  like  the  column  to  evolve. 
If  you’d  rather  see  this  column  do  other 
things,  write  to  me.  I  propose  using  this 
space  for  three  major  purposes: 

( 1 )  Printing  useful  subroutines  written 
in  C.  This  month’s  column  is  an  example 
of  the  sort  of  subroutine  I  have  in  mind. 
In  future  columns,  I’d  like  to  talk  about 
things  like  binary  (AVL)  tree  manage¬ 
ment  functions,  operating  system  primi¬ 
tives  (such  as  queue  management  rou¬ 
tines),  useful  text  manipulation 
primitives  not  found  in  the  standard  li¬ 
brary  (like  the  expression  recognizer  in 
grep  discussed  in  DDJ  No.  96),  graphics 
routines,  and  so  on.  I’ve  written  compil¬ 
ers,  operating  systems,  word  processors, 
and  the  like,  and  I  find  myself  using  sev¬ 
eral  pieces  of  these  larger  programs  over 
and  over  again.  It’s  those  sorts  of  pieces 
that  I’d  like  to  print  here.  In  addition  to 
subroutines,  complete  small  programs 
will  appear  here  from  time  to  time,  such 
as  an  MSDOS  version  of  the  Unix  utility 
“make,”  a  simple  sort  utility,  and  the 
like. 

Anything  you’re  interested  in  is  a 
potential  topic,  provided  that  you  write 
and  tell  me  what  you  want  to  know 
about.  By  the  same  token,  if  you  have 
useful  subroutines  you’d  like  to  share, 
please  send  them  to  me  (on  an  IBM  PC- 
compatible  disk,  if  possible,  or  on  pa¬ 
per,  if  not).  While  we’re  on  the  topic  of 
participation,  I’ve  never  written  a  bug- 
free  program  in  my  life.  Consequently, 
although  a  program  might  run  fine  on 
my  own  system  when  compiled  with 
my  own  compiler,  I’m  sure  that  unno¬ 
ticed  bugs  creep  in  from  time  to  time. 
If  you  find  a  bug,  write  and  tell  me;  I’ll 
print  the  correction  as  soon  as  possible. 
If  you  have  a  better  way  to  do  some¬ 
thing  and  have  a  working  program  that 
does  it  (I’m  not  especially  interested  in 
theory),  send  it  in. 

10 


(2)  Discussing  technical  and  more  the¬ 
oretical  issues.  Discussing  some  of  the 
underlying  theory  of  the  C  language 
and  of  compilers  in  general  can  help 
you  understand  a  lot  of  the  weirdness 
of  the  C  language.  By  knowing  about 
things  such  as  stack  frames  and  assem¬ 
bly  language  subroutine-calling  con¬ 
ventions,  you  find  that  a  lot  of  the 
hard-to-understand  parts  of  the  lan¬ 
guage  become  much  clearer.  This  kind 
of  knowledge  also  makes  it  easier  to 
debug  programs,  so  an  occasional  dip 
into  theory  could  be  useful. 

(3)  Providing  a  general  forum  on  C- 
related  topics.  Want  to  start  a  public 
discussion  on  your  own  pet  peeve? 
Send  it  in. 

Adding  Unix  Command  Line 
Functions 

Unix  provides  an  operating  environ¬ 
ment  particularly  helpful  to  program¬ 
mers.  Pipes,  redirection,  and  wild-card 
expansion  are  all  features  that  Unix  it¬ 
self  provides,  available  to  any  program 
running  under  Unix  regardless  of  the 
language  in  which  that  program  was 
written.  For  example,  when  you  say 
“grep  something  *.c”  on  the  command 
line,  Unix  expands  the  *.c  to  a  list  of  all 
files  in  the  current  directory  that  end 
in  .c.  By  the  time  your  main(  )  function 
is  executed,  Unix  will  have  filled  argv 
with  the  names  of  these  files  rather 
than  with  the  *,c.  Similarly,  when  you 
say  “foo  >  bar”  or  “foo  |  bar,”  Unix 
automatically  takes  care  of  sending  the 
output  of  foo  to  the  file  bar  (in  the  first 
case)  or  to  the  program  bar  (in  the 
second). 

Most  operating  systems  aren’t  as 
nice.  MSDOS  does  redirection,  but,  be¬ 
cause  it  doesn’t  differentiate  between 
stdout  and  stderr,  most  compilers  can’t 
let  MSDOS  actually  do  the  redirection. 
MSDOS  pipes  are  useable,  but  it 
doesn’t  do  wild-card  expansion.  CP/M 
doesn’t  even  do  pipes.  To  make  up  for 


these  shortcomings,  most  manufactur¬ 
ers  of  C  compilers  implement  some  or 
all  of  these  Unix  functions  inside  the  C 
program  itself  (rather  than  in  the  oper¬ 
ating  system  where  they  belong). 
That’s  one  reason  why  a  two-line  pro¬ 
gram  can  compile  into  8K  of  code. 

Unfortunately,  most  compiler  man¬ 
ufacturers  don’t  give  you  full  Unix 
compatibility.  I/O  redirection  almost 
always  is  included,  and  wild-card  ex¬ 
pansion  almost  always  is  omitted. 
Quoted  arguments  (which  let  you  get 
imbedded  spaces  into  a  single  argv  en¬ 
try)  usually  are  not  implemented  ei¬ 
ther.  This  month,  we  will  see  how  to 
add  pipes,  wild-card  expansion,  and 
quoted  arguments  to  the  Aztec  Cl  I 
compiler.  It’s  not  hard  to  modify  these 
routines  to  work  with  many  other  com¬ 
pilers.  In  a  future  column,  I’ll  give  a 
version  that  works  with  Lattice  C. 

Root  Modules 

Before  your  main(  )  subroutine  is  acti¬ 
vated,  a  certain  amount  of  housekeep¬ 
ing  must  be  done.  To  accomplish  this, 
most  C  compilers  provide  a  subroutine 
that  you  usually  don’t  see.  This  sub¬ 
routine,  called  a  root  module,  first  does 
various  initializations  then  calls  your 
main(  )  function.  The  root  module  for 
CP/M  compilers  often  is  inserted  into 
your  program  automatically  by  the 
linker.  MSDOS  compilers  typically  re¬ 
quire  you  to  provide  the  name  of  the 
root  module  as  the  first  filename  argu¬ 
ment  submitted  to  link. 

Among  other  things,  the  root  mod¬ 
ule  initializes  the  I/O  system  and 
parses  the  command  line  into  the  argv 
array.  Redirection,  wild-card  expan¬ 
sion,  and  so  forth  are  also  done  in  the 
root  module.  So,  to  add  these  func¬ 
tions,  all  you  have  to  do  is  modify  the 
root.  On  the  down  side,  anything  you 
add  to  the  root  module  will  increase 
the  size  of  every  program  you  compile. 
It’s  often  a  good  idea  to  have  several 


■1  7  A 


Dr.  Dobb’s  Journal,  March  1985 


roots  around — with  varying  complex¬ 
ity  and  size — and  to  use  the  one  most 
suitable  to  a  particular  application. 

Different  manufacturers  call  root 
modules  different  things  (Croot,  root, 
_main,  Smain,  and  so  on).  Sometimes 
there  are  two  such  functions.  For  ex¬ 
ample,  the  Computer  Innovations 
compiler  uses  an  assembly  language 
routine  Smain,  which  calls  a  C  routine 
_main,  which  calls  main(  ).  Similarly, 
in  Lattice  C  the  assembly  language 
routine  c  calls  __main(  )  (written  in  C), 
which  in  turn  calls  your  main(  ).  The 
root  module  for  Aztec  C  called  croot(  ) 
is  written  in  C. 

Fortunately,  the  source  for  the  root 
module  usually  is  provided  with  the 
compiler,  so  you  can  modify  it  as  neces¬ 
sary.  The  version  of  croot  given  here 
differs  from  the  distribution  version  as 
follows: 

(1)  Any  word  on  the  command  line 
containing  the  characters  *  or  ?  is  as¬ 
sumed  to  be  a  filename  containing 
wild-card  characters.  Croot  will  search 
the  current  user  area  on  the  indicated 
drive  for  any  files  matching  the  indi¬ 
cated  name.  The  *  and  ?  are  expanded 
exactly  the  way  they  are  in  CP/M;  that 
is,  a  *  will  match  any  character  repeat¬ 
ed  zero  or  more  times,  and  a  ?  will 
match  any  single  character  falling  in 
the  position  in  a  name  that  corresponds 
to  the  position  of  the  question  mark. 
No  character  except  a  period  may  fol¬ 
low  a  *.  The  expanded  list  of  files  is 
passed  through  to  main(  ).  If  you  have 
three  files  on  your  disk  with  the  exten¬ 
sion  .c,  then  the  command  line  “pro¬ 
gram  *.c”  results  in  main(  )  being 
passed  an  argc  of  4;  argv[l],  argv[2], 
and  argv[3]  contain  the  names  of  the 
three  files.  The  files  are  listed  in  no 
particular  order. 

(2)  Any  argument  surrounded  by  dou¬ 
ble  quotes  (“arg”)  is  treated  as  a  single 
argument,  even  if  there  are  embedded 
blanks  in  the  string.  For  example,  the 
command  line: 

program  “this  is  one  argument” 
“this  is  another” 

enters  main(  )  with  argc  set  to  three. 
Argv[  1  ]  points  at  the  string: 

this  is  one  argument 
Argv[2]  points  at  the  string: 

this  is  another 

The  quotes  are  stripped  off  for  you.  The 
wild-card  characters  *  and  ?  are  ig¬ 
nored  if  they  are  found  in  a  quoted  ar- 

Dr.  Dobb's  Journal,  March  1985 


gument  (i.e.,  they  are  passed  through  to 
main(  )  as  a  *  and  a  ?).  If  the  quotes 
aren’t  present,  croot  will  try  to  expand 
these  characters  into  a  filename. 

(3)  I/O  redirection  has  not  been  modi¬ 
fied  from  the  Manx  release  version. 
The  character  >  causes  standard  out¬ 
put  to  be  “redirected”  to  the  file  whose 
name  is  immediately  to  the  right  of  the 
character  instead  of  to  the  console. 
The  character  <  causes  standard  in¬ 
put  to  be  taken  from  the  file  whose 
name  is  immediately  to  the  left  of  the 
character  instead  of  from  the  console. 
You  have  to  be  careful  about  using 
pipes  and  redirection  at  the  same  time, 
though  (see  below). 

(4)  The  pipe  character  (|)  causes  stan¬ 
dard  output  from  the  program  to  the 
left  of  the  character  to  be  used  as  stan¬ 
dard  input  to  the  program  to  the  right 
of  the  character.  Two  temporary  files 
called  SPIPE.IN  and  SPIPE.OUT  are 
created  (on  the  A:  disk  in  the  current 
user  area);  they’re  automatically  de¬ 
leted  when  they  are  no  longer  needed. 
The  program  at  the  end  of  a  pipe  has  to 
support  redirection,  but  it  need  not 
support  pipes.  You  may  use  more  than 
one  pipe  on  a  single  command  line.  For 
example,  the  command  line 

grep  -n  foo  |  sort  -d  |  lpr 
executes  grep  first,  invoking  it  with  the 
arguments  -n  and  foo.  The  output  from 
grep  is  used  as  the  input  to  the  program 
sort,  which  is  passed  the  argument  -d  as 
well.  Finally,  the  output  from  sort  is 
passed  to  the  program  lpr. 

Croot  does  this  chaining  in  two 
steps.  It  creates  a  temporary  file  in  the 
first  program  in  the  chain,  pretending 
that  it  saw  a  redirection  directive  (  > 
Spipe.out)  on  the  command  line.  Then 
when  executing  the  function  exit(  ), 
croot  loads  in  the  next  program,  using 
execl(  ),  and  executes  it.  Croot  redi¬ 
rects  the  temporary  file  to  the  second 
program  by  adding  a  <  Spipe.in  to  the 
command  line  sent  to  the  second  pro¬ 
gram  (the  name  is  changed  before  the 
command  line  is  modified). 

Given  the  above  example,  you  could 
accomplish  the  same  thing  by  typing: 
grep  -n  foo  >  Spipe.out 
ren  Spipe.in  =  Spipe.out 
sort  -d  <  Spipe.in  >  Spipe.out 
era  Spipe.in 

ren  Spipe.in  =  Spipe.out 
lpr  <C  Spipe.in 
era  Spipe.in 


If  the  temporary  files  can’t  be  created, 
croot  does  not  erase  the  Spipe.in  file. 
However,  it  will  attempt  to  abort  any 
ongoing  submit  file  by  deleting  the  file 
SSS.sub. 

Pipes  may  be  used  along  with  redi¬ 
rection.  For  example,  the  command  line 
program  1  <foo  |  program2  >  bar 
is  perfectly  legal.  In  this  case,  pro¬ 
gram  1  takes  its  input  from  the  file  foo 
and  sends  its  output  to  program2.  Pro- 
gram2,  in  turn,  puts  its  own  output  into 
the  file  bar.  You  have  to  be  careful, 
though:  you  can’t  direct  output  to  two 
places  at  once  or  get  input  from  two 
places  at  once.  For  example,  “pro¬ 
gram  1  >foo  |  program2”  is  illegal,  as 
is  “program  1  |  program  2  <foo.” 

Support  Routines 

Croot  requires  several  support  routines; 
most  of  these  are  documented  in  Listing 
One  (page  14).  Some  of  these  are  useful 
enough  to  merit  additional  explanation. 

do  wild 

int  do_wild(fname,  argv,  maxarg  ) 
char  *fname,  **argv ; 
int  maxarg ; 

A  general  purpose  directory-searching 
utility,  do_wild(  )  creates  an  argv-like 
array  of  pointers  to  strings,  one  string 
for  each  file  in  the  current  directory 
that  matches  the  filespec  “fname.”  It 
returns  the  number  of  valid  elements 
in  the  argv  array  after  the  expansion; 
the  return  value  can  be  used  to  update 
argc. 

If  fname  contains  no  wild-card  char¬ 
acters,  only  one  name  is  expanded.  If 
fname  does  not  exist,  nothing  is  done, 
and  the  routine  returns  a  zero.  If 
fname  contains  a  *  or  a  ?,  argv  will 
contain  a  pointer  to  a  string  for  each 
possible  matching  name.  If  the  string 
begins  with  x:  (where  x  may  be  re¬ 
placed  by  any  valid  CP/M  drive  id),  the 
indicated  drive  is  searched  instead  of 
the  current  drive.  On  entry,  the  param¬ 
eter  argv  should  point  at  the  beginning 
of  an  uninitialized  array  of  character 
pointers.  Maxarg  is  the  maximum  per¬ 
mitted  size  of  argv. 
execl 

execl(  name,  argl, . . .  argN, 

(char  *)0  ) 

char  ‘name,  *argl  .  .  .  *argN; 


12 

175 


Execl(  )  chains  to  the  program  “name” 
by  overwriting  the  current  program 
and  transferring  control  to  the  new 
program;  the  extension  .COM  is  auto¬ 
matically  appended  onto  “name.” 

Execl(  )  has  a  variable  number  of 
arguments.  Argl  through  argN  are 
concatenated  and  passed  through  to 
the  new  program  as  its  command  line. 
The  rightmost  argument  in  the  list 
must  be  zero.  Because  of  the  way  the 
next  program  is  loaded,  the  length  of 
all  the  concatenated  arg  strings  may  be 
at  most  79  characters.  Note  that 
execl(  )  inserts  a  space  between  each 
string.  Any  characters  beyond  the  79 
are  truncated. 

Execl(  )  should  not  return.  If  it  does 
return,  then  execl(  )  can’t  open  the 
specified  file.  Execl(  )  does  not  print 
an  error  message  in  this  latter  case. 
Note  that  the  contents  of  the  default 
FCB  and  the  default  DMA  buffer  (at 
addresses  0x5c  and  0x80)  are  unde¬ 
fined  if  execl(  )  returns.  Execl(  ) 
makes  no  attempt  to  close  any  files,  so 
be  sure  to  close  any  opened  files  before 
the  execl(  )  call. 

Execl(  )  uses  two  assembly  language 
files  to  load  the  next  program  in  the 
pipe,  “loader”  and  “loadldr”( Listing 
Two,  page  27).  Loader  reads  the  next 
program  into  memory  then  transfers 
control.  Loadldr  puts  the  loader  itself 
into  memory. 
initfcb 

initfcb(filename,  fcb) 

char  *filename,  *fcb; 

I nitfcb(  )  initializes  the  fcb  (CP/M  file 
control  block)  pointed  at  by  “fcb"  to 

jhe  filename  “filename.”  If  the  file¬ 
name  has  x:  as  the  first  two  characters 
of  the  string,  the  drive  field  is  set  to 
drive  x  as  well;  all  other  fields  are  filled 
with  zeroes. 

A  *  in  the  filename  is  expanded  out  to 
fill  the  rest  of  the  field  with  ?s.  So,  the 
wild-card  characters  *  and  ?  are  treated 
as  they  are  by  CP/M.  For  example: 

xxx. yyy  becomes  xxx  yyy 
xx*. y*  becomes  xx??????y?? 

“  ”  or  *.*  becomes  ??????????? 

The  filename  argument  may  be  termi¬ 
nated  by  either  a  \0  or  a  space. 

DDJ 


(Continued  from  page  8)  \ 

author  of  MBOOT  and  MBOOT  is  only  I 
an  adaptation  of  the  original  MODEM- 
.ASM  by  Ward  Christensen.  This  is  an 
easy  mistake  for  Walt  to  make  and  I 
doubt  if  anyone  will  be  hearing  from 
either  Keith’s  or  Ward’s  legal 
representatives. 

Secondly,  my  dBASE  II®  command 
program  DBSECURE.CMD  is  a  RUN¬ 
TIME®  simulator,  but  requires  the  tar¬ 
get  environment  to  own  already  a  li¬ 
censed  copy  of  dBASE  II®.  While 
DBSECURE  will  scramble  and 
SCRUNCH  command  files  like  RUN¬ 
TIME®,  it  relies  on  DBASE.COM  to  ex¬ 
ecute  the  command  file.  This  require¬ 
ment  is  clearly  indicated  in  the 
documentation. 

Finally,  NEWBASE.COM  and  NEW- 
BASE. ASM  will  no  longer  support  the 
SKIP-SIGNON-OPTION.  The  editors 
at  DDJ  brought  it  to  my  attention  that 
such  a  feature  could  be  used  to  by-pass 
the  copyright  notice  of  Ashton-Tate 
and  that  was  not  my  intention. 

As  most  authors  for  this  magazine,  I 
am  available  to  try  to  answer  questions 
and  help  solve  problems  with  my  pub¬ 
lished  software.  However,  on  behalf  of 
all  of  us  who  put  our  name,  address  and 
phone  number  on  the  line,  please  call  at 
reasonable  hours  (check  time  differ¬ 
ences)  and  always  include  a  self-ad¬ 
dressed,  stamped  envelope  when 
writing. 

Gene  Head 

2860  NW  Skyline  Drive 
Corvallis,  Oregon  97330 
(505)758-0279 


DD) 


13 


Dr.  Dobb's  Journal.  March  1985 


C  Chest  (Text  begins  on  page  10) 

Listing  One 


/*  Croot.c 

* 

*  Wildcard  expansion  &  pipes  for  Aztec  CII 

* 

*  This  version  of  Croot.c  has  been  extensively  modified  by  A.  I.  Holub. 

*  Manx  Software  Systems  is  in  no  way  responsible  for  this  program. 

* 

*  The  portions  of  this  program  printed  in  boldface  and  are  the  original 

*  croot.  They  are  Copyright  (c)  1981,  1982  by  Manx  Software  Systems  and 

*  (c)  1982  by  Thomas  Fenwick. 

* 

*  The  portions  of  this  program  not  printed  in  boldface  are 

*  Copyright  (c)  1985,  Allen  I.  Holub.  These  portions  may  be  reproduced 

*  for  personal,  non-profit,  use  only.  All  other  use  is  prohibited. 

* 

*  You  should  replace  the  croot  in  libc.lib  with  this  module. 

*/ 

#include  "aierrno.h" 
linclude  "arfcntl.h" 

^include  "a:io.h" 


#def ine 

MAXARGS 

128 

/* 

Maximum  argc 

*/ 

#def  ine 

LOADERSIZE 

49 

/* 

Size  of  the  loader  in  bytes 

*/ 

#d  e  f ine 

DEF  FCB 

0x5c 

/* 

CP/M  Default  FCB  location 

*/ 

#def ine 

FCBSIZE 

36 

/* 

Size  of  an  FCB  in  bytes 

*/ 

#def ine 

CBUF 

( ( char 

* )  0x80 ) 

/*  CP/M  command  line  buffer 

#def ine 

CBUFEND 

(char 

* ) (0x80+ 1 28- 

LOADERSIZE) 

/* 

*  Various  BDOS  function  calls: 

*/ 

#define  s r ch f ir s t ( f cb )  (  bdos(  0x11,  (fcb)  )  &  Oxff  ) 

#define  s r chnex t ( f cb )  (  bdos(  0x12,  (fcb)  )  &  Oxff  ) 

#define  bdosopen(f cb)  (  bdos(  OxOf,  (fcb)  )  &  Oxff  ) 

int  badfd(),  noper(); 

/*  Channel  table:  relates  fd's  to  devices 

*/ 


struct  channel  chantab[]  = 
{ 


{ 

2, 

o. 

1, 

0, 

noper , 

2 

), 

{ 

o, 

2, 

1. 

0, 

noper , 

2 

}, 

{ 

o. 

2, 

1, 

0, 

noper , 

2 

}, 

{ 

o. 

o, 

o, 

0, 

badf d , 

0 

), 

{ 

o. 

o. 

o. 

0, 

badfd. 

0 

), 

{ 

o, 

o. 

o, 

0, 

badf d , 

0 

}, 

{ 

o. 

o. 

o. 

0, 

badfd , 

0 

), 

{ 

o. 

o. 

0, 

0, 

badfd , 

0 

), 

( 

o. 

o. 

0, 

0, 

badfd. 

0 

), 

l 

o. 

o. 

0, 

0, 

badfd. 

0 

}, 

{ 

}; 

o. 

o. 

0, 

0, 

badfd. 

0 

), 

static  char 

*Argv[MAXARGS]  ; 

(Continued  on  page  16) 


Dr.  Dobb’s  Journal,  March  1985 


14 

177 


X^nCSl  (Listing  Continued,  text  begins  on  page  10) 

L  isting  One 


static 

static 

static 

static 

/* 

* 

*/ 

char 

char 

/* - 

Croot  (  ) 
{ 


char 

Ar gbuf [ 128]; 

int 

Argc; 

int 

Doingpipe  =  0; 

char 

*Cmdtail ; 

File  names  for  pipe  processing.  The  padding  lets  us  patch  the  name 
with  DDT  rather  than  re-compiling. 


*Pi pe_in  =  " A : $PIPE . IN\0\0\0\0"  ; 

*Pi pe_out  =  "A: $PIPE . 0UT\0\0\0"  ; 

- */ 


/*  Root  module  for  Aztec  CII.  This  is  not  the  best  C  programming 

*  in  the  world.  It  uses  gotos;  what  some  of  the  code  is  doing 

*  is,  by  necessity,  somewhat  obscure.  On  the  other  hand  it 

*  does  a  bunch  of  complex  stuff  relatively  efficently. 

*/ 


register  int  k; 

register  char  *cp,  *fname; 

blockmv ( Argbuf ,  CBUF  +  1,  127);  /*  Copy  the  command  line  somwhere  */ 
Argbuf[*CBUF  &  0x7f]  =  0;  /*  safe  and  null  terminate  it.  */ 

Argv[0]  =  /*  Initialize  argv  and  argc  */ 

cp  =  Argbuf; 

Argc  =  1; 


while  (Argc  <  MAXARGS) 

( 

while  ( *cp  ==  '  ' ) 

++cp; 

if  (*cp  ==  0) 
break ; 

else  if (  *cp  ==  '  |  '  ) 

f 

Doingpipe  =  1 ; 
Cmdtail  =  ++cp; 

/* 

*  Pretend  we  s 
*/ 

fname  =  Pipe_out  ; 
k  =  1; 

goto  redir2; 


/*  Skip  leading  blanks  */ 

/*  At  end  of  command  line  */ 
/*  Process  a  pipe  */ 

a  ">"  to  the  Pipe_out  file. 


) 

else  if  (*cp  ==  ’>*)  /*  redirect  output  */ 

{ 

k  =  1; 

goto  redirect; 

} 


(Continued  on  page  18) 


16 


Dr.  Dobb's  Journal.  March  1985 


C  Chest  (Listing  Continued,  text  begins  on  page  10) 

Listing  One 


redirect : 


r  ed i r  2  : 


skippast : 


else  if  (*cp  ==  '<')  /*  redirect  input  */ 

{ 

k  =  0; 

while  (*++cp  ==  '  ' ) 


fname  =  cp; 

while  (*++cp) 

{ 

if  (*cp  ==  '  ' ) 

{ 

*cp++  =  0; 
break ; 

} 

} 

close(k)  ; 

k  =  k  ?  creat(fname,  0666)  :  open(fname,  0_RD0NLY)  ; 
if  (k  ==  -1) 

{ 

perr("Can't  open  file  for  redirection  <"); 
perr ( fname) ; 
perr(">") ; 
exit( 10) ; 

) 

if(  Doingpipe  ) 

( 

/*  The  rest  of  the  cmd  tail  is  for  the  next 

*  process  so  stop  processing  it. 

*/ 

break ; 


) 

else  if  (*cp  ==  ' " ' ) 

{ 

Argv[Argc++]  =  ++cp; 

while  (  *cp  &&  (*cp  !=  ’"')  ) 

{ 

c  p++ ; 

) 

*cp++  =  0; 

) 

else  if  (  haswild(cp)  ) 

{ 

Argc  +=  do_wild(cp,  Argv  +  Argc,  MAXARGS-Argc-1 ) ; 
goto  skippast; 

) 

else 


{ 

Ar  g v [ Ar  gc++ ]  =  cp; 

while  (*++cp) 

{ 

if  (  cp  — =  )  (Continued  on  page  20) 


Dr.  Dobb  s  Journal,  March  1985 


18 

179 


c  wriest  (Listing  Continued,  text  begins  on  page  10) 

Listing  One 


} 


{ 


} 

exit(main(Argc,Argv)); 


*cp++  =  0; 
break ; 


/* 


*/ 


execl(  name,  args  ) 
char  *name,  *args; 
( 


Execl  has  a  variable  number  of  arguments  starting 
with  args  and  continuing  until  a  0  arg  is  found. 

It  is  called  with : 

execl(  filename,  argl,  arg2  ,  ...  ,  argN,  0  ); 

All  arguments  must  be  character  pointers. 

Execl  overlays  the  current  program  with  the  program 
named  "name".  All  the  args  are  concatenated  together 
with  the  args  separated  from  one  another  by  spaces. 

The  concatenated  array  is  used  as  the  command  line 
for  the  new  process.  Execv  adds  the  .com  to  the  end  of 
name . 


register  char  *cp,  *dest,  **argp; 


strcpy  (CBUF,  name 

); 

strcat  (CBUF,  ".COM" 

); 

ini t  fcb ( CBUF ,  DEF_FCB 

); 

/* 

Try  to  open  the  file 

*/ 

if(  bdosopen ( DEF_FCB ) 

==  Oxff  ) 

return(O) ; 

/* 

Can't  do  it 

*/ 

/* 

*  Put  together  a  new  command  line  from  the  arguments 

*/ 


dest  =  CBUF  +  1; 

for(argp  =  Sargs  ;  *argp  &&  dest  <  CBUFEND  ;  argp++  ) 

{ 

for(cp  =  *argp  ;  *cp  &&  dest  <  CBUFEND  ;  *dest++  =  *cp++  ) 


*dest++  =  '  ' ; 

) 

*dest  =  '\0'  ; 

*CBUF  =  (int)dest  -  (int)CBUF  ; 

loadldr();  /*  We  won't  return  from  loadldr  */ 


a  on 


Dr.  Dobb's  Journal,  March  1985 


exit(code) 

{ 

/*  Finish  up  the  execution  of  a  C  program.  Chain  to  the 

*  next  process  if  appropriate. 

*/ 


register  char  *tailp,  *src, 

,  *dest 

9 

closall_(  )  ; 

/* 

Close  all  open 

files 

*/ 

unlink(Pip  e_i n ) ; 

/* 

Get  rid  of  the 

pipe  temp 

file  */ 

if  (  code  ) 

/* 

Try  to  abort  an  ongoing 

submit  */ 

unlink("A:$$$.SUB" )  ; 

/* 

f  ile . 

*/ 

if(  Doingpipe  ) 

( 

/* 

Chain  to  next 

program  in 

pipe  */ 

rename (Pip e_out ,  Pipe_in); 


/*  Get  the  file  name  from  the  command  tail.  First  strip  out 

*  leading  blanks,  then  terminate  the  file  name  with  a  null 

*  if  necessary  (the  rest  of  the  command  tail  will  be 

*  arguments  to  the  next  file. 

*/ 

while(*Cmdtail  ==  '  ') 

Cmdtail++ ; 

tailp  =  Cmdtail; 

while(  *tailp  &&  *tailp  !=  '  '  ) 

tailp++ ; 

if(  *tail p  ) 

*tailp++  =  '  \0 '  ; 

/*  Chain  to  the  next  program  */ 

execl(  Cmdtail,  "<",  Pipe_in,  tailp,  (char  *)0  ); 


/*  If  execl  returns  then  we  couldn't  open  the  file  */ 

per r ( "Coul dn ' t  open  file  at  end  of  pipe:  "); 
perr ( Cmdtail  )  ; 
per r ( " . COM"  )  ; 

unlink ( "A :$$$. SUB" )  ;  /*  Try  to  kill  a  submit  file  */ 

unlink("$$$.SUB") ; 


boot  ( ) ; 


/* - */ 

perr(str) 
char  *s  t  r ; 

{ 

/*  Print  a  string  by  brute  force.  We  can  use  this 

*  whether  or  not  stdout  is  open. 

*/ 

while(*str) 

bdos(2 ,  *str++) ; 


Dr.  Dobb’s  Journal.  March  1985 


i^ilCSl  (Listing  Continued,  text  begins  on  page  10) 

Listing  One 


) 

/* - */ 

badfd()  /*  Called  when  a  bad  file  descriptor  is  found  */ 

{ 

errno  =  EBADF ; 
return  -1; 

} 

/* - */ 

noper()  /*  Do  nothing,  return  0  */ 

( 

return  0; 

) 

/* - */ 


initfcb( filename ,  fcb) 


char 

{ 

♦filename , 

/* 

Initialize 

* 

pointed  to 

* 

designator 

* 

*/ 

?  .  See  the 

*  f  c  b  ; 

the  fcb  pointed  at  by  "fcb"  with  the  filename 
by  "filename".  Filename  may  include  a  drive 
(X:)  or  either  of  the  wild  card  characters  *  or 
text  for  more  details. 


register  char  *fp; 

register  int  i; 

for  (  i  =  FCBSIZE  ,  fp  =  fcb  ;  — i  >=  0  ;  *fp++  =  0  ) 


if(  *filename  &&  filename[l]  ==  ':') 

( 

/*  Fill  in  the  drive  field  of  the  fcb 

*/ 

*fcb  =  (char)(  toupper ( *f ilename )  -  'A'  +  1); 
filename  +=  2; 

} 


expand_name( f cb ,  filename); 

) 

/* - 


*/ 


expand_name ( dest ,  src) 
char  *src,  *dest; 

{ 

/*  Turn  the  filename  at  src  into  the  name  and  type  fields  of  an  fcb 

*  pointed  to  by  dest  (dest  is  the  address  of  an  fcb,  src  is  an 

*  ascii  string) . 

* 

*  "xxx.yyy"  becomes  "xxx  yyy" 

*  "xx*.x*"  becomes  "xx??????y??" 

*  ""  or  becomes  "???????????" 

* 

*  The  src  parameter  may  be  terminated  by  either  a  '\0*  or  a 

*  space  ( '  ' ) . 


22 

182 


Dr.  Dobb's  Journal.  March  1985 


) 

/* - 

#def ine 
#de  f ine 
#def  ine 


int 

char 

int 

{ 


register  char  *dot,  *end,  i; 

dot  =  ++dest;  /*  Skip  past  drive  field  */ 

for  (  i= 1 1  ;  — i>=0  ;  )  /*  Dest  =  ????????.???  */ 

*dot  +  +  =  ( *sr c )  ?  '  '  :  '?'  ; 

dot  =  &dest [ 8 ]  ; 
end  =  &des  t [ 1 0  ]  ; 

while(  *src  &&  *src  !=  '  '  ) 

{ 

if  ( *src  ==  '  * '  ) 


if  (  dest  <  dot  ) 

while  (dest  <  dot) 

*dest++  =  '?' 

else 


while  (dest  <=  end) 

*dest++  =  ' ?  '  ; 

} 

el^e  if  (*src  ==  '.') 

dest  =  dot; 

else 


*dest++  =  *src; 


src++ ; 

) 


*/ 


DEF_FCB 
srchf irst( fcb) 
srchnext( fcb) 


0x5c 
(  bdos( 
(  bdos( 


/*  CP/M  Default  FCB  location 
Oxll,  (fcb)  )  &  Oxff  ) 

0x12,  (fcb)  )  &  Oxff  ) 


*/ 


do_wild( f name ,  argv,  maxarg  ) 
*fname,  **argv  ; 
maxarg  ; 


register  char 
register  unsigned  int 
register  int 
extern  char 


*dest,  *src; 
tmp ; 

i,  argc  =  0; 
*alloc ( ) ; 


/*  Expand  fname  (which  may  contain  wild  card  characters) 

*  into  a  series  of  entries  in  argv.  Return  an  updated  argc. 

*  Maxarg  is  the  maximum  permitted  value  of  argc. 

*  Fname  may  be  terminated  by  either  a  * \ 0 f  or  a  '  '. 

*/ 


initfcb( fname ,  DEF_FCB) ; 

if  (  (tmp  =  srchf ir st (DEF_FCB) )  !=  Oxff  ) 

( 

do 

{ 

if(  (dest  =  fname  =  alloc(13))  ==  0  ) 

{ 


( Continued  on  next  page ) 


Dr.  Dobb's  Journal.  March  1985 


23 

183 


C ^  X^nCSI  (Listing  Continued,  text  begins  on  page  10) 

Listing  One 


perr("Not  enough  memory  to  expand  wildcard"); 
exit ( 1 )  ; 

) 

src  =  ((char  *)((tmp<<5)  +  0x80))  +  1; 

if  (  *(fnarae  +  1)  ==  ':') 

{ 

*dest++  =  *fname; 

*dest++  =  '  :  '  ; 

} 

for(  i  =  1  ;  i  <=  11  ;  i++  ) 

{ 

/*  Transfer  name  from  DMA  buffer  to 

*  argument. 

*/ 


return(  argc  ); 

} 

/* _ *  j 

haswild ( str ) 
char  *str; 

{ 

/*  Return  true  if  str  has  a  or  a  '?'  in  it. 

*/ 

register  int  c; 

do  ( 

c  =  *str++; 

i f ( c  ==  ||  c  ==  '?') 

return ( 1 )  ; 

)  while( c  &&  c  ! =  '  ' ) ; 

return(O) ; 

)  End  Listing  One 


26 


Dr.  Dobb’s  Journal.  March  1985 


Listing  Two 


LOADER. ASM  Assembly  language  support  routines  for  pipes 

This  loader  is  essentially  that  used  by  BDS  C  (written  by  Leor 
Zolman)  .  The  loader  is  copied  to  just  below  the  tpa 

(into  the  default  disk  buffer)  by  loadldr.  This  location  limits  the 
size  of  the  command  tail  for  the  next  program  in  the  pipe  to 
128  -  49  =  79  characters  (128  =  buffer  size,  49  =  loader  size). 

We're  putting  the  loader  where  we  do  because  an  8080  doesn't  have 
a  relative  jump  instruction.  Since  we  are  actually  copying  the 
loader  to  the  place  where  it  will  execute,  any  labels  defined 
at  link  time  will  be  wrong  (because  they'll  be  pointing  into 
the  original  code,  not  the  copy).  So,  we  have  to  copy  it  to 
some  known  place  to  know  where  to  jump  to  when  we  loop.  The  bottom  of 
the  bdos,  however,  is  not  a  known  place.  It  will  vary  from 
system  to  system. 

Before  this  loader  is  called,  the  calling  program  should  put 
the  new  command  line  into  the  buffer  at  0x80. 

It  should  also  have  an  opened  the  file  to  be 

loaded  with  the  fcb  for  that  file  in  the  default  FCB  space 


» 

(at 

0x5c )  . 

closec 

eq  u 

16 

CP/M  function: 

Close  file. 

reads 

equ 

20 

CP/M  function: 

Read  sequential 

sdma 

equ 

26 

CP/M  function: 

Set  DMA  address 

fcb 

equ 

5CH 

Default  FCB  location 

tpa 

equ 

100H 

Start  of  transient  program  area 

t  bu  f  f 

equ 

80H 

start  of  command  tail  buffer 

bdos 

equ 

05H 

bdos  j ump  vector 

bdos  v 

equ 

06H 

pointer  to  bdos 

start  address 

LOADER  (local) 

On  entry : 

D/E  =  the  DMA  address  (  0x100  initially  ) 

B/C  =  points  at  an  fcb  for  the  opened  file.  This  fcb  is  physically 
located  just  beneath  the  loader  in  memory. 

SP  =  just  below  the  bdos. 

On  exit: 


All  registers  are  destroyed 


loader:  lxi 
loadl:  push 

push 
mvi 
call 
pop 
push 
mvi 
call 
pop 
pop 
ora 

jz 


d  ,  t  pa 

d 

b 


c , sdma 
bdos 
d 
d 


c , reads 
bdos 
b 
d 


a 

t  pa-8 


•.destination  address  of  new  program 

; push  dma  addr 

;push  fcb  pointer 

;set  DMA  address  for  new  sector 

;get  pointer  to  working  fcb  in  DE 
;and  re-push  it 

; read  a  sector 

jrestore  fcb  pointer  into  BC 
;and  dma  address  into  DE 
;end  of  file? 

;if  not,  get  next  sector  (goto  'load2:') 


(Continued  on  next  page) 

Dr.  Dobb's  Journal,  March  1985  27 

185 


L  X^ilGSl  (Listing  Continued,  text  begins  on  page  10) 

Listing  Two 


mov  d,b  ;move  the  fcb  pointer  to  d/e 

mov  e ,  c 

ravi  c.closec  ;  close  the  file 

call  bdos 

mvi  c.sdma  ;  reset  DMA  pointer,  tbuf  is  at  0x80 

lxi  d  ,  tbuf  f 

call  bdos 

jmp  tpa  ;  and  go  invoke  the  program 

load2:  lxi  h,80h  ;bump  dma  address 

dad  d 
xchg 

jmp  tpa-46  ;and  go  loop  (at  loadl) 


L0ADLDR_  (public) 

This  routine  loads  the  loader  into  memory,  modifys  the  stack  pointer 
and  registers  as  required  by  the  loader,  and  passes  control  to  the 
loader.  Since  loadldr  jumps  directly  to  the  loader,  it  will  not 
return  to  the  calling  routine. 

On  exit; 

B  =  default  FCB  address  (0x5c) 

D  =  tpa  start  address  (0x100) 

SP  =  first  address  of  BDOS 


public  loadldr_ 

loadldr_: 

lxi  d, loader 

lxi  h , tpa-49 

mvi  b,49 

LI :  ldax  d 

mov  m , a 

inx  d 

inx  h 

dcr  b 

jnz  LI 

lxi  b , f cb 

lxi  d , tpa 

lhld  bdosv 

sphl 

jmp  tpa-49 

end 


;  d  =  sr c ; 

;  h  =  d est ; 

;  b  =  49;  /*  size  of  loader  */ 

;  do  { 

;  *d++  =  *h++; 

;  ) 

;  while(  --b  >  0  )  ; 

;  b  =  address  of  fcb; 

;  d  =  dma  address ; 

;  h  =  address  of  BDOS 
;  sp  =  address  of  BDOS 

;  jump  to  the  loader 


End  Listings 


28 


■4  O  C 


Dr.  Dobb's  Journal.  March  1985 


REALIZABLE  FANTASIES 


The  CNU  Manifesto 


by  Richard  Stallman 

This  month's  installment  of  Realiz¬ 
able  Fantasies  features  a  guest  ap¬ 
pearance  by  Richard  Stallman.  Stall- 
man,  inventor  of  the  original 
much-imitated  Emacs  editor  and  for¬ 
merly  of  the  Artificial  Intelligence 
Lab  at  MIT,  has  worked  extensively  on 
compilers,  editors,  debuggers,  com¬ 
mand  interpreters,  the  Incompatible 
Timesharing  System  (ITS)  and  the 
Lisp  Machine  operating  system.  He 
pioneered  terminal -independent  dis¬ 
play  support  in  ITS.  Since  then  he  has 
implemented  one  crashproof  file  sys¬ 
tem  and  two  window  systems  for  Lisp 
machines,  and  designed  a  third  win¬ 
dow  system  now  being  implemented ; 
this  one  will  be  ported  to  many  sys¬ 
tems  including  GNU. — M.S. 

GNU,  which  stands  for  Gnu’s  Not 
Unix,  is  the  name  for  the  complete 
Unix-compatible  software  system  that 
I  am  writing  so  that  I  can  give  it  away 
free  to  everyone  who  can  use  it.  Many 
other  programmers  are  helping  me. 
Contributions  of  time,  money,  pro¬ 
grams  and  equipment  are  greatly 
needed. 

So  far  we  have  a  portable  C  and  Pas¬ 
cal  compiler  which  compiles  for  Vax 
and  68000,  an  Emacs-like  text  editor 
with  Lisp  for  writing  editor  com¬ 
mands,  a  yacc-compatible  parser  gen¬ 
erator,  a  linker,  and  around  35  utili¬ 
ties.  A  shell  (command  interpreter)  is 
nearly  completed.  When  the  kernel 
and  a  debugger  are  written,  by  the  end 
of  1985  1  hope,  it  will  be  possible  to 
distribute  a  GNU  system  suitable  for 
program  development.  After  this  we 
will  add  a  text  formatter,  an  Empire 
game,  a  spreadsheet,  and  hundreds  of 
other  things,  plus  on-line  documenta¬ 
tion.  We  hope  to  supply,  eventually, 
everything  useful  that  normally  comes 
with  a  Unix  system,  and  more. 

GNU  will  be  able  to  run  Unix  pro¬ 
grams,  but  will  not  be  identical  with 

Dr.  Dobb  s  Journal.  March  1985 


Unix.  We  will  make  all  improvements 
that  are  convenient,  based  on  our  expe¬ 
rience  with  other  operating  systems.  In 
particular,  we  plan  to  have  longer  file¬ 
names,  file  version  numbers,  a  crash¬ 
proof  file  system,  filename  completion, 
perhaps,  terminal-independent  display 
support,  and  eventually,  a  Lisp-based 
window  system  through  which  several 
Lisp  programs  and  ordinary  Unix  pro¬ 
grams  can  share  a  screen. 

Both  C  and  Lisp  will  be  available  as 
system  programming  languages.  We 
will  try  to  support  UUCP,  MIT  Chaos¬ 
net,  and  Internet  protocols  for 
communication. 

GNU  is  aimed  initially  at  machines 
in  the  68000/16000  class,  with  virtual 
memory,  because  they  are  the  easiest 
machines  to  make  it  run  on.  The  extra 
effort  to  make  it  run  on  less  powerful 
machines  will  be  left  to  someone  who 
wants  to  use  it  on  them. 

Why  I  Must  Write  GNU 

If  I  like  a  program,  I  must  share  it  with 
other  people  who  like  it.  Software  sell¬ 
ers  want  to  divide  the  users  and  con¬ 
quer  them,  making  each  user  agree  not 
to  share  with  others.  I  cannot  in  good 
conscience  sign  a  nondisclosure  or  soft¬ 
ware  license  agreement.  Lor  years  I 
worked  within  the  Artificial  Intelli¬ 
gence  Lab  to  resist  such  tendencies. 
My  efforts  were  wasted.  I  cannot  re¬ 
main  in  an  institution  where  such 
things  are  done  for  me  against  my  will. 

So  that  I  can  continue  to  use  com¬ 
puters  without  violating  my  principles 
I  have  decided  to  put  together  a  body 
of  free  software  sufficient  to  enable  me 
to  get  along  without  any  software  that 
is  not  free.  I  have  resigned  from  the  AI 
lab  to  deny  MIT  any  legal  excuse  for 
preventing  me  from  giving  GNU  away. 

Why  GNU  Will  Be  Compatible  with 
Unix 

Unix  is  not  my  ideal  system,  but  it  is 


not  too  bad.  The  essential  features  of 
Unix  seem  to  be  good  ones,  and  I  think 
I  can  fill  in  what  Unix  lacks  without 
spoiling  them.  Lurthermore  a  system 
compatible  with  Unix  would  be  conve¬ 
nient  for  many  other  people  to  adopt. 

How  GNU  Will  Be  Available 

GNU  is  not  in  the  public  domain.  Ev¬ 
eryone  will  be  permitted  to  modify  and 
redistribute  GNU,  but  no  distributor 
will  be  allowed  to  restrict  its  further 
redistribution.  That  is  to  say,  propri¬ 
etary  modifications  will  not  be  al¬ 
lowed.  I  want  to  make  sure  that  all  ver¬ 
sions  of  GNU  remain  free. 

Why  Many  Other  Programmers 
Want  to  Help 

I  have  found  many  other  programmers 
who  are  excited  about  GNU  and  want 
to  help.  Many  programmers  are  un¬ 
happy  about  the  commercialization  of 
system  software.  It  may  enable  them 
to  make  more  money,  but  it  requires 
that  they  feel  like  competitors  with 
other  programmers  rather  than  like 
comrades.  The  fundamental  act  of 
friendship  among  programmers  is  the 
sharing  of  progams;  marketing  ar¬ 
rangements  now  in  use  essentially  for¬ 
bid  programmers  to  treat  others  as 
friends.  The  purchaser  of  software 
must  choose  between  friendship  and 
obeying  the  law.  Naturally,  many  de¬ 
cide  that  friendship  is  more  important. 
But  those  who  believe  in  law  often  do 
not  feel  at  ease  with  either  choice. 
They  become  cynical  and  think  that 
programming  is  just  a  way  of  making 
money. 

By  working  on  and  using  GNU  rath¬ 
er  then  proprietary  programs,  we  can 
be  hospitable  to  everyone  and  obey  the 
law.  In  addition,  GNU  serves  as  an  ex¬ 
ample  to  inspire  and  a  banner  to  rally 
others  to  join  us  in  sharing.  This  can 
give  us  a  feeling  of  harmony,  which  is 
impossible  if  we  use  software  that  is 

30 

187 


not  free.  For  about  half  the  program¬ 
mers  I  talk  to,  this  is  an  important  hap¬ 
piness  that  money  cannot  replace. 

How  You  Can  Contribute 

1  am  asking  computer  manufacturers 
for  donations  of  machines  and  money. 
I’m  asking  individuals  for  donations  of 
programs  and  work. 

One  computer  manufacturer  has  al¬ 
ready  offered  to  provide  a  machine. 
We  can  use  more.  One  consequence 
you  can  expect  if  you  donate  machines 
is  that  GNU  will  run  on  them  at  an  ear¬ 
ly  date.  The  machine  should  be  able  to 
operate  in  a  residential  area,  and  not 
require  sophisticated  cooling  or  power. 

I  have  found  very  many  program¬ 
mers  eager  to  contribute  part-time 
work  to  GNU.  For  most  projects,  such 
part-time  distributed  work  would  be 
very  hard  to  coordinate;  the  parts, 
written  independently,  would  not  work 
together.  But  for  the  particular  task  of 
replacing  Unix,  this  problem  is  absent. 
A  complete  Unix  system  contains  hun¬ 
dreds  of  utility  programs,  each  of 
which  is  documented  separately.  Most 
interface  specifications  are  fixed  by 
Unix  compatibility.  If  pach  contribu¬ 
tor  can  write  a  compatible  replacement 
for  a  single  Unix  utility,  and  make  it 
work  properjy  ip  place  of  the  original 
on  a  Unix  system,  then  these  utilities 
will  work  right  when  put  together. 
Even  if  Murphy  creates  a  few  unex¬ 
pected  problems,  assembling  these 
components  will  be  a  feasible  task. 
(The  kernel  will  require  closer  commu¬ 
nication  and  will  be  worked  on  by  a 
small,  tight  group.) 

If  I  get  donations  of  money,  I  may 
be  able  to  hire  a  few  people  full  or  part- 
time.  The  salary  won’t  be  high  by  pro¬ 
grammer’s  standards,  but  I’rq  looking 
for  people  for  whom  building  commu¬ 
nity  spirit  is  as  important  as  making 
money.  I  view  this  as  a  way  of  enabling 
dedicated  people  to  devote  their  full 
energies  to  working  op  GNU  by  spar¬ 
ing  them  the  need  to  make  a  living  in 
another  way. 

Why  All  Computer  Users  Will 
Benefit 

Once  GNU  is  written,  everyone  will  be 
able  -to  obtain  good  system  software 
free,  just  like  air.  This  means  much 
more  than  just  saving  everyone  the 
price  of  a  Unix  license.  It  means  that 

31 


much  wasteful  duplication  of  system 
progamming  will  be  avoided.  This  ef¬ 
fort  can  go  instead  into  advancing  the 
state  of  the  art. 

Complete  system  sources  will  be 
available  to  everyone.  As  a  result,  a 
user  who  needs  changes  in  the  system 
will  always  be  free  to  make  them  him¬ 
self,  or  hire  any  available  programmer 
or  company  to  make  them  for  him.  Us¬ 
ers  will  no  longer  be  at  the  mercy  of 
one  progammer  or  company  that  owns 
the  sources  and  is  in  sole  position  to 
make  changes. 

Schools  will  be  able  to  provide  a  su¬ 
perior  educational  environment  by  en¬ 
couraging  all  students  to  study  and  im¬ 
prove  the  system  code.  Harvard's 
computer  lab  used  to  have  the  policy 
that  no  program  could  be  installed  on 
the  system  if  its  sources  were  not  on 
public  display,  and  upheld  it  by  actual¬ 
ly  refusing  to  install  certain  progams.  I 
was  very  much  inspired  by  this. 

Finally,  the  overhead  of  considering 
who  owns  the  system  software  and 
what  one  is  or  is  not  entitled  to  do  with 
it  will  be  lifted.  Arrangements  to  make 
people  pay  for  using  a  program,  in¬ 
cluding  licensing  of  copies,  always  im¬ 
pose  a  tremendous  cost  on  society 
through  the  cumbersome  mechanisms 
necessary  to  figure  out  how  much  (that 
is,  which  programs)  a  person  must  pay 
for.  Furthermore,  only  a  police  state 
can  force  everyone  to  obey.  Consider 
the  analogy  of  a  space  station  where 
air  must  be  manufactured  at  great 
cost:  charging  each  breather  per  liter 
of  air  might  be  fair,  but  wearing  the 
metered  oxygen  mask  all  day  and  all 
night  would  be  intolerable  even  if  ev¬ 
eryone  could  afford  to  pay  the  bill. 
And  the  TV  cameras  everywhere  to  see 
if  you  ever  took  it  off  would  be  outra¬ 
geous.  It  would  be  better  to  support  the 
air  plant  with  a  head  tax  and  chuck  the 
masks.  Copying  all  or  parts  of  a  pro¬ 
gram  is  as  natural  to  a  progammer  as 
breathing,  and  as  productive.  It  ought 
to  be  as  free. 

Some  easily  rebutted  objections 
to  GNU's  goals 

“Nobody  will  use  it  if  it  is  free,  because 
that  means  they  can’t  rely  on  any  sup¬ 
port.  You  have  to  charge  for  the  pro¬ 
gram  to  pay  for  providing  the  sup¬ 
port.”  If  people  would  rather  pay  for 
GNU  plus  service  than  get  GNU  free 


without  service,  a  company  to  provide 
just  service  to  people  who  have  ob¬ 
tained  GNU  free  ought  to  be 
profitable. 

We  must  distinguish  between  sup¬ 
port  in  the  form  of  real  programming 
work  and  mere  handholding.  The  for¬ 
mer  is  something  one  cannot  rely  on 
from  a  software  vendor.  If  your  prob¬ 
lem  is  not  shared  by  enough  people,  the 
vendor  will  tell  you  to  get  lost.  If  your 
business  needs  to  be  able  to  rely  on  sup¬ 
port,  the  only  way  is  to  have  all  the  nec¬ 
essary  sources  and  tools.  Then  you  can 
hire  any  available  person  to  fix  your 
problem  and  you  will  not  be  at  the  mer¬ 
cy  of  any  individual.  With  Unix,  the 
price  of  sources  puts  this  out  of  consid¬ 
eration  for  most  businesses.  With  GNU 
this  will  be  easy.  It  is  still  possible  that 
there  will  be  no  available  competent 
person,  but  this  problem  cannot  be 
blamed  on  distribution  arrangements. 
GNU  does  not  eliminate  all  the  world’s 
problems,  only  some  of  them. 

Meanwhile,  the  users  who  know 
nothing  about  computers  need  hand¬ 
holding,  i.e.,  they  need  for  others  to  do 
for  them  the  things  which  they  could 
easily  do  themselves ,  but  don’t  know 
how  to.  Such  services  could  be  provided 
by  companies  that  sell  just  handholding 
and  repair  service.  If  it  is  true  that  users 
would  rather  spent)  money  and  get  a 
product  yvith  services,  they  will  qlso  be 
willing  to  buy  the  service,  having  got 
the  product  free.  The  service  companies 
will  compete  in  quality  and  price;  users 
will  not  be  tied  to  any  particular  one. 
Meanwhile,  those  of  us  who  don’t  need 
the  service  should  be  able  to  use  the 
program  without  paying  for  the  service. 

“You  cannot  reach  many  people 
without  advertising,  and  you  must 
charge  for  the  program  to  support 
that.  It’s  no  use  advertising  a  program 
people  can  get  free.”  There  are  various 
forms  of  free  or  very  cheap  publicity 
that  can  be  used  to  inform  numbers  of 
computer  users  about  something  like 
GNU.  But  it  may  be  true  that  one  can 
reach  more  microcomputer  users  with 
advertising.  If  this  is  really  so,  a  busi¬ 
ness  whjch  advertises  the  service  of 
copying  and  mailing  GNU  for  a  fee 
ought  to  be  successful  enough  to  pay 
for  its  advertising  and  more.  This  way, 
only  the  users  who  benefit  from  the  ad¬ 
vertising  pay  for  it.  On  the  other  hand, 
if  many  people  get  GNU  from  their 
Dr.  Dobb' s  Journal.  March  1985 


inn 


friends,  and  such  companies  don’t  suc¬ 
ceed,  this  will  show  that  advertising 
was  not  really  necessary  to  spread 
GNU.  Why  is  it  that  free  market  advo- 


Dr.  Dobb  s  Journal,  March  1985 


cates  don't  want  to  let  the  free  market 
decide  this? 

“My  company  needs  a  proprietary 
operating  system  to  get  a  competitive 
edge.”  GNU  will  remove  operating  sys¬ 
tem  software  from  the  realm  of  compe¬ 
tition.  You  will  not  be  able  to  get  an 
edge  in  this  area,  but  neither  will  your 
competitors  be  able  to  get  an  edge  over 
you.  You  and  they  will  compete  in 
other  areas,  while  benefiting  mutually 
in  this  one.  If  your  business  is  selling 
an  operating  system,  you  will  not  like 
GNU,  but  that’s  tough  on  you.  If  your 
business  is  something  else,  GNU  can 
save  you  from  being  pushed  into  the 
expensive  business  of  selling  operating 
systems.  I  would  like  to  see  GNU  devel¬ 
opment  supported  by  gifts  from  many 
manufacturers  and  users,  reducing  the 
cost  to  each. 

“Don’t  programmers  deserve  a  re¬ 
ward  for  their  creativity?”  If  anything 
deserves  a  reward,  it  is  social  contribu¬ 
tion.  Creativity  can  be  a  social  contri¬ 
bution,  but  only  insofar  as  society  is 
free  to  use  the  results.  If  programmers 
deserve  to  be  rewarded  for  creating  in¬ 
novative  programs,  by  the  same  token 
they  deserve  to  be  punished  if  they  re¬ 
strict  the  use  of  these  programs. 

“Shouldn’t  a  programmer  be  able  to 
ask  for  a  reward  for  his  creativity?” 
There  is  nothing  wrong  with  wanting 
pay  for  work,  or  seeking  to  maximize 
one’s  income,  as  long  as  one  does  not 
use  means  that  are  destructive.  But  the 
means  customarily  used  in  the  area  of 
software  development  today  are  based 
on  destruction.  Extracting  money  from 
users  of  a  program  by  restricting  their 
use  of  it  is  destructive  because  the  re¬ 
strictions  reduce  the  amount  that  and 
the  ways  in  which  the  program  can  be 
used.  This  reduces  the  amount  of 
wealth  that  humanity  derives  from  the 
program.  When  there  is  a  deliberate 
choice  to  restrict,  the  harmful  conse¬ 
quences  are  deliberate  destruction. 
The  reason  a  good  citizen  does  not  use 
such  destructive  means  to  become 
wealthier  is  because,  if  everyone  did  so, 
we  would  all  become  poorer  from  the 
mutual  destructiveness.  This  is  Kant¬ 
ian  ethics,  or,  the  Golden  Rule.  Since  I 
do  not  like  the  consequences  that  result 
if  everyone  hoards  information,  I  am 
required  to  consider  it  wrong  for  one 
person  to  do  so.  Specifically,  the  desire 
to  be  rewarded  for  one’s  creativity  does 


not  justify  depriving  the  world  in  gen¬ 
eral  of  all  or  part  of  that  creativity. 

“Won’t  programmers  starve?”  I 
could  answer  that  nobody  is  forced  to 
be  a  programmer.  Most  of  us  cannot 
manage  to  get  any  money  for  standing 
on  the  street  and  making  faces.  But  we 
are  not,  as  a  result,  condemned  to 
spend  our  lives  standing  on  the  street 
making  faces,  and  starving.  We  do 
something  else.  But  that  is  the  wrong 
answer,  because  it  accepts  the  ques¬ 
tioner’s  implicit  assumption  that  with¬ 
out  ownership  of  software,  program¬ 
mers  cannot  possibly  be  paid  a  cent. 
Supposedly  it  is  all  or  nothing.  The  real 
reason  programmers  will  not  starve  is 
because  it  will  still  be  possible  for  them 
to  get  paid  for  programming;  just  not 
as  much  as  now. 

Restricting  copying  is  not  the  only 
means  for  making  a  profit  in  software 
development.  It  is  the  most  common 
means  because  it  brings  in  the  most 
money.  If  it  were  prohibited,  or  reject¬ 
ed  by  the  customer,  software  business 
would  move  to  other  methods  of  profit¬ 
making  that  are  now  used  less  often. 
Probably  programming  would  not  be 
as  lucrative  as  it  now.  But  that  is  not  an 
argument  against  the  change.  It  is  not 
considered  an  injustice  that  sales 
clerks  make  the  salaries  that  they  now 
do.  If  programmers  made  the  same, 
that  would  not  be  an  injustice  either. 
(In  practice  they  would  still  make  con¬ 
siderably  more  than  that.) 

“Don’t  people  have  a  right  to  control 
how  their  creativity  is  used?”  Control 
over  the  use  of  one’s  ideas  really  consti¬ 
tutes  control  over  other  people’s  lives; 
and  it  is  usually  used  to  make  their 
lives  more  difficult.  People  who  have 
carefully  studied  the  issue  of  intellec¬ 
tual  property  rights  (such  as  lawyers) 
say  that  there  is  no  intrinsic  right  to 
intellectual  property.  The  kinds  of  sup¬ 
posed  intellectual  property  rights  that 
the  government  recognizes  were  creat¬ 
ed  by  specific  acts  of  legislation  for 
specific  purposes.  For  example,  the 
patent  system  was  established  to  en¬ 
courage  inventors  to  disclose  the  de¬ 
tails  of  their  inventions.  Its  purpose 
was  to  help  society  rather  than  to  help 
inventors.  At  the  time,  the  life  span  of 
1 7  years  for  a  patent  was  short  com¬ 
pared  with  the  rate  of  advance  of  the 
state  of  the  art.  Since  patents  are  an 
issue  only  among  manufacturers,  for 

33 

189 


whom  the  cost  and  effort  of  a  license 
agreement  are  small  compared  with 
setting  up  production,  the  patents  of¬ 
ten  do  not  do  much  harm.  They  do  not 
obstruct  most  individuals  who  use  pat¬ 
ented  products. 

The  idea  of  copyright-did  not  exist  in 
ancient  times,  when  authors  frequently 
copied  lengthy  extracts  from  other  au¬ 
thors  in  works  of  non-fiction.  This 
practice  was  useful,  and  is  the  only 
way  many  authors’  works  have  sur¬ 
vived  even  in  part.  The  copyright  sys¬ 
tem  was  created  expressly  for  the  pur¬ 
pose  of  encouraging  authorship.  In  the 
domain  for  which  it  was  invented- 
books,  which  could  be  copied  economi¬ 
cally  only  on  a  printing  press — it  did 
little  harm,  and  did  not  obstruct  most 
of  the  individuals  who  read  the  books. 

All  intellectual  property  rights  are 
just  licenses  granted  by  society  be¬ 
cause  it  was  thought,  rightly  or  wrong¬ 
ly,  that  society  as  a  whole  would  bene¬ 
fit  by  granting  them.  But  in  any 
particular  situation,  we  have  to  ask: 
Are  we  really  better  off  granting  such 
license?  What  kind  of  act  are  we  li¬ 
censing  a  person  to  do?  The  case  of 
programs  today  is  very  different  from 
that  of  books  a  hundred  years  ago.  The 
fact  that  the  easiest  way  to  copy  a  pro¬ 
gram  is  from  one  neighbor  to  another, 
the  fact  that  a  program  has  both 
source  code  and  object  code,  which  are 
distinct,  and  the  fact  that  a  program  is 
used  rather  than  read  and  enjoyed, 
combine  to  create  a  situation  in  which 
a  person  who  enforces  a  copyright  is 
harming  society  as  a  whole  both  mate¬ 
rially  and  spiritually;  in  which  a  person 
should  not  do  so  regardless  of  whether 
the  law  enables  him  to  or  not. 

“Won’t  everyone  stop  programming 
without  a  monetary  incentive?  Actual¬ 
ly,  many  people  will  program  with  ab¬ 
solutely  no  monetary  incentive.  Pro¬ 
gramming  has  an  irresistible 
fascination  for  some  people,  usually 
the  people  who  are  best  at  it.  There  is 
no  shortage  of  professional  musicians 
who  keep  at  it  even  though  they  have 
no  hope  of  making  a  living  that  way. 
But  really  this  question,  though  com¬ 
monly  asked,  is  not  appropriate  to  the 
situation.  Pay  for  programmers  will 
not  disappear,  only  become  less.  So  the 
right  question  is:  Will  anyone  program 
with  a  reduced  monetary  incentive? 
My  experience  shows  that  they  will. 

34 

190 


For  more  than  ten  years,  many  of  the 
world's  best  programmers  worked  at 
the  Artificial  Intelligence  Lab  for  far 
less  money  than  they  could  have  had 
anywhere  else.  They  got  many  kinds  of 
non-monetary  rewards:  fame  and  ap¬ 
preciation,  for  example.  And  creativity 
is  also  fun,  a  reward  in  itself.  Then 
most  of  them  left  when  offered  a 
chance  to  do  the  same  interesting  work 
for  a  lot  of  money.  What  the  facts  show 
is  that  people  will  program  for  reasons 
other  than  riches;  but  if  given  a  chance 
to  make  a  lot  of  money  as  well,  they 
will  come  to  expect  and  demand  it. 
Low-paying  organizations  do  poorly  in 
competition  with  high-paying  ones, 
but  they  do  not  have  to  do  badly  if  the 
high-paying  ones  are  gone. 

“We  need  the  programmers  desper¬ 
ately.  If  they  demand  that  we  stop 
helping  our  neighbors,  we  have  to 
obey.”  You’re  never  so  desperate  that 
you  have  to  obey  this  sort  of  demand. 
Remember,  millions  for  defense,  but 
not  a  cent  for  tribute. 

“Programmers  need  to  make  a  living 
somehow.”  There  are  plenty  of  ways 
by  which  programmers  can  make  a  liv¬ 
ing  without  selling  the  right  to  use  a 
program.  Here  are  a  number  of 
examples: 

•  A  manufacturer  introducing  a  new 
computer  will  pay  for  the  porting  of 
operating  systems  onto  the  new 
hardware. 

•  The  sale  of  teaching,  handholding 
and  maintenance  services  could  also 
employ  programmers. 

•  People  with  new  ideas  could  distrib¬ 
ute  programs  as  freeware,  asking  for 
donations  from  satisfied  users.  I  am 
told  that  several  people  are  already 
working  this  way  successfully. 

•  Users  with  related  needs  can  form 
users’  groups,  and  pay  dues.  A  group 
would  contract  with  programming 
companies  to  write  programs  that 
the  group’s  members  would  like  to 
use. 

All  sorts  of  development  can  be  funded 
with  a  software  tax: 

•  Suppose  everyone  who  buys  a  com¬ 
puter  has  to  pay  x  percent  of  the 
price  as  a  software  tax.  The  govern¬ 
ment  gives  this  to  an  agency  like  the 
NSF  to  spend  on  software 
development. 

•  But  if  the  computer  buyer  makes  a 
donation  to  software  development 


himself,  he  can  take  a  credit  against 
the  tax.  He  can  donate  to  the  project 
of  his  own  choosing— often,  chosen 
because  he  hopes  to  use  the  results 
when  it  is  done.  He  can  take  a  credit 
for  any  amount  of  donation  up  to  the 
total  tax  he  had  to  pay. 

•  The  total  tax  rate  could  be  decided 
by  vote  of  the  payers  of  the  tax, 
weighted  according  to  how  much  tax 
they  paid  in  the  previous  year. 

The  consequences: 

•  The  computer-using  community 
supports  software  development. 

•  This  community  decides  what  level 
of  support  is  needed. 

•  Users  who  care  which  projects  their 
share  is  spent  on  can  choose  this  for 
themselves. 

In  the  long  run,  making  programs 
free  is  a  step  toward  the  post-scarcity 
world,  where  nobody  will  have  to  work 
very  hard  just  to  make  a  living.  People 
will  be  free  to  devote  themselves  to  ac¬ 
tivities  that  are  fun,  such  as  program¬ 
ming,  after  spending  the  necessary  ten 
hours  a  week  on  required  tasks  such  as 
legislation,  family  counseling,  robot 
repair  and  asteroid  prospecting.  There 
will  be  no  need  to  be  able  to  make  a 
living  from  programming. 

We  have  already  greatly  reduced  the 
amount  of  work  that  the  whole  society 
must  do  for  its  actual  productivity,  but 
only  a  little  of  this  has  translated  itself 
into  leisure  for  workers  because  much 
nonproductive  activity  is  required  to  ac¬ 
company  productive  activity.  The  main 
causes  of  this  are  bureaucracy  and  iso¬ 
metric  struggles  against  competition. 
Free  software  will  greatly  reduce  these 
drains  in  the  area  of  software  produc¬ 
tion.  We  must  do  this  in  order  for  tech¬ 
nical  gains  in  productivity  to  translate 
into  less  work  for  us. 

Richard  Stallman,  166  Prospect 
Street,  Cambridge,  MA  02139.  Copy¬ 
right  ®  1985  Richard  Stallman.  Per¬ 
mission  is  granted  to  make  and  dis¬ 
tribute  copies  of  this  article  as  long  as 
the  copyright  and  this  notice  appear, 
and  the  copies  are  distributed  at  no 
charge. 

DDJ 


Reader  Ballot 

Vote  for  your  favorite  feature/article. 

Circle  Reader  Service  No.  191. 

Dr.  Dobb's  Journal,  March  1985 


Programming  in  Logic 


by  John  Malpas 


Dr.  Dobb's  Journal.  March  1985 


Logic  programming  has  been  rela-  is  read  as  if,  and  the  commas  are 

tively  little  known  in  the  United  read  as  and,  so  the  whole  clause  can  be 
States  until  recently.  A  number  of  read:  The  conclusion  is  true  if  condi- 
factors  are  converging,  however,  that  tionl  and  condition2  and  .  .  .  condi- 
augur  well  for  an  increase  in  its  use  tionN  are  all  true. 
and  popularity.  First  of  all,  several  in-  Prolog  is  the  most  widespread  logic 
terpreters  offering  respectable  perfor-  programming  language.  A  Prolog  in- 
mance  and  support  are  now  commer-  terpreter  is  based  on  a  general  purpose 
cially  available,  and  apparently  other,  inference  mechanism  (typically  using 
high-performance  interpreters  will  the  resolution  and  unification  tech- 
soon  be  released.  Second,  the  luke-  niques);  to  take  advantage  of  this 
warm  attitude  of  hardware  manufac-  mechanism,  the  programmer  need  only 
turers  towards  logic  programming  declare  the  relationships  between  data 
seems  to  be  changing:  the  new  Tek-  items  in  Horn  clause  form.  A  logic  pro- 

tronix  machine  was  released  with  Pro-  gram  does  not  need  overt  control  con- 

log  already  running  on  it,  and  Apple  structs  (such  as  for  or  while)  because 

plans  to  offer  a  version  of  Prolog  for  the  interpreter  makes  most  of  the  con- 

the  Macintosh.  Third,  there  is  an  in-  trol  decisions. 

creasing  demand  from  computer  peo-  One  consequence  of  this  approach  is 
pie  on  all  levels  for  artificial  intelli-  portability.  A  logic  program  could  be 
gence  technology,  and  one  of  the  most  made  to  run  on  all  of  the  common  ver- 

promising  areas  of  A!  research  has  sions  of  Prolog  with  relative  ease.  Be- 

been  the  area  of  programming  in  logic.  cause  logic  programs  depend  on  logic, 

Logic  programming  (see  Table  1,  not  on  the  implementation  details  of 

Prolog's  admirers  claim  that  to  use  the  language  is  to 
program  in  logic.  Is  the  claim  true >  and  is  programming 
in  logic  something  we  should  want  to  do? 

any  particular  interpreter,  they  are 
guaranteed  to  be  upwardly  compatible 
with  faster  interpreters  or  more  effi¬ 
cient  hardware.  [But  see  D.  E.  Corte- 
si’s  discussion  of  parallelism  (page 
50)  for  an  important  caveat. — Ed ./ 
Because  the  interpreter  makes  most  of 
the  fundamental  control  decisions,  and 
because  of  the  portability  over  hard¬ 
ware,  any  developments  in  the  imple¬ 
mentation  of  Prolog  interpreters  and  in 
the  hardware  substratum  are  particu¬ 
larly  interesting.  There  have  been  sev¬ 
eral  such  developments  recently. 

The  first  Prolog  was  implemented 
by  Colmerauer  in  Fortran  and  was 
very  slow  and  awkward  to  use  (for  the 

36 

191 


page  37)  is  based  on  the  idea  that 
statements  in  first-order  predicate  log¬ 
ic,  cast  in  Horn  clause  form,  can  be 
used  directly  as  a  programming  lan¬ 
guage.  In  Horn  clause  form,  one  con¬ 
clusion  is  followed  by  zero  or  more 
conditions,  as  follows: 

conclusion  :- 
condition  1, 
condition2, 

conditionN. 

John  Malpas,  Pulsetrain,  747  Green¬ 
wich  St.,  New  York,  NY  10014.  Copy¬ 
right  1984,  Pulsetrain. 


development  of  Prolog  see  Table  2,  be¬ 
low  right).  In  1977  Warren  and  Per¬ 
eira  wrote  an  interpreter/compiler  in 
DEC- 10  assembly  language  that 
showed  that  Prolog  could  be  just  as  ef¬ 
ficient  as  Lisp.  A  number  of  versions 
have  been  written  since  then  in  C  for 
Unix  machines.  Clark  and  McCabe’s 
original  Micro-PROLOG  was  in  assem¬ 
bly  language  on  PCs,  but  the  latest  ver¬ 
sion  (called  Sigma-PROLOG)  is  writ¬ 
ten  in  C  and  runs  on  larger  machines. 
There  are  several  versions  of  Prolog 
written  in  Lisp  for  Lisp  machines. 

In  1983,  1COT  (where  the  Japanese 
fifth  generation  project  is  being  devel¬ 
oped)  announced  their  Personal  Se¬ 
quential  Inference  Machine,  which  has 
a  processor  instruction  set  optimized  to 
run  Prolog — a  Prolog  machine.  When 
1  saw  this  computer,  it  had  80MB  of 
main  memory.  Warren  has  also  fol¬ 
lowed  the  approach  of  optimizing  pro¬ 
cessor  microcode  to  run  Prolog.  His 
company  (Quintus  Computer)  has  a 
fast  Prolog  running  on  a  VAX  and  a 
Sun  workstation.  Other  fast  Prolog  in¬ 
terpreters  are  under  development  at 
UC  Berkeley  and  Argonne  National 
Laboratories.  Prolog  for  machines 
based  on  parallel  architecture  is  being 
developed  by  Shapiro  in  Israel,  at  Im¬ 
perial  College  in  London,  and  at  ICOT. 

In  the  area  of  microcomputers,  there 
are  currently  three  commercial  ver¬ 
sions  of  Prolog  for  the  IBM  PC,  and 
Apple  will  announce  a  version  of  Pro¬ 
log  for  the  Macintosh  this  spring. 
There  are  other  implementations  as 
well,  some  written  in  Lisp  and  of  value 
for  learning  the  language.  The  three 
versions  for  the  PC  are  true  commer¬ 
cial  products  (for  sources  of  Prolog  see 
Table  3,  page  38). 

Features  of  Prolog 

The  fact  that  Prolog  is  an  implementa¬ 
tion  of  logic  as  a  programming  lan¬ 
guage  makes  the  language  distinctive 
in  several  ways. 

Prolog  Can  Express  Knowledge 

Logic  is  a  language  capable  of  expres¬ 
sing  situations  and  problems  that  pro¬ 
vides  a  way  to  describe  accurately  de¬ 
cision-making  behavior.  Logic  has 
precise  semantics;  that  is,  it  is  possible 
to  understand  unambiguously  the 
meaning  of  a  logical  expression.  Logi¬ 
cal  expressions  can  be  represented  by 

37 


R.A.  Kowalski,  Logic  for  Problem  Solving,  North  Holland,  1979  (Much  of  the 
logic  programming  research  done  in  the  last  five  years  has  been  inspired  by  this 
book.) 

K. L.  Clark  and  F.G.  McCabe,  Micro-PROLOG:  Programming  in  Logic,  Prentice  Hall, 
1 984  (This  is  the  best  introduction  to  programming  in  Prolog;  all  examples  are  in 
Micro-PROLOG  syntax.) 

W.F.  Clocksin  and  C.S.  Mellish,  Programming  in  Prolog,  Springer-Verlag,  1981 
(This  is  the  standard  introductory  text  for  Prolog  in  DEC- 10  syntax.) 

L.  Wos,  R.  Overbeek,  E.  Lusk,  and  J.  Boyle,  Automated  Reasoning,  Introduction 
and  Applications,  Prentice  Hall,  1 984  (This  contains  a  good  introduction  to  sym¬ 
bolic  logic.) 

P.J.  Hayes,  "In  Defense  of  Logic,"  Proceedings  of  5th  IJCAI,  1 977 

Table  1 

Further  Reading 


1879:  Gotlob  Frege  invents  predicate  calculus. 

1965:  J.A.  Robinson,  "A  Machine-Oriented  Logic  Based  on  the  Resolution  Prin¬ 
ciple,"  JACM[  12(1)  1965]  pp.  23-41 . 

1968:  D.W.  Loveland,  "Mechanical  Theorem  Proving  by  Model  Elimination," 
JACMl'l  5(2)  1968], 

1971 :  Robert  Kowalski  and  Donald  Kuehner,  "Linear  Selection  with  Resolution 
Function,"  Artificial  Intelligence  (2  1971)  pp.  227-260. 

1973:  Alain  Colmerauer  at  Marseilles  writes  first  Prolog  in  Fortran  (see  Philips 
Russel,  Technical  Report  of  the  University  of  Marseilles,  Sept.  1975). 

1974:  Robert  Kowalski,  "Predicate  Logic  as  a  Programming  Language,"  Pro¬ 
ceedings  of  IFIP,  1974. 

1977:  David  Warren  and  Fernando  Pereira’s  DEC-10  Prolog  interpreter/compil¬ 
er,  University  of  Edinburgh  (see  Warren  and  Pereira,  "Prolog:  The  Language  and 
Its  Implementation  Compared  to  Lisp,"  ACM SIGPLAN  Notices  [  1 2(8)  1977]). 

1 980:  C-Prolog  for  Unix  systems;  Micro-PROLOG  for  PCs  from  Imperial  College 
(see  K.L.  Clark  and  F.G.  McCabe,  Micro-PROLOG:  Programming  in  Logic  Prentice 
Hall,  1984). 

1981:  Fifth  generation  project  announced  by  ICOT  in  Japan,  based  on  logic 
programming  technology  (see  Fifth  Generation  Computer  Systems,  ed.  T. 
Moto-Oka,  North  Holland,  1982). 

1982:  LM-Prolog  for  Lisp  machines  (see  K.M.  Kahn  and  M.  Carlsson,  "LM- 
Prolog  User  Manual,  Release  1 .0,"  Technical  Report  of  Uppsala  University,  July 
1983). 

1983:  E.Y.  Shapiro  designs  Concurrent  Prolog  at  ICOT  (see  Shapiro,  "A  Subset 
of  Concurrent  Prolog  and  Its  Interpreter,"  Technical  Report  #3  ICOT,  Oct.  1 983). 

Table  2 

Development  of  Logic  Programming 


Dr.  Dobb’s  Journal,  March  1985 


IQ? 


different  logic  languages  such  as  Horn 
clauses.  An  expression  in  one  logic  lan¬ 
guage  is  translatable  into  an  expres¬ 
sion  in  any  other  logic  language. 

Kowalski  has  stated  that  “algorithm 
=  logic  +  control,”  which  can  be  un¬ 
derstood  as  follows:  an  algorithm  is 
composed  of  the  set  of  data  relation¬ 
ships  defining  an  application  domain 
(that  is,  its  logic)  and  the  control  infor¬ 
mation  about  how  to  use  those  data  re¬ 
lationships.  In  logic  programming,  be¬ 
cause  the  interpreter  takes  care  of  the 
control  part  of  this  equation,  a  pro¬ 
grammer’s  primary  responsibility  is  to 
learn  the  internal  logic  of  an  application 
domain.  Writing  an  application  pro¬ 
gram  in  Prolog  amounts  to  expressing 
this  internal  logic  as  Horn  clauses. 


Prolog  Describes,  It  Does  Not 
Simulate 

When  a  procedural  language  program 
is  used  to  model  a  process  in  the  world, 
the  relationship  between  the  program 
and  the  world  is  one  of  simulation. 
This  means  that  the  program  is  work¬ 
ing  on  the  same  level  as  the  process  it¬ 
self,  and  so  must  have  large  amounts  of 
data  from  the  process. 

On  the  other  hand,  when  a  logic  pro¬ 
gram  is  used  to  model  a  process  in  the 
world,  the  relationship  between  the 
program  and  the  world  being  modeled 
is  one  of  description.  The  program  ex¬ 
ists  on  a  higher  level  than  the  process  it 
describes,  because  it  contains  state¬ 
ments  and  generalizations  (knowl¬ 
edge)  about  the  process. 


A  program  is  a  simulation  of  the 
world  whenever  the  state  of  the  com¬ 
puter  itself  (i.e.,  CPU  registers,  memo¬ 
ry,  etc.)  is  an  integral  part  of  the  mod¬ 
el.  In  the  case  of  a  descriptive  program, 
however,  the  state  of  the  computer 
functions  only  to  implement  the  logic 
of  the  program  and  does  not  play  a  role 
in  the  model  per  se. 

Prolog  Does  Not  Use  Destructive 
Assignment 

Logic  programming  achieves  its  de¬ 
scriptive  power  by  dispensing  with  the 
destructive  assignment  statement. 
Once  a  logical  variable  assumes  a  val¬ 
ue,  the  programmer  cannot  arbitrarily 
decide  to  give  it  another  value.  In  con¬ 
trast,  all  procedural  languages  depend 
upon  destructive  assignment  as  their 
most  essential  semantic  feature. 

Destructive  assignment  is  best  under¬ 
stood  as  an  optimization  of  memory 
resources.  In  a  language  based  on  de¬ 
structive  assignment,  any  change  in  a 
data  value  in  the  program  is  irrevoca¬ 
ble;  there  will  be  no  trace  of  previous 
values  of  the  variable  unless  the  pro¬ 
grammer  has  made  provisions  to  save 
them. 

Prolog  Statements  Have  Both 
Declarative  and  Procedural 
In  terpre  ta  tions 

There  are  two  ways  to  read  a  Horn 
clause  logic  program:  declaratively 
and  procedurally.  Consider  the  exam¬ 
ple  rule: 

pays_too_much_rent  (X)  :- 
lives_in  (X,  new_york). 

There  is  a  procedural  way  to  read  this 
rule: 

To  find  someone  who  pays  too  much 

rent, 

find  a  resident  of  New  York, 
or 

To  prove  that  John  pays  too  much 

rent, 

verify  that  he  lives  in  New  York. 
But  it  is  also  possible  to  read  the  rule 
declaratively,  which  enables  Horn 
clauses  to  be  used  as  a  knowledge  rep¬ 
resentation  language: 

Someone  who  pays  too  much  rent  is 
someone  who  lives  in  New  York, 
or 

All  people  who  live  in  New  York 
pay  too  much  rent. 

In  the  declarative  reading,  the  rule  be¬ 
comes  a  definition  of  a  relationship 


Programming  Logic  Systems,  Inc. 

31  Crescent  Drive 
Milford,  CT  06460 

Micro-PROLOG  (IBM  PC,  other  PCs,  CP/M) 

Sigma-PROLOG  (written  in  C  for  Unix  machines) 

APES  (A  Prolog  Expert  System  Shell) 

Logicware 

1000  Finch  Avenue  West 
Toronto,  Ontario 
Canada  M3J  2V5 

M-Prolog  (assembly  language  on  IBM  mainframe,  cross  compiled  to  PC, 
VAX,  etal.) 

Expert  Systems  International 

11 50  First  Avenue 

King  of  Prussia,  PA  1 9406 

PROLOG- 1  (assembly  language  on  MSDOS,  CP/M,  VAX,  Macintosh) 

ES/P  Advisor  (Expert  System  Shell) 

SRI  International 

333  Ravenswood  Avenue 

Menlo  Park,  CA  94025 

C-Prolog  (written  in  C  for  Unix  machines  with  at  least  256K  addressable 
memory) 

University  of  New  Hampshire 

UNH  Prolog  (written  in  C  for  Berkeley  Unix) 

University  of  Edinburgh 

DEC- 10  Prolog  (assembly  language) 

Waterloo  University,  Canada 

Waterloo  Prolog  (assembly  language  on  IBM  mainframes) 

Public  Domain 

UNSW  Prolog  (written  in  C  for  Unix  machines) 

Table  3 

Sources  of  Prolog 


Dr.  Dobb  s  Journal.  March 


38 

193 


To  give  some  idea  of  what  logic  pro¬ 
gramming  code  is  like,  it  will  be  useful 
to  examine  a  simple  program.  The  sim¬ 
plest  Horn  clause  is  an  assertion  of 
fact,  consisting  of  a  conclusion  fol¬ 
lowed  by  no  conditions: 

lives_in(john,  new_york). 
which  can  be  read:  John  lives  in  New 
York.  A  rule  is  another  form  of  Horn 
clause  that  shows  the  dependence  of 
one  fact  on  other  facts: 

pays_too_much_rent(john) :- 

lives_in(john,  new_york). 
which  can  be  read:  If  John  lives  in  New 
York,  he  pays  too  much  rent.  The 
word  “john”  is  a  constant,  and  these 
rules  and  facts  contain  explicit  knowl¬ 
edge  applicable  to  “john.” 

A  logical  variable  in  Prolog  is  writ¬ 
ten  as  a  word  beginning  with  a  capital 
letter.  The  “too  much  rent”  rule  above 
can  be  generalized  so  that  it  applies  to 
all  New  York  residents  by  replacing 
“john”  with  a  variable: 

pays_too_much_rent(X)  :- 
lives_in(X,  new_york). 
which  may  be  read:  Anyone  who  lives 
in  New  York  pays  too  much  rent.  The 
variable  “X”  will  match  any  constant, 
so  this  rule  can  be  used  to  assert  that 
the  rent  of  any  New  York  resident  is 
excessive. 

A  rule  utilizing  variables  in  this  way 
contains  implicit  knowledge  that  is  ap¬ 
plicable  to  all  people  who  live  in  New 
York. 

Prolog  is  built  on  the  model  of  symbolic 
logic  created  by  Frege.  There  are  two 
varieties  of  logic  to  consider:  proposi¬ 
tional  logic  and  first-order  predicate 
logic. 

Propositional  logic  considers  only  re¬ 
lations  between  (unanalyzed)  proposi¬ 
tions,  and  only  such  relations  as  bear  on 
the  propositions’  truth  or  falsity.  Propo¬ 
sitions  can  be  combined  with  other 
propositions  to  form  new  propositions 
via  the  logical  connectives  and,  or,  not, 
and  implies.  Propositional  logic  con¬ 
cerns  itself  with  what  propositions  can 
be  derived  from  what  other  proposi¬ 
tions.  For  example,  from  proposition  P 
or  Q,  where  P  and  Q  are  propositions, 
we  can  derive  Q.  Also,  from  P  implies  Q 
and  P  we  can  derive  Q. 

First-order  predicate  logic  involves 
more  complex  expressions  (formulae) 
that  contain  variables  and  that  become 
propositions  only  when  values  from 
some  domain  of  discourse  are  substi¬ 
tuted  for  these  variables.  Thus,  P(x) 
becomes  a  proposition  and  can  be  true 
or  false  only  when  the  variable  x  is  as¬ 
signed  a  specific  value  or  is  otherwise 
quantified.  The  ways  of  quantifying  x 
that  are  particularly  important  for 
first-order  predicate  logic  are  via  the 
universal  and  existential  quantifiers, 
which  turn  the  variable  x  into,  respec¬ 
tively,  for  every  x  and  there  exists  an  x. 
First-order  predicate  logic  is  suffi¬ 
ciently  powerful  to  express  a  good  deal 
of  mathematics  and  to  serve  as  the 
model  for  a  programming  language. 
Prolog  depends  on  the  resolution  tech¬ 
nique  in  its  implementation  of  first- 
order  predicate  logic. 

Resolution  is  a  decision  algorithm 
for  satisfiability  in  first-order  predi¬ 
cate  logic.  Because  the  truth  of  a  for- 

mula  in  first-order  predicate  logic  de¬ 
pends  on  how  values  are  assigned  to  its 
variables,  one  generally  talks  about  the 
satisfiability  of  a  formula,  rather  than 
about  its  truth.  A  formula  is  satisfiable 
if  and  only  if  it  is  true  under  some  ad¬ 
missible  assignment  of  values  to  its 
variables.  Thus,  if  P(a)  means  “a  is  a 
prime  number”  and  Q(a)  means  “a  is 
greater  than  1 1  and  less  than  1 7,”  the 
formula  there  exists  an  x  such  that 
(P(x)  and  Q(x))  is  satisfiable,  namely, 
by  the  value  assignment  x  =  13. 

Resolution  is  a  partial  decision  algo¬ 
rithm;  it  will  invariably  determine  the 
unsatisfiability  of  an  unsatisfiable  for¬ 
mula,  though  it  is  not  guaranteed  to  halt 
if  the  formula  is,  in  fact,  satisfiable. 
(This  halting  problem  is  a  consequence 
of  the  fundamental  unsolvability  of  first- 
order  predicate  logic,  not  a  deficiency  of 
the  algorithm.)  By  recasting  the  formu¬ 
la,  though,  you  can  demonstrate  its  sa¬ 
tisfiability  (if  it  is  satisfiable),  throwing 
the  halting  problem  into  the  unsatisfia¬ 
bility-proving  half  of  the  task. 

The  resolution  method  requires  that 
the  formula  be  in  a  particular  form, 
variably  referred  to  as  clause  form, 
Horn  clause  form,  or  conjunctive  nor¬ 
mal  form.  (These  terms  are  not  really 
interchangeable,  but  for  this  sketchy 
discussion  we  can  regard  them  as  so.) 
The  basic  idea  is  this:  we  eliminate  all 
existential  quantifiers  by  a  trick  that 
involves  treating  the  variables  they 
quantify  as  functions  of  a  particular 
sort.  Once  the  existential  quantifiers 
are  eliminated,  we  simply  drop  univer¬ 
sal  quantifiers,  assuming  that  all  re¬ 
maining  variables  are  universally 
quantified.  And  we  transform  the  for¬ 
mula  into  a  logically  equivalent  formu¬ 
la  whose  top-level  components  are  all 

connected  by  ands,  whose  second-level 
components  are  all  connected  by  ors, 
and  whose  third-level  components  are 
all  elementary  formulae  or  their  nega¬ 
tions.  For  example,  one  simple  formula 
in  this  form  is  (A  or  B)  and  (not-C  or  D). 

Resolution  consists  of  a  single  rule, 
repeatedly  applied.  The  rule  simply 
combines  the  top-level  components  of 
the  formula  pairwise,  selecting  compo¬ 
nents  that  have  an  element  in  common, 
with  the  proviso  that  in  one  case  the  ele¬ 
ment  is  negated.  The  rule  throws  out 
these  conflicting  elements  and  sticks 
the  result  together  into  one  component. 
Thus,  (A  or  B)  and  (not-A  or  C)  resolves 
to  (B  or  C)  by  throwing  out  the  A  and 
the  not-A.  If,  after  repeatedly  banging 
components  against  each  other  this 
way,  we  come  up  empty-handed  (com¬ 
ponents  annihilate  each  other  utterly), 
then  the  formula  is  unsatisfiable. 

Unfortunately,  not  all  the  compo¬ 
nents  are  so  simple  in  form;  does  P(a) 
cancel  out  not-P(x)?  Answering  that 
question  in  the  particular  case  involves 
a  process  not  unlike  finding  the  lowest 
common  denominator  for  two  fractions 
and  is  accomplished  by  an  algorithm 
called  unification.  Unification  finds  a 
substitution  for  x  and  a  that  is  just  suf¬ 
ficiently  general  to  stand  for  both. 

Resolution  as  originally  defined  is 
not  an  efficient  algorithm.  In  1965,  J.A. 
Robinson  defined  an  improved  resolu¬ 
tion  algorithm  that  got  around  some  of 
the  problems  of  combinatorial  explo¬ 
sion  that  plagued  resolution,  but  this 
still  left  unsolved  the  problem  of  how  to 
decide  which  pairs  of  components  to  re¬ 
solve.  Current  work  in  AI  is  emphasiz¬ 
ing  heuristic  strategies  that  take  advan¬ 
tage  of  knowledge  about  the  domain  of 
discourse  in  guiding  resolution. 

40 

194 


Dr.  Dobb's  Journal.  March  1985 


(called  “pays_too_much_rent”)  that 
holds  between  various  data  items.  The 
translation  of  a  rule  into  specific  ma¬ 
chine  actions  is  the  job  of  the  interpret¬ 
er,  not  of  the  programmer. 

Prolog  Can  Be  Used  to  Implement  a 
Metalanguage 

Prolog  is  an  inherently  extensible  lan¬ 
guage  because  it  is  possible  to  modify 
the  behavior  of  the  interpreter  within 
the  context  of  the  language  itself. 
Kowalski  refers  to  this  phenomenon  as 
the  meta-language/object  language 
relationship. 

A  meta-language  controls  the  inter¬ 
pretation  of  an  object  language.  A 
meta-language  can  be  implemented  in 
the  object  language  (Prolog  itself)  as  a 
program  running  on  top  of  the  inter¬ 
preter.  The  meta-language  program 
receives  queries  and  assertions  from 
the  user,  transforms  them  in  consulta¬ 
tion  with  the  knowledge  base,  then 
constructs  queries  in  the  object  lan¬ 
guage  and  executes  them. 

When  first  confronting  Prolog  code, 
many  people  mistakenly  assume  that 
an  end  user  will  have  to  learn  how  to 
write  Prolog  queries  in  order  to  use  a 
Prolog  program.  One  way  that  a  meta¬ 
language  can  be  used  is  to  give  Prolog 
an  alternative  syntax;  as  a  result,  the 
user  interface  to  a  program  is  com¬ 
pletely  specifiable.  Another  applica¬ 
tion  of  a  meta-language  involves  sav¬ 
ing  the  inference  path  used  by  Prolog 
to  answer  a  query,  so  that  the  user  can 
see  how  an  answer  was  generated.  This 
ability  to  explain  how  an  answer  was 
arrived  at  is  an  essential  component  of 
expert  systems.  Note  that  this  is  some¬ 
thing  more  than  mere  extensibility, 
which  allows  the  programmer  to  add  to 
the  repertoire  of  the  language.  Here 
we  are  considering  the  ability  of  a 
piece  of  software  to  reason  about  its 
own  reasoning  processes. 

Applications  of  Logic 
Programming 

Prolog  has  been  used  for  artificial  in¬ 
telligence  applications  as  well  as  more 
conventional  applications,  some  of 
which  are  ennumerated  below. 

Relational  Data  Bases 

Because  the  relational  data  base  model 
is  a  logical,  well-developed  formalism, 
it  can  be  represented  in  Horn  clause 

Dr.  Dobb  s  Journal,  March  1985 


form  in  a  straightforward  manner. 
Prolog  has  also  proved  particularly 
useful  for  data  base  front  ends.  Many 
types  of  query  languages  have  been  im¬ 
plemented  in  Prolog,  including  QBE, 
SQL,  relational  algebra,  and  one  simi¬ 
lar  to  VisiCalc. 

Software  Engineering 

Kowalski  has  shown  that  a  program 
specification  can  be  directly  trans¬ 
formed  into  a  logic  program,  provided 
that  it  is  written  in  a  logical  specifica¬ 
tion  language  (such  as  DeMarco’s  data 
flow  diagrams).  The  initial  translation 
produces  a  top  down  design.  Each  piece 
of  the  design  can  be  successively  refined 
independently  of  the  other  pieces. 

Natural  Language 

Colmerauer  originally  invented  Prolog 
to  perform  natural  language  process¬ 
ing.  Warren  and  Pereira  have  contin¬ 
ued  work  in  this  field  with  the  Definite 
Clause  Grammar  formalism  (a  form  of 
top  down  parser  for  natural  language). 
More  recently,  researchers  at  ICOT 
have  implemented  BUP  (a  bottom  up 
parser  for  natural  language). 

Knowledge  Representation 

Knowledge  representation  paradigms 
used  in  artificial  intelligence,  such  as 
semantic  networks,  frames,  production 
rules,  and  objects,  can  all  be  expressed 
in  logic  and  implemented  in  logic  pro¬ 
gramming.  According  to  Hayes,  se¬ 
mantic  networks  and  frames  are  essen¬ 
tially  alternative  syntaxes  for  logic. 

Expert  Systems 

Once  knowledge  has  been  represented, 
an  expert  system  can  put  it  to  use  for 
expert  consultation.  Expert  systems 
have  been  built  with  Prolog  in  a  variety 
of  areas,  including  equation  solving, 
medicine,  law,  architecture,  factory 
automation,  etc. 

Future  applications  are  left  as  an  ex¬ 
ercise  for  the  reader. 

DD| 


41 

195 


A  Tour  of  Prolog 

by  D.  E.  Cortesi 


The  distinction  between  code  and  data 
in  programming  is  as  fundamental  as 
that  between  theory  and  observation  in 
science — and  just  as  false. 


Prolog  is  a  member  of  the  general  class  of  nonprocedural 
programming  languages  or,  as  I  prefer  to  call  them,  the 
descriptive  languages.  There  have  been  descriptive  lan¬ 
guages  before — notably  the  simulation  languages  like 
GPSS-  but  most  are  designed  to  serve  specific  application 
domains,  while  Prolog  is  intended  for  general  programming. 
Descriptive  languages  require  an  approach  to  problem  solv¬ 
ing  that  is  radically  different  from  the  procedural  approach 
used  with  an  ordinary  language  like  Pascal  or  BASIC. 

Not  many  readers  know  Prolog  or  have  Prolog  interpret¬ 
ers  to  experiment  with.  When  planning  this  issue  of  DDJ,  the 
editors  realized  that  it  would  contain  several  large  programs 
written  in  Prolog.  Because  Prolog  is  so  different,  they  won¬ 
dered  whether  the  readers  would  be  able  to  follow  the  pro¬ 
gram  listings. 

I  had  been  exploring  Prolog  in  my  spare  time,  so  I  volun¬ 
teered  to  describe  it  from  a  programmer’s  perspective.  This 
article  is  not  meant  to  be  a  complete  tutorial  (see  Clocksin 
and  Mellish1  for  that).  It  is  rather  an  orientation  tour  to  give 
you  enough  exposure  to  Prolog’s  style  so  that  you  can  make 
sense  of  the  listings  in  the  other  articles. 

A  Caution 

Be  aware  that  the  various  implementors  of  Prolog  don’t 
agree  on  its  syntax  and  writers  on  Prolog  don’t  agree  on  the 
names  of  its  parts  of  speech.  In  this  overview,  I’ve  cast  the 
examples  in  a  presentation  syntax  that  uses  the  tricks  of 
professional  typesetting  for  clarity.  A  sidebar  that  accompa¬ 
nies  the  article  (“Prolog  Syntax,”  page  46)  explores  simple 
mappings  to  the  syntax  schemes  of  common  implementa¬ 
tions.  I’ve  noted  where  my  nomenclature  varies  from  other 
writers’  usage. 

Prescribing  and  Describing 

We  use  the  familiar  programming  languages  to  prescribe  the 
solutions  to  a  set  of  problems.  We  build  the  prescription 
from  two  radically  different  materials:  active  code  and  pas¬ 
sive  data.  The  passage  of  time  is  an  implicit  part  of  a  pre¬ 
scription.  “To  solve  one  of  these  problems,”  we  tell  the  com¬ 
puter,  “do  this,  then  do  that”;  that  can  be  correctly 
understood  only  in  the  context  created  by  this. 

All  that  changes  in  Prolog.  In  Prolog,  we  describe  the 
solutions  to  a  set  of  problems.  “A  solution  to  one  of  these 
problems,”  we  tell  the  computer,  “looks  like  this  or  this  or 
this”;  each  this  must  be  (in  principle)  a  true  description  of  a 
solution,  independent  of  its  place  in  the  program.  There  is  no 
distinction  between  code  and  data.  We  simply  describe  what 
is  true  about  the  universe  of  discourse,  using  identical  mech¬ 
anisms  to  describe  true  facts  and  true  interpretations  of  the 
facts. 

Assertions  and  Queries 

Let’s  tie  these  abstract  notions  down  with  an  informal  exam¬ 
ple.  The  problem  is  one  that  arises  in  a  card-playing  pro¬ 
gram:  defining  what  a  card  is.  The  honor  cards  are  defined  as 
follows: 


David  E.  Cortesi,  430  Sherman  Ave.  #212,  Palo  Alto,  CA 
94306. 


44 

196 


Dr.  Dobb's  Journal,  March  1 985 


card(a). 

card(k). 

card(q). 

card(j). 

These  are  sentences  of  fact  that  we  may  assert  (add  to  the 
program)  at  once.  We’ll  deal  with  matters  of  syntax  later; 
for  the  moment  you  should  find  it  believable  that,  given 
those  sentences,  a  Prolog  system  could  conduct  a  dialogue 
along  these  lines: 

card(k)? 

yes. 

card(w)? 

not  so  far  as  I  know. 

This  is  a  conversation  with  a  hypothetical  Prolog  system.  I 
don’t  know  of  any  real  systems  whose  dialogue  is  so  natural 
nor  whose  negative  answer  is  so  humble.  Nevertheless,  we 
have  here  the  essence  of  Prolog  operation:  a  number  of  sen¬ 
tences  are  asserted  (usually  they  are  loaded  from  a  file);  the 
system  is  given  a  pattern  to  test  against  those  sentences;  and 
it  replies  “yes”  or  “no”  depending  on  whether  or  not  it  can 
match  the  pattern. 

It  probably  occurs  to  you  that  it  wouldn’t  take  a  particular¬ 
ly  clever  system  to  hold  the  example  dialogue.  A  simple  com¬ 
parison  loop  would  do  it.  Of  course,  smarter  dialogues  are 
possible.  For  example,  all  systems  allow  the  query  pattern  to 
contain  variables  as  a  way  of  requesting  more  information. 
(In  this  article,  I  shall  indicate  variables  with  italicized  words; 
see  the  sidebar  “Prolog  Syntax”  for  the  variety  of  ways  to 
recognize  variables  in  real  systems.)  Thus  we  may  ask: 

card(w/m/  )? 
yes,  what  —  a. 
yes,  what  =  k. 
yes,  what  =  q. 
yes,  what  =  j. 

The  system  finds  not  just  one  but  four  matches  to  the  query 
cdrd{what  )?  and  reports  the  resulting  value  of  the  variable 
what  in  each  case. 

Compound  Sentences 

Let’s  continue  the  example  by  describing  the  other  nine  play¬ 
ing  cards.  Simply  to  assert  nine  more  facts,  card(  10)  through 
card(2),  would  be  in  the  spirit  of  Prolog,  but  Prolog  would  be 
a  poor,  flat  notation  if  it  didn’t  let  us  construct  sentences  in 
terms  of  other  sentences.  We  can  describe  the  remaining 
cards  by  asserting  a  single  sentence: 

card(x  )  if 

integer(x  )  and 
less(  1  x  )  and 
less(x  1 1 ). 

Although  awkwardly  phrased,  this  sentence  translates  as  the 
rule:  “Something  x  is  a  card  if  it  is  an  integer  between  2  and 
10  inclusive.”  Now  we  should  be  able  to  conduct  the  follow¬ 
ing  dialogue: 

Dr.  Dobb's  Journal,  March  1985 


card(6)? 

yes. 

This  reveals  a  greater  cleverness  in  the  underlying  system.  To 
answer  the  question,  the  system  had  to  identify  6  with  the  x  in 
our  latest  sentence,  then  test  the  conjunction  of  three  subordi¬ 
nate  assertions,  verifying  the  truth  of  integer(6),  less(  1  6),  and 
less(6  1 1 ).  Shortly,  we  will  examine  how  this  is  done,  but  first 
let’s  round  out  the  idea  of  descriptive  programming. 

Descriptive  Programming 

The  five  sentences  asserted  so  far  constitute  a  purely  descrip¬ 
tive  solution  to  the  problem  of  defining  a  card.  We  say  that 
they  define  the  card  relation.  The  sentences,  which  are  inde¬ 
pendent  of  each  other,  together  enumerate  all  possible  cases 
of  the  card  relation.  We  don’t  care  in  which  order  a  Prolog 
system  checks  our  question  card(6)?  It  may  test  the  question 
against  the  five  sentences  in  any  order;  it  will  turn  up  the  right 
answer  regardless.  This  has  important  implications  for  perfor¬ 
mance  (see  the  sidebar  “Prolog  in  Parallel”  on  page  50). 

Such  pure,  timeless  description  is  an  ideal  rarely  achieved 
in  practice;  working  Prolog  programs  usually  have  some  de¬ 
pendencies  on  their  sequence  (we’ll  see  why  later).  Never¬ 
theless,  pure  description  should  be  the  goal  of  a  Prolog  pro¬ 
grammer,  just  as  clean  structure  and  avoidance  of  side 
effects  are  goals  when  using  a  prescriptive  language. 

Descriptive  Arithmetic 

The  implementation  of  arithmetic  in  the  Micro-PROLOG 
product2  is  an  example  <pf  just  how  far  you  can  go  in  the 
direction  of  pure  description.  In  that  system,  all  arithmetic  is 
implemented  through  two  relations  built  into  the  system. 
The  SUM  relation  is  defined  as: 

SUM(a  bs  ) 

for  all  sets  of  three  numbers  such  that  a  +b  =s.  In  our  ex¬ 
ample,  a  set  of  sentences  defined  the  card  relation.  The  SUM 
relation  is  implemented  so  that  you  could  almost  believe  it 
was  defined  by  a  huge  data  base  of  sentences  such  as: 

SUM(OOO). 

SUM(0  1  1). 

SUM(  1 5  9  24). 

SUM(6  32761  32767). 

and  so  on  for  all  negative  and  fractional  numbers  as  well. 
This  permits  you  to  use  SUM  for  either  addition 

SUM(6  8  x  )? 
yes,  x  =  14. 

or  subtraction 

SUM(x  8  14)? 
yes,  x  =  6. 

with  equal  facility. 

45 

197 


Micro-PROLOG’s  built-in  relation  PROD  is  defined  as 
follows: 

PROD  (abed) 

over  all  numbers  such  that  ( a  *b  )  =  (c  +  d  ).  PROD  also  is 
implemented  as  if  the  system  contained  an  infinite  data  base 
of  sentences  about  numbers:  PROD(16  3  47  1)  and  so  forth. 
The  PROD  relation  answers  queries  needing  multiplication, 
division,  or  modulus: 

PROD(  16  4  prod  0)7 
yes,  prod  =  64. 

PROD(  1 6  quotient  64  0)7 
yes,  quotient  =  4. 

PROD(  1 6  q  65r)7 
yes,  q  —  4,  r  =  1 . 

[  Extra  credit  puzzle:  If  PROD  were  limited  to  the  domain  of 
integers  between  0  and  32767  inclusive-  which  Micro-PRO- 
LOG  is  not  how  many  sentences  would  its  imaginary  data 
base  contain?  ] 

Let’s  use  SUM  to  define  a  relation  we’ll  need  later,  the 
predecessor/successor  relation: 

succ(x  y  )  if 
SUM(  1  xy  ). 

We  may  read  this  as:  “The  successor  of  a  number  x  is  the 
number  1  greater  than  x.”  The  interesting  thing  is  that,  be¬ 
cause  of  the  descriptive  nature  of  SUM,  we  can  work  this 
relation  in  either  direction: 

succ(7  what  )? 
yes,  what  =  8. 
succ(  what  14)7 
yes,  what  =  1 3. 

Therefore,  the  rule  may  also  be:  “The  predecessor  of  a  num¬ 
ber  y  is  x,  the  number  1  less  than  y." 

Other  Prolog  systems  handle  arithmetic  relationships 
much  more  awkwardly:  they  attempt  to  graft  the  arithmetic 
expressions  of  procedural  languages  onto  Prolog  syntax.  In 
these  systems,  the  predecessor /successor  relation  would  be 
defined  with  syntax  such  as: 

succfxy  )  if 
y  =x  +1. 

Not  only  does  this  introduce  the  whole  truckload  of  reserved 
operator  symbols  and  operator  precedence  rules  into  Pro¬ 
log — in  sharp  contrast  to  the  economy  of  SUM  and  PROD — 
but  we  are  not  sure  whether  we  can  use  a  sentence  of  this 
form  to  query  for  a  predecessor  as  well:  it  is  unclear  whether 
the  assertion  y  =x  +1  has  meaning  when  worked  “back¬ 
wards.”  Such  a  system  would  require  an  explicit  predecessor 
rule: 

predfy  x  )  if 

x=y-  1. 


Prolog  Syntax 

Many  Prolog  interpreters  have  been  built,  most  of  them  aca¬ 
demic  experiments,  each  with  its  own  features  and  its  own 
syntax  as  dictated  by  the  interests  and  tastes  of  its  developers 
and  the  direction  of  their  research.  One  of  the  most  influential 
Prolog  interpreters  was  built  for  the  DEC- 10  at  the  University 
of  Edinburgh.3  The  best  Prolog  tutorial  text1  is  based  on  this 
“Edinburgh  syntax.”  Because  of  this  book,  the  newer  prod¬ 
ucts  for  personal  computers  tend  to  follow  the  Edinburgh  syn¬ 
tax  as  well.  However,  it  would  be  unrealistic  to  expect  that 
any  interpreter  would  follow  the  syntax  exactly;  after  all,  an 
implementor  must  display  creativity  somehow. 

Another  influential  implementation  is  the  Micro-PROLOG 
product;  because  it  is  available  for  both  Z80-based  CP/M 
systems  and  for  MSDOS,  Micro-PROLOG  has  the  potential  to 
outnumber  all  other  Prologs  put  together. 

There  are  only  eight  syntactic  concepts  in  the  Prolog  nota¬ 
tion:  four  kinds  of  items  (numbers,  variables,  constants,  and 
lists)  and  four  higher  groupings  (clauses,  conjunctions,  facts, 
and  rules).  Even  after  adding  whatever  protocols  are  needed 
to  signal  the  interpreter  that  a  particular  input  represents  a 
query  or  an  assertion,  you  still  have  a  very  simple  language. 
Let’s  see  how  these  eight  elements  are  formed  in  the  two 
main  syntactic  traditions. 

Although  systems  should  differ  little  in  the  formation  of 
literal  numbers,  they  will  differ  in  the  magnitude  and  preci¬ 
sion  of  the  numbers  supported.  Some  systems  will  support 
only  signed  integers  while  others  allow  reals. 

On  the  other  hand,  systems  will  differ  dramatically  in  the 
rules  for  forming  variables.  Edinburgh-style  systems  take  as 
a  variable  any  token  that  begins  with  an  initial  cap:  John  is  a 
variable,  but  john  is  a  constant.  This  allows  a  good  range  of 
meaningful  variable-names,  but  it  is  easy  to  make  a  mistake 
and  type  an  initial  cap  in  what  should  be  a  constant,  as  in  the 
assertion 

likes(John.Mary). 

which  asserts  that  anyone  likes  everybody. 

We  may  class  the  underscore  as  a  capital  letter,  so  that 
_john  becomes  a  variable.  The  underscore  alone  is  some¬ 
times  an  “anonymous  variable,”  a  nameless  place-holder 
used  to  name  any  argument  that  is  required  but  not  used 
elsewhere  in  the  sentence. 

Micro-PROLOG  limits  variables  to  the  initial  letters 
“XxYyZz”  followed  by  zero  or  more  digits.  This  restriction 
pinches:  it  makes  it  difficult  to  read  sentences  of  more  than 
trivial  length.  Contrast  these  versions  of  the  Quicksort 

sentence: 

qsort([A  B  C  I  Tail)  Out)  if 

part([A  B  C I  Tail]  Left  Right)  and 
qsort(Left  Sleft)  and 
qsort(  Right  Sright)  and 
cat(Sleft  Sright  Out). 

qsort([X  Y  Z I  x]  y)  if 

part([X  Y  Z I  x]  z  XI )  and 


46 

198 


Dr.  Dobb  s  Journal,  March  1985 


qsort(z  Y 1 )  and 
qsort(Xl  Z1 )  and 
cat(Yl  Z1  y). 

The  readability  of  variables  is  less  important  than  it  might 
seem  at  first;  apparently  no  Prolog  system  retains  variable- 
names  after  entering  a  sentence  to  the  data  base!  When  the 
sentences  are  listed  afterward,  they  usually  have  system¬ 
generated  numeric  place-holders  in  the  variable  positions. 
Micro-PROLOG  simply  restricts  input  variables  to  what  the 
system  will  generate  anyway. 

After  the  Prolog  system  has  set  the  rules  for  variables  and 
has  preempted  a  set  of  special  punctuation  characters,  what¬ 
ever  is  left  is  a  constant.  Uncapitalized  words  are  constants 
in  all  systems,  but  particular  systems  may  use  many  other 
tokens  as  constants  as  well.  Hyphenated  words  are  usually 
valid  constants,  although  some  systems  may  take  mother-in- 
law  as  an  arithmetic  expression.  In  Micro-PROLOG,  certain 
characters  are  both  constants  and  delimiters;  that  is,  they 
cannot  be  grouped  with  other  characters  in  larger  tokens, 
but  they  may  be  used  as  constant  tokens  themselves.  The 
square  brackets  fall  in  this  category:  [  alone  is  a  constant, 
but  [a]  is  interpreted  as  three  constants  in  a  row. 

Most  systems  allow  some  kind  of  escape  so  that  a  constant 
can  be  made  from  an  arbitrary  sequence.  Edinburgh  systems 
use  the  single  quote  (‘The  Tree’  is  a  constant),  while  in  Mi¬ 
cro-PROLOG,  the  double  quote  (“The  Tree”)  serves  that 
function.  This  creates  a  problem,  because  in  Edinburgh  sys¬ 
tems  the  double  quote  supports  a  glitch:  it  indicates  a  list 
composed  of  the  quoted  ASCII  characters.  Micro-PROLOG 
achieves  the  same  result  with  the  built-in  relation  STRING- 
OF(c  list  ),  meaning  list  is  a  list  composed  of  the  letters  of 
constant  c.  The  Micro-PROLOG  relation  can  be  worked 
backwards,  from  a  list  to  a  constant,  but  not  so  the  Edin¬ 
burgh  syntactic  glitch. 

In  most  Prolog  dialects,  a  list  pattern  is  [car  I  cdr  ],  but  in 
Micro-PROLOG  it’s  {car  \cdr  )  using  parentheses.  The  Ed- 
inburgh-influenced  systems  use  the  brackets  and  parenthe¬ 
ses  to  make  a  distinction: 

cat([  ]  x  x  ). 

saying,  in  effect,  that  a  list  as  an  argument  is  different  from 
a  list  of  arguments.  The  Micro-PROLOG  syntax  doesn’t 
make  that  distinction  (for  good  reason,  as  we’ll  see  shortly), 
so  its  equivalent  formulation  would  be: 

(cat  (  )  X  X) 

We  can  convert  an  Edinburgh-style  sentence  to  Micro-PRO¬ 
LOG  form  by  making  a  series  of  simple  transformations,  one 
of  which  is  to  convert  all  square  brackets  to  parentheses. 

This  brings  us  to  the  clause,  the  fundamental  way  of  nam¬ 
ing  a  relationship  between  items.  In  this  article.  I’ve  followed 
the  Edinburgh  style,  making  a  clause  look  like  a  procedure 
call  in  a  prescriptive  language.  That  puts  the  relation-name 
ori  a  higher  level  than  its  arguments,  so  that 

length([  ]  0). 


With  a  descriptive  definition  of  arithmetic,  however,  the  pre-  I 
decessor  relation  is  simply: 

pred(y  x  )  if 
succ(x>’ ). 

Unfortunately,  the  designers  of  Micro-PROLOG  stopped 
short  of  implementing  all  the  implications  of  descriptive 
arithmetic.  Their  interpreter  insists  that  the  SUM  and  PROD 
relations  be  given  enough  literal  data  so  that  the  query  has  a 
determinate  answer.  This  means  that  the  question  SUM(x  y 
18)?  or  “what  numbers  x  and  y  add  up  to  18?”  won’t  be 
answered;  the  answer  is  indeterminate  because  an  infinity  of 
number  pairs  add  up  to  18.  I  think  these  relations  should 
generate  random  integers  for  the  unknowns,  in  arbitrary  se¬ 
quence,  until  they  have  enough  knowns  to  solve  the  query. 
How  charming  it  would  be  to  ask  the  system  PROD(o  b  p  r  )? 
and  be  told  “yes,  a  =  2,  b  =  138,  p  =11,/-  =  265,”  or 
some  other  arbitrary  but  consistent  answer! 

Micro-PROLOG’s  descriptive  approach  to  arithmetic  is 
not  important  in  itself;  let  it  serve  as  an  example  of  the  power 
and  economy  made  possible  with  a  descriptive  style  of  pro¬ 
gramming  and  as  an  example  of  how  strange  and  unset¬ 
tling  such  a  style  initially  can  seem. 

Getting  Formal 

It’s  time  for  some  formal  nomenclature.  Writers  on  Prolog 
don’t  agree  on  the  names  of  Prolog’s  parts  of  speech  (any 
more  than  its  implementors  agree  on  a  syntax),  so  some  of 
these  names  are  my  own  invention.  The  named  concepts, 
however,  are  always  present  and  easy  to  identify. 

A  Prolog  program  consists  of  a  collection,  usually  called  a 
data  base ,  of  sentences  that  define  relations.  A  sentence’s 
initial  word  identifies  the  relation  of  which  it  is  a  part;  all  the 
sentences  that  begin  with  that  word  define  the  relation.  In  a 
preceding  example,  we  defined  the  card  relation  in  five  sen¬ 
tences,  each  beginning  with  the  word  “card.” 

In  addition  to  the  relations  defined  by  the  programmer, 
certain  fundamental  relations  are  defined  by  code  built  into 
the  Prolog  interpreter. 

A  clause  is  a  word  followed  by  a  parenthesized  list  of 
(possibly  zero)  items :  card(6),  for  example.  We’ll  explore 
the  variety  of  items  shortly.  (What  I  call  a  clause  different 
writers  have  dubbed  a  predicate,  a  function,  and  an  atom.) 

Sentences  have  two  forms.  A  fact  consists  of  a  single 
clause: 

card(k). 

rank(k  I  3). 

The  sense  of  a  fact  is  that  the  named  relation  is  always  true 
of  the  given  items.  The  second  form  of  a  sentence  is  the  rule. 
It  consists  of  a  clause,  an  if  symbol,  and  one  or  more  clauses 
separated  by  and  symbols: 

succ(x  y  )  if 
SUM(x  1  y  ). 

card(x  )  if 

integer(x  )  and 
less(  1  x  )  and 


Dr.  Dobb's  Journal.  March  1985 


47 

199 


less(jc  1 1 ). 

A  sequence  of  one  or  more  and-delimited  clauses  is  called  a 
conjunction.  The  sense  of  a  rule  is  that  the  relation  is  true  of 
these  items  as  long  as  all  of  the  clauses  of  the  conjunction  are 
also  true. 

The  function  of  the  Prolog  interpreter  is  to  answer  que¬ 
ries.  The  form  of  a  query  is  simply  a  conjunction,  usually 
delimited  by  some  kind  of  query  symbol  to  trigger  the  inter¬ 
preter  into  action.  The  interpreter  answers  a  query  by  verify¬ 
ing  each  clause  within  it  against  the  data  base.  Now,  take 
this  slowly:  a  clause  is  verified  ( 1 )  when  it  matches  a  fact,  or 
(2)  when  it  matches  the  clause  at  the  head  of  a  rule  and  the 
conjunction  from  that  rule  has  been  verified  as  a  query. 

In  other  words,  to  answer  the  query  card(6)?  the  inter¬ 
preter  will  match  the  query’s  first  and  only  clause  to  the 
clause  at  the  head  of  the  following  rule: 

card(x  )  if 

integer!*  )  and 
less(  1  x  )  and 
less(x  1 1 ). 

As  a  result,  it  will  pose  the  conjunction 

integer(6)  and 

less(  1  6)  and 

less(6  1 1 )? 

to  itself  as  a  new  query. 

Zowie!  Recursion!  This  nesting  of  queries  continues  until 
all  clauses  match  facts  or  system-defined  relations  or  until  a 
clause  that  can’t  be  matched  causes  the  query  to  fail.  That’s 
the  entirety  of  Prolog  execution:  the  recursive  matching  of 
clauses  into  the  data  base  until  all  clauses  have  been  verified 
or  until  some  clause  can’t  be  matched. 

In  principle,  there  is  no  defined  order  for  searching  the 
sentences  of  a  relation  and  no  defined  order  for  verifying  the 
clauses  of  a  conjunction.  In  practice,  programmers  rely 
heavily  on  the  expectation  that  the  system  will  test  the  sen¬ 
tences  of  a  relation  in  the  order  they  are  asserted,  not  at¬ 
tempting  the  second  until  the  first  has  failed.  Also  in  prac¬ 
tice,  there  is  a  strong  assumption  that  the  clauses  of  a 
conjunction  will  be  verified  in  order  from  left  to  right. 

Item  Types 

We’ve  seen  almost  the  whole  of  Prolog  syntax:  a  relation  that 
is  composed  of  sentences,  a  sentence  that  is  either  a  fact  or  a 
rule,  and  a  query  that  is  merely  a  conjunction  to  be  tested 
one  clause  at  a  time.  All  that  remains  is  to  specify  what  sort 
of  items  we  may  name  as  the  arguments  of  a  clause.  There 
are  four:  the  number ,  the  constant,  the  variable,  and  the  list. 

The  number  needs  no  explanation;  it  is  simply  a  literal 
number.  Some  Prologs  support  only  integer  arithmetic, 
while  others  allow  floating-point  numbers. 

The  constant  is  a  notion  that  may  be  unique  to  Prolog.  A 
constant  is  a  literal  word.  It  doesn’t  stand  for  anything,  being 
neither  the  name  of  something  nor  a  character  array  or 
string.  The  value  of  a  constant  is  simply  itself,  just  as  the 
value  of  6  is  6,  the  value  of  “a”  is  “a,”  and  the  value  of 


seems  to  say  “length  is  true  of - ”  In  fact,  this  is  a  false 

distinction,  albeit  a  harmless  one.  The  interpreter’s  match¬ 
ing  process  does  not  depend  on  the  form  of  the  clause;  it 
would  work  just  as  well  if  the  relation-name  were  inside  the 
parentheses: 

(length  [  ]  0). 

Matching  is  just  a  comparison  of  items  from  left  to  right. 

Clauses  are  combined  to  make  conjunctions,  sequences  of 
clauses  that  are  true  only  if  all  their  component  clauses  are 
true.  A  query  is  a  conjunction.  A  rule  is  a  clause  plus  the 
conjunction  that  qualifies  it,  and  a  fact  is  a  clause  that  needs 
no  qualification.  There  is  an  implicit  and  between  each 
clause  of  a  conjunction  and  an  implicit  (/'between  the  head 
of  a  rule  and  its  conjunction. 

Whoever  devised  the  Edinburgh  syntax  established  the  co¬ 
lon-hyphen  (:-)  as  the  if  symbol,  set  the  comma  as  the  and 
symbol,  and  required  a  period  at  the  end  of  an  asserted  sen¬ 
tence  (but  a  question  mark  at  the  start  of  a  query!).  Thus, 
the  Edinburgh  form  of  length  is 

length([  ]  0). 

length([Head  I  Tail]  Len)  :- 
length(Tail  Tien). 
succ(Tlen  Len). 

Some  systems,  however,  may  not  permit  multi-line  input. 

The  Micro-PROLOG  syntax  for  clauses,  conjunctions,  and 
rules  is  rigorously  logical  but  even  less  friendly: 

((length  (  )  0)) 

((length  (X  I  Y)Z) 

(length  Y  x) 

(succ  x  Z)) 

In  this  inner,  or  fundamental,  syntax  of  Micro-PROLOG, 
everything  is  lists.  A  clause  is  a  list  whose  first  item  is  its 
naming  constant.  A  sentence  is  a  list  of  clauses.  We  can 
formalize  these  elements  using  the  presentation  syntax  of 
this  article: 

clause([  name  I  args  ])  if 
constant(name  ). 

conjunction^  ]). 

conjunction([cl£/c  ])  if 
clause(c  )  and 
conjunction(e/c  ). 

sentence! [head I  conj  ])  if 
c\ause(head  )  and 
conjunction(c0/iy  ). 

Undoubtedly,  Edinburgh-style  interpreters  store  asserted 
sentences  in  a  similar  nested-list  arrangement;  they  simply 
put  a  nice  front  end  on  it. 

So  does  Micro-PROLOG,  except  that  it  uses  a  variety  of 
front  ends,  each  written  in  Prolog.  Because  the  system  allows 


48 

?nn 


Dr.  Dobb's  Journal,  March  1985 


“card”  is  “card." 

The  function  of  the  constant,  of  course,  is  to  supply  a  com¬ 
fortably  large  set  of  atomic  symbols  that  need  no  further 
interpretation  so  we  can  write  interesting  facts  in  human- 
readable  form.  That  different  systems  have  different  rules 
for  forming  constants  is  a  major  irritation  (see  the  sidebar 
“Prolog  Syntax”). 

The  variable  is  a  syntactic  place-holder;  it  means  “what¬ 
ever  matches  to  this  position.”  In  these  hypothetical  exam¬ 
ples,  I’m  italicizing  variables,  but  we  obviously  cannot  do 
that  with  real  Prologs.  The  syntactic  space  of  word-like  ob¬ 
jects  must  be  divided  between  constants  and  variables.  Be¬ 
cause  different  implementors  draw  the  line  in  different  ways, 
a  variable  in  one  system  may  be  a  constant  in  another. 

List  Notation 

Most  Prologs  support  the  list  data  structure  of  Lisp  (indeed, 
most  Prologs  are  essentially  special  bodies  on  a  Lisp  chassis). 
However,  a  Prolog  system  does  not  construct  lists;  it  de¬ 
scribes  them,  and  the  syntax  for  describing  a  list  is  central  to 
most  Prolog  applications. 

A  list  is  described  in  full  by  a  list  of  items  in  brackets,  so 
that  [akq[8]10]isa  list  of  five  items  (three  constants,  a 
list,  and  a  number),  while  [  ]  is  the  empty  list.  (Most  Prologs 
use  special  brackets  like  these.  One  of  Micro-PROLOG’s  less 
endearing  features  is  that  it  uses  ordinary  parentheses.) 

Often  it  is  convenient  to  describe  only  the  head  of  a  list, 
referring  to  the  remainder  of  the  list  with  a  variable.  This  is 
done  with  a  list  pattern  that  employs  the  list  constructor 
symbol,  usually  the  stile  ( I ).  Thus,  the  pattern  [  6 1  etc  ]  is  a 
description  of  all  lists  having  the  number  6  as  their  first  item. 
The  pattern  describes  the  first  item  exactly  but  says  nothing 
about  the  remainder  of  the  list  except  that  the  variable  etc 
stands  for  it. 

Now,  mark  two  crucial  rules.  First,  the  tail  of  a  list  is  a 
list.  Thus,  when  the  pattern  [6 1  etc  ]  is  matched  to  the  list  [6 
5  4],  etc  stands  for  [5  4],  a  list.  Should  it  be  matched  to  [6  5], 
etc  would  stand  for  [5] — the  list  [5],  not  the  number  5. 

Second,  the  empty  list  is  the  tail  of  all  lists.  This  rule 
supplies  a  tail  for  lists  that  have  but  one  visible  element, 
enabling  the  matching  of  list  patterns  to  be  more  consistent. 
Thus,  the  pattern  [6  I  etc  ]  describes  the  one-item  list  [6], 
with  etc  standing  for  [  ],  the  empty  list  that  is  the  tail  of  all 
lists,  even  short  ones. 

From  these  rules  come  certain  common  effects.  First,  the 
empty-list  pattern,  [  ],  matches  only  the  empty  list;  no  other 
pattern  does  this.  It  is  really  the  only  special  case  among  lists. 

Second,  the  pattern  [  head  \  tail  ]  matches  any  nonempty 
list.  A  list  pattern  with  a  single  variable  preceding  the  list 
constructor  matches  any  list  that  contains  at  least  one  item. 

In  fact,  the  pattern  [  a  k  next  I  etc  ]  matches  any  list  that 
contains  at  least  three  items,  the  first  two  being  the  constants 
a  and  k  and  the  third  being  unspecified.  The  variable  etc  is 
identified  with  whatever  items  follow  those  three  items  (or 
with  the  empty  list  if  there  are  no  more). 

Recursive  Descriptions 

How  many  items  does  a  list  contain?  Let’s  describe  the  solu¬ 
tions  to  that  problem  in  an  example  of  real  Prolog  program¬ 
ming.  We  could  enumerate  all  possible  solutions: 

Dr.  Dobb's  Journal,  March  1985 


the  meta-variable,  Prolog  programs  can  construct  and  then 
execute  other  Prolog  programs.  In  essence,  Micro-PRO¬ 
LOG’s  syntax  is  protean:  it  can  be  whatever  you  wish  to 
disguise  it  as.  The  friendliest  of  the  distributed  front  ends  is 
called  SIMPLE;  its  form  of  clauses  and  rules  is  much  like  the 
presentation  syntax  used  in  this  article. 

Prolog  in  Parallel 

There  is  an  implicit  or  between  the  sentences  that  define  a 
relation.  Take  the  following  card  relation: 

card(a). 
card(k). 
card(q). 
card(j). 
card(x  )  if 

integer(.v  )  and 
less(  1 ,  x  )  and 
less(x,  1 1 ). 

Together  these  sentences  say  that  a  card  is  this  or  this  or  .  .  . 
this.  The  Prolog  implementation  must  search  through  the 
statements  to  answer  a  question  about  a  card,  but  (in  princi¬ 
ple)  the  order  in  which  it  tests  them  doesn’t  matter.  If  hard¬ 
ware  is  available  to  make  concurrent  tests  of  all  five  sen¬ 
tences  in  parallel,  fine.  Such  parallelism  is  called 
Or-parallelism  because  of  the  implicit  or  between  sentences. 

Notice,  too,  in  the  last  sentence,  that  there  is  (in  principle) 
no  preferred  order  for  testing  the  three  subordinate  assertions. 
Proposed  Prolog  systems  would  evaluate  all  three  assertions 
concurrently.  This  parallelism  is  called  And-parallelism  be¬ 
cause  of  the  and  relation  between  the  clauses  of  a  conjunction. 
Either  kind  of  parallelism  (properly  called  concurrency)  or 
both  could  be  used  to  speed  Prolog  execution.  The  Japanese 
“Fifth  Generation”  project  proposes  to  explore  such  highly 
parallel  architectures  for  Prolog-like  languages. 

Notice,  however,  the  careful  use  of  “in  principle”  in  the 
preceding  paragraphs.  We  can  express  many  algorithms, 
like  the  card  relation  and  most  of  the  other  examples  in  this 
article,  as  sets  of  pure,  achronic,  descriptive  sentences.  Such 
descriptions  executed  on  concurrent  hardware  would  pro¬ 
duce  the  same  results  as  those  produced  under  a  single¬ 
thread  interpreter.  But  it  is  not  always  easy  to  see  the  time- 
free,  descriptive  solution  to  a  problem  (especially  when  your 
mind  is  thoroughly  steeped  in  the  if-then  patterns  of  proce¬ 
dural  notations);  sometimes  there  is  no  solution. 

For  instance,  no  nondeterministic  description  exists  of  a 
program  that  prompts  its  user  and  then  takes  input  from  the 
keyboard.  There  is  some  time  tl  when  no  prompt  is  visible, 
some  time  t2  when  a  prompt  is  visible  but  no  response  has 
been  typed,  and  some  time  t3  when  the  response  is  com¬ 
plete — these  three  times  are  not  the  same.  Such  a  program 
must  recognize  the  flow  of  time.  Otherwise,  it  theoretically 
could  read  nonexistent  input  and  prompt  afterward. 

As  noted  in  the  main  article,  similar  time  dependencies 
arise  when  programs  assert  and  retract  facts  dynamically. 
To  make  such  programs  work,  the  available  Prolog  inter¬ 
preters  promise  to  evaluate  the  clauses  of  a  conjunction  in 

50 

201 


length(  [  ]  0  ). 
length(  [x  ]  1  ). 
length(  [xy  ]  2  ). 

That,  however,  gets  tedious  very  quickly.  A  general  ap¬ 
proach  to  problem  descriptions  of  this  sort  is  to  enumerate 
only  the  limiting  case(s)  then  to  describe  the  general  case 
recursively.  If  we  apply  that  approach  to  this  problem,  we 
soon  arrive  at: 

length([  ]  0). 

Iength([6  It]/  )  if 
lengthf?  k  )  and 
succ(k  l  ). 

In  English  this  says:  “The  length  of  the  empty  list  is  zero, 
while  the  length  of  a  nonempty  list  is  one  more  than  the 
length  of  its  tail.” 

Please  examine  the  two  Prolog  sentences  that  define  the 
length  relation.  Do  you  understand  them?  Can  you  see  how 
they  are  sufficient  to  permit  the  dialogue 

length([a  k  q]  len  )? 
yes,  len  =  3. 

to  take  place? 

If  they  seem  utterly  opaque,  that  might  be  for  either  of 
two  reasons.  You  may  not  be  familiar  with  the  use  of  recur¬ 
sion  to  solve  problems.  If  you  are  baffled  by  the  idea  of 
describing  length  in  terms  of  length,  turn  to  the  sidebar  “The 
Recursive  Method”  (at  right).  It  covers  a  general  approach 
to  recursion  and  may  get  you  over  the  initial  hump. 

If  you  are  comfortable  with  the  idea  of  recursion  but  can't 
see  how  a  Prolog  implementation  might  handle  the  given 
sentences,  turn  to  the  sidebar  “Executing  a  Query”  on  page 
54.  Watching  the  Prolog  system  work  its  way  to  the  root  of  a 
query  may  clarify  these  issues. 

List  Operations 

Let’s  do  one  more  fundamental  list  operation,  the  member 
relation.  This  is  the  relation  that  is  true  (verifiable)  if  a  par¬ 
ticular  item  is  a  member  of  a  given  list.  We  wish  to  be  able  to 
ask  such  questions  as: 

member(j  [k  j  9  3  2])? 
yes. 

member(a  [k  j  9  3  2])? 
not  so  far  as  I  know. 

The  recursive  method  works  well  here.  Because  Prolog 
makes  the  head  item  of  a  list  immediately  visible,  one  limit¬ 
ing  case  is  that  in  which  the  desired  item  is  visibly  the  head  of 
the  list: 

member! item  f  item  I  etc  )). 

We  may  read  this  as:  “Some  item  item  is  a  member  of  any 
list  that  begins  with  item.” 

[This  use  of  variables  troubled  me  at  first.  It  seemed  too 
easy!  I  wanted  to  write  something  like 

52 

or\o 


order  from  left  to  right.  Programs  that  rely  on  that  promise 
could  never  be  executed  on  And-parallel  hardware.  In  my 
opinion,  there  should  be  a  Prolog  equivalent  of  Ada’s  and- 
then  operator  to  make  time  dependencies  within  a  conjunc¬ 
tion  explicit. 

Other  cases  too  elaborate  to  go  into  here — make  it  seem 
essential  to  test  the  sentences  of  a  relation  in  a  particular 
order.  Present  interpreters  number  the  sentences  of  a  rela¬ 
tion  in  the  sequence  they  are  entered  and  promise  to  evaluate 
them  in  that  sequence.  Programmers  writing  code  that  relies 
on  that  promise  are  operating  sometimes  from  necessity  but 
more  often  from  procedural  habit.  Such  code  will  not  exe¬ 
cute  reliably  on  Or-parallel  hardware. 

The  Recursive  Method 

There  are  two  ways  to  completely  express  a  lengthy  process 
in  short  terms:  iteration  and  recursion.  Iteration — the  for, 
while,  and  until  loops  and  other  variations  on  the  theme  of 
“go  back  and  do  it  again” — is  a  customary  part  of  any  proce¬ 
dural  language.  Thus, 

for  i  :=  1  to  10  do 
sum  :=  sum  +  a[ i ]; 

is  a  short  but  complete  summary  of  a  10-step  process  of  sum¬ 
mation.  We  may  summarize  the  iterative  design  approach  as: 
(1)  decide  how  to  solve  the  problem  for  one  item  and  (2) 
decide  how  to  repeat  that  solution  for  all  the  other  items. 

Prolog  supports  no  iteration.  Fortunately,  many  problems 
will  yield  to  a  recursive  approach.  We  may  summarize  the 
recursive  design  approach  this  way:  (1)  enumerate  the  sim¬ 
ple  cases  for  which  a  solution  is  immediately  known;  (2) 
specify  how  to  reduce  a  complicated  case  to  one  that  is 
slightly  simpler;  (3)  specify  a  general  solution  in  the  terms 
“if  I  knew  the  answer  to  the  next-simpler  case,  the  answer  to 
this  case  would  be  .  . .  .” 

Because  step  (2)  supplies  the  means  to  generate  the  next- 
simpler  case,  and  because  repeated  application  of  step  (2) 
must  eventually  produce  a  case  for  which  step  ( 1 )  has  defined 
an  answer,  we  may  write  the  general  solution  as  “return  the 
result  of  (some  small  function)  on  the  result  returned  by  ap¬ 
plying  this  general  solution  to  the  next-simpler  case.” 

Raising  a  number  to  an  integral  power  is  an  example  of  a 
problem  that  we  can  handle  this  way.  We  want  to  describe 
the  solutions  to 

power(x  p  z  ) 

such  that  c  is  the  result  of  raising  x  to  the  pib  power. 

Step  (I):  a  simple  case  for  which  we  know  the  solution 
immediately  is 

power(x  0  1 ). 

or  anything  raised  to  the  zeroeth  power  is  1 .  The  complicated 
cases  comprise  all  those  with  p  greater  than  zero.  (Actually, 
we  know  that  power(x  1  x  ),  but  it  turns  out  we  don’t  need 
the  knowledge.) 


Dr.  Dobb's  Journal,  March  1985 


Step  (2):  to  specify  how  to  move  the  complicated  cases  one 
step  in  the  direction  of  being  a  simpler  case,  we  simply  need 
to  reduce  p  by  1,  thus  moving  it  one  step  toward  the  simple 
case  of  p  =0. 

Step  (3):  applying  the  magic  incantation  “if  I  knew  the 
answer  to  the  next-simpler  case,  the  answer  to  this  case 
would  be  .  .  . we  can  say  if  we  knew  the  answer  to  power(x 
p  -1  x-lo-the-p-less-one ),  we  could  multiply  it  by  x  to  yield 
power(x  p  x-to-the-p  ).  Or  in  Prolog: 

power(x  p  z  )  if 

sum(  1  simplerp  p  )  and 
power(.v  simplerp  simplerz  )  and 
prod(x  simplerz  z  0). 

Any  recursive  design  should  be  tested  for  termination;  an 
infinite  regression  is  not  a  valid  answer.  Will  the  nesting  ever 
cease?  The  formulation  for  power  is  unsafe  in  this  regard.  If 
it  is  queried  with  p  as  an  integer  greater  than  zero,  it  will 
work;  each  evaluation  will  reduce  p  by  one  step  toward  the 
simple  case  of  p  —  0,  which  it  must  ultimately  reach.  But 
query  it  with  a  fractional  p,  and  it  will  skip  right  past  p  =0 
and  continue  on  toward  the  horizon — as  it  will  if  it  is  queried 
for  any  p  less  than  zero.  To  play  it  safe,  the  clauses 

integer(p  )  and 

less(0,  p  )  and 

should  be  inserted. 

The  speed  of  an  iterative  solution  is  proportional  to  the 
number  of  iterations.  Similarly,  the  speed  of  a  recursive  solu¬ 
tion  is  proportional  to  the  depth  of  its  nesting.  The  power 
algorithm  just  given  nests  p  deep.  Another  recursive  design 
for  power  nests  to  drastically  fewer  levels;  math  fans  may 
enjoy  following  it: 

power(x  0  1 ). 

power(x  1  .V  ). 

power(x  p  z  )  if 
less(  1  p  )  and 
integer]/?  )  and 
prod(2  q  p  r)  and 
power]  x  q  xbyq  )  and 
prod (xbyq  xbyq  xevn  0)  and 
condmul(x  xevn  r  z). 

condmul(x  xevn  0  xevn  ). 

condmul(x  xevn  1  z  )  if 
prodf  .v  xevn  z  0). 

The  main  difference  is  that  it  uses  a  faster  way  to  march  the 
general  case  toward  the  simple  case,  dividing  it  by  two  rather 
than  reducing  it  by  one.  This  formulation  also  illustrates  one 
way  to  handle  a  conditional  operation  in  Prolog  without  rely¬ 
ing  on  statement  sequence. 

Recursive  solutions  most  often  are  needed  to  describe  lists 
of  any  length.  The  limiting  cases  typically  involve  the  empty 
list  or  the  list  of  one  item.  The  method  of  moving  the  general 
case  toward  the  simple  case  is  to  shorten  a  list  by  dropping  its 
head. 


member]  z7em  [xl  etc  ])  if 
equal(;7em  x  ). 

to  make  the  comparison  explicit.  But  that  impulse  is  a  hang¬ 
over  from  the  habits  of  procedural  languages,  where  com¬ 
parison  is  an  “operation”  to  be  “executed”  upon  “data.”  In 
Prolog,  we  don’t  write  a  test  for  equality;  we  write  a  descrip¬ 
tion  of  equality.  The  variable-binding  rules  of  Prolog  inter¬ 
preters  are  so  implemented  that  the  more  succinct  statement 
works.  Consider  matching  query  member(fred  [mack 
pete])?  versus  the  sentence  member](7em  [item  I  etc  ]).  The 
matching  process  begins  with  the  binding  of  the  constant 
“fred”  to  the  variable  item.  Instantly,  the  remainder  of  the 
question  becomes: 

...[mack  pete])? 

...[fred  I  etc  ]). 

This  soon  will  result  in  detection  of  a  mismatch.] 

We  now  have  established  the  limiting  case.  In  the  general 
case  - when  the  desired  item  isn’t  visible  as  the  head  item 
we  must  hope  that  it  appears  somewhere  in  the  tail  of  the  list. 
If  it  does,  it  must  be  the  head  of  some  portion  of  the  tail.  So 
we  drop  the  present  head  and  try  again: 

member] /7ew  [  head  I  rest  ])  if 
member]  ;7em  rest  ). 

This  passes  the  termination  test:  each  application  of  the  gen¬ 
eral  rule  talks  about  a  shorter  list.  The  process  sooner  or 
later  must  either  bring  about  the  limiting  case  or  else  pose  a 
query,  member](7ew  [  ]),  that  will  match  nothing  and  cause 
the  whole  query  to  fail. 

This  formulation  of  the  membership  problem  will  work, 
but  it  is  not  perfect  Prolog.  The  two  sentences  aren’t  inde¬ 
pendent;  certain  queries  will  match  both  of  them.  I  have 
followed  up  the  implications  of  that  in  the  sidebar  “Cutting 
and  Failing”  on  page  58. 

Having  It  Both  Ways 

Conventional  (procedural)  programs  are  time-bound  and  lin¬ 
ear,  so  they  can  flow  in  one  direction  only.  Long  use  of  proce¬ 
dural  languages  makes  this  seem  a  natural  and  inevitable  part 
of  programming,  but  it  is  neither.  We’ve  already  noted  how, 
when  arithmetic  is  implemented  in  a  descriptive  way,  a  single 
relation  SUM  supplies  both  addition  and  subtraction.  When 
we  based  a  successor  relation  on  it,  the  identical  relation 
served  to  describe  the  predecessor  relation  as  well  -the  prede¬ 
cessor  being  simply  the  successor  “in  reverse.” 

This  ability  to  work  Prolog  relations  backward  seems 
strange  and  upsetting  at  first.  It  takes  a  conscious  effort  to 
recall  that  a  reverse  use  is  even  possible  and  to  check  for  it. 
For  example,  a  programmer  who  is  oriented  toward  proce¬ 
dural  languages  inevitably  will  identify  the  member  relation 
with  a  Pascal  or  C  function  that  searches  for  one  item  in  a 
list.  Such  a  function  can  be  used  in  those  languages  only  for 
that  purpose,  but  this  is  not  so  of  the  member  relation  in 
Prolog.  Sure,  Prolog  will  test  to  see  if  a  member  exists  in  a 
list,  but  it  is  also  perfectly  happy  reporting  what  members  do 
exist  in  a  list: 


Dr.  Dobb's  Journal.  March  1985 


53 

203 


member(x  [fred  bob  sam])? 
yes,  x  =  fred. 
yes,  x  =  bob. 
yes,  x  =  sam. 

Furthermore,  Prolog  can  be  made  to  generate  an  infinite 
number  of  lists  that  contain  a  given  item.  The  query  mem- 
ber(x  what  )?  or  “What’s  a  list  containing  something?’’ 
leads  to  an  infinite  recursion  producing  successive  answers: 

yes,  what  =  [x  I  etc  ]. 
yes,  what  =  [ head  x  I  etc  ]. 
yes,  what  =  [head  head  x  I  etc  ]. 

Not  a  useful  result,  but  a  strange  and  beautiful  one. 

Showing  Off 

We  could  use  any  number  of  enjoyable  list  problems  for 
further  practice:  reversing  them,  extracting  the  first  or  last  n 
items,  catenating  them  (an  especially  interesting  Prolog  al¬ 
gorithm  that  can  be  worked  backwards  in  several  useful 
ways).  But  space  is  limited,  so  let’s  glance  instead  first  at 
programs  for  sets  and  trees  and  finally  at  a  sort. 

A  set  is  a  list  that  contains  no  duplicates.  Let’s  first  define 
a  relation  undup(/«  out  )  such  that  out  is  the  list  in  with  its 
duplicate  items  removed: 

undup(  [][]). 

i.e.  the  empty  list  has  no  duplicates; 

undup([/t  1 1  ]  [h  I  tnew  ])  if 
remove! t  tlessh  )  and 
undup(tlessh  tnew  ). 

i.e.  other  lists  are  undup’d  by  removing  all  instances  of  the 
head  from  the  tail  then  undup-ing  the  tail. 

Of  course,  we  now  require  a  definition  of  the  relation  re¬ 
moved  in  out )  such  that  out  is  the  list  in  with  all  occur¬ 
rences  of  item  x  removed: 

remove(x  [  ]  [  ]). 

i.e.  removing  x  from  the  empty  list  yields  the  empty  list; 

remove(x  [x  1 1  ]  tnew  )  if 
remove(x  t  tnew  ). 

i.e.  removing  x  from  a  list  headed  by  x  produces  the  tail  of 
that  list  with  x’s  removed; 

remove(x  [h\t  ][h\  tnew  ])  if 
not-same(x  h  )  and 
remove(x  t  tnew  ). 

i.e.  removing  x  from  a  list  headed  by  something  else  (h  )  pro¬ 
duces  a  list  still  headed  by  h  but  with  x’s  removed  from  its  tail. 

Naturally,  it’s  easiest  to  build  a  set  from  scratch,  simply 
banning  all  duplicate  additions  to  it.  Let’s  define  the  relation 
addset(x  in  out  )  such  that  out  is  the  set  in  with  item  x  added: 


For  example,  the  normal  Prolog  list-pattern  syntax  makes 
it  easy  to  describe  the  first  element  of  a  list  {head  in  the 
pattern  [head  I  tail  ]),  but  there  is  no  primitive  way  to  de¬ 
scribe  the  last  item  in  a  list.  Can  we  describe  a  relation 
last(//.«  item  ),  meaning  that  item  is  the  last  thing  in  list ? 

Let’s  apply  the  recursive  method.  The  empty  list  isn't  a 
factor  here  because  it  has  no  last  (or  any  other)  item.  The 
shortest  list  having  a  last  element  is  the  one-item  list;  we  may 
at  once  assert: 

last([on/y  ]  only  ). 

This  means  “the  last  item  in  the  list  [only  ]  is  the  item 
only 

A  way  of  simplifying  a  list  is  to  drop  its  head.  If  we  knew 
the  last  item  in  the  tail  of  a  list,  we  would  know  the  last  item 
in  the  whole  list: 

last([x  y\etc  ]  item  )  if 
last([>>  I  etc  ]  item  ). 

The  solution  must  pass  the  termination  test:  does  it  advance 
toward  the  limiting  case?  Yes,  each  application  of  that  sen¬ 
tence  poses  a  new  query  about  a  list  that  is  shorter  by  one 
item.  Ultimately,  it  will  pose  a  query  about  a  one-item  list, 
for  which  we  have  a  fact. 

A  good  Prolog  solution  should  pass  two  more  tests:  inde¬ 
pendence  and  completeness.  Are  the  two  sentences  indepen¬ 
dent;  that  is,  do  they  describe  distinct  situations?  These  do, 
but  only  because  the  second  one  explicitly  describes  lists  of 
two  or  more  items.  If  we  had  written 

last([head\  tail  ]  item  )  if 
last(fn//  item  ). 

then  a  one-item  list  could  match  to  either  rule.  Are  the  sen¬ 
tences  complete?  Yes,  the  first  covers  one-item  lists  and  the 
second  covers  all  those  with  two  or  more  items;  thus,  they 
cover  all  lists  that  have  a  last  item.  These  two  sentences 
constitute  a  good,  recursive  Prolog  description  of  the  last- 
item-of-a-list  problem. 

Executing  a  Query 

A  Prolog  query  is  a  conjunction  or  a  list  of  clauses  each  of 
which  is  to  be  verified.  Let’s  observe  the  actions  of  the  inter¬ 
preter  as  it  executes  the  query 

(1)  length([a  q  j]  vv/iaf )? 

against  a  data  base  of  this  length  relation: 

length([  ]  0). 

length! [head  I  tail  ]  len  )  if 
length!  tail  tlen  )  and 
sum (tlen  1  len  ). 

A  query  is  verified  by  verifying  its  clauses;  a  clause  is 
verified  by  matching  it  to  the  facts  and  rules  of  the  data  base. 


54 


Dr.  Dobb’ s  Journal,  March  1985 


addset(x  [  ]  [x  ]). 

In  that  matching  process  lies  much  of  the  magic  of  Prolog. 
Many  texts  laboriously  describe  how  variables  are  “instanti- 

i.e.  adding  x  to  the  empty  set  produces  the  set  composed  of 

ated”  (I  prefer  to  say  “bound”)  as  they  are  matched  to  con- 

x ; 

stants,  but  the  process  is  simply  copying  with  substitution'. 
the  interpreter  copies  out  the  matched  rule,  substituting  new 

addset(x  [x  1 1  ]  [x  1 1  ]). 

values  wherever  a  variable  was  matched. 

The  interpreter  commences  by  comparing  the  first  (and 

i.e.  adding  x  to  the  set  already  headed  by  x  produces  no 
change; 

only)  clause  of  the  query  to  the  first  sentence  in  the  data  base: 

length([a  q  j]  what  ) 

addset(x  [h  1  /  ]  [h  1  tandx  ])  if 
not-same(x  h  )  and 

length([  ]  0) 

addset(x  t  tandx  ). 

They  don’t  match;  the  mismatch  comes  just  after  the  open¬ 
ing  bracket  when  the  constant  a  doesn’t  match  the  closing 

i.e.  adding  x  to  a  set  not  headed  by  x  is  done  by  adding  x  to 
the  tail  of  the  set. 

bracket.  The  interpreter  tries  again  at  the  next  sentence: 

The  clause  not-same(a  b  ),  used  in  addset  and  remove,  is 

length([a  q  j]  what ) 

one  that  the  underlying  system  should  define.  It  is  extremely 
difficult  to  describe  the  not-equal  relation  in  Prolog. 

length) [/marfl  tail  ]  len) .  .  . 

We  can  implement  -  or  describe-  binary  trees  in  terms  of 

This  succeeds.  A  new  copy  of  the  rule  is  made  with  the  fol- 

nested  lists.  In  fact,  their  Prolog  description  is  simply  a  re¬ 
phrasing  of  the  theoretical  definition  of  a  binary  tree.  Let’s 

lowing  substitutions: 

say  that  an  empty  list  represents  the  empty  tree,  while  a 

a  for  head 

three-item  list  [  Isub  item  rsub  ]  represents  the  nonempty 

[q  j]  for  tail 

tree,  item  is  the  value  at  the  root  of  the  tree,  and  hub  and 
rsub  are  the  left  and  right  subtrees.  Now  we  can  define  the 

what  for  len 

relation  addtree(x  in  out  )  such  that  out  is  the  binary  tree  in 

The  copied  conjunction,  with  substituted  values  of  the  bound 

with  item  x  added  to  it: 

variables,  reads: 

addtree(x  [  ]  [[  ]  x  [  ]]). 

i.e.  adding  x  to  an  empty  tree  produces  the  tree  with  value  x 

(2)  length([q  j]  tlen  2)  and 
sum {tlen  2  1  what  )? 

and  empty  subtrees; 

The  subscript  in  tlen  2  indicates  that  the  variable  tlen  is 
unique  and  local  to  this  query. 

addtree(x  [/  x  r  ]  [/  x  r  ]). 

i.e.  x  is  already  in  the  tree  that  has  it  as  a  value; 

The  first  clause  of  the  new  query  matches  the  second  sen¬ 
tence  in  the  data  base,  with  the  following  bindings: 

q  for  head 

addtree(x  [/  v  r  ]  [/new  v  r  ])  if 

[j]  for  tail 

less(x  v  )  and 
addtree(x  /  Inew ). 

tlen2  for  len 

The  conjunction  is  copied  out  once  more,  with  substitution, 

i.e.  x  goes  into  the  left  subtree  of  a  tree  that  has  a  greater 

to  produce  yet  another  nested  query: 

value; 

(3)  length([j]  tlen  3)  and 

addtree(x  [l  v  r][l  v  mew  ] )  if 
less(v  x  )  and 

sum(tlen  3  1  tlen  2)? 

addtreefx  r  mew  ). 

i.e.  x  goes  in  the  right  subtree  of  one  with  a  lesser  value. 

Its  first  clause  again  matches  the  second  sentence,  this  time 
binding  the  following: 

Lovely,  isn’t  it?  No  doubt  you  can  already  visualize  the  Pro- 

j  for  head 

log  description  of  a  tree  search,  the  relation  intree(x  tree  ) 

[  ]  for  tail 

that  is  true  if  x  appears  at  some  level  of  tree. 

Let  us  return  to  the  domain  of  lists  and  sets  long  enough  to 

tlen3  for  len 

describe  the  Quicksort  algorithm:  the  relation  qsortfm  out  ) 
such  that  out  is  the  list  in  with  its  items  in  sorted  order.  First, 

Its  conjunction  is  copied  out;  after  substitution,  it  yields: 

we  enumerate  enough  of  the  limiting  cases  to  cut  the  recur- 

(4)  length([  ]  tlen  4)  and 

sion  off  at  the  two-item  list: 

qsort([  111). 

sumft/cn  4  1  tlen  3)? 

Dr.  Dobb’s  Journal,  March  1985 


56 

205 


i.e.  the  empty  list  is  sorted; 

The  first  clause  of  this  conjunction  matches  the  first 
sentence: 

qsort([x  ]  [a-  ]). 

length([  ]  lien  4) . .  . 

i.e.  the  one-item  list  is  sorted; 

lengthf  [  ]  0). 

qsortfja  z  ]  [a  z  ])  if 

As  a  result,  the  variable  tlen  4  is  bound  to  the  literal  number  0; 

leqfa  z  ). 

it  is  effectively  rewritten  to  0  wherever  it  appears  in  the  pend- 

qsort([a  z  ]  [z  a  ])  if 

ing  queries.  Having  verified  the  first  clause  of  query  (4),  the 

less(z  a  ). 

interpreter  considers  its  second  clause,  which  now  reads: 

i.e.  two-item  lists  are  sorted  by  exchanging  their  items  (we 

.  . .  sumfO  1  tlen  3) 

assume  that  the  leq(  )  and  less(  )  clauses  are  system 

defined). 

Assume  that  sum  is  a  built-in  relation;  the  system  adds  the 

Now  we  can  describe  the  general  solution  of  Quicksort: 

known  arguments  and  binds  their  sum  to  the  unknown  one, 

the  items  of  the  input  list  are  partitioned  into  two  lists,  which 

causing  tlen  3  to  be  rewritten  as  1  wherever  it  appears.  Query 

are  sorted  separately: 

(4)  is  now  complete;  the  system  may  return  to  consideration 
of  query  (3). 

qsort([a  b  c\t  ]  out  )  if 

The  second  clause  of  query  (3)  now  reads: 

part([a  belt  ]  / r  )  and 

qsort(/  si )  and 

. . .  sumfl  1  tlen  2) 

qsortfr  sr  )  and 

catenatefs/  sr  out  ). 

After  that  clause  has  been  evaluated,  references  to  tlen  2  will 
read  2.  That  completes  processing  of  query  (3);  the  inter- 

Assuming  that  the  catenate  relation  already  has  been  de- 

preter  may  return  to  query  (2). 

scribed,  we  need  only  to  define  the  partitioning  scheme. 

Its  second  clause  now  reads: 

pa  rtf  in  /  r  ),  such  that  /  and  r  are  lists  containing  all  the  items 

of  in  between  them  with  every  item  in  /  being  less  than  any 

.  .  .  sum(2  1  what ) 

item  in  r. 

I’ve  worked  out  two  ways  to  describe  the  part  relation.  The 

Its  evaluation  rewrites  all  appearances  of  what  as  3.  The  text 

first  makes  two  passes  over  the  data: 

of  query  ( 1 )  now  reads: 

partfin  l  r)  if 

length([a  q  j]  3). 

pivotf  in  p  )  and 

partle(/n  pi)  and 

Because  this  user-entered  query  has  now  been  verified  as 

partge(/npr  ). 

truthful,  the  system  can  notify  the  user  “yes”  and  may  also 
display  what  =  3.  That’s  not  essential;  a  very  simple  Prolog 

Leaving  aside  the  pivot  relation,  which  describes  p  as  a 

might  require  the  user  to  request  a  display  explicitly,  using  a 

pivot  value  based  on  list  in,  we  need  definitions  of  partle  and 

built-in  relation  such  as: 

partge.  The  partle  relation  selects  those  elements  of  in  that 

are  less  than  or  equal  to  p: 

length([a  q  j]  what )  and 
displayfveha/ )? 

partlef [  ]p[  ]). 

partle([/i  1 1  ]  p  [h  1  z  ])  if 

Had  that  been  the  case,  verification  of  the  first  clause  would 

leqfh  p  )  and 

have  resulted  in  rewriting  the  query  as: 

partlef  /  p  z  ). 

partle([h  1 1  ]  p  z  )  if 

lengthf [ a  q  j]  3)  and 

lessfp  h  )  and 

display(3)? 

partlef  /  p  z). 

Nothing  special  has  to  be  done  by  the  system — the  display 

The  partge  relation  is  similar  but,  of  course,  selects  only 

argument  falls  out  naturally  from  the  implicit  rewriting  of 

items  that  are  greater  than  p. 

variables  as  they  are  bound. 

It  seemed  somewhat  inefficient  to  pass  twice  over  the  input 

Thus,  relatively  simple  matching  and  rewriting  rules,  ap- 

list  when  dividing  it,  so  I  worked  out  an  alternate  scheme: 

plied  recursively,  produce  effects  all  out  of  proportion  to 
their  size. 

partf  in  l  r  )  if 

pivotf  in  p  )  and 

Cutting  and  Failing 

partemfp  in  l  r  ). 

Let  us  consider  taking  our  member  relation,  defined  by  the 

partemfp  [][][]). 

following  sentences: 

58 


OA/? 


Dr.  Dobb’s  Journal.  March  1985 


partenffp  [h  \t  ]  [h  I  /  ]  r  )  if 
leq(h  p  )  and 
partem(/>  1 1  r  ). 
partem(p  [h\t  ]  l  [h  I  r  ])  if 
less(/7  h  )  and 
partem)/?  t  l  r  ). 

Finally,  we  may  define  the  pivot  relation  in  a  variety  of 
ways.  The  simplest  is  to  use  the  first  item:  pivot([fz  1 1  ]  h  ). 
That  causes  poor  performance,  however,  when  the  input  is 
already  sorted.  Because,  by  our  definition  of  the  general  case 
of  qsort,  we  need  to  describe  a  pivot  only  for  lists  of  three  or 
more  elements,  we  get  a  better  average  partition  with  a  pivot 
that  is  the  mean  of  the  first  and  third  items: 

pivot)  [a  b  cl  t  ]  p  )  if 
SUM(a  c  s  )  and 
PROD(2  ps  0). 

I’ve  tested  these  versions  of  Quicksort  on  Micro-PROLOG, 
although  not  on  lists  long  enough  to  compare  their  relative 
speeds. 

Storing  Data 

But,  I  hear  you  asking,  how  can  we  conduct  a  meaningful 
test  of  a  sort  if  the  only  sample  data  is  what  we  can  type  in  a 
query?  It  may  be  amusing  to  have  a  dialogue  like 

qsort([3  2  1  ]  what  )? 
yes,  what  =  [1  2  3]. 

but  this  hardly  amounts  to  useful  data  processing.  Where  do 
we  put  stuff  so  we  can  work  on  it? 

That  appears  to  be  a  natural  question,  but  it  reveals  un¬ 
thinking  prejudices. 

Procedural  languages  enforce  a  radical  distinction  between 
code  and  data.  Data  is  static;  it  is  stored  in  variables;  it  is 
acted  upon  by  code.  Code  is  dynamic;  it  is  expressed  by  state¬ 
ments;  it  takes  data  as  input  and  produces  data  as  output. 
These  notions  are  so  engrained  in  the  conventional  program¬ 
ming  languages  that  we  aren’t  even  aware  of  them — until  we 
encounter  a  notation  like  Prolog,  which  denies  them  all! 

Prolog  makes  no  difference  between  data  and  code;  the 
distinction  is  simply  not  relevant.  But,  you  ask,  how  do  we 
store  information?  We  do  it  by  asserting  the  truth  of  that 
information.  The  asserted  sentences  become  part  of  the  data 
base.  Everything  is  sentences;  we  may  think  of  any  sentence 
as  either  code  or  data.  Consider  that  the  query 

qsort([3  2  1  ]  what  )? 
yes,  what  =  [1  2  3]. 

could  be  answered  equally  well  by  our  algorithmic  qsort  defi¬ 
nition  or  by  a  simple  assertion  of  fact:  qsort([3  2  1  ]  [  1  2  3]). 
In  principle,  there  is  no  difference  between  the  algorithmic 
qsort  (code)  and  the  qsort  implemented  by  an  infinite  set  of 
facts  (data). 

But  practical  problems  arise  from  the  mode  of  interactive 
use.  How  can  we  develop  data  during  one  query,  yet  have  the 
same  data  available  for  another  later  query?  Suppose,  for 


member)  (7em  [item  I  tail  ]). 
member(item  [head I  tail  ])  if 
membeff/rem  tail ). 

and  apply  to  it  the  query 

member(j  [j  k])? 

These  sentences  are  not  independent;  the  query  matches 
both  of  them.  It  matches 

member) [item  I  tail  ]). 

with  item  bound  to  j  and  tail  bound  to  [k],  and  it  also 
matches 

member) (tew  [head I  tail  ]) . . . 

with  item  bound  to  j,  head  to  j,  and  tail  to  [k].  Which  sen¬ 
tence  would  Prolog  choose  to  use,  and  what  would  happen 
then? 

The  Prologs  that  run  on  personal  computers  practically 
guarantee  that  the  sentences  of  a  relation  will  be  tested  in  the 
order  they  were  entered  to  the  data  base.  If  the  limiting  case 
is  entered  first,  as  here,  it  will  be  matched  first.  You’ll  find 
plenty  of  cases  where  programmers  rely  on  this  implicit  or¬ 
der  of  evaluation.  If  the  interpreter  is  asked  to  find  only  one 
answer  to  the  query,  it  will  match  to  the  first  sentence  (a 
fact)  and,  having  verified  the  query,  will  reply  “yes”  and 
stop. 

But  in  some  cases,  the  interpreter  will  continue  to  look  for 
any  further  answers  to  a  query.  In  our  example,  it  would 
continue  on  to  match  the  query  to  the  second  sentence.  That 
sentence  is  a  rule,  and  its  conjunction  specifies  testing  the 
query,  rewritten  as  follows: 

member(j  [ k] )? 

This  does  not  match  the  first  sentence,  but  it  does  match  the 
second  one,  binding  item  toy  head  to  k,  and  tail  to  the  empty 
list.  This  results  in  a  further  nested  query 

member(j  [  ])? 

which  matches  neither  sentence.  Thus,  this  branch  of  the  trail 
ends  with  failure.  No  additional  answers  have  been  found;  the 
only  effect  has  been  some  wasted  processing  time. 

We  could  have  avoided  the  extra  work  had  we  used  the 
built-in  “cut”  relation  in  the  first  sentence.  Cut  has  only  the 
descriptive  meaning  of  true,  so  inserting  “cut)  )  and  .  .  .”  in 
any  conjunction  has  no  logical  effect.  However,  this  insertion 
has  the  powerful  side  effect  of  telling  the  interpreter  to  at¬ 
tempt  no  alternative  sentences  for  this  relation!  To  use  it  in 
our  example,  we  would  write  the  first  sentence  as: 

member  {item  [item  I  tail  ])  if 
cuff  ). 

(The  clause  cuff  )  is  written  as  (/)  in  Micro-PROLOG  and  ! 


Dr.  Dobb  s  Journal,  March  1985 


60 

207 


instance,  that  we  want  to  test  our  addset  relation,  retaining 
its  output  to  use  as  input  to  the  next  test. 

We  start  by  asserting  that  one  particular  set — which  we 
name  with  an  unusual  constant — exists  and  is  null: 

“The  Set”(  [  ]  ). 

We  can  query  it  just  like  any  other  relation: 

“The  Set  "(what  )? 
yes,  what  =  [  ]. 

More  importantly,  however,  we  can  query  it  as  part  of  a  test 
program,  test-add(x  ),  that  describes  the  effect  of  testing 
addset  with  item  x: 

test-add(x  )  if 

“The  Set”(/n  )  and 
addset(x  in  out  )  and 
display(o«t  )  and 
retract([“The  Set”  in  ])  and 
assert([“The  Set”  out  ]). 

With  this  program,  we  begin  using  Prolog  syntax  in  a  lin¬ 
ear,  time-bound  way.  The  display,  retract,  and  assert  relations 
all  are  system-defined.  Each  has  a  trivial  descriptive  sense  and 
an  important  side  effect  on  the  Prolog  environment. 

Descriptively,  the  display(x  )  relation  is  simply  true  for 
any  item  x.  Therefore,  “display(;7ew  )  and...”  may  be  insert¬ 
ed  in  any  conjunction.  Its  side  effect  is  to  display  x  at  the 
console  (the  relation  is  called  PP  in  Micro-PROLOG). 

The  relation  retract(/zT?  )  is  true  when  list  describes  a  sen¬ 
tence  presently  in  the  Prolog  data  base.  Its  side  effect  is  to 
delete  that  sentence. 

The  relation  assert(//Tt  )  is  true  when  list  describes  a  valid 
Prolog  sentence.  Its  side  effect  is  to  assert  that  new  sentence, 
making  it  part  of  the  data  base.  The  format  for  describing  a 
sentence  as  a  list  is  system-dependent. 

Thus,  the  sense  of  the  test-add  relation  is:  get  the  current 
value  of  “The  Set”;  add  item  x  to  it  and  display  the  result; 
replace  the  definition  of  “The  Set”  with  the  augmented  set. 
It  permits  dialogues  like  this: 

test-add(  5 )? 

[5] 

yes. 

test-add(fred)? 

[5  fred] 
yes. 

“The  Set  "(what  )? 
yes,  what  =  [5  fred]. 

A  test-add  query  is  clearly  time  dependent.  It  makes  no  sense 
unless  the  retraction  is  done  after  addset  and  before  assert. 
This  is  not  elegant. 

This  is,  in  fact,  an  indefensible  kludge,  so  I  won’t  try  to 
defend  it.  It  is  how  the  concept  of  the  global  variable  is  intro¬ 
duced  into  Prolog — and  how  we  can  simulate  almost  any  sort 
of  data  storage  mechanism.  For  example,  extending  the 
method  of  test-add  to  make  a  test  bed  for  binary  tree  algo- 

62 

?nn 


in  Edinburgh-style  systems.)  The  effect,  upon  a  successful 
match  to  this  rule,  would  be  to  cut  off  any  attempt  to  find  an 
alternate  sentence  for  member.  The  second  sentence  would 
be  removed  from  consideration  as  soon  as  the  first  was 
matched.  Only  if  the  first  sentence  failed  to  match  could  the 
second  be  applied. 

This  interpretation  of  cut,  of  course,  assumes  that  the  in¬ 
terpreter  processes  sentences  sequentially  in  the  order  they 
are  asserted.  If  it  does  not,  it  might  attempt  the  second  sen¬ 
tence  before  the  first  one,  and  adding  cut  to  the  first  accom¬ 
plishes  nothing. 

Consider  the  execution  of  the  original  member  relation, 
without  cut,  in  a  machine  that  has  And-parallel  hardware 
(see  the  sidebar  “Prolog  In  Parallel”).  The  system  can  exe¬ 
cute  tests  of  both  sentences  of  the  member  relation  at  once, 
and  it  will  find  that  both  match  the  query.  Now  what?  Well, 
the  machine  might  favor  facts  over  rules,  which  would  make 
it  select  the  right  path.  Or  it  might  keep  an  entry  sequence 
number  with  each  sentence  and  favor  the  one  with  the  lower 
sequence  number.  Or  it  might  simply  follow  up  both  paths  in 
parallel.  In  the  last  case,  the  first  sentence,  being  a  fact,  will 
terminate  quickly  with  success,  while  the  second  will  end  in 
failure  one  nesting  level  later. 

In  an  And-parallel  machine,  the  effect  of  the  cut  relation 
is  nondeterministic.  One  can  imagine  some  kind  of  hardware 
feature  in  which  execution  of  cut  commits  intersentence 
fratricide,  the  cutting  sentence  aborting  parallel  execution  of 
any  other  sentence  from  the  same  relation,  but  would  that  be 
useful?  We  could  not  tell  how  far  another  execution  unit 
might  have  gone,  executing  a  different  sentence  of  the  rela¬ 
tion,  before  the  one  execution  unit  reached  the  cut-clause.  So 
programmers  using  cut  to  avoid  accidental,  multiple  uses  of 
a  file-read  or  some  other  do-once  clause  -as  is  recommend¬ 
ed  in  Prolog  tutorials — should  reflect  that  this  program  is 
not  suitable  for  And-parallel  execution. 

When  you  are  designing  Prolog  solutions,  it  is  always  best 
to  describe  a  relation  in  sentences  that  are  independent  of 
each  other  in  all  cases,  without  regard  to  execution  sequence. 
We  could  accomplish  that  in  our  example  using  a  not-same 
relation: 

member(/7em  [item  I  tail  ]). 

member(z7ew  [head  I  tail  ])  if 
not-same(;7em  head )  and 
member(z7ew  tail ). 

These  sentences  are  complete  and  independent;  the  solution 
has  no  dependency  on  either  order  of  execution  or  degree  of 
parallelism. 

The  “fail”  relation  has  the  descriptive  meaning  “false — 
this  sentence  does  not  match.”  It  has  some  uses,  but  it  is  hard 
to  find  examples  that  do  not  rely  on  order  of  execution. 

One  possible  use  of  fail  is  to  implement  a  not-same 
relation: 

not-same(z7ew  item  )  if 
cut(  )  and  fail(  ). 

not-same(xj> ). 


Dr.  Dobb's  Journal.  March  1985 


This  is  sequence  dependent,  as  you  might  expect  from  the 
appearance  of  cut(  ).  If  a  not-same  clause  is  being  tested 
with  arguments  that  are  the  same,  the  clause  will  match  to 
the  first  sentence.  The  cut  clause  ensures  that  the  interpreter 
will  attempt  no  further  sentences  of  not-same;  the  fail  clause 
ensures  that  the  query  fails — the  same  items  are  not  not- 
same.  A  query  of  not-same  with  arguments  that  are  not  the 
same  will  not  match  to  the  first  sentence,  so  the  interpreter 
will  test  it  (successfully)  against  the  second  one.  That  sen¬ 
tence  is  a  fact,  so  not-same  is  verified. 

As  with  most  definitions  that  use  cut(  ),  this  is  utterly 
without  meaning  unless  the  order  of  evaluation  of  sentences 
is  certain;  thus,  it  is  unusable  in  an  And-parallel  machine. 


rithms  (using  a  global  “The  Tree”)  is  simple.  More  compli¬ 
cated  schemes  are  also  possible:  we  can  use  assert  and  retract 
to  implement  (not  describe!)  a  sparse  array.  (The  array  is  a 
relation  with  its  elements  stored  as  facts  of  the  form  array- 
(sub  value  ),  meaning  the  array  element  at  position  sub  has 
this  value.  ) 

The  Meta-Variable 

Some  Prologs  permit  a  variable  to  appear  in  the  position  of 
the  relation-name  in  a  clause.  This  feature,  the  meta-vari¬ 
able,  makes  it  possible  to  write  Prolog  algorithms  about  Pro¬ 
log. 

As  an  example  of  how  we  might  use  the  meta-variable, 
consider  the  “over”  notation  of  APL.  In  that  language,  a 
function  may  be  applied  “over”  an  array.  For  instance,  in 

+  /3  I  9  6  4 

the  plus  is  the  function  being  applied,  and  the  slash  means 
apply  it  “over”  the  following  list  of  numbers.  The  effect  of 
“over”  is  to  insert  the  function  symbol  between  every  pair  of 
numbers,  so  that  the  preceding  line  is  equivalent  to: 

3+ 1 +9+6+4 

In  short,  “plus  over"  is  equivalent  to  summation,  “max  over” 
to  largest,  and  so  on. 

We  can  write  an  “over”  relation  in  Prolog  if  we  use  the 
meta-variable.  The  relation  would  be  over  (fun  list  out  )  with 
out  as  the  result  of  applying  fun  between  every  pair  of  items 
in  list.  A  relation  named  fun  must  exist  and  have  the  form 
fun  (a  b  z  ),  meaning  a  fun  b  yields  z.  In  addition,  we  need  a 
sentence,  identity(/i/«  id  ),  such  that  id  is  the  “identity  ele¬ 
ment”  of  the  function:  the  value  is  such  that  fun  (a  id  a  ). 

Here  is  a  collection  of  sample  functions: 

plus(a  b  z)  if 
SUM(cr  b  z  ). 

identity(plus  0). 

times(a  b  z)  if 
PROD(o  b  z  0). 

identity(times  1 ). 

max(cr  b  a  )  if 
leq (b  a  ). 

max(a  b  b  )  if 
less(a  b  ). 


identity(max  9E99) 

And  here  is  the  definition  of  “over”: 

over(fun  [  ]  id  )  if 
identity(/w«  id  ). 

over(fun  [a  I  /  ]  z  )  if 
over  (fun  t  b  )  and 
fun  (a  b  z  ). 

This  is  quite  faithful  to  the  APL  model,  even  executing  right- 
to-left  through  the  items  and  returning  the  identity  element 
when  applied  to  an  empty  array. 

Summary 

Prolog  is  a  notation  that  allows  us  to  express  many  algo¬ 
rithms  in  neat  and  elegant  ways  and  that  forces  others  into 
distorted  shapes  (as  Bob  Morein  puts  it,  if  it’s  hard  in  For¬ 
tran,  it’s  easy  in  Prolog  -  and  vice  versa!).  Nevertheless,  it  is 
educational  to  rewrite  familiar  algorithms  in  a  new  notation. 

But  the  greatest  value  of  Prolog  is  that,  in  the  process  of 
understanding  it,  a  programmer  must  alter  and  reexamine 
the  fundamental  assumptions  of  his  or  her  craft.  Prolog  dem¬ 
onstrates  that  the  distinction  between  code  and  data  is  artifi¬ 
cial  and  unnecessary,  and  it  exposes  the  many  assumptions 
we  make  about  the  flow  of  time.  Prolog  or  a  notation  de¬ 
scended  from  it  may  or  may  not  become  important  in  the 
fifth  and  further  generations  of  computing;  it  doesn’t  matter. 
What  matters  is  that,  after  learning  it,  you  will  think  about 
programming  in  a  whole  new  way. 

References 

1.  Programming  in  Prolog ,  W.  F.  Clocksin  and  C.  F.  Mel- 
lish.  Springer- Verlag,  New  York  1981. 

2.  Micro-PROLOG  is  a  product  of  Logic  Programming  Asso¬ 
ciates,  10  Burntwood  Close,  SW  18  3JU,  London,  England. 

3.  Documented  in  User's  Guide  to  DECsystem-10  Prolog ,  L. 
M.  Pereira,  F.  Pereira,  and  D.  Warren,  and  in  A  Guide  to 
Version  3  of  DEC-10  Prolog ,  L.  Byrd,  F.  Pereira,  and  D. 
Warren.  Both  papers  are  published  through  the  Department 
of  Artificial  Intelligence,  University  of  Edinburgh,  Edin¬ 
burgh,  Scotland. 

DD| 


Dr.  Dubb's  Journal.  March  1985 


63 

209 


Tax  Advisor 

A  Prolog  Program 
Analyzing  Income 
Tax  Issues 

by  Dean  Schlobohm 


The  winner  of  our  A!  programming  con¬ 
test  is  a  program  that  knows  how  it 
knows  what  it  knows,  and  why  it  wants 
to  know  what  it  doesn't  know. 


A  year  ago,  in  March  1984,  we  celebrated  DDJ’s  new  rela¬ 
tionship  with  M&T  Publishing  by  announcing  an  artificial 
intelligence  programming  contest.  We  would  award  a  $1000 
prize,  we  said,  to  the  entry  that  best  implemented  artificial 
intelligence  techniques  in  a  practical  application  on  a  mi¬ 
crocomputer.  We  didn't  say  so,  but  we  expected  the  “practi¬ 
cal"  criterion  to  direct  people  toward  the  “ applied "  end  of 
AI  work,  and  it  did.  Although  we  gave  some  guidelines 
( strive  for  memory  efficiency,  real-world  usefulness,  and 
advancing  the  state  of  the  art),  we  left  the  judging  criteria 
somewhat  loose,  in  keeping  with  the  nature  of  the  definition 
of  artificial  intelligence.  We  didn't  pretend  to  know  exactly 
what  we  were  looking  for,  but  we  figured  that  we'd  know  it 
when  we  saw  it.  And  we  did. 

It  could  have  been  a  can  of  worms.  We  didn’t  specify 
processor,  language,  operating  system,  disk  format.  We 
didn't  even  specify  that  it  be  for  a  disk-based  machine!  For 
all  we  knew,  we  might  get  a  thousand  different  programs  for 
a  thousand  different  machines. 

We  weren’t  too  worried,  though.  The  prize  was  not  ex¬ 
travagant  for  the  amount  of  work  that  was  required,  and  we 
expected  to  receive  a  small  number  of  entries.  And  we  did. 
After  preliminary  culling,  we  found  ourselves  with  only  four 
entries.  For  four  machines  and  four  disk  formats.  From 
Mexico  came  Gerardo  Cisneros’  program  for  solving  certain 
kinds  of  algebraic  problems  that  arise  in  quantum  mechan¬ 
ics.  We  were  awed  by  the  idea  of  his  crunching  matrices  of 
many-electron  wavefunctions  on  a  56  TPA  CP/M  system. 
There  was  Richard  Grigonis’s  Prison  Doctor,  which  took 
the  overused  Doctor  model  ofWeizenbaum's  Eliza  program 
and  turned  it  into  something  ingenious  and  new.  This  was 
only  the  latest  in  a  series  of  entries  by  Richard,  one  of  our 
most  creative  contributors  and  the  author  of  last  year's  con¬ 
troversial  "Sixth  Generation  Computers."  Stephan  Heu- 
mann  brought  us  Logicon,  a  Commodore  64  BASIC  program 
that  simplified  Boolean  expressions.  Stephan's  program 
combined  the  formalisms  of  G.  Spencer-Brown  with  some 
insights  of  his  own  and  made  clever  use  of  color  to  discrimi¬ 
nate  levels  of  analysis.  Then  there  was  Dean  Schlobohm’s 
tax  advisor  program,  a  solid  and  impressive  little  expert 
system.  I'd  add,  “ just  in  time  for  doing  your  taxes,"  but  I 
doubt  that  many  readers  o/DDJ  are  prepared  to  make  use 
of  this  kind  of  legal  tax  advice. 

Judging  was  no  trivial  matter,  even  with  so  few  entries.  We 
enlisted  the  aid  of  referees  and  friends  with  the  necessary 
machines  and  appropriate  expertise.  We  ran  every  program 
past  an  AI  expert  who  judged  whether  it  embodied  the  tech¬ 
niques  of  AI,  a  programmer  competent  to  evaluate  the  quali¬ 
ty  of  the  code,  someone  conversant  with  the  subject  area  of 
the  program  (logic  and  physics,  anyway;  we  winged  it  a  little 
regarding  taw  and  forensics),  a  tester  who  could  evaluate 
whether  and  how  well  the  program  actually  worked,  and 
three  editors.  In  the  end,  we  chose  a  program  that  demon¬ 
strated  clear  understanding  of  what  applied  AI  techniques 
are  and  how  an  AI  program  ought  to  behave.  It  also  turned 
out  to  be  a  good  introduction  to  programming  in  Prolug,  and 


Dean  A.  Schlobohm,  MICRO- AI,  DO.  Box  91,  Rheem  Val¬ 
ley,  CA  94570.  Copyright  ®  1984  Dean  A.  Schlobohm. 


64 

210 


Dr.  Dobb's  Journal,  March  1985 


this  issue  consequently  shaped  itself  into  a  Prolog  issue.  One 
criterion  that  emerged  in  the  judging  process  was  justifica¬ 
tion:  an  Al  program  ought  to  be  able  to  tell  you  why  it  did 
what  it  did.  The  program  we  chose  did  that,  too. 

We  may  yet  print  some  of  the  runners-up.  But  this  month 
we  are  pleased  to  present  the  winner  of  our  Al  programming 
contest,  Dean  Schlobohm’s  Tax  Advisor.  — Ed. 

The  prototype  computer  program  TA  (Tax  Advisor)  as¬ 
sists  attorneys  in  analyzing  the  constructive  ownership 
of  stock  rules  set  forth  in  Section  318(a).1  Basically, 
Section  318(a)  sets  forth  a  number  of  rules  under  which  a 
given  person  (e.g.,  an  individual,  trust,  estate,  partnership,  or 
corporation)  will  be  deemed  to  own  stock  that  is  actually,  or 
constructively  by  the  application  of  Section  318(a),  owned 
by  another  person.  For  example,  Section  3 1 8(a)(  1 )  provides 
that  an  individual  is  considered  to  own  the  stock  actually 
owned  by  his  or  her  spouse,  children,  grandchildren,  and 
parents.  Similarly,  Section  318(a)(2)(A)  provides  that  stock 
owned  by  a  partnership  is  considered  to  be  proportionally 
owned  by  its  partners. 

Although  the  rules  set  forth  in  Section  318(a)  involve  little 
uncertainty,  their  application  to  a  given  factual  situation  is 
at  times  tedious.  Furthermore,  because  stock  that  is  deemed 
to  be  owned  by  a  given  person  as  a  result  of  the  application  of 
Section  318(a)  may,  under  certain  conditions,  be  reattrib¬ 
uted  to  another  person,  the  statute  provides  many  opportuni¬ 
ties  for  the  attorney  to  make  a  mistake  in  his  or  her  analysis. 
As  a  result,  I  have  developed  two  prototype  computer  pro¬ 
grams  that  perform  the  required  legal  analysis. 

The  computer  programs  are  written  in  the  language  Pro¬ 
log.  Basically,  Prolog  permits  rules  of  the  form: 

If:  A  is  true;  and 

B  is  true;  and 
C  is  true. 

Then:  D  is  true. 

to  be  represented  as: 

D:- 

A,B,C. 

One  version  of  TA  consists  of  a  set  of  Prolog  rules  that 
describe  all  of  the  legal  rules  of  Section  318(a).  For  example, 
assume:  (1)  spouse(H,W)  means  that  H  is  the  spouse  of  W; 
(2)  owns(I,N,C)  means  that  I  actually  owns  N  shares  of  cor¬ 
poration  C  stock;  and  (3)  num-con-own(H,N,C)  means  that 
H  constructively  owns,  as  a  result  of  Section  318(a)(1)(A), 
N  shares  of  corporation  C  stock. 

Then  the  rule  of  Section  318(a)(1)(A)  that  an  individual  is 
deemed  to  own  the  stock  owned  by  his  or  her  spouse  may  be 
expressed  by  the  Prolog  clause: 

num-con-own(H,N,C)  :- 
spouse(H,W), 
owns(W,N,C). 

A  second  version  of  TA  includes  the  facility  to  “explain” 
its  reasoning.  For  example,  the  program  will  ask  the  user 

Dr.  Dobb's  Journal,  March  1985 


only  for  the  information  that  it  needs  to  reach  its  conclusion. 
If  the  user  is  unsure  why  the  computer  asks  a  given  question, 
he  or  she  can  ask  the  computer  why  the  information  is  need¬ 
ed.  The  computer  will  then  set  forth  an  explanation  of  its 
reasoning  in  terms  of  the  rules  that  it  has  used  up  to  the  point 
the  question  is  asked.  Moreover,  the  user  can  ask  the  com¬ 
puter  for  help  in  response  to  a  given  question.  For  example,  if 
the  user  enters  help  in  response  to  the  question  “Does  the 
client  have  any  children?”  the  program  will  explain  that 
“children”  includes  legally  adopted  children.  After  the  pro¬ 
gram  has  reached  its  conclusion,  the  user  can  ask  the  com¬ 
puter  how  the  program  reached  its  result.  This  explanation 
will  consist  of  the  set  of  rules  and  facts  that  the  computer 
used  in  reaching  its  conclusion. 

Unlike  other  artificial  intelligence  programs  in  the  legal 
area,2  the  major  goal  of  TA  has  been  to  develop  a  program 
that  could  be  used  by  a  practicing  attorney  with  little  or  no 
knowledge  of  computers.  Thus,  a  substantial  amount  of  time 
has  gone  into  designing  an  interface  so  that  an  attorney 
could  use  TA  without  understanding  the  computer  language 
in  which  it  is  written. 

Both  programs  currently  run  on  a  Compupro  8 1 6A  micro¬ 
computer  under  the  CP/M-863  operating  system.  Because 
the  current  version  of  the  Prolog  interpreter  (Prolog-864)  in 
which  the  programs  are  written  can  access  only  128K  of 
RAM,  the  explanation  features  have  only  been  included  in  a 
program  that  contains  the  family  attribution  rules  of  Section 
318(a)(1).  The  program  without  the  explanation  features, 
however,  has  correctly  analyzed  the  examples  set  forth  in 
Treasury  Regulations  §§1.318-2,  3,  and  4. 

A  Brief  Review  of  Section  31 8(a) 

Basically,  Section  318(a)  sets  forth  rules  under  which  cer¬ 
tain  individuals  and  entities  are  considered  to  own  corporate 
stock  that  is,  in  fact,  actually  owned  by  someone  else.  This  is 
called  “constructive  ownership  of  stock”  or  “attribution  of 
stock  ownership.”  Section  318(a)  creates  four  types  of  rela¬ 
tionships  that  may  create  primary  attribution  of  ownership. 
These  four  relationships  are: 

( 1 )  Attribution  between  members  of  a  family; 

(2)  Attribution  from  an  entity  (e.g.,  a  partnership  or  corpo¬ 
ration)  to  person  holding  a  beneficial  interest  in  the  entity 
(e.g.,  a  partner  or  shareholder); 

(3)  Attribution  from  a  person  (e.g.,  a  partner  or  sharehold¬ 
er)  to  an  entity  (e.g.,  a  partnership  or  corporation); 

(4)  Attribution  to  owners  of  options  to  acquire  stock. 

Each  of  these  will  now  be  discussed  in  order,  followed  by  a 
discussion  of  reattribution. 

Attribution  Between  Family  Members 

Section  318(a)(1)  provides  that  an  individual  is  considered 
as  owning  stock  owned,  directly  or  indirectly,  by  or  for: 

(1)  his  or  her  spouse,  other  than  a  spouse  who  is  legally 
separated  from  the  individual  under  a  decree  of  divorce  or 
separate  maintenance; 

(2)  his  or  her  children,  grandchildren,  and  parents.  For  pur¬ 
poses  of  Section  318(a)(1),  a  legally  adopted  child  of  an 
individual  is  treated  as  a  child  of  such  individual. 

It  should  be  noted  that  although  a  grandparent  is  deemed 

65 

211 


to  own  stock  owned  by  his  or  her  grandchildren,  a  grandchild 
is  not  deemed  to  own  stock  owned  by  his  or  her  grandparents. 
Furthermore,  an  individual  is  not  deemed  to  own  stock 
owned  by  his  or  her  brothers,  sisters,  aunts,  uncles,  and  other 
relatives. 

As  an  example  of  Section  318(a)(1),  assume  that  H,  an 
individual,  his  wife,  W,  his  son,  S,  and  his  grandson  (S’  son), 

G,  each  own  25  outstanding  shares  of  Corporation  X.  Then 

H,  W,  and  S  are  each  considered  as  owning  100  shares,  the 
25  shares  they  actually  own  and  75  shares  from  the  other 
three  individuals.  G,  however,  is  considered  as  owning  only 
50  shares  because  the  shares  of  his  grandparents,  H  and  W, 
are  not  attributed  to  him. 

Attribution  from  an  Entity  to  a  Beneficiary 
of  That  Entity 

Attribution  from  Partnerships  and  Estates 

Section  318(a)(2)(A)  provides  that  stock  owned,  directly  or 
indirectly,  by  a  partnership  or  an  estate  is  considered  to  be 
owned  proportionately  by  its  partners  or  beneficiaries. 

For  example,  assume  that  A,  an  individual,  owns  a  50% 
interest  in  a  partnership.  Further,  assume  that  this  partner¬ 
ship  owns  50  out  of  the  100  outstanding  shares  of  Corpora¬ 
tion  X,  the  remaining  50  shares  being  owned  by  A.  Then  A  is 
considered  to  be  the  owner  of  75  shares,  the  50  shares  he  or 
she  actually  owns  plus  50%  of  the  shares  owned  by  the 
partnership. 

Attribution  from  Trusts 

Except  for  “grantor”  trusts  and  “pension”  trusts,  stock 
owned,  directly  or  indirectly,  by  a  trust  is  considered  as 
owned  by  its  beneficiaries  in  proportion  to  the  actuarial  in¬ 
terest  of  the  beneficiaries  in  the  trust. 

For  example,  assume  that  a  testamentary  trust  owns  25  of 
the  outstanding  100  shares  of  stock  of  Corporation  X.  Fur¬ 
ther,  assume  that  A,  an  individual,  holds  a  vested  remainder 
in  the  trust  having  a  value,  computed  actuarially,  equal  to 
4%  of  the  value  of  the  trust  property.  Then  A  is  considered  as 
constructively  owning  1  share  (4%  of  25). 

In  the  case  of  a  “grantor”  trust  (e.g.,  a  revocable  trust), 
stock  owned,  directly  or  indirectly,  by  the  trust  will  be  con¬ 
sidered  as  being  owned  by  the  person  who  is  treated  as  the 
owner  of  the  trust  pursuant  to  Sections  67 1-677. 5 

Attribution  from  Corporations 

Section  318(a)(2)(C)  provides  that  if  50%  or  more  in  value 
of  the  stock  of  a  corporation  is  owned,  directly  or  indirectly, 
by  a  person,  such  person  is  considered  as  owning  the  stock 
owned,  directly  or  indirectly,  by  the  corporation  in  that  pro¬ 
portion  which  the  value  of  the  stock  that  such  person  so  owns 
bears  to  the  value  of  all  of  the  stock  of  the  corporation. 

For  example,  assume  A  and  B,  unrelated  individuals,  own 
70%  and  30%,  respectively,  in  the  value  of  the  stock  of  Cor¬ 
poration  M.  Assume  further  that  Corporation  M  owns  50  of 
the  100  outstanding  shares  of  stock  of  Corporation  O,  the 
remaining  50  shares  being  owned  by  A.  Then  A  is  considered 
as  owning  85  shares,  the  50  shares  that  he  or  she  actually 
owns  plus  70%  of  the  50  shares  owned  by  Corporation  M. 
Because  B  does  not  own  50%  or  more  of  Corporation  M,  he 
or  she  is  deemed  to  own  no  shares  in  Corporation  O. 

66 

212 


Attribution  to  Entities 

Attribution  to  Partnerships  and  Estates 

Generally,  Section  318(a)(3)(A)  provides  that  stock  owned 
by  a  partner  or  a  beneficiary  of  an  estate  is  considered  as 
owned  by  the  partnership  or  estate.  It  should  be  noted  that 
all  of  the  shares  owned  by  a  partner  or  a  beneficiary  of  an 
estate  are  attributed  to  the  partnership  or  estate,  not  just  a 
partner’s  or  beneficiary’s  pro  rata  share. 

For  example,  assume  that  A,  an  individual,  has  a  50% 
interest  in  a  partnership.  Further,  assume  that  A  owns  50 
shares  of  stock  in  Corporation  X.  Then  the  partnership  is 
considered  as  owning  the  50  shares  of  Corporation  X  stock 
owned  by  A. 

Attribution  to  Trusts 

Except  in  the  case  of  “grantor”  trusts  and  “pension”  trusts, 
stock  that  is  owned,  directly  or  indirectly,  by  a  beneficiary 
of  a  trust  is  considered  as  owned  by  the  trust,  unless  the 
beneficiary’s  interest  is  considered  a  “remote  contingent  in¬ 
terest.”  A  “contingent  interest”  of  a  beneficiary  is- consid¬ 
ered  “remote”  if,  under  the  maximum  exercise  of  discretion 
by  the  trustee  in  favor  of  the  beneficiary,  the  value  of  such 
interest,  computed  actuarially,  is  5%  or  less  of  the  value  of 
the  trust  property. 

For  example,  assume  that  A,  an  individual,  holds  a  vested 
remainder  in  a  testamentary  trust  having  a  value,  computed 
actuarially,  of  4%  of  the  value  of  the  trust  property.  Further¬ 
more,  assume  that  A  owns  75  shares  of  stock  in  Corporation 
X.  Then  the  testamentary  trust  is  considered  to  own  A’s  75 
shares  of  Corporation  X  because  A’s  interest  is  vested  and 
not  contingent. 

In  the  case  of  a  “grantor”  trust,  all  stock  owned  by  the 
grantor  is  deemed  to  be  owned  by  the  trust. 

Attribution  to  Corporations 

If  50%  or  more  in  value  of  the  stock  in  a  corporation  is 
owned,  directly  or  indirectly,  by  any  person,  then  the  corpo¬ 
ration  is  deemed  to  own  the  stock  owned  by  that  person. 
Thus,  as  in  the  case  of  attribution  from  partners  and  benefi¬ 
ciaries  to  partnerships  and  estates,  respectively,  the  corpora¬ 
tion  is  deemed  to  own  all  of  the  stock  owned  by  the  share¬ 
holder,  not  just  a  proportionate  amount. 

For  example,  assume  that  A  owns  70%  of  the  value  of 
Corporation  M.  Assume  that  Corporation  M  and  A  each 
own  50  of  the  1 00  outstanding  shares  of  stock  of  Corporation 
O.  Then  Corporation  M  is  considered  as  owning  100  shares 
of  Corporation  O,  the  50  shares  that  it  actually  owns  and  the 
50  shares  that  A  owns. 

Attribution  as  a  Result  of  Options 

Section  318(a)(4)  provides  that  if  any  person  has  an  option 
to  acquire  stock,  such  stock  will  be  considered  as  owned  by 
such  person.  For  purposes  of  this  Section,  an  option  to  ac¬ 
quire  such  an  option  and  each  one  of  a  series  of  such  options 
is  considered  as  an  option  to  acquire  the  stock. 

For  example,  assume  that  A  and  B,  unrelated  individuals, 
own  all  of  the  100  outstanding  shares  of  Corporation  X,  each 
owning  50  shares.  Assume  further  that  A  has  an  option  to 
acquire  25  of  B’s  shares  and  has  an  option  to  acquire  a  fur¬ 
ther  option  to  acquire  the  remaining  25  of  B’s  shares.  Then  A 

Dr.  Dobb's  Journal.  March  1985 


is  considered  as  owning  the  100  shares  of  stock  of  Corpora¬ 
tion  X. 

Rules  of  Reattribution 

Section  318(a)(5)  provides  certain  rules  for  “reattribution.” 
Except  as  provided  below,  any  stock  that  is  deemed  to  be 
owned  by  a  person  as  a  result  of  one  of  the  four  attribution 
rules  set  forth  above  is  considered  to  be  actually  owned  by 
such  person  for  purposes  of  reapplying  the  above  four  attri¬ 
bution  rules. 

For  example,  if  a  trust  owns  50%  of  the  stock  of  Corpora¬ 
tion  X,  stock  of  Corporation  Y  owned  by  Corporation  X  is 
attributed  to  the  trust  and  may  then  be  further  attributed  to 
the  beneficiaries  of  the  trust. 

There  are,  however,  two  major  exceptions  to  this  rule. 
First,  stock  constructively  owned  by  an  individual  as  a  result 
of  the  application  of  the  family  attribution  rules  of  Section 
3 1 8(a)(  1 )  is  not  treated  as  owned  by  such  individual  for  the 
purpose  of  again  applying  the  family  application  rules  in 
order  to  make  another  individual  the  constructive  owner  of 
such  stock.  For  example,  assume  H,  an  individual,  owns  50 
shares  of  stock  in  Corporation  X.  Further,  assume  that  H’s 
son,  S,  and  daughter,  D,  each  owns  50  shares  in  Corporation 
X.  Then  S  is  deemed  to  own  only  100  shares  in  Corporation 
X,  the  50  shares  he  actually  owns  plus  the  50  shares  actually 
owned  by  his  father,  H.  He  is  not  deemed  to  own  the  stock 
owned  by  his  sister,  D. 

There  is  a  further  exception  to  the  exception  for  reattribu¬ 
tion  of  family  ownership  that  permits  reattribution,  in  effect, 
among  family  members.  Section  318(a)(5)(D)  provides 
that,  when  an  individual  is  deemed  to  own  stock  by  either 
applying  the  family  attribution  rules  or  the  option  rules,  then 
he  or  she  is  considered  to  own  the  stock  by  reason  of  the 
option  rules.  In  other  words,  the  option  rules  take  precedence 
over  the  family  attribution  rules. 

Finally,  Section  318(a)(5)(C)  provides  that  stock  con¬ 
structively  owned  by  a  partnership,  estate,  trust,  or  corpora¬ 
tion  by  reason  of  the  attribution  rules  set  forth  in  Section 
318(a)(3)  will  not  be  considered  as  owned  by  the  entity  for 
purposes  of  applying  the  attribution  rules  set  forth  in  Section 
318(a)(2)  in  order  to  make  another  the  constructive  owner 
of  such  stock.  For  example,  if  two  unrelated  individuals  are 
beneficiaries  of  the  same  trust,  stock  owned  by  one  of  the 
beneficiaries  that  is  attributed  to  the  trust  under  Section 
318(a)(3)  will  not  be  reattributed  from  the  trust  to  the  other 
beneficiary.  It  should  be  noted,  however,  that  stock  construc¬ 
tively  owned  by  reason  of  Section  3 1 8(a)(2)  may  be  reattrib¬ 
uted  under  Section  3  1 8(a)(3).  Thus,  for  example,  if  all  of  the 
stock  of  Corporations  X  and  Y  is  owned  by  A,  stock  of  Cor¬ 
poration  Z  held  by  Corporation  X  is  attributed  to  Corpora¬ 
tion  Y  through  A. 

The  Section  318(a)  Rules  in  Prolog 

This  part  of  the  paper  will  discuss  the  Prolog  implementation 
of  the  rules  set  forth  in  Section  318(a)  and  the  Treasury 
Regulations  thereunder.6  The  complete  listing  of  the  pro¬ 
gram  is  contained  in  Listing  One  (page  75).  In  Listing  Two 
(page  79),  several  of  the  examples  set  forth  in  the  Treasury 
Regulations  under  Section  3 1 8(a)  are  solved  using  TA. 

Dr.  Dobb's  Journal.  March  1985 


The  Interface  Program — section318 

The  Prolog  procedure  section318  controls  the  general  inter¬ 
action  between  the  computer  program  and  the  user.  After 
displaying  an  introductory  message  to  the  user,  TA  asks  the 
user  for  the  name  of  the  client.  This  will  be  used  throughout 
the  program  in  communicating  various  information  to,  and 
asking  various  questions  of,  the  user.  For  purposes  of  the 
remainder  of  this  paper,  the  client’s  name  will  be  Client. 

TA  then  calls  the  procedure  type(Client).  This  procedure 
simply  asks  whether  Client  is  an  individual,  partnership,  es¬ 
tate,  trust,  grantor  trust,  or  corporation.  Once  the  user  has 
told  the  computer  what  type  of  entity  Client  is,  type  then 
adds  this  information  to  the  computer’s  data  base.  This  in¬ 
formation  is  later  used  to  restrict  the  amount  of  searching 
that  the  computer  must  do  to  reach  its  conclusion.  For  exam¬ 
ple,  as  discussed  below,  if  Client  is  not  an  individual,  the 
program  will  not  ask  for  the  family  members  of  Client  and 
will  automatically  set  the  number  of  shares  attributed  to 
Client  as  a  result  of  the  family  attribution  rules  set  forth  in 
Section  3 1 8(a)(  1 )  to  zero. 

TA  then  asks  the  user  for  the  name  of  the  corporation 
whose  stock  may  be  attributed  to  Client  under  Section 
318(a).  For  purposes  of  the  remainder  of  this  paper,  the 
corporation  in  question  will  be  called  Corp. 

After  this  is  done,  TA  will  attempt  to  find  the  number 
of  shares  of  Corp  stock  that  Client  actually  owns.  The 
program  does  this  by  calling  the  procedure  num_a_own- 
(Client,  Corp,  N_actual).  This  procedure  will  be  discussed 
below. 

After  TA  knows  the  number  of  shares  of  Corp  stock  that 
Client  actually  owns,  it  then  calls  the  procedure 
num_con_own(Client,  Corp,  N_con),  which  determines  the 
number  (N_eon)  of  shares  of  stock  of  Corp  that  the  Client 
constructively  owns  as  a  result  of  the  rules  set  forth  in  Sec¬ 
tion  318(a).  This  procedure  is  the  major  procedure  of  TA 
and  will  be  discussed  in  detail  below. 

Finally,  section318  prints  out  the  number  of  shares  of 
Corp  stock  that  Client  actually  and  constructively,  by  appli¬ 
cation  of  Section  318(a),  owns.  After  this  is  done,  TA  asks 
the  user  if  he  or  she  wants  to  run  the  program  again  for  a  new 
client.  If  the  answer  is  yes,  the  program  will  start  over.  If  the 
user  answers  no,  the  program  returns  the  user  to  the  Prolog 
interpreter  and  awaits  new  commands. 

The  Prolog  Procedure  for  Actual  Ownership — 
numaown 

The  Prolog  procedure  num_a_own  determines  the  number 
of  shares  of  Corp  stock  that  Client  actually  owns.  The  proce¬ 
dure  first  knows  that  a  client  will  not  actually  own  any  stock 
in  himself.  Thus,  the  first  clause  of  the  procedure  is  simply: 

num_a_own(Client,  Client,  0) !. 

If  the  client  is  not  identical  to  the  corporation, 
num_a_own(Client,  Corp,  N_actual)  then  asks  the  user  for 
the  number  of  shares  of  stock  that  Client  actually  owns  in 
Corp.  The  program  expects  the  user  to  enter  an  integer,  and 
if  he  or  she  does  not  do  so,  it  prints  an  error  message  and 
again  asks  the  user  for  the  number  of  shares  of  stock  that 
Client  actually  owns. 

67 

213 


Once  TA  knows  how  many  shares  of  stock  the  client  actu¬ 
ally  owns,  it  adds  this  fact  to  its  data  base.7 

The  Prolog  Procedure  for  the  Constructive  Owner¬ 
ship  Rule  Set  Forth  in  Section  318(a) — 
num  con_own 

The  procedure  mim_con_own(Client,  Corp,  Number)  is  de¬ 
fined  as: 

num_con_own(Client,  Corp,  Number) 
num_family(Client,  Corp,  Nl), 
num_from(C'lient,  Corp,  N2,  zzzz), 
num_to(Client,  Corp,  N3), 
num_options(Client,  Corp,  N4), !, 

Number  is  Nl  +  N2  +  N3  +  N4. 

The  procedure  simply  calls  four  other  procedures,  num- 
_family,  num__from,  num_to,  and  num_options.  As  dis¬ 
cussed  below,  these  four  procedures  find  the  number  of 
shares  of  stock  that  Client  constructively  owns  in  Corp  by  the 
application  of  the  rules  of  Sections  318(a)(1),  (2),  (3),  and 
(4),  respectively. 

num_con_own  then  simply  adds  the  number  of  shares 
that  Client  constructively  owns  as  a  result  of  each  of  the  four 
constructive  ownership  rules  to  find  the  total  number  of 
shares  that  Client  constructively  owns  in  Corp  as  a  result  of 
Section  318(a).8 

The  Rule  of  Section  318(a)(1) — num  family 
The  procedure  num_family(Client,  Corp,  Number,  zzzz)  de¬ 
termines  the  Number  of  shares  that  Client  owns  in  Corp  as  a 
result  of  the  family  attribution  rules  set  forth  in  Section 
3 1 8(a)(  I ).  First,  the  procedure  checks  to  determine  whether 
Client  is  an  individual.  If  Client  is  not  an  individual,  the 
procedure  quickly  determines  that  the  number  of  shares  of 
stock  that  Client  owns  as  a  result  of  the  family  attribution 
rules  is  zero.  In  this  regard,  if  the  user  has  told  TA  that 
Client  is  a  partnership,  estate,  trust,  grantor  trust,  or  corpo¬ 
ration,  then  the  computer  knows  that  the  Client  is  not  an 
individual. 

If  Client  is  an  individual,  num_family  then  asks  the  user 
for  the  names  of  Client’s  spouse,  children,  grandchildren, 
and  parents.  This  is  done  by  the  procedure  family_of(Client, 
List)  where  List  is  a  list  of  all  family  members  of  Client.9 

This  procedure  for  determining  the  family  members  of 
Client  is  presently  very  crude.  In  particular,  a  more  extensive 
program  would  contain  the  rule  that  a  child  includes  a  legal¬ 
ly  adopted  child  of  an  individual.  Furthermore,  a  more  so¬ 
phisticated  program  would  contain  rules  for  determining  the 
various  family  relationships  between  individuals.  For  exam¬ 
ple,  it  could  contain  rules  such  that  if  the  program  knew  that 
John  is  the  son  of  Dean  then  the  computer  could  automati¬ 
cally  infer  that  Dean  is  the  parent  of  John.  These  rules  have 
not  been  included  in  the  present  program  due  to  memory 
constraints. 

Once  TA  has  determined  the  family  members  of  Client,  it 
then  calls  the  procedure  family_own(Client,  Corp,  List,  M, 
N).  This  procedure  recursively  goes  down  the  List  of  family 
members  and  determines  the  number  of  shares  of  stock  of 
Corp  that  each  family  member  actually  or  constructively,  as  a 

68 

214 


result  of  Section  318(a),  owns.  In  determining  the  number  of 
shares  that  a  family  member  constructively  owns,  the  pro¬ 
gram  uses  only  the  rules  of  Section  318(a)(2)  and  318(a)(4). 
In  other  words,  the  computer  knows  that  pursuant  to  Section 
318(a)(5)(B),  stock  constructively  owned  by  an  individual  as 
a  result  of  the  family  attribution  rules  may  not  be  considered 
as  actually  owned  by  the  individual  for  purposes  of  again  ap¬ 
plying  the  family  attribution  rules.  Furthermore,  TA  knows 
that  the  attribution  rules  of  Section  318(a)(3)  cannot  apply, 
because  those  rules  attribute  stock  ownership  to  an  entity 
such  as  an  estate,  trust,  or  corporation.  As  a  result,  fami- 
ly_own  simply  calls  num_from  (the  Section  318(a)(2)  rule) 
and  num_options  (the  Section  318(a)(4)  rule). 

Once  TA  determines  the  number  of  shares  that  each  fam¬ 
ily  member  actually  and  constructively  owns  in  Corp,  num- 
_family  returns  the  total  number  of  such  shares. 

The  Rule  of  Section  31 8(a)(2) — num  from 
The  Prolog  procedure  num__from( Client,  Corp,  Number,  Old- 
Client)  determines  the  Number  of  shares  that  Client  owns  of 
Corp  as  a  result  of  the  “from”  attribution  rules  set  forth  in 
Section  318(a)(2).  The  procedure  first  calls  entity_of.  enti- 
ty_of  determines  the  names  of  all  partnerships,  estates, 
trusts,  grantor  trusts,  and  corporations  in  which  Client  has 
an  interest  and  which  may  actually  or  constructively  own 
stock  of  Corp.  For  example,  assume  that  John  is  the  Client 
and  Corporation  X  is  the  Corp.  Then  TA  will  ask  questions 
such  as: 

Is  John  a  50  percent  or  more  shareholder  in  a  corporation 
that  owns,  actually  or  constructively,  stock  in 
Corporation_X? 

If  the  user  answers  yes  (by  typing  y)  to  such  a  question, 
the  computer  will  then  ask  the  user  for  the  name  of  the  entity 
and  automatically  add  the  type  of  the  entity  to  the  comput¬ 
er’s  data  base.  For  example,  if  the  user  states  that  Corpora¬ 
tion  Y  may  actually  or  constructively  own  stock  in  Corpora¬ 
tion  X  in  response  to  the  above  question,  the  computer  will 
add  the  fact  corporation(Corporation_Y)  to  its  data  base. 
Finally,  entity_of  asks  the  user  what  percentage  interest  Cli¬ 
ent  has  in  the  entity  in  question.  This  is  necessary  because 
the  rules  set  forth  in  Section  318(a)(2)  state  that  stock 
owned  by  an  entity  is  considered  to  be  proportionately  owned 
by  its  beneficiaries. 

It  should  be  noted  that  TA  currently  does  not  automatical¬ 
ly  determine  whether  a  client  owns  a  50%  or  more  ownership 
interest  in  a  given  corporation.  It  simply  asks  the  user  wheth¬ 
er  there  is  such  a  corporation.  In  particular,  the  50%  owner¬ 
ship  requirement  must  be  determined  by  applying  the  Sec¬ 
tion  318(a)  attribution  rules.  This  rule  presently  is  not  in  the 
system. 

After  TA  determines  the  identity  and  percentage  owner¬ 
ship  interest  of  Client  in  entities  that  may  actually  or  con¬ 
structively  own  stock  in  Corp,  it  then  calls  the  procedure 
entity_own(Client,  Corp,  List,  M,  N,  OldClient),  which  de¬ 
termines  the  number  of  shares  that  each  such  entity  in  List 
actually  or  constructively  owns  in  Corp.  Once  this  is  done, 
entity_own  determines  Client’s  proportional  interest  in  such 
ownership.  In  other  words,  entity_own  first  calls  nu- 

Dr.  Dobb's  Journal.  March  1985 


m  a  r»wn(H,  Corp,  Nl)  to  determine  the  number  of  shares, 
Nl,  which  the  entity,  H,  actually  owns  in  Corp.  For  example, 
if  Client  owns  a  50%  interest  in  a  partnership,  PS,  and  PS 
owns  100  shares  of  the  stock  in  Corp,  entity_own  will  deter¬ 
mine  that  Client  constructively  owns  50  shares  of  stock  in 
Corp  as  a  result  of  Section  3 1 8(a)(2)(A). 

For  each  entity  in  which  Client  has  an  interest  and  which 
may  actually  or  constructively  own  stock  of  Corp,  enti- 
ty_own  will  then  use  the  attribution  rules  of  Section 
318(a)(2)  and  318(a)(4)  to  determine  the  number  of  shares 
of  stock  that  the  entity  constructively  owns.  These  rules  ac¬ 
count  for  the  reattribution  rules  set  forth  in  Section 
318(a)(5)(A).  entity_own,  however,  does  not  include  the 
constructive  ownership  rule  set  forth  in  Section  318(a)(3) 
because  Section  318(a)(5)(C)  provides  that  stock  construc¬ 
tively  owned  by  an  entity  by  reason  of  the  applicaton  of  the 
“to”  attribution  rules  set  forth  in  Section  318(a)(3)  will  not 
be  considered  as  owned  by  the  entity  for  purposes  of  apply¬ 
ing  the  “from”  attribution  rules  of  Section  318(a)(2). 

The  problem  of  reattribution  that  is  inherent  in  Section 
318(a)  has  been  easily  handled  in  the  Prolog  rules  because 
Prolog  permits  a  recursive  definition  of  procedures.  Thus, 
the  procedure  num_from  was  able  to  call  itself  as  applied  to 
a  different  entity  and,  as  a  result,  accounts  for  the  reattribu¬ 
tion  rules  of  Section  318(a)(5)(A). 

The  Rule  of  Section  318(a)(3) — num  to 
The  procedure  num_to(Client,  Corp,  Number)  determines 
the  Number  of  shares  that  Client  constructively  owns  of  Corp 
as  a  result  of  the  “to”  attribution  rules  set  forth  in  Section 
318(a)(3).  Because  these  rules  apply  only  if  Client  is  not  an 
individual,  num  to  returns  zero  if  Client  is  an  individual. 
Furthermore,  if  Client  and  Corp  are  the  same,  num_to  again 
returns  zero. 

If  Client  is  not  an  individual  and  is  not  the  same  as  Corp, 
num„to  then  forms  a  list  of  all  those  beneficiaries  of  Client 
who  may  own  stock,  actually  or  constructively,  in  Corp.  This 
procedure  is  similar  to  the  procedure  entity_of  discussed 
above.  The  only  difference  is  that  if  the  procedure  deter¬ 
mines  that  the  entity  is  a  trust,  it  then  asks  the  user  whether 
the  beneficiary’s  interest  in  the  trust  is  a  “remote  contingent 
interest.”  If  the  user  answers  yes,  the  beneficiary  is  not  in¬ 
cluded  in  the  list  of  beneficiaries  who  may  own  stock  in 
Corp.10 

Once  TA  has  determined  the  beneficiaries  of  Client  who 
may  actually,  or  constructively,  own  stock  of  Corp,  num_to 
then  determines  the  number  of  shares  of  stock  that  each  such 
person  actually  and  constructively,  as  a  result  of  Sections 
3 1 8(a)(  1  )-(4),  owns  of  Corp.  In  other  words,  num_to  calls  a 
procedure  to_entity_own,  which  then  calls  num_a_own, 
num  family,  num_from,  num_to,  and  num_options.  This 
accounts  for  the  reattribution  rules  set  forth  in  Section 
318(a)(5)(A). 

The  Rule  of  Section  318(a)(4) — num  options 
Finally,  the  procedure  num_options(Client,  Corp,  Number) 
handles  the  “options”  attribution  rules  set  forth  in  Section 
318(a)(4).  This  procedure  simply  asks  the  user  whether  Cli¬ 
ent  has  an  option,  or  an  option  to  acquire  an  option,  to  ac¬ 
quire  stock  of  Corp.  If  the  user  answers  yes,  num_options 

Dr.  Dobb’s  Journal.  March  1985 


then  asks  the  user  how  many  shares  of  Corp  stock  Client  has 
an  option  on. 

Examples 

Listing  Two  contains  several  computer  runs  for  various  fac¬ 
tual  situations.  These  examples  have  been  taken  from  the 
examples  set  forth  in  the  Treasury  Regulations  under  Sec¬ 
tion  318(a).  The  user  input  has  been  underlined  for  clarity. 

Listing  Three  (page  84)  contains  an  example  of  a  run  in 
which  the  procedures  num_a_own,  num_con_own,  num_ 
family,  num_from,  num__to,  and  num_options  have  been 
“traced.”  This  permits  the  reader  to  see  how  Prolog  recur¬ 
sively  applies  the  various  procedures  in  the  case  of  a  reattri¬ 
bution  problem. 

Simple  Explanations 

Although  the  Section  318(a)  program  correctly  determines 
the  number  of  shares  of  stock  of  a  given  corporation  that  a 
person  constructively  owns  as  a  result  of  the  Section  318(a) 
attribution  rules,  most  attorneys  using  the  program  will  also 
want  to  know  how  the  computer  reached  its  conclusion.  For 
example,  if  a  senior  partner  asks  an  associate  to  determine 
the  number  of  shares  of  stock  of  a  given  corporation  that  an 
individual  owns  as  a  result  of  the  Section  318(a)  attribution 
rules  and  the  associate  simply  gives  the  number,  the  senior 
partner  would  undoubtedly  ask  the  associate  to  prepare  a 
memo  outlining  the  reasoning  he  or  she  used  to  arrive  at  the 
number  of  shares.  Furthermore,  if  the  Section  318(a)  pro¬ 
gram  were  used  to  prepare  a  Tax  Court  petition,  the  attorney 
would  need  to  present  a  well-reasoned  opinion  supporting  his 
or  her  position.  This  could  be  done  only  by  explaining  how 
the  Section  318(a)  attribution  rules  applied  to  the  facts  in 
question.  Finally,  if  the  attorney  simply  relied  upon  the  nu¬ 
meric  output  from  the  program  and  did  not  review  the  com¬ 
puter’s  reasoning,  the  attorney  could  leave  himself  or  herself 
open  to  a  malpractice  suit.  Thus,  any  useful  “knowledge- 
based”  system  in  the  legal  field  must  permit  the  user  to  ask 
the  computer  to  explain  how  it  reached  its  conclusion. 

The  issue  of  how  to  have  the  computer  generate  useful 
explanations  is  quite  complex.  For  example,  explanations  by 
human  experts  are  usually  tailored  to  their  audiences.  In 
particular,  an  associate  would  use  a  different  (and  more 
technical)  explanation  of  how  he  or  she  arrived  at  the  num¬ 
ber  of  shares  of  stock  that  an  individual  owns  as  a  result  of 
the  Section  3 1 8(a)  attribution  rules  if  he  or  she  were  writing 
a  memo  to  a  senior  tax  partner  as  opposed  to  explaining  the 
problem  to  a  client.  Furthermore,  in  most  situations,  the 
high  level  domain  rules  that  are  incorporated  into  the  com¬ 
puter  program  may  not  adequately  contain  their  justifica¬ 
tions.  Although  these  problems  present  a  number  of  interest¬ 
ing  questions,  which  should  be  addressed  by  future  research, 
they  will  not  be  considered  in  this  paper. 11 

As  will  be  illustrated  below,  most  of  the  above  complex¬ 
ities  that  arise  in  generating  useful  explanations  have  been 
avoided  in  TA  because  the  rules  set  forth  in  Section  318(a) 
are  justifications  for  themselves.  In  other  words,  the  tax 
rules  set  forth  in  the  Internal  Revenue  Code  generally  need 
no  further  justification  other  than  the  fact  that  Congress 
enacted  such  statutory  provisions.  Furthermore,  the  complex 

70 

215 


problem  of  dealing  with  the  interpretation  of  case  law  is 
avoided  because  there  are  very  few  cases  under  Section 
318(a). 

The  remainder  of  this  part  of  the  paper  will  discuss  how 
the  Prolog  program  in  Listing  Four  (page  87)  implements  a 
simple  explanation  facility.12 

Answering  How  the  Computer  Reached  Its 
Conclusion 

In  order  to  understand  how  TA  explains  how  it  arrives  at  a 
given  conclusion,  consider  the  following  fact  situation.  As¬ 
sume  that  TA  is  asked  how  many  shares  of  stock  of  Corpora¬ 
tion  X  the  client,  Dean,  owns  as  a  result  of  the  family  attri¬ 
bution  rules.  Furthermore,  assume  that  Dean  has  only  one 
family  member,  his  spouse,  Mary.  Finally,  assume  that 
i  Mary  actually  owns  1,000  shares  of  Corporation  X  stock. 
Based  on  these  facts,  TA  will  explain  its  reasoning  as  follows: 

START  OF  EXPLANATION 
The  number  of  shares  of  Corporation_X  stock  construc¬ 
tively  owned  by  Dean  as  a  result  of  Section  3 1 8(aX  1 )  is  1 ,000, 
determined  as  follows: 

An  individual  is  deemed  to  own  the  stock  owned  by  his  family. 
Section  318(a)(1). 

FACT:  Dean  is  an  individual. 

A  spouse  is  a  family  member.  Section  318(a)(l)(A)(i). 


FACT:  Mary  is  the  spouse  of  Dean. 

FACT:  Mary  actually  owns  1,000  shares. 

FACT:  Dean  has  no  more  family  members. 

END  OF  EXPLANATION 

As  can  be  seen,  the  explanation  generated  by  TA  applies 
the  rules  set  forth  in  Section  318(a)(1)  to  the  facts  in 
question. 

In  order  to  explain  its  reasoning,  each  rule  of  TA  contains 
an  extra  argument,  H,  which  contains  a  list  of  descriptions  of 
the  Prolog  rules  that  were  successful  when  the  program  fi¬ 
nally  reached  its  conclusion.13  For  example,  when 
num_a_own  determines  how  many  shares  of  Corporation  X 
stock  Mary  actually  owns,  it  adds  the  description: 

FACT:  Mary  actually  owns  1,000  shares. 

to  the  list  of  descriptions. 

Furthermore,  whenever  a  Prolog  rule  is  successful,  the 
rule  adds  a  description  of  itself  as  the  first  element  in  the  list 
for  generating  the  explanation.  For  example,  the  predicate 
spouse_own  adds  the  description  found  by  get_rule  (2,Z)  to 
the  head  of  the  list  once  it  has  determined  both  that  Dean 
has  a  spouse  and  the  number  of  shares  of  stock  the  spouse 
actually  owns.  In  particular,  get_rule(2,Z)  simply  sets  the 
first  element  of  the  list  to  the  description: 


71 

216 


Dr.  Dobb  s  Journal,  March  1985 


A  spouse  is  a  family  member.  Section  318(a) 

(lXAHi). 

Once  TA  has  determined  how  many  shares  of  stock  Dean 
constructively  owns  as  a  result  of  the  Section  3 18(a)(  1 )  fam¬ 
ily  attribution  rules,  it  asks  the  user  if  he  or  she  wants  an 
explanation  of  its  conclusion.  If  the  user  answers  yes,  TA 
calls  the  predicate  how,  which  simply  prints  each  element  of 
the  list  H  containing  the  descriptions.  (See  Listing  Four). 

Answering  Why  a  Given  Question  Is  Asked 

In  the  full  Section  318(a)  program  in  Listing  One,  TA  asks 
the  user  for  only  those  facts  required  to  reach  its  conclusion. 
To  make  the  program  more  useful,  the  user  should  be  able  to 
ask  the  computer  why  it  asks  a  given  question.  For  example, 
when  the  computer  asks  the  user: 

Is  Dean  an  individual? 

the  user  should  be  able  to  ask  why  the  computer  needs  to 
know  this  information. 

In  the  prototype  program  in  Listing  Four,  the  computer 
will  answer  why  by  returning: 

An  individual  is  deemed  to  own  the  stock  owned  by  his 
family.  Section  318(a)(1). 

In  other  words,  TA  is  telling  the  user  that  it  needs  to  know 
if  Dean  is  an  individual  in  order  to  determine  whether  it 
should  apply  the  family  attribution  rules  of  Section 
3 1 8(a)(  1 ).  If  the  user  is  still  not  satisfied  with  this  explana¬ 
tion,  the  user  can  again  type  why  in  response  to  the  question: 
Is  Dean  an  individual?  This  time,  the  program  will  return: 

This  is  what  the  Internal  Revenue  Code  provides! 

In  other  words,  in  the  simple  Section  318(a)  program,  the 
ultimate  explanation  of  any  rule  is  that  the  Internal  Revenue 
Code  contains  the  rule. 

In  order  to  implement  the  why  feature,  an  additional  argu¬ 
ment,  W,  is  added  to  each  rule.  This  argument  contains  a  list 
of  all  rules  that  have  been  used  by  the  program  up  to  the 
point  at  which  the  computer  asks  for  further  information.  In 
the  above  example,  the  program  has  just  entered  the  rule 
num  family. 

TA  first  attempts  to  satisfy  this  rule  by  determining 
whether  Client  is  an  individual.  As  a  result,  it  asks  the 
question: 

Is  Dean  an  individual? 

At  this  point  in  the  program,  the  argument  W  contains  a  list 
with  one  element,  the  description: 

An  individual  is  deemed  to  own  the  stock  owned  by  his 
family.  Section  318(aXl). 

Thus,  when  the  user  types  why  in  response  to  the  question, 
Prolog  simply  returns  the  first  element  of  the  list,  W,  and 
then  asks  the  question  again.  If  the  user  again  asks  why,  the 

Dr.  Dobb’s  Journal.  March  1985 


program  will  print  the  next  element  of  the  list  and  again  ask 
the  question.  When  the  list  contains  no  further  elements,  the 
program  will  simply  explain  that: 

This  is  what  the  Internal  Revenue  Code  provides! 

See  Listing  Five  (page  90)  for  further  examples  of 
explanations. 

Providing  Help  When  the  Computer  Asks  Questions 

If  the  attorney  using  the  program  is  not  familiar  with  the 
given  area  of  tax  law,  he  or  she  may  need  help  in  answering 
some  questions  that  the  computer  asks.  For  example,  in  order 
to  keep  the  questions  short,  TA  simply  asks  the  question: 

Does  Dean  have  a  spouse? 

The  user  may  need  some  assistance  in  determining  how  the 
word  spouse  is  defined.  The  present  version  of  TA  permits 
the  user  to  type  help  in  response  to  the  computer’s  request  for 
information.  For  example,  if  the  user  types  help  in  response 
to  the  question: 

Does  Dean  have  a  spouse? 
the  computer  will  answer: 

A  spouse  does  not  include  one  who  is  legally  separated  from 
Dean  under  a  decree  of  divorce  or  separate  maintenance.  Sec¬ 
tion  318(a)(l)(A)(i). 

Thus,  TA  gives  the  user  some  assistance  in  determining  how 
the  word  spouse  is  defined  for  purposes  of  Section  318(a). 
The  present  system,  however,  does  not  further  define  the 
terms  “legally  separated,”  “decree  of  divorce,”  or  “separate 
maintenance.”  For  example,  TA  would  not  assist  the  user  in 
determining  whether  an  interlocutory  decree  of  divorce  un¬ 
der  California  law  constitutes  a  “decree  of  divorce”  for  pur¬ 
poses  of  Section  3 1 8(a).  It  appears,  however,  that  it  would  be 
possible  to  build  further  help  facilities  into  the  program  if 
additional  memory  were  available. 

Finally,  it  should  be  recognized  that  this  help  facility  may 
be  insufficient  in  an  actual  system  designed  to  assist  attor¬ 
neys  because  it  requires  the  attorney  to  know  when  to  ask  for 
help.  In  particular,  the  attorney  might  simply  assume  that 
the  word  spouse  for  purposes  of  Section  3 1 8(a)  includes  any 
person  who  is  married  to  the  client  even  if  legally  separated 
from  him  or  her.  Thus,  unless  the  user  knew  to  ask  for  help, 
he  or  she  might  incorrectly  answer  the  question. 

Conclusion 

This  paper  has  discussed  two  Prolog  programs  that  imple¬ 
ment  the  rules  contained  in  Section  318(a).  Unlike  other 
computer  programs  that  have  been  developed  in  various  le¬ 
gal  fields,  the  major  purpose  of  developing  TA  has  been  to 
illustrate  that  Prolog  may  be  used  to  implement  legal  reason¬ 
ing  systems  in  a  fairly  straightforward  manner.  Further¬ 
more,  this  paper  has  attempted  to  show  that  Prolog  may  also 
be  used  to  implement  simple  explanation  facilities  for  knowl- 

72 

217 


edge-based  programs. 

TA  achieves  its  degree  of  success,  however,  mostly  be¬ 
cause  the  tax  rules  of  Section  318(a)  are  very  well  defined 
and  not  subject  to  interpretation  by  a  large  number  of  cases. 
It  is  presently  unclear  how  the  rules  set  forth  in  case  law 
could  be  incorporated  into  the  present  framework  in  an  area 
where  case  law  is  at  least  as  important  as  the  statutory 
provisions. 

The  prototype  programs  also  illustrate  that  it  is  possible  to 
design  interfaces  to  legal  reasoning  programs  that  permit  a 
user  who  is  not  a  computer  expert  to  use  the  program.  In 
particular,  more  effort  has  gone  into  devising  a  suitable  user 
interface  than  into  the  more  complex  issues  of  representing 
the  rules  and  knowledge  required  to  implement  the  Section 
318(a)  programs.  In  narrow,  well-defined  areas  of  the  law, 
however,  this  method  may  be  acceptable.  If  it  is,  programs 
that  are  useful  to  attorneys  may  be  able  to  be  developed 
much  more  quickly  than  currently  appears  possible. 

Finally,  it  should  be  recognized  that  the  explanation  facil¬ 
ities  described  in  this  paper  are  extremely  simple.  Because 
the  best  explanation  of  Section  3 1 8(a)  is  in  terms  of  the  rules 
of  that  Section,  however,  the  explanation  facility  of  TA  is 
probably  adequate.  In  other  situations,  however,  such  simple 
explanations  would  clearly  be  inadequate. 

Notes 

1  Unless  otherwise  stated,  all  Section  references  are  to  the 
Internal  Revenue  Code  of  1954,  as  amended. 

2  See  for  example:  L.  T.  McCarty,  “Reflections  on  TAX- 
MAN:  An  Experiment  in  Artificial  Intelligence  and  Legal 
Reasoning,”  Harvard  Law  Review  (1977)  90,  837-93;  A. 
Hustler,  Programming  Law  in  Logic,  Department  of  Com¬ 
puter  Science,  University  of  Waterloo,  Research  Report  CS- 
82-13  (1982). 

3  CP/M-86  is  a  trademark  of  Digital  Research,  Inc. 

4  Prolog-86  is  a  trademark  of  MICRO-AI. 

5  It  is  unclear,  however,  whether  the  shares  of  stock  owned 
by  a  grantor  trust  will  also  be  attributed  to  the  beneficiaries 
of  the  trust  pursuant  to  Section  31 8(a)(2)(B)(i)  discussed 
above.  TA  implicitly  contains  the  rule  that  shares  of  stock 
owned  by  a  grantor  trust  are  attributed  to  the  beneficiaries 
pursuant  to  Section  3 1 8(a)(2)(  B)(i). 

6  The  programs  discussed  in  this  paper  are  written  in  Prolog- 
86.  Prolog-86  is  based  on  the  University  of  New  South 
Wales  Prolog,  which  runs  under  Unix  (Unix  is  a  trademark 
of  Bell  Telephone  Laboratories)  and  is  similar  in  many  ways 
to  the  original  DECSystem-10  Prolog  written  by  L.  M.  Per¬ 
eira,  L.  C.  N.  Pereira,  and  D.  H.  D.  Warren.  Prolog-86  cur¬ 
rently  runs  under  CP/M-86,  and  MSDOS  and  PCDOS.  Pro¬ 
log-86  is  available  through  Solution  Systems,  335-D 
Washington  Street,  Norwell,  MA  02061.  Those  readers  who 
are  interested  in  learning  more  about  Prolog  should  read  the 
book  Programming  In  Prolog  by  W.  F.  Clocksin  and  C.  S. 
Mellish  and  published  by  Springer- Verlag. 

7  As  a  result  of  restrictions  on  numbers  in  Prolog-86,  the 
implementation  language  of  TA,  fractional  shares  are  not 
allowed.  Furthermore,  the  number  of  shares  must  be  less 
than  approximately  30,000.  These  restrictions  could  be  lift¬ 
ed  if  the  Prolog  interpreter  permitted  real  numbers  (i.e.. 


fractions)  and  “long”  integers. 

8  In  Prolog,  the  operator  is  causes  arithmetic  evaluation. 
Thus,  N  is  N1  +  N2  +  N3  +  N4  sets  N  to  the  sum  of  Nl, 
N2,  N3,  and  N4  if  N  is  previously  uninstantiated. 

9  A  list  is  an  ordered  sequence  of  elements  of  any  length.  In 
Prolog,  a  list  is  represented  as:  [a,  b,  c],  a,  b,  and  c  are  the 
elements  of  the  list.  The  null  list  is  represented  as  [  ]. 

10  Section  318(a)(3)(B)(i).  In  this  regard,  TA  does  not  assist 
the  user  in  determining  whether  a  beneficiary’s  interest  is  a 
“remote  contingent  interest”  as  opposed  to  a  “vested”  inter¬ 
est  or  a  contingent  interest  that  is  not  remote.  In  a  larger 
system,  such  rules  must  be  included  because  it  is  unlikely 
that  a  user  of  the  system  would  know  how  Section 
318(a)(3)(B)(i)  defines  a  remote  contingent  interest. 

11  See  for  example:  J.  A.  Goguen,  J.  L.  Weiner,  and  C. 
Linde,  “Reasoning  and  Natural  Explanation,”  Int.  J.  Man- 
Machine  Studies  (1983)  19,  521-559;  William  R.  Swartout, 
“XPLAIN:  A  System  for  Creating  and  Explaining  Expert 
Consulting  Programs,”  Artificial  Intelligence  (1983)  21, 
285-325;  and  William  J.  Clancey,  “The  Epistemology  of  a 
Rule-Based  Expert  System — a  Framework  for  Explana¬ 
tion,”  Artificial  Intelligence  ( 1983)  20,  215  251. 

12  Due  to  memory  constraints,  the  Prolog  program  described 
in  Listing  Four  only  implements  the  family  attribution  rules 
of  Section  318(a)(1).  If  the  Prolog  interpreter  were  able  to 
access  more  than  128K  of  RAM,  it  would  be  fairly  easy  to 
include  the  simple  explanation  system  in  the  full  Section 
318(a)  program  listed  in  Listing  One. 

13  The  design  of  the  explanation  facilities  set  forth  in  this 
paper  is  based  on  the  article  by  K.  L.  Clark  and  F.  G. 
McCabe  with  an  appendix  by  P.  Hammond,  “PROLOG:  A 
Language  for  Implementing  Expert  Systems,”  Machine  In¬ 
telligence  (1982) 10,  455  475. 


DDJ 


Reader  Ballot 

Vote  for  your  favorite  feature/article. 
Circle  Reader  Service  No.  1 94. 


74 

218 


Dr  Dobb's  Journal,  March  1985 


Tax  Advisor  (Text  begins  on  page  64) 

Listing  One 


%  THE  COMPLETE  SECTION  318(a)  PROLOG  PROGRAM 
%  By  Dean  A.  Schlobohm 

%  e  Copyright  1984  by  Dean  A.  Schlobohm.  All  Rights  Reserved. 

%  Version  1.0 

%  Date:  6/11/84 
%  Bugs: 

%  1.  Does  not  include  the  rule  that  stock  cannot  count  more 

%  than  once. 

%  2.  When  it  asks  for  partners,  etc.  that  own  stock,  you  must 

%  enter  all  partners  who  actually  or  constructively  may  own 
%  stock. 

%  3.  Does  not  handle  "How",  "Help"  and  "Why" 

%  To  Use:  type  "prolog  sec31 8 . pro<RET> "  then  after  Prolog  is 
%  loaded  type  "section318 ! <RET>" 


%  preliminary  functions 
append ( [] ,L,L)  :-  ! . 

append ( [X | LI ) , L2 , (X | L3] )  :-  append ( LI , L2 , L3 ) . 

appendall ((],[]). 
appendall(  [H] ,H)  . 

appendall (  [H 1 , H2 ], L )  :-  append ( HI , H2 , L ) . 
appendall (  [ H 1 , H  2 | T ] ,L)  :- 
append ( HI , H2 , X ) , 
appendall (T , Z ) , ! , 
append ( X , Z , L ) . 

member ( X, [X  I _) )  :-  !. 

member ( X , [_| Y] )  :-  member  (X,Y). 

add ( X )  :-  X,  !  . 

add(X)  :-  asserta(X) , ! . 


%  The  Interface  Program. 

section318  :- 

prompt (Old , 1  ' ) , 

nl,nl,nl,nl,nl,nl,nl,nl,nl,nl,nl,nl,nl,nl,nl,nl,nl,nl,nl,nl, 
print( '  THIS  PROGRAM  WILL  DETERMINE  THE  NUMBER  OF  SHARES 

OF'  )  , 

print ('STOCK  A  CLIENT  ACTUALLY  AND  CONSTRUCTIVELY  OWNS  AS  A 
RESULT' ) , 

pr int (  'OF  SECTION  318(a)  OF  THE  INTERNAL  REVENUE  CODE.'), 
nl ,  n  1 , 

ask('  Enter  the  name  of  the  Client.  (One  word)  ' 

.Client )  , nl , 
type(Client)  , 

ask('  Enter  the  name  of  the  Corporation.  (One  word)  ' 

, Corp ) , nl , 

add(corporation(Corp) ) , ! , 
num_a_own ( Client , Corp , N_actual ) , 
num_con_own ( Cl ient , Corp , N_con ) , ! , nl , nl , 

print('  The  number  of  shares  of  ' ,Corp, '  stock  actually 

owned  by ' ) , 

pr int ( Cl ient , '  is  ' ,N_actual ,'.'), nl , nl , 

print(  '  The  number  of  shares  of  ' ,Corp,  '  stock 

constructively  owned'), 

print('by  ', Client,'  is  ', N_con ,'.'), nl , nl , 1 , 

ask('  Do  you  want  to  try  another  Client?  ',X),1, 

( (X  =  y)  ;  (X  =  'Y' ) ) , ! , nl.nl, 
prompt (_, Old ) , 
sect ion3 1 8 . 

indi vidual ( Cl ient )  :-  ind(Client). 
individual (Cl ient )  :- 

( par tnership ( Cl ient )  ; 
estate ( Cl ient )  ; 
trust (Cl ient )  ; 
grantor_trust(Client )  ; 
corporation(Client)), 

!  ,  f  a  i  1 . 


Dr.  Dobb's  Journal,  March  1985 


( Continued  on  next  page) 

75 


oh  n 


Tax  Advisor  (Listing  Continued,  text  begins  on  page  64) 

Listing  One 


individual (Client ) 

type(Client )  ,  !  , 
individual (Client )  . 


type(Client) 

(ind(Client)  ; 
partnership(Client )  ; 
estate ( Cl ient )  ; 
trust ( Client )  ; 
grantor_trust ( Cl ient )  ; 

corporation (Client)),!. 
type(Client) 

print('  Is  Client,'  an  Individual,  Partnership,  Estate, 

Trust ,  '  )  , 

pr in ( 'Grantor  trust,  or  Corporation?  (enter  i,  p,  e,  t,  g, 
or  c)  ' ) , 
ra tom( X ) , nl , 

assert_type (X, Client)  ,  !  . 

assert_type ( i ,C )  add ( ind (C ) ) ,  !  . 
assert_type(p,C)  add ( par t nership ( C ) ) , ! . 

assert_type ( e ,C )  add ( estate ( C )),! . 

assert_type ( t ,C )  add ( trust ( C )),!  . 

assert_type(g,C)  add ( grantor_trust ( C ) ) , ! . 

assert_type(c,C)  add ( corporat ion ( C ) )  ,  !  . 

%  the  rule  for  actual  ownership 


num_a_own (Cl ient , Cl ient , 0 )  !. 

num_a_own ( Cl ient , Corp , N  ) 

print('  How  many  shares  of  ' ,Corp, '  stock  does  ', Client), 

pr in (' actual ly  own?  '), 
ratom(X ) , nl , 
integer ( X ) , 

N  =  X,  !  . 

num_a_own (C 1 ient , Corp , N ) 

print(’  Error  —  You  must  enter  an  integer  !'), nl ,!  , 

num_a_own (Client, Corp, N) . 

%  the  main  rule  of  Section  318(a) 


num_con_own ( Cl ient ,Corp .Number )  :  - 
num_f ami ly (Client, Corp, Nl)  , 
num_f rom(Client ,Corp , N  2 , zzzz  )  , 
num_to(Client ,Corp,N3 )  , 
num_opt ions (Client ,Corp  ,N4  )  ,  !  , 
Number  is  Nl  +  N2  +  N3  +  N4. 


%  Section  318(a)(1) 

%  Section  318(a)(2) 

%  Section  318(a)(3) 

%  Section  318(a)(4) 


%  the  rule  of  Section  318(a)(1) 

num_f ami ly (Client, Corp .Number )  : - 

individual(Client) , ! , 
f ami ly_of (Client, List_of_f ami ly ) , 

f ami ly_own ( Cl ient , Corp , List_of_f ami ly , 0 , Number )  . 
num_f  ami ly (Client, Corp, 0) . 


%  definition  of  family_of 


f ami ly_of (C , L ) 
nl, 

prin('  Does  ',C,'  have  a  spouse,  child,  grandchild,  or 

parent?  ' ) , 
ratomtx ) , nl , 

( (X  =  y)  ;  (X  =  ' Y '  )  )  ,  !  , 

ask('  What  is  the  name  of  the  first  family  member?  ' 

,  Y  )  ,  n  1 ,  !  , 
add ( ind ( Y ) ) , 
family_of2(C,L2) , ! , 
append (  [ Y)  , L2 , L )  . 
f amily_of (C, [ )  )  . 


f ami ly_of 2 (C , L) 
nl , 

prin( '  Does  ',C,'  have  any  more  family  members?  '), 

ratom(X) , nl , 


76 


Dr.  Dobb's  Journal.  March  1985 


( (X  =  y )  ;  (X  =  1  Y '  )  )  ,  !  , 

ask('  What  is  the  name  of  the  next  family  member? 

,V),nl,l, 

add ( ind ( Y ) ) , 
f ami ly_of 2 (C , L2 ) , ! , 
append ( ( Y ] , L2 , L ) . 
f ami ly_of  2 ( C , [ ]  ) . 

%  definition  of  family_own 

%  note  Section  318(a)(5)(B)  is  included  since  family_own  does 
%  not  include  num_family. 

f  ami ly_own (C 1 ient , Corp,[],N,N):-  !. 
family_own(Client,Corp, [ P | T  j ,M,N) 

P  /=  Client , ! , 
num_a_own ( P , Cor p , N 1 )  , 
num  f rom ( P , Corp , N2 , zzzz ) , 

optTon_l ieu_f ami ly (Cl ient , Corp , P , N3 ) ,  %  Section  318(a)(5)(D) 

N 4  is  N1  +  N2  -  N3, 
num_opt ions ( P , Corp, N5 )  , 

N6  is  M  +  N4  +  N5 , 

!  , 

family_own(Client,Corp,T,N6,N) , ! . 
family_own(Client,Corp, [ P | T ] ,M,N) 
f ami ly_own (Client, T,M,N) . 

opt ion_l ieu_f ami ly (Client, Corp, P,N)  : - 

print('  Does  ', Client,'  own  an  option  (or  an  option  to 

acquire '  )  , 

print('an  option)  to  acquire  the  stock  in  ' ,Corp, '  which 
’,P), 

pr in (' actual ly  owns?  '), 
ratom ( X ) , nl , 

((X  =  y)  ;  (X  =  ’?*)),!, 

print('  How  many  shares  of  ' ,Corp,'  stock  does  ', Client), 

prin('have  an  option  on?  ’), 

ratom  (N )  ,nl ,  nl ,! .  (Continued  on  next  page) 


Dr.  Dobb' s  Journal.  March  1985 


77 


OOI 


IdX  r AO VISOr  (Listing  Continued,  text  begins  on  page  64) 

Listing  One 


opt ion_l ieu_f ami ly ( Client ,Corp , P , 0 )  !. 

%  the  rule  of  section  318(a)(2) 

%  entity-of  returns  a  list  ((name  percent)  (name2  percent2 ) . . . ) 

num_f  rom (Client, Co rp,0,OldClient) 
nl, 

print('  Do  you  want  to  consider  applying  the  rules  of'), 

prin( 'Section  318(a)(2)  to  Client,'?  (enter  y  or  n)  '), 
ratom( X ) , nl , 

(  ( X  =  n  )  ;  (  X  =  '  N  '  )  )  ,  !  . 
num_f rom( Cl ient ,Corp,N,01dClient)  : - 
ps(P)  , 

entity_of (Client, Corp, LI, partnership,?) , 
est ( E ) , 

entity_of ( Client , Corp , L2,estate,E), 
tr ( T ) , 

entity_of (Cl ient, Corp, L3,trust,T) , 
gt(Tl ) , 

ent ity_of ( Cl ient , Corp , L4 , grantor_trust ,T1 ) , 
corp(C ) , 

entity_of ( Cl ient ,Corp , L5 , corpora t ion , C ) , 

appendal 1 ( [L1,L2,L3,L4,L5] ,L), 

ent i ty_own (Client, Co rp,L,0,N,OldClient) . 

ent i ty_of (Client, Co rp,L, Entity, Prompt )  : - 
nl , 

print('  Is  ', Cl ient , Prompt ) , 

prin( 'which  owns,  actually  or  constructively,  stock  in  ' 

, Corp , ' ?  ' ) , 
ratom ( X ) , nl , 

( ( X  =  y )  ;  ( X  =  'Y')),!, 

prin('  What  is  the  name  of  the  ', Entity,'?  '), 

ratom( Y ) , nl , 
add ( Ent ity ( Y ) ) , 

prin('  What  percentage  interest  does  ', Client,'  own?  '), 

ratom( Z ) ,nl , 

append ( (Y) , [ Z ) , P 1 )  ,  !  , 

ent i ty_of (Client, Corp, L2, Entity , Prompt ) , ! , 
append ( [PI] ,L2,L). 

ent i ty_of (Client, Corp , ( ] , Ent i ty .Prompt ) . 

ps(  '  a  partner  in  a  partnership'). 
est('  a  beneficiary  of  an  estate'). 

tr(  '  a  beneficiary  of  a  trust  (not  described  in  Section  401(a))'). 
gt('  the  grantor  of  a  grantor  trust'). 

corp( '  a  50  percent  or  more  shareholder  in  a  corporation'). 

%  note  Section  318(a)(5)(C)  is  included  since  entity_own  does 
%  not  include  num_to 

ent ity_own (Cl ient , Corp ,(), N , N ,_)  1. 

ent i ty_own( Client , Corp , ( [H,P] j  T ] , N , M ,01dCl ient ) 

H  /=  Client, 

H  /=  OldClient, 
num_a_own ( H , Corp , N 1 ) , 
num_f rom(H , Corp, N2, Client)  , 
num_opt ions ( H , Corp , N3 ) , 

N4  is  (Nl  +  N2  +  N3)  *  P  /  100,  %  this  creates  an  error  if 

%  N1+N2+N3  >  600 

N5  is  N4  +  N, 

!  , 

ent i ty_own (Client, Corp, T,N5,M, OldClient) . 
ent i ty_own ( Cl ient , Corp ,  [ H | T  j  , N , M , OldCl ient ) 

ent ity_own (Client ,Corp ,T,N,M, OldClient) . 

%  the  rule  of  Section  318(a)(3) 

%  ben_of_entity  returns  a  list  (name  name2  ...) 

num_to( Cl ient ,Corp , 0 ) 

not( individual(Client)  )  , 

Client  /=  Corp, 

print('  Do  you  want  to  consider  applying  the  rules  of'), 

prin (' Section  318(a)(3)  to  '.Client,'?  (enter  y  or  n)  '), 


78 


ooo 


Dr.  Dobb' s  Journal,  March  1985 


ratom (X ) , nl , 

( (X  =  n)  ;  (X  =  '  N  '  )  )  ,  !  . 
num_to (Client, Corp , N ) 

not( individual(Client) ) , 

Client  /=  Corp, ! , 

ben_of_ent i ty (Client, Corp, Ll,partner, partnership), 
ben_of _ent i ty(Client, Co rp,L2, beneficiary, estate), 
ben_of_ent i ty (Client, Corp, L3, beneficiary, trust), 
ben_of _ent i ty (Client, Corp, L4 , grantor ,grantor_t rust ) , 
ben_of _ent i ty (Client, Corp, L5, share holder, corporation), 
appendal 1 (  (L1,L2,L3,L4,L5] ,  L )  , 
to_entity_own(Client,Corp,L,0,N) . 
num_to( Client , Corp, 0 ) . 

ben_of_ent i ty ( Cl ient , Corp , L , Prompt , Entity)  : - 
Entity (Client ) , 

print('  Is  there  a  '.Prompt,'  of  '.Client,'  who  owns, 

actually '  )  , 

print 'or  constructively,  stock  in  '.Corp,'?  '), 
ratom ( X ) , nl , 

((X  =  y)  ;  (X  =  '  Y ' ) ) , 

( ( Ent i ty  =  trust , 

ask('  Is  the  interest  a  remote  contingent  interest?  ' 

, X2  ) , nl , 

( (X2  /=  y)  ,  ( X2  /=  ' Y ' ) ) )  ; 

(Entity  =  corporation, 

ask('  Does  the  shareholder  own  50%  or  more  of  the  stock? 

/ 

,X3)  , 

nl, ( <X3  =  y)  ;  (X3  =  '  Y  '  )  )  )  ; 

(Entity  /=  trust  ,  Entity  /=  corporation)), 
prin( '  What  is  the  name  of  the  '.Prompt,'?  '), 

ratom ( Y ) , nl , nl , 
type ( Y )  ,  !  , 

ben_of_ent i ty (Cl ient , Corp , L2 .Prompt .Entity) , ! , 
appendt [ Y ) ,L2,L) . 

ben_of_entity (Client, Corp, [] .Prompt, Entity)  !. 

to_entity_own(Client,Corp, (]  ,N,N)  !. 

to_entity_own(Client,Corp, [ H | T ] ,N,M) 

H  /=  Client, 
num_a_own ( H , Corp , Nl )  , 
num_f ami ly(H,Corp,N2)  , 
num_f rom ( H , Corp , N3 , Client ) , 
num_to ( 1 , Cor p , N  4 ) , 
num_opt ions ( H , Corp , N5 ) , 

N6  is  N  +  Nl  +  N2  +  N3  +  N4  +  N5, 

!  , 

to_ent i ty_own (Cl ient ,Corp,T,N6,M) . 
to_ent i ty_own (Client, Corp ,  [H  |  T]  ,  N ,  M )  :  - 
!  , 

to_ent i ty_own (Client, Co rp,T,N,M) . 

%  the  rule  of  Section  318(a)(4) 

num_opt ions ( Cl ient , Corp , N ) 
nl , 

print)'  Does  '.Client,'  own  an  option  (or  an  option  to 

acquire  '  )  , 

prin( 'an  option)  to  acquire  the  stock  in  '.Corp,'?  '), 
ratom ( X ) , nl , 

(  (  X  =  y  )  ;  ( X  =  '  Y '  )  )  ,  !  , 

print('  How  many  shares  of  ' ,Corp, '  stock  does  '.Client), 

print 'have  an  option  on?  '), 
ratom ( Y ) , nl , 
integer ( Y ) , ! , 

num_opt ions (Client, Corp , M ) , 

N  is  M  +  Y,  !  . 

num_opt  ions  (Client,  Corp ,  0  ) ,  End  Listing  One 


Listing  Two 

::  %  EXAMPLE  1.  Reg.  Section  1.318-2(b). 

:  section318! 

THIS  PROGRAM  WILL  DETERMINE  THE  NUMBER  OF  SHARES  OF  STOCK  A 
CLIENT  ACTUALLY  AND  CONSTRUCTIVELY  OWNS  AS  A  RESULT  OF  SECTION 
318(a)  OF  THE  INTERNAL  REVENUE  CODE. 

( Continued  on  next  page) 

79 


Dr.  Dobb's  Journal.  March  1985 


00-3 


/ HX  /\  U  VlSOr  (Listing  Continued,  text  begins  on  page  64) 

Listing  Two 


Enter  the  name  of  the  Client.  (One  word) 

Is  H  an  Individual,  Partnership,  Estate,  Trust,  Grantor 
trust,  or  Corporation?  (enter  i,  p,  e,  t,  g,  or  c)  i^ 

Enter  the  name  of  the  Corporation.  (One  word)  Corporation 

How  many  shares  of  Corporation  stock  does  H  actually  own?  2^ 

Does  H  have  a  spouse,  child,  grandchild,  or  parent?  £ 

What  is  the  name  of  the  first  family  member?  W 

Does  H  have  any  more  family  members?  ^ 

What  is  the  name  of  the  next  family  member?  GS^ 

Does  H  have  any  more  family  members?  ii 

How  many  shares  of  Corporation  stock  does  W  actually  own?  25^ 

Do  you  want  to  consider  applying  the  rules  of  Section 
318(a)(2)  to  W?  (enter  y  or  n)  ri 

Does  H  own  an  option  (or  an  option  to  acquire  an  option)  to 
acquire  the  stock  in  Corporation  which  W  actually  owns?  ri 

Does  W  own  an  option  (or  an  option  to  acquire  an  option)  to 
acquire  the  stock  in  Corporation?  ri 

How  many  shares  of  Corporation  stock  does  S  actually  own?  25 

Do  you  want  to  consider  applying  the  rules  of  Section 
318(a)(2)  to  S?  (enter  y  or  n)  in 

Does  H  own  an  option  (or  an  option  to  acquire  an  option)  to 
acquire  the  stock  in  Corporation  which  S  actually  owns?  n 

Does  S  own  an  option  (or  an  option  to  acquire  an  option)  to 
acquire  the  stock  in  Corporation?  in 

How  many  shares  of  Corporation  stock  does  GS  actually  own? 

25 

Do  you  want  to  consider  applying  the  rules  of  Section 
318(a)(2)  to  GS?  (enter  y  or  n)  £ 

Does  H  own  an  option  (or  an  option  to  acquire  an  option)  to 
acquire  the  stock  in  Corporation  which  GS  actually  owns?  ri 

Does  GS  own  an  option  (or  an  option  to  acquire  an  option)  to 
acquire  the  stock  in  Corporation?  £ 

Do  you  want  to  consider  applying  the  rules  of  Section 
318(a)(2)  to  H?  (enter  y  or  n)  n^ 

Does  H  own  an  option  (or  an  option  to  acquire  an  option)  to 
acquire  the  stock  in  Corporation?  ri 

The  number  of  shares  of  Corporation  stock  actually  owned  by  H 
is  25 . 

The  number  of  shares  of  Corporation  stock  constructively 
owned  by  H  is  75. 

Do  you  want  to  try  another  Client? 


:  %  EXAMPLE  2.  Reg.  Section  1.318-2(c),  Example(2). 

THIS  PROGRAM  WILL  DETERMINE  THE  NUMBER  OF  SHARES  OF  STOCK  A 
CLIENT  ACTUALLY  AND  CONSTRUCTIVELY  OWNS  AS  A  RESULT  OF  SECTION 
318(a)  OF  THE  INTERNAL  REVENUE  CODE. 


80 


Enter  the  name  of  the  Client.  (One  word)  A 


(Continued  on  page  82) 
Dr.  Dobb's  Journal.  March  1985 


/« X  /i  avisur  (Listing  Continued,  text  begins  on  page  64) 

Listing  Two 


Enter  the  name  of  the  Corporation.  (One  word)  Corporation 

How  many  shares  of  Corporation  stock  does  A  actually  own?  75 

Does  A  have  a  spouse,  child,  grandchild,  or  parent.  n 

Do  you  want  to  consider  applying  the  rules  of  Section 
318(a)(2)  to  A?  (enter  y  or  n) 

Is  A  a  partner  in  a  partnership  which  owns,  actually  or 
constructively,  stock  in  Corporation?  ri 

Is  A  a  beneficiary  of  an  estate  which  owns,  actually  or 
constructively,  stock  in  Corporation?  n 

Is  A  a  beneficiary  of  a  trust  (not  described  in  Section 
401(a))  which  owns,  actually  or  constructively,  stock  in 
Corporation?  ^ 

What  is  the  name  of  the  trust?  Trust 

What  percentage  interest  does  A  own?  £ 

Is  A  a  beneficiary  of  a  trust  (not  described  in  Section 
401(a))  which  owns,  actually  or  constructively,  stock  in 
Corporation?  n 

Is  A  the  grantor  of  a  grantor  trust  which  owns,  actually  or 
constructively,  stock  in  Corporation?  ti 

Is  A  a  50  percent  or  more  shareholder  in  a  corporation  which 
owns,  actually  or  constructively,  stock  in  Corporation?  n 

How  many  share  of  Corporation  stock  does  Trust  actually  own? 
25 

Do  you  want  to  consider  applying  the  rules  of  Section 
318(a)(2)  to  Trust?  (enter  y  or  n)  ri 

Does  Trust  own  an  option  (or  an  option  to  acquire  an  option) 
to  acquire  the  stock  in  Corporation?  ri 

Does  A  own  an  option  (or  an  option  to  acquire  an  option)  to 
acquire  the  stock  in  Corporation?  ri 

The  number  of  shares  of  Corporation  stock  actually  owned  by  A 
is  75. 

The  number  of  shares  of  Corporation  stock  constructively 
owned  by  A  is  1. 

Do  you  want  to  try  another  Client?  £ 


:  %  EXAMPLE  3.  Reg.  Section  1.318-2(c),  Example(4). 

THIS  PROGRAM  WILL  DETERMINE  THE  NUMBER  OF  SHARES  OF  STOCK  A 
CLIENT  ACTUALLY  AND  CONSTRUCTIVELY  OWNS  AS  A  RESULT  OF  SECTION 
318(a)  OF  THE  INTERNAL  REVENUE  CODE. 

Enter  the  name  of  the  Client.  (One  word)  A 

Enter  the  name  of  the  Corporation  (One  word)  Corporation  O 

How  many  shares  of  Corporation^  stock  does  A  actually  own? 
50 

Does  A  have  a  spouse,  child,  grandchild,  or  parent?  n 

Do  you  want  to  consider  applying  the  rules  of  Section 
318(a)(2)  to  A?  (enter  y  or  n ) 

Is  A  a  partner  in  a  partnership  which  owns,  actually  or 
constructively,  stock  in  Corporat ion_0? 


Dr.  Dobb's  Journal,  March  1985 


82 

225 


Is  A  a  beneficiary  of  an  estate  which  owns,  actually  or 
constructively,  stock  in  Corporation_0?  ri 

Is  A  a  beneficiary  of  a  trust  (not  described  in  Section 
401(a))  which  owns,  actually  or  constructively,  stock  in 
Corporation_0?  ji 

Is  A  the  grantor  of  a  grantor  trust  which  owns,  actually  or 
constructively,  stock  in  Corporat ion_0?  ji 

Is  A  a  50  percent  or  more  shareholder  in  a  corporation  which 
owns,  actually  or  constructively,  stock  in  0orporation_O?  ^ 

What  is  the  name  of  the  corporation?  Corporation  M 

What  percentage  interest  does  A  own?  70^ 

Is  A  a  50  percent  or  more  shareholder  in  a  corporation  which 
owns,  actually  or  constructively,  stock  in  Corporat ion_0?  n 

How  many  shares  of  Corporat ion_0  stock  does  Corporat ion_M 
actually  own?  5^ 

Do  you  want  to  consider  applying  the  rules  of  Section 
318(a)(2)  to  Corporation_M?  (enter  y  or  n)  n 

Does  Corporat ion_M  own  an  option  (or  an  option  to  acquire  an 
option)  to  acquire  the  stock  in  Corporation_0?  ri 

Does  A  own  an  option  (or  an  option  to  acquire  an  option)  to 
acquire  the  stock  in  Corporation_0?  n 

The  number  of  shares  of  Corporation_0  stock  actually  owned  by 
A  is  50. 

The  number  of  shares  of  Corporat ion_0  stock  constructively 
owned  by  A  is  35. 

Do  you  want  to  try  another  Client?  £ 


THIS  PROGRAM  WILL  DETERMINE  THE  NUMBER  OF  SHARES  OF  STOCK  A 
CLIENT  ACTUALLY  AND  CONSTRUCTIVELY  OWNS  AS  A  RESULT  OF  SECTION 
318(a)  OF  THE  INTERNAL  REVENUE  CODE. 


Enter  the  name  of  the  Client.  (One  word)  Corporation  M 

Enter  the  name  of  the  Corporation.  (One  word)  Corporation  O 

How  many  shares  of  Corporat ion_0  stock  does  Corporation  M 
actually  own?  5J3 

Do  you  want  to  consider  applying  the  rules  of  Section 
318(a)(2)  to  Corporation_M?  (enter  y  or  n)  n 

Do  you  want  to  consider  applying  the  rules  of  Section 
318(a)(3)  to  Corporat ion_M?  (enter  y  or  n) 

Is  there  a  shareholder  of  Corporat ion_M  who  owns,  actually  or 
constructively,  stock  in  Corporation_0?  ^ 

Does  the  shareholder  own  50%  or  more  of  the  stock? 

What  is  the  name  of  the  shareholder?  A 

Is  there  a  shareholder  of  Corporat ion_M  who  owns,  actually  or 
constructively,  stock  in  Corporation^?  ri 

How  many  shares  of  Corporation  O  stock  does  A  actually  own? 

5£  ~ 

Does  A  have  a  spouse,  child,  grandchild,  or  parent?  n 

Do  you  want  to  consider  applying  the  rules  of  Section 
318(a)(2)  to  A?  (enter  y  or  n)  ri 

Does  A  own  an  option  (or  an  option  to  acquire  an  option)  to 
acquire  the  stock  in  Corporat ion_0?  n 


83 

226 


(Continued  on  next  page) 
Dr.  Dobb  s  Journal.  March  1985 


1  a X  r 1 U  VlSOr  (Listing  Continued,  text  begins  on  page  64) 

Listing  Two 


Does  Corporat ion_M  own  an  option  (or  an  option  to  acquire  an 
option)  to  acquire  the  stock  in  Corporation_0?  n 

The  number  of  shares  of  0orporation_O  stock  actually  owned  by 
Corporation_M  is  50. 

The  number  of  shares  of  Corporation_0  stock  constructively 
owned  by  Corporation_M  is  50. 

Do  you  want  to  try  another  Client?  n^  End  Listing  Two 


Listing  Three 

:  %  An  Example  From  Req.  Section  1 . 318-4 (c  )( 1  ) 


trace [num  a  own,num  con  own,num  family, num  from]! 
trace [num  to,num  options]! 


section318  ! 


THIS  PROGRAM  WILL  DETERMINE  THE  NUMBER  OF  SHARES  OF  STOCK  A 
CLIENT  ACTUALLY  AND  CONSTRUCTIVELY  OWNS  AS  A  RESULT  OF  SECTION 
318(a)  OF  THE  INTERNAL  REVENUE  CODE. 


Enter  the  name  of  the  Client.  (One  word)  Corporation  Y 

Is  Corporat ion_Y  an  Individual,  Partnership,  Estate,  Trust, 
Grantor  trust,  or  Corporation?  (enter  i,  p,  e,  t,  g,  or  c)  £ 


Enter  the  name  of  the  Corporation.  (One  word)  Corporation  Z 

C | >num_a_own (' Corporat ion_Y ' ,  ' Corporat ion_Z 1 ,  _6 ) 

How  many  shares  of  Corporation_Z  stock  does  Corporation_Y 
actually  own?  £ 


E 

C 

C 

R 

E 

C 


<num_a_own ( 1 Corporat ion_Y ' ,  ' Corporat ion_Z ' ,  0) 
>num_con_own( 1 Corporat ion_Y' ,  1 Corporat ion_Z ' ,  _4 ) 

>num_f ami ly (' Corporat ion_Y ' ,  ' Corporat ion_Z 1 ,  _ 2 6 ) 

>num_f ami ly ( 1 Corporat ion_Y ' ,  'Corporation_Z ' ,  0) 
<num_f ami ly (' Corporat ion_Y ' ,  ' Corporation_Z 1 ,  0) 
>num_f rom( 1 Corporat ion_Y 1 ,  1 Corporat ion_Z ' ,  0,  zzzz) 


Do  you  want  to  consider  applying  the  rules  of  Section 
318(a)(2)  to  Corporation_Y?  (enter  y  or  n )  n 


E | | <num_f rom( 1 Corporat ion_Y 1 ,  ' Corporat ion_Z 1 ,  0,  zzzz) 

C j j >num_to( 1 Corporation_Y 1 ,  'Corporation_Z 1 ,  0) 

Do  you  want  to  consider  applying  the  rules  of  Section 
318(a)(3)  to  Corporation_Y?  (enter  y  or  n)  £ 


R |  | >num_to( 1 Corporat ion_Y ' ,  'Corporation_Z 1 ,  _ 2 8 ) 

Is  there  a  shareholder  of  Corporation_Y  who  owns,  actually  or 
constructively,  stock  in  Corporation_Z?  £ 


Does  the  shareholder  own  50%  or  more  of  the  stock?  £ 
What  is  the  name  of  the  shareholder?  A 


Is  A  an  Individual,  Partnership,  Estate,  Trust,  Grantor 
trust,  or  corporation?  (enter  i,  p,  e,  t,  g,  or  c)  £ 

Is  there  a  shareholder  of  Corporat ion_Y  who  owns,  actually  or 
constructively,  stock  in  Corporation_Z?  in 


C | | | >num_a_own ( ' A ' ,  1 Corporat ion_Z 1 ,  _ 1 36) 

How  many  share  of  Corporation_Z  stock  does  A  actually  own?  £ 

E  I  I  I <num_a_own ( ' A' ,  ' Corpora t ion_Z ' ,  0) 

C | I | >num_family ( 'A' ,  ' Corporat ion_Z ' ,  _ 1 37) 


Dr.  Dobb's  Journal.  March  1985 


84 

227 


Tax  Advisor  (Listing  Continued,  text  begins  on  page  64) 

Listing  Three 


Does  A  have  a  spouse,  child,  grandchild,  or  parent?  n 

E  II  I  <num_f ami ly ( 1 A 1 ,  ' Corporat ion_Z 1 ,  0) 

C I [ I >num_f rom( ' A' ,  'Corporation_Z ' ,  0,  1 Corporat ion_Y ' ) 

Do  you  want  to  consider  applying  the  rules  of  Section 
318(a)(2)  to  A?  (enter  y  or  n)  jr 

R | |  | >num_f rom ( 1 A ' ,  ' Corporat ion_Z ' ,  _138,  ' Corporat ion_Y ’ ) 

Is  A  a  partner  in  a  partnership  which  owns,  actually  or 
constructively,  stock  in  Corporation_Z?  ri 

Is  A  a  beneficiary  of  an  estate  which  owns,  actually  or 
constructively,  stock  in  Corporation_Z?  n 

Is  A  the  grantor  of  a  grantor  trust  which  owns,  actually  or 
constructively,  stock  in  Corporation_Z?  ri 

Is  A  a  50  percent  or  more  shareholder  in  a  corporation  which 
owns,  actually  or  constructively,  stock  in  Corporation_Z? 

What  is  the  name  of  the  corporation?  Corporation  X 

What  percentage  interest  does  A  own?  100 

Is  A  a  50  percent  or  more  shareholder  in  a  corporation  which 
owns,  actually  or  constructively,  stock  in  Corporation_Z?  in 

C| | | | >num_a_own( ' Corporation_X ’ ,  ' Corporat ion_Z 1 ,  _242) 

How  many  shares  of  Corporation_Z  stock  does  Corporation_X 
actually  own?  50 

E I  I  I  I <num_a_own ( ' Corporat ion_X ' ,  ' Corporat ion_Z ' ,  50) 

C | I  I  I >num_f rom( ' Corporat ion_X ' ,  'Corporation_Z ' ,  0,  'A') 

Do  you  want  to  consider  applying  the  rules  of  Section 
318(a)(2)  to  Corporat ion_X?  (enter  y  or  n)  jn 

E I  I  I  I <num_f rom ( ' Corporat ion_X ' ,  1 Corporat ion_Z ' ,  0,  'A') 

C) i 1 \>num_options( ' Corporat ion_X ‘ ,  1 Corporat ion_Z ' ,  _ 2 4  4) 

Does  Corporat ion_X  own  an  option  (or  an  option  to  acquire  an 
option)  to  acquire  the  stock  in  Corporation_Z?  ri 

R  I >num_opt ions ( 1 Corporat ion_X ' ,  ' Corporat ion_Z ' ,  0) 

E  | <num_opt ions ( ‘ Corporat ion_X ' ,  ' Corporation_Z 1 ,  0) 

E  <num_f rom( ' A ' ,  ' Corporat ion_Z  ' ,  50,  ' Corporat ion_Y 1 ) 

C  >num_to ( ’ A 1 ,  'Corporation_Z  '  ,  0) 

R  >num_to(,A',  ' Corporat ion_Z ' ,  _ 1 39 ) 

R  >num_to ( 1 A ' ,  1 Corporat ion_Z  1 ,  0) 

E  <num_to( 'A' ,  ' Corporat ion_Z  ’ ,  0) 

C  >num_option( 'A' ,  1 Corporat ion_Z  1 ,  _ 1 40) 

Does  A  own  an  option  (or  an  option  to  acquire  an  option)  to 
acquire  the  stock  in  Corporation_Z?  ri 

R  I >num_opt ions ( ' A ' ,  ' Corporat ion_Z  ' ,  0) 

E  I  <num_opt ions ( ' A 1 ,  ' Corporat ion_Z ' ,  0) 

E  <num_to( 'Corporation_Y ' ,  ' Corporat ion_Z ' ,  50) 

C  >num_opt ions (' Corporat ion_X 1 ,  ' Corporat ion_Z 1 ,  _ 2 9 ) 

Does  Corporat ion_Y  own  an  option  (or  an  option  to  acquire  an 
option)  to  acquire  the  stock  in  Corporation_Z?  ri 

R  I >num_opt ions (' Corporat ion_Y ' ,  ' Corporat ion_Z  1 ,  0) 

E  | <num_opt ions (' Corporat ion_Y ' ,  1 Corporat ion_Z 1 ,  0) 

E  <num_con_own (' Corporat ion_Y ' ,  ' Corporat ion_Z 1 ,  50) 

The  number  of  shares  of  Corporation_Z  stock  actually  owned  by 
Corporat ion_Y  is  0. 

The  number  of  shares  of  Corporation_Z  stock  constructively 
owned  by  Corporat ion_Y  is  50. 


86 

228 


Do  you  want  to  try  another  Client?  ri 


End  Listing  Three 

Dr  Dobb's  Journal.  March  1985 


Listing  Four 


%  THE  SECTION  318(a)  PROLOG  PROGRAM  WHICH  ANSWERS  HOW,  HELP  AND 
%  WHY 

%  By  Dean  A.  Schlobohm 

%  (c)  Copyright  1984  by  Dean  A.  Schlobohm.  All  Rights  Reserved. 

%  Version  1.0 
%  Date:  6/11/84 
%  Bugs: 

%  1.  Only  includes  the  Section  318(a)(1)  family  attribution 

%  rules. 

%  2.  Very  crude  English  interface. 

%  To  Use:  type  "prolog  sec318-l .pro<RET>"  then  after  Prolog  is 
%  loaded  type  "section318! <RET>" 


*  preliminary  functions 
append( [ 1 , L, L)  : -  ! . 

append(  [X | LI ]  ,  L2 ,  [X | L3 ]  )  :-  append ( LI , L2 , L3 ) . 

appendall (  (!,[]). 
appendal 1 (  (H ) , H ) . 

appendal 1 ( [ HI , H2 ] , L )  :-  append ( HI , H2 , L) . 
appendal 1 ( (HI , H2 | T) , L )  :- 
append (HI  , H2  ,X) , 
appendall (T , Z ) , ! , 
append ( X , Z , L ) . 

member (X , (X  I _) )  :-  !. 

member (X, (_|  Y] )  :-  member  (X,Y). 

add ( X )  :-  X,  !  . 

add(X)  :-  asserta ( X ) , ! . 


%  The  Interface  Program. 

section318  :- 

prompt(Old, '  1 ) , 

nl,nl,nl,nl,nl,nl,nl,nl,nl,nl,nl,nl,nl,nl,nl,nl,nl,nl,nl,nl, 
pr i nt (  1  THIS  PROGRAM  WILL  DETERMINE  THE  NUMBER  OF  SHARES 

OF*  )  , 

pr int ( ' STOCK  A  CLIENT  CONSTRUCTIVELY  OWNS  AS  A  RESULT  OF 
SECTION  '), 

print(  ' 318(a)  (1 )  OF  THE  INTERNAL  REVENUE  CODE.'), 
nl , nl , 

ask('  Enter  the  name  of  the  Client.  (One  word)  1 

.Client ) , nl , 

ask('  Enter  the  name  of  the  Corporation.  (One  word)  ' 

, Corp ) , nl , 

add(corporation(Corp) ) , ! , 
num_f ami ly (Client , Corp, N_con , [ ) , H ) 

,  !  ,  n  1 ,  n  1 , 

print('  The  number  of  shares  of  ' ,Corp,'  stock 

constructively' ), 

print('owned  by  ', Client,'  as  a  result  of  Section  318(a)(1) 
is  '  , N_con ,'.'), 

nl , nl , ! , 

ask( '  Do  you  want  to  know  how  I  reached  this  conclusion?  ' 
,  Z  )  ,  1  , 

( (Z  =  y)  ;  (Z  =  '  Y '  ) ) , n 1 , 

how ( num_f ami ly (Client , Corp , N_con , W, H ) ) , nl , n 1 , 
prompt (_, Old ) . 

indi vidual ( Cl ient , W , H )  :- 

print'  Is  ',  Client,  '  an  individual?  '), 

ratom(X) , nl , ! , 

respondl(X,individual(Client,W,H) ,W) . 

respondl (y , indi v  idual ( Client ,  ,[H]),  )  :- 

concatf  ['  FACT:  ',ClTent,'  Ts  an  indi vidual .'], H )  , 
assertal ind (Client, _, [H] ) ) , ! . 
respondl(why,individual(Client,W,H), [) )  : - 

print('This  is  what  the  Internal  Revenue  Code  provides!'),!, 
nl. 


(Continued  on  next  page) 

Dr.  Dobb's  Journal.  March  1985  87 

229 


Tax  Advisor  (Listing  Continued,  text  begins  on  page  64) 

Listing  Four 


individual(Client, [) ,H)  ,  l. 
respondl (why ,individual(Client,W,H) , [P|T] )  :  - 

print ( P ) , nl ,  ! , 
individual(Client,T,H)  . 
respondl (help, Y, 2  ) 

pr int ( 1  Sect  ion  318(a)(1)  only  applies  to  individuals .'),!, nl , 
Y. 

respondl ( n , individual ( Cl ient ,_, [H ) ) ,  ) 

concat(('  FACT:  ', Client,1  Ts  not  an  indi vidual . 1 ] , H ) , 
asserta(non_ind(Client,_,  [H])),!,fail. 
respondl ( X , Y , 2 ) 

print('I  do  not  understand!  Enter  y,  n,  help,  or 
why. ' ) , ( ,nl, 

Y. 

%  the  rule  for  actual  ownership 
num_a_own (C ,C , 0 , W, (H ] )  !. 

num_a_own ( Cl ient , Corp , N , W, (H) )  :- 

print('  How  many  shares  of  ' ,Corp, '  stock  does  Client), 

pr in ( 1  actual ly  own?  '), 
ratom ( X ) , nl , 
integer (X) , 

concatl I1  FACT:  Client,'  actually  owns  ',X,' 

shares . ' ] , H ) , 

N  =  X,  !  . 

num_a_own ( Cl ient , Corp , N ,W, [H] )  :- 

print! '  Error  — >■  You  must  enter  an  integer  !'),  nl ,!  , 

num_a_own (Client, Corp , N , W, [H ) ) . 

%  the  rule  of  Secton  318(a)(1) 

num_f ami ly ( Cl ient , Corp ,N ,W, H )  :- 

get_rule (1,2), 
append ( W , [ 2 ) , X ) , 
individual(Client,X,Y),  !, 
spouse_own( Cl ient ,Corp,Nl,X,Q), 
family_own(Client,Corp,N2,X,R) , 

N  is  Nl  +  N2, 

appendall ( l [2]  ,Y,Q,R]  ,H),  !. 

num_f  ami ly ( Cl ient ,_, 0 ,_, H )  :- 
get_rule (1,2), 
non_ind (Client, _,Y) , I , 
append (  [2 ] , Y,H  )  ,  1  . 

%  definition  of  spouse_own 

spouse_own(Client,Corp,N,W,H)  :- 
get_rule (2,2), 
append (W, [2] ,A), 
spouse (Client, LI, A, HI), 
append( [2 ] ,H1 ,H2 ) , 
ind_own (Client, Corp, LI, N,H2,H3)  , 
appendall( [ [2] ,H1,H3] ,H), ! . 

spouse_own (Client ,_, 0 ,_,[ H ]  )  :-  concatl  [ 1  FACT:  ', Client,' 

has  no  spouse H ),! . 

%  definition  of  family_own 

family_own(Client ,Corp,N ,W,H )  :- 

get_rule (3,2), 
append (W, [2 ] , A) , 
family(Client,Ll,A,Hl) , 
append ( [2) ,H1,H2), 

ind_own ( Cl ient , Corp , LI , Nl , H2  ,  H3 )  ,  !  , 
f ami ly_own( Client , Corp,N2,W,H4) , ! , 

N  is  Nl  +  N2, 

appendall ([[2),H1,H3,H4],H),!. 

f ami ly_own ( Cl ient ,_, 0 ,_, ( H ] )  :- 

concatl ( '  FACT:  '.Client,'  has  no  more  family 

members . ' ] , H ) , ! . 

%  definition  of  ind_own 

%  note  Section  318(a)(5)(B)  is  included  since  ind_own  does 

%  not  include  num_family. 

%  does  NOT  include  the  other  attribution  rules! 


88 


oon 


Dr.  Dobb’s  Journal.  March  1985 


ind_own(Client,Corp, [] ,0,_, [) ) : • 
ind_own(Client,Corp, [P] ,N,W,H)  t- 
P  /=  Client, ! , 
nura_a_own ( P , Corp, N , W, HI ) , 

!  , 

H  =  HI , ! . 

ind_own ( Cl ient , Corp 0 ,W, W)  !. 

%  the  definition  of  a  family  for  purposes  of  Section  31. 
spouse(C,  [L]  , W ,  [H]  ) 

prin(‘  Does  ',C,'  have  a  spouse?  '), 

ratom ( X ) , n 1 , 

respond 2(X, spouse (C,[L],W,[H]),W),!. 

spousel (C, [ L ] [H] )  j- 

prin(  '  What  is  the  name  of  the  spouse?  '), 

ratom ( L ) , nl , 

concatl  ['  FACT:  ',L,'  is  the  spouse  of  1 , C H ),! • 

family(C, [ L } ,W, [H] ) 

print'  Does  '  ,C, '  have  a  child,  grandchild  or  parent?  ’), 

ratomt  X ) , nl , 

respond2(X, familytC, [L],W, [H] ),W),!. 
familyl (C, [L] ,_, [H] ) 

print'  What  is  the  name  of  the  child,  grandchild  or 

parent?  '  )  , 
ratom ( L ) , nl , 

concatt ( 1  FACT:  ',L,'  is  a  family  member  of 

',C,'.'],H),1. 

respond2(y,Relation(Client,Person,_,H),_)  : - 
concat ( [Relation, ' 1 '], Z ) , 

Z(Client,Person,_,H) , ! . 

respond2(why,Relation(Client, Person, _,H) , [] )  :- 

printt'This  is  what  the  Internal  Revenue  Code  provides!'),!, 
nl , 

Relation(Client,Person,  [ ] ,H)  ,  !  . 
respond 2 ( why , Re  la t ion ( Cl ient , Person ,_, H ) , [P | T) )  : - 
print ( P ) , nl , ! , 

Relation(Client,Person,T,H) . 
respond2( help, spouse (Cl ient, Person, W,H) ,_)  :- 

printt'A  spouse  does  not  include  one  who  is  legally  separated 
from '  )  , 

print(Client, '  under  a  decree  of  divorce  or  separate 
maintenance . ' ) , 

printt 'Section  318(a)(l)(A)(i). ' ), 

!  ,  nl , 

spouse (Client, Per son, W,H) . 
respond 2 ( help,family(Client,Person,W,H) ,_)  :  - 

printt'A  child  includes  a  legally  adopted  child.  Section 
318(a)(1)(B).'), 

!  ,  nl , 

family(Client,Person,W,H) . 
respond 2 ( n , Relation (Client, [ H J ) ,_)  :-  !,fail. 
respond2 ( X , Y , Z )  :- 

print('I  do  not  understand!  Enter  y,  n,  help,  or 
why. ' ) , ! , nl , 

Y . 


%  definition  of  rules  to  use  in  how  and  why 

get_rule ( 1 , ' An  individual  is  deemed  to  own  the  stock  owned  by 
his  family.  Section  318(a)(1).'). 
get_rule( 2 , 'A  spouse  is  a  family  member.  Section 
318(a) (1) (A) ( i) . ' ) . 

get_rule ( 3 , ' A  child,  grandchild  or  parent 

is  a  family  member.  Section  318 ( a) ( 1 ) (A) ( i i ) . ' ) . 

%  definition  of  “how" 

how ( num_f  amily(Client , Corp ,N ,_,  [H | T]  ) )  :  - 

pr int ( '  START  OF  EXPLAINATION ' ) , 

nl , 

print('  The  number  of  shares  of  ' ,Corp, '  stock 

constructively  owned'), 

print('by  '.Client,'  as  a  result  of  Section  318(a)(1)  is 

,N, ' , ’ ) , 

print (' determined  as  f ol lows : ' ) , nl , 
print ( H  )  ,  !  , 


( Continued  on  next  page ) 


Dr.  Dobb’s  Journal,  March  1985 


89 

231 


Tax  Advisor  (Listing  Continued,  text  begins  on  page  64) 

Listing  Four 


howl (T) , ! . 
how(_)  : - 

print!'  I  can  not  give  you  an  explaination ! ' ) , ! . 

howl! 11 )  : - 
nl , 

print! '  END  OF  EXPLAINATION ') , 

!  . 

howl ( [H | T] ) 
nl, 

print(H)  , 
howl (T ) , 

!  .  End  Listing  Four 


Listing  Five 


THIS  PROGRAM  WILL  DETERMINE  THE  NUMBER  OF  SHARES  OF  STOCK  A 
CLIENT  CONSTRUCTIVELY  OWNS  AS  A  RESULT  OF  SECTION  318(a)(1)  OF  THE 
INTERNAL  REVENUE  CODE. 

Enter  the  name  of  the  Client.  (One  word)  Dean  Schlobohm 

Enter  the  name  of  the  Corporation.  (One  word)  Corporation 

Is  Dean_Schlobohm  an  individual?  why 

An  individual  is  deemed  to  own  the  stock  owned  by  his  family. 
Section  318(a)(1). 

Is  Dean_Schlobohm  an  individual?  why 

This  is  what  the  Internal  Revenue  Code  provides! 

Is  Dean_Schlobohm  an  individual?  y 

Does  Dean_Schlobohm  have  a  spouse?  why 

An  individual  is  deemed  to  own  the  stock  owned  by  his  family. 
Section  318(a)91). 

Does  Dean_Schlobohm  have  a  spouse?  why 

A  spouse  is  a  family  member.  Section  3 18 ( a ) ( 1 ) ( A ) ( i ) . 

Does  Dean_Schlobohm  have  a  spouse?  why 

This  is  what  the  Internal  Revenue  Code  provides! 

Does  Dean-Schlobohm  have  a  spouse?  help 

A  spouse  does  not  include  one  who  is  legally  separated  from 
Dean  Schlobohm  under  a  decree  of  divorce  or  separate  maintenance. 
Section  318 ( a ) ( 1 ) ( A) ( i ) . 

Does  Dean_Schlobohm  have  a  spouse?  y 

What  is  the  name  of  the  spouse?  Mary  Schlobohm 

How  many  shares  of  Corporation  stock  does  Mary_Schlobohm 
actually  own?  300 

Does  Dean_Schlobohm  have  a  child,  grandchild  or  parent?  why 

An  individual  is  deemed  to  own  the  stock  owned  by  his  family. 
Section  318(a)(1). 


Does  Dean_Schlobohm  have  a  child,  grandchild  or  parent?  why 

A  child,  grandchild  or  parent  is  a  family  member.  Section 
318 (a)(1) (A) ( ii ) . 


( Continued  on  page  92) 

90 


P.3? 


Dr.  Dobb's  Journal.  March  1985 


Tax  Advisor  (Listing  Continued,  text  begins  on  page  64) 

Listing  Five 


Does  Dean_Schlobohm  have  a  child,  grandchild  or  parent?  why 

This  is  what  the  Internal  Revenue  Code  provides! 

Does  Dean_Schlobohm  have  a  child,  grandchild  or  parent?  help 

A  child  includes  a  legally  adopted  child.  Section  318(a)(1)(B). 

Does  Dean_Schlobohm  have  a  child,  grandchild  or  parent? 

What  is  the  name  of  the  child,  grandchild  or  parent? 

Dean  F  Schlobohm 

How  many  share  of  Corporation  stock  does  Dean_F_Schlobohm 
actually  own?  100 

Does  Dean_Schlobohm  have  a  child,  grandchild  or  parent?  11 

The  number  of  shares  of  Corporation  stock  constructively 
owned  by  Dean_Schlobohm  as  a  result  of  Section  318(a)(1)  is  400. 

Do  you  want  to  know  how  I  reached  this  conclusion?  ^ 

START  OF  EXPLANATION 

The  number  of  shares  of  Corporation  stock  constructively 
owned  by  Dean_Schlobohm  as  a  result  of  Section  318(a)(1)  is  400, 
determined  as  follows: 

An  individual  is  deemed  to  own  the  stock  owned  by  his  family. 

Section  318(a)(1). 

FACT:  Dean_Schlobohm  is  an  individual. 

A  spouse  is  a  family  member.  Section  3 18 ( a ) ( 1 ) ( A ) ( i ) . 

FACT:  Mary_Schlobohm  is  the  spouse  of  Dean_Schlobohm. 

FACT:  Mary_Schlobohm  actually  owns  300  shares. 

A  child,  grandchild  or  parent  is  a  family  member.  Section 
318(a) (1) (A) (ii)  . 

FACT:  Dean_F_Schlobohm  is  a  family  member  of  Dean_Schlobohm . 

FACT:  Dean_F_Schlobohm  actually  owns  100  shares. 

FACT:  Dean_Sch lobohm  has  no  more  family  members. 

END  OF  EXPLANATION  End  Listings 


Dr.  Dobb's  Journal,  March  1985 


92 

233 


16-BIT  SOFTWARE  TOOLBOX 


by  Ray  Duncan 


Z-8001  CPU  for  S-100  Bus 

Yes,  gentle  reader,  we  occasionally  do 
discuss  some  other  kind  of  micropro¬ 
cessor  besides  the  Intel  family.  This 
month,  we’d  like  to  announce  the  avail¬ 
ability  of  an  S-100  bus  Z-8001  CPU 
card  called  the  Super  Z-10  from  Way 
Engineering.  The  CPU  runs  at  10 
MHz,  is  fully  compatible  with  the 
IEEE  696  standard,  and  includes  two 
on-board  RS-232  ports,  32K  of  phan¬ 
tom  EPROM,  and  a  complete  Forth 
monitor  in  EPROM.  You  can  use  the 
Z-8001  CPU  card  with  Compupro 
high-speed  static  RAM  boards  and 
with  most  popular  S-100  hard  disk 
controllers.  The  introductory  cost  of 
the  Super  Z-10  CPU  is  $1000.00. 

Digital  Research  CP/M-8000  is 
available  in  a  customized  version  for 
the  Super  Z-10  and  includes  the  usual 
CP/M  utilities  (PIP,  ED,  DDT,  and  so 
on)  as  well  as  a  C  compiler,  assembler, 
and  linker.  The  BIOS  is  written  in  C 
and  assembly  language,  and  the  source 
code  is  supplied.  A  disk-based  Forth 
development  system  that  runs  under 
CP/M-8000  will  also  be  available  in 
the  near  future.  CP/M-8000,  which  re¬ 
quires  a  hard  disk  and  256K  of  RAM, 
costs  $350.00.  For  more  information, 
contact  Way  Engineering  at  2011  Tu¬ 
lip  Tree  Fane,  Fa  Canada,  CA  91011 
(213)  245-1480.  I  am  taking  delivery 
of  one  of  these  systems  within  the  next 
few  days,  so  you  should  see  a  detailed 
report  in  a  later  column. 

MacFeedback 

Mike  Cohen  of  the  Bronx  writes:  “In 
the  December  1984  issue,  you  incor¬ 
rectly  state  that  the  Macintosh  68000 
assembler  requires  two  machines.  It 
simply  isn’t  true,  as  I’ve  used  that  as¬ 
sembler  along  with  Consulair’s  excel¬ 
lent  Mac  C  compiler  on  a  single  Mac¬ 
intosh  with  no  problems.  True,  Apple 

96 

OOii 


provides  a  debugger,  which  does  re¬ 
quire  two  machines,  but  the  Macsbug 
debugger  (which  is  also  provided  on 
the  same  disk)  requires  only  one  ma¬ 
chine.  It  does  have  some  problems  in 
128K,  since  it  can’t  remain  resident 
while  the  assembler  or  editor  are  being 
used.  There  are  no  such  problems  on  a 
512K  Macintosh,  since  I  keep  the  full 
screen  debugger  resident  while  editing, 
assembling,  and  compiling  programs. 

“Also,  the  statement  that  most  high- 
level  languages  are  ‘incredibly  weak, 
bug-ridden,  and  slow’  is  untrue  of  ev¬ 
erything  other  than  MS-Basic.  Mac- 
Pascal  is  one  of  the  best  versions  of 
Pascal  I’ve  seen  on  any  micro  and  rea¬ 
sonably  fast — it  can  calculate  1500 
Factorial  (using  extended  precision 
math)  in  an  amazing  10  seconds.  Con- 
sulair  Corp.’s  Mac  C  compiler  is  fast, 
powerful,  and  bug-free.  It’s  reasonably 
Unix-compatible,  although  using  the 
Mac  Toolbox  library  rather  than 
STDLIB  results  in  much  smaller  appli¬ 
cations  (a  text  editor  can  be  easily 
done  in  less  than  4K). 

“To  get  things  started  with  68000 
software,  here  are  two  functions  I 
wrote  for  use  with  the  Mac  C  compiler 
to  provide  the  standard  file  ‘Open’  and 
‘Save  as’  dialogs.  Most  of  the  code  was 
output  directly  from  Mac  C  (particu¬ 
larly  the  in-line  trap  instructions  and 
parameter  saving). 

“The  function  01dFileName( Count, 
typeFist, Reply)  will  display  the  open 
dialog,  which  lists  all  files  of  the  speci¬ 
fied  type.  NewFileName(Default- 
Name, Reply)  will  display  the  save  dia¬ 
log,  with  the  default  filename  displayed 
for  editing.  Both  functions  return  a  zero 
if  the  request  was  cancelled,  otherwise 
returning  nonzero,  and  Reply  (pointer 
to  struct  SFReply,  as  defined  in  Inside 
Macintosh  and  packages. h  supplied 
with  Mac  C)  will  contain  the  filename 
as  volume  reference  number  of  the  file 
selected.”  See  Listing  One  (page  102) 


for  Mr.  Cohen’s  contribution. 

Well,  having  tried  or  observed 
friends  being  tried  by  MacPascal, 
MacBasic,  and  MacForth,  I’ll  stand  by 
my  earlier  statements  about  those  lan¬ 
guages  at  least.  They  don’t  meet  my 
criteria  for  “insanely  great.”  On  the 
Apple  assembler,  I  was  in  error.  I 
based  my  previous  column  on  Apple’s 
own  product  announcement,  which 
clearly  stated  “two  machines  re¬ 
quired,”  and  had  not  received  the  actu¬ 
al  software  when  I  submitted  the  col¬ 
umn  to  DDJ  for  publication.  The 
Macsbug  debugger,  by  the  way,  is  in¬ 
sanely  clever.  When  loaded,  it  simply 
requests  some  space  on  the  heap  and 
then  hides  itself  there  like  any  other 
data  object,  grabbing  back  control 
when  an  exception  is  detected. 

More  Mac  News 

As  far  as  program  development  tools 
go  for  the  Mac,  there  is  hope  for  the 
rest  of  us  after  all.  Mainstay  of  Agoura 
Hills,  CA,  has  announced  MacASM,  a 
macroassembler  for  the  Macintosh 
with  an  integral  full-screen  editor  and 
resource  compiler  that  costs  only 
$125.00. 1  already  have  received  sever¬ 
al  favorable  letters  from  DDJ  readers 
about  this  product.  You  can  contact 
Mainstay  at  (818)  991-6540. 

But  the  neatest  news  of  all  is  the  im¬ 
pending  release  of  Neon  from  Kriya 
Systems,  which  we  recently  saw  dem¬ 
onstrated  at  the  Las  Vegas  Comdex 
Apple  booth.  This  is  a  high-level,  ob¬ 
ject-oriented  language,  a  la  Smalltalk, 
that  will  sell  for  only  $150.00  and  al¬ 
lows  full  access  to  the  Macintosh’s 
Toolbox  and  graphics  capabilities. 
Best  of  all,  the  package  includes  exten¬ 
sive  source  code,  a  600-page  manual, 
and  a  toll-free  license  to  distribute 
Neon  embedded  in  application  pro¬ 
grams.  Based  on  a  32-bit  implementa¬ 
tion  of  Forth,  Neon  makes  available  to 

Dr.  Dobb's  Journal.  March  1985 


the  programmer  the  full  spectrum  of 
Forth  commands  and  extensibility. 
The  scheduled  release  date  of  Neon  is 
February  15,  so  if  all  goes  well,  it 
should  be  available  by  the  time  you 
read  this.  You  can  reach  Kriya  at  505 
N.  Lake  Shore  Drive,  Suite  5510,  Chi¬ 
cago,  IL  6061 1 . 

At  the  other  end  of  the  spectrum,  we 
have  the  advertisements  heralding 
“CP/M  for  the  Macintosh”  from  IQ 
Software  in  Fort  Worth,  TX.  I’ll  bet 
this  advertisement  made  the  Apple  soft¬ 
ware  gurus  gnash  their  teeth  and  pull 
their  hair  out!  I  can’t  imagine  anything 
more  antithetical  to  the  whole  MacMi- 
lieu  than  DIR,  PIP,  ED,  and  STAT. 

80286  Opcode  of  the  Month 

One  of  the  opcodes  added  in  the  80286 
was  the  instruction  called  BOUNDS, 
which  checks  whether  a  number  falls 
within  limits  and  generates  a  hardware 
interrupt  5  if  it  does  not.  This  nifty  op¬ 
code  was  added  for  the  sake  of  compil¬ 
er  writers  who  wanted  an  efficient  way 
to  check  the  validity  of  an  array  index 
at  runtime.  Don’t  rush  to  use  this  in¬ 
struction  in  your  assembly  language 
programming  experiments  on  the  IBM 
PC/AT,  however,  or  you  may  get  an 
unpleasant  surprise. 

It  seems  that  way  back  when  the  PC/ 
AT  was  just  a  gleam  in  the  eye  of  the 
president  of  the  Entry  Systems  Division 
(in  fact,  no  such  division  even  existed 
then),  somebody  at  either  IBM  or  Mi¬ 
crosoft  assigned  interrupt  5  to  the  Print 
Screen  ROM  BIOS  routine.  So,  when 
the  80286  executes  a  BOUNDS  instruc¬ 
tion,  detects  a  value  outside  of  the  spec¬ 
ified  range,  and  executes  interrupt  5  . . . 
you  guessed  it!  To  add  insult  to  injury, 
the  80286  assumes  that  the  interrupt  5 
handler  will  fix  up  the  offending  value, 
so  it  executes  the  BOUNDS  instruction 
again.  Of  course,  the  ROM  BIOS  pro¬ 
gram  to  print  the  screen  carefully  saves 
and  restores  all  the  registers,  including 
the  one  containing  the  nasty  value  that 
caused  the  interrupt,  because  it  doesn’t 
know  anything  about  BOUNDS  excep¬ 
tions  or  fix  ups  or  anything  else  for  that 
matter.  The  result:  one  screen  dump  af¬ 
ter  another  until  you  give  up  and  restart 
the  computer. 

IBM  has  distributed  a  fix  for  this 
problem  in  its  Software  Support  Center 
news  bulletin.  The  fix  involves  installa- 

Dr.  Dobb's  Journal,  March  1985 


tion  of  a  new  interrupt  5  handler  that 
does  the  following  when  it  receives  con¬ 
trol: 

( 1 )  It  uses  the  address  pushed  on  the 
return  stack  to  peek  into  the  program 
that  was  executing  to  determine 
whether  a  BOUNDS  opcode  caused  the 
exception. 

(2)  If  the  cause  was,  in  fact,  a 
BOUNDS  opcode,  then  it  performs 
both  the  appropriate  fix  up  and  an  in¬ 
terrupt  return. 

(3)  Otherwise,  it  transfers  control  to 
the  Print  Screen  ROM  BIOS  routine. 
Not  very  aesthetically  pleasing,  but 
this  fix  seems  to  work. 

DOS  3.0  Differences 

In  my  last  column,  I  discussed  some  of 
the  documented  differences  between 
PCDOS  2.0  and  DOS  3.0.  Here’s  news 
about  one  I  missed.  The  unexplained 
failure  of  a  once  perfectly  happy  pro¬ 
gram  prompted  me  to  investigate  the 
behavior  of  the  Find  First  and  Find 
Next  functions  (04EH  and  04FH,  re¬ 
spectively).  Much  to  my  surprise,  I 
found  that  the  definition  of  the  Find 
Next  function  has  been  changed  in 
DOS  3.0  for  no  obvious  reason.  Fur¬ 
thermore,  the  change  is  not  document¬ 
ed  on  DOS  Technical  Manual  page  vi, 
which  is  supposed  to  warn  the  pro¬ 
grammer  about  new  or  modified  DOS 
function  calls. 

Under  DOS  2.0,  function  4FH  did 
not  take  any  register  arguments  but  as¬ 
sumed  that  the  information  from  the 
previous  call  to  Find  First  or  Find 
Next  was  in  the  current  DTA.  Under 
DOS  3.0,  the  manual  says  that  function 
4FH  requires  DS:DX  to  “contain  the 
information  from  a  previous  'Find 
First’  call.”  Because  there’s  no  con¬ 
ceivable  reason  why  function  4FH 
would  want  the  original  filename 
string  address,  I  interpret  this  rather 
vague  wording  to  mean  that  DS:DX 
should  point  to  the  same  buffer  that 
you  set  the  DTA  to  before  calling  Find 
First.  In  the  case  of  my  program  at 
least,  the  problem  turned  out  to  be  not 
function  4FH  at  all  but  the  fact  that 
function  4EH  does  not  return  register 
AX  =  0  for  a  successful  call  as  it  did 
under  DOS  2.0.  Apparently,  under 
DOS  3.0,  you  absolutely  must  check 
the  carry  bit  first;  if  it  isn’t  set,  don’t 
assume  that  register  AX  will  contain 


anything  in  particular. 

Naturally,  a  day  or  two  after  I’d 
wasted  the  afternoon  tracking  down 
the  above,  I  got  a  letter  from  Dan 
Daetwyler  serving  up  the  same  infor¬ 
mation  in  his  usual  lucid  way.  He  went 
on  to  write:  “The  DOS  3.0  writers  fi¬ 
nally  saw  fit  to  give  us  an  interface  to 
the  resident  PRINT  function.  It’s  de¬ 
scribed  as  interrupt  2FH  in  the  man¬ 
ual.  I  had  a  critical  need  for  this  func¬ 
tion,  and  it  was  one  of  the  reasons  I 
quickly  hopped  on  the  V3.0  bandwag¬ 
on.  So  I  read  the  manual  and  then  tried 
to  implement  the  function.  The  man¬ 
ual  states  that  the  AL  register  (and  for 
some  calls,  some  other  registers)  must 
be  set  up  before  the  interrupt  is  issued. 
A  set  of  values  for  AL  are  given,  with 
specific  details  of  the  return  values, 
other  registers,  etc.  Not  bad! 

“What’s  the  problem?  Simple.  No¬ 
where  is  the  AH  register  mentioned. 
But  it  turns  out  that  unless  AH  con¬ 
tains  a  binary  one  at  interrupt  time, 
you  will  have  no  chance  at  all  of  get¬ 
ting  the  thing  to  work!  I  found  it  out  by 
the  cut  and  try  method,  and  lucked 
out.  Since  then  I’ve  disassembled  the 
PRINT  module  and  confirmed  my 
‘guess.’  Not  so  incidentally,  this  inter¬ 
rupt  is  apparently  going  to  be  used  for 


noc 


Declarations:  globals 
char  c0;int  x0; 

char  c,cl , ca [ 100 ] ; int  x , xl , xa [ 100 ] ; 
Declarations:  locals 

char  cl ,cl 1 , cal [ 100 ) ; int  x 1 , x 1 1 , xa 1 [ 100 ] ; 


Test 

Z80A  4  MHz 

286  6  MHz 

* 

BDS  C  v 1 . 4  3 

C86  vl.33 

Ratio 

statement 

msec 

msec 

1 

5 

1 

5.00 

c=  1  a  1  ; 

2 

6 

1 

6.00 

x  =  l ; 

3 

6 

1 

6 . 00 

x  =  l 234 ; 

4 

15 

4 

3.75 

if (c) x=l 

5 

15 

4 

3.75 

if (x) x=l 

6 

13 

5 

2.60 

c  <<=  1; 

7 

21 

5 

4 . 20 

c  <<=  4 ; 

8 

11 

4 

2.75 

x  <<=  1; 

9 

19 

5 

3.80 

x  <<=  4 ; 

10 

33 

5 

6.60 

H 

II 

A 

A 

0 

11 

69 

5 

13.80 

c  >>=  4; 

12 

31 

4 

7.75 

x  >>=  1; 

13 

67 

5 

13.40 

X 

V 

V 

14 

8 

2 

4.00 

c  =  ca  ( 0 )  ; 

15 

17 

10 

1.70 

c=c a [c0 ] 

16 

8 

2 

4.00 

x  =  xa  [0 ) ; 

17 

22 

6 

3.67 

x  =  xa [ x  0 ] 

18 

8 

2 

4.00 

c=c0 ; 

19 

8 

1 

8.00 

x  —  x  0  ; 

20 

11 

4 

2.75 

c=c+l ; 

21 

5 

3 

1.67 

C++ ; 

22 

9 

2 

4.50 

x=x+l ; 

23 

9 

2 

4.50 

x  +  +  ; 

24 

4 

1 

4.00 

cl= 1  a  1 ; 

25 

11 

1 

11.00 

xl  =  l ; 

26 

11 

1 

11.00 

x  1  =  1 2  3  4  ; 

27 

20 

4 

5.00 

if (cl)xl« 

■i; 

28 

27 

4 

6.75 

if  (xl)  xl- 

29 

16 

5 

3.20 

cl  <<=  1 

30 

24 

5 

4.80 

II 

V 

V 

H 

0 

31 

25 

4 

6.25 

xl  <<=  1 

32 

33 

5 

6.60 

xl  <<=  4 

33 

37 

5 

7.40 

cl  >>=  1 

34 

72 

5 

14.40 

cl  >>=  4 

35 

46 

4 

11.50 

xl  >>=  1 

36 

81 

5 

16.20 

xl  >>=  4 

37 

12 

2 

6 . 00 

cl=cal [0 

; 

Test 

Z80A  4  MHz 

286  6  MHz 

# 

BDS  C  v 1 . 4 3 

C86  vl.33 

Ratio 

statement 

msec 

msec 

38 

21 

10 

2.10 

cl=cal [c0 )  ; 

39 

22 

3 

7.33 

x l=xa 1 (0 ) 

t 

40 

33 

6 

5.50 

x l=xal [ x0 ) ; 

41 

8 

2 

4.00 

cl=c0 ; 

42 

15 

2 

7.50 

x l=x0 ; 

43 

11 

4 

2.75 

cl=cl+l; 

44 

4 

3 

1.33 

cl  +  +  ; 

45 

24 

2 

12.00 

xl=xl+l ; 

46 

17 

2 

8.50 

xl  +  +  ; 

47 

253 

18 

14.06 

movmem (&ca  [0] , &ca [ 50 ] ,1) 

48 

274 

21 

13.05 

movmem (&ca [0] , &ca [ 50 ] ,5) 

49 

218 

47 

4.64 

strcpy (&ca [50] , &ca [0] ) ; 

/*1  char* 

/ 

50 

764 

192 

3.98 

strcpy (&ca[50],S.ca[2)); 

/*11  chars*/ 

51 

14 

14 

1.00 

dummy ( ) ; 

/*just  returns*/ 

Notes : 

1.  All  times  accurate  to  1  microsecond. 

2.  Z80A  tests  were  run  at  4  MHz  in  an  IMS  International  8000 
CPM80  system.  80286  tests  were  run  at  6  MHz  (no  wait  states) 
on  a  Lomas  Data  Products  LDP2  MSDOS  2.1  system. 

3.  For  BDS-C  both  -o  and  -e  optimizations  were  used. 

4.  Test  program  generation  and  result  reporting  done  with 
T/Maker. 


Figure  1 

Thomas  Morgan's  Benchmarks 


Dr.  Dobb's  Journal,  March  1985 


some  of  the  TopView  or  file-sharing 
functions,  and  if  you  issue  the  inter¬ 
rupt  with  the  AH  register  containing 
large  values  (OBBH  range),  you  end  up 
going  to  a  totally  different  function. 
Undocumented,  of  course! 

“I’m  getting  a  bit  tired  of  getting 
manuals  filled  with  good  advice  (it 
really  is)  on  programming  techniques 
and  then  finding  out  that  the  operating 
system  violates  these  ‘good  practices’ 
right  and  left.  The  PRINT  module,  for 
example,  not  only  includes  a  signifi¬ 
cant  array  of  self-modifying  code 
(ugh),  but  it  even  ‘relocates’  (within  its 
own  area  .  .  .  why?)  its  interrupt  ser¬ 
vice  routines  then  does  adjustment  of 
the  addresses  to  allow  for  the  self-im¬ 
posed  relocation!  (Double  ugh.)  I’m 
sure  I'll  eventually  find  out  that  there 
is  a  rationale  for  this,  but  I  strongly 
suspect  it  will  be  a  ‘cop-out’.  I’ve  never 
yet  found  a  reason  for  flagrant  viola¬ 
tion  of  good  programming  practices! 
This  case  seems  to  be  caused  by  a  de¬ 
sire  to  save  a  couple  of  hundred  bytes 
of  memory.  Well,  everyone  knows  that 
DOS  is  growing  fast  enough,  without 
wasted  memory,  but  it  also  appears 
that  the  memory  could  have  been 
saved  by  simple  reorganization  of  the 
source  module.  If  1  ever  get  the  cour¬ 
age  to  complete  the  disassembly,  I’ll 
find  out!” 

Microsoft  Assembler  Bug 
of  the  Month 

The  program  in  Listing  Two  (page 
102)  will  assemble  without  any  errors; 
however,  when  you  try  to  link  it,  you 
get  the  error  message: 

Invalid  Object  Module. 

Input  file:  TEST.OBJ(A) 
pos:  00044  Record  Type:  AO 

In  search  of  further  enlightenment,  we 
looked  this  message  up  in  the  DOS  3.0 
manual  (page  A-58)  and  found  the 
advice: 

Explanation:  LINK.  Object  mod- 
ule(s)  was  incorrectly  formed  or  un¬ 
observed  errors  occurred  during 
compilation.  The  disk  may  be  bad. 
Action:  Recompile  the  module  to  ei¬ 
ther  the  same  or  a  different  disk. 

There’s  no  explanation  at  all  of  what 
Dr.  Dobb  s  Journal.  March  1985 


the  various  record  types  for  the  Linker 
consist  of  or  what  “pos:  00044”  is  sup¬ 
posed  to  mean  (if  you  knew  the  latter, 
you  could  probably  figure  out  the  of¬ 
fending  instruction  regardless  of  the  in¬ 
scrutable  error  message). 

Repeated  attempts  at  assembly  and 
linking,  on  different  disks,  using  differ¬ 
ent  versions  of  DOS  and/or  the  Linker, 
produced  the  same  or  other  confusing 
error  messages.  To  make  a  long  story 
short,  either  the  Assembler  or  the 
Linker  can’t  cope  with  a  DD  data  dec¬ 
laration  statement  inside  the  invoca¬ 
tion  of  a  structure  macro.  Our  thanks 
to  the  boys  at  Microsoft  for  yet  anoth¬ 
er  interesting  puzzle;  we  trashed  an  en¬ 
tire  day  figuring  out  that  this  was  the 
only  problem  in  a  5000-line  graphics 
driver. 

Some  80286  Timings 

Thomas  Moran  writes:  “Here  are 
some  comparisons  of  individual  C 
statement  times  using  BDS  C  on  a  Z80 
and  C86  on  an  80286.  Most  of  the  C 
programs  I  run  seem  to  be  around 
three  times  as  fast  on  the  286  as  the 
Z80.  (They  run  slightly  faster  on  the 
Z80  than  on  the  IBM  PC.) 

“Perhaps  the  most  interesting  re¬ 
sults  are  ( 1 )  the  C86  code  for  a  subrou¬ 
tine  call  takes  as  long  to  execute  on  the 
80286  as  the  BDS  C  code  on  the  Z80, 
and  (2)  strcpy  is  a  horror  in  both  cases. 
One  often  codes  *p+  +  ...  for  scan¬ 
ning  and  copying  since  C  has  no  lan¬ 
guage  constructs  to  handle  strings. 
Perhaps  a  smart  compiler  could  realize 
that  a  subroutine  could  be  replaced  by 
a  few  machine-repeated  move  or  scan 
instructions.”  Thomas’s  timings  are 
presented  in  the  figure  on  page  100. 

16-Bit  Software  Toolbox 
Installment 

For  my  own  code  contribution  this 
month,  I’m  providing  two  8086  assem¬ 
bly  language  subroutines  of  general  in¬ 
terest  (Listing  Three,  page  104).  The 
first,  BIN_TO_ASC,  converts  a  signed 
32-bit  integer  to  an  ASCII  string  in  the 
desired  radix  (the  radix  must  be  in  the 
range  2-36).  The  upper  part  of  the 
32-bit  value  is  passed  in  DX  and  the 
lower  part  in  AX;  the  radix  is  passed  in 
CX;  and  SI  contains  the  last  byte  ad¬ 
dress  of  the  area  to  store  the  converted 


ASCII  string.  It  is  the  responsibility  of 
the  user  to  make  sure  that  the  destina¬ 
tion  buffer  is  large  enough  for  any 
number  that  might  be  printed  in  the 
desired  radix. 

DIVIDE  is  a  general  purpose  32-bit 
by  16-bit  unsigned  divide  subroutine. 
The  32-bit  dividend  is  passed  with  the 
upper  part  in  DX  and  the  lower  part  in 
AX;  the  divisor  is  passed  in  CX.  The 
32-bit  quotient  is  returned  in  DX:AX, 
and  the  remainder  in  BX.  Contrast  this 
with  the  8086’s  built-in  unsigned  DIV 
instruction,  which  also  divides  DX:AX 
by  a  16-bit  value  but  can  return  only  a 
16-bit  quotient;  this  is  inadequate  for 
an  output  conversion  routine  that  must 
extract  digits  from  a  32-bit  argument. 
The  code  for  DIVIDE  may  look  a  little 
obscure  at  first,  but  it  will  become 
clearer  if  you  simply  consider  each  of 
the  registers  DX,  AX,  and  CX  as  a  sin¬ 
gle  digit.  Then  you’ll  find  that  it  works 
exactly  like  normal  long  division  done 
by  hand. 

DD| 

(Listings  begin  on  next  page) 

Reader  Ballot 

Vote  for  your  favorite  feature/article 
Circle  Reader  Service  No.  1 95. 


101 


007 


16-Bit  Toolbox 

Listing  One 


(Text  begins  on  page  96) 


Mike  Cohen's  standard  file  "Open"  and  "Save"  dialogs  for  the  Macintosh 

;Consulair  Mac  C  Compiler  1.0 
STRI NG_FORMAT  0 
INCLUDE  M68KLIB.D 
OldFi leName: 

CLR.L  -(SP) 

LINK  A6,#-20 

MOVEM.L  D0/D1/D2,-16 (A6) 

PEA  -20 ( A6 ) 

DC . W  $A8  7  4  ; GetPor  t 

MOVE . W  #80, -(SP) 

MOVE . W  #80, -(SP)  ; location 

CLR.L  -(SP)  ; Prompt 

CLR.L  -(SP)  ; Fi leFi Iter 

MOVE . W  D0 , - ( SP )  ;numTypes 

MOVE . L  Dl , - ( SP )  ; TypeLi st 

CLR.L  -(SP)  ;DlgHook 

MOVE . L  D2 , -  (SP )  ; Reply 

MOVE . W  #2,-(SP)  ;routine  selector  for  GetFile 
DC . W  $A9EA  ; Pack  3 

MOVE. L  -20 ( A6 ) , - (SP) 

DC . W  $A873  ; SetPor  t 

MOVE. L  -8 ( A6 ) , A0 

MOVE . B  (A0),D0 

EXT  D0 

EXT.L  D0 

RETX 

XREF  OldFi leName 


NewF i leName : 

CLR.L  -(SP) 

LINK  A6,#-16 

MOVEM.L  D0/D1 , -12 (A6 ) 

PEA  -16  ( A6 ) 

DC . W  $A874  ;GetPor t 

MOVE .W  #80, -(SP) 

MOVE . W  #80, -(SP)  ; location 

PEA  @1  ; Prompt 

MOVE . L  D0 , - ( SP )  ; Or igName 

CLR.L  -(SP)  ; DigHook 

MOVE . L  Dl , -  ( SP )  ; Reply 

MOVE . W  #1,-(SP)  ;routine  selector  for  PutFile 

DC . W  $A9EA  ; Pack3 

MOVE . L  -16 ( A6 )  ,-  (SP) 

DC . W  $A873  ; SetPor  t 

MOVE . L  -8  ( A6 )  ,A0 

MOVE . B  ( A0 ) ,D0 

EXT  D0 

EXT.L  D0 

RETX 

@1:  DCB  8 

DC .B  ' Save  As : ' 

XREF  NewFi  leName  End  Listing  One 


Listing  Two 


Demonstrates  obscure  Microsoft  assembler/linker  bug. 


title  'Show  Structure  Bug' 
page  60,132 

dseg  segment  para  public  'DATA' 

(Continued  on  page  104 ) 


102 


Dr.  Dobb  s  Journal.  March  1985 


16-Bit  Toolbox  (Listing  Continued,  text  begins  on  page  96) 
Listing  Two 


f  red 

s  tr  uc 
dd 

1  dup  (?) 

f  red 

ends 

harry 

f  red 

<> 

dseg 

ends 

cseg 

segment 

para  public 

pgm 

proc 

ret 

far 

pgm 

endp 

cseg 

ends 

[declare  a  structure  macro 
[consisting  of  a  double-word 
;data  declaration 

; invoke  the  structure  macro 

'CODE  ' 

;this  is  a  Zen  program... 


end  pgm 


End  Listing  Two 


Listing  Three 


General  purpose  Binary-to-ASCI I  conversion  and  unsigned  divide  subroutines. 


Convert  32  bit  binary  value  to  ASCII  string. 

Copyright  (C)  1984  Ray  Duncan 

Call  with  DX:AX  =  signed  32  bit  value 
CX  =  radix 

SI  =  last  byte  of  area  to  store  resulting  string 
(make  sure  enough  room  is  available  to  store 
the  string  in  the  radix  yoq  have  selected.) 


Destroys  AX,  BX,  CX,  DX,  and  SI. 


bin_to_asc  proc  near 


mov 

or 

pushf 

jns 

not 

not 

add 

adc 

binl: 

mov 

or 

jz 

call 

add 

cmp 

jle 

add 

bin2: 

mov 

dec 

jmp 

bin3 : 

popf 

jns 

mov 

byte  ptr  [si] 
dx  ,dx 

binl 

dx 

ax 

ax ,  1 
dx ,  0 


bx ,  ax 
bx ,  dx 
bin3 
divide 
bl, *0' 
bi, ’9’ 
bi  n2 

bl , ' A ' - ' 9 ' -1 
[ si ] , bl 
s  i 

binl 


bin4 

byte  ptr  [si] 


;  convert  DX:AX  to  ASCII. 

[force  storage  of  at  least  1  digit. 

'0  1 

[test  sign  of  32  bit  value, 

[and  save  sign  on  stack. 

[jump  if  it  was  positive. 

[it  was  negative,  take  2's  complement 
[of  the  value. 


[divide  the  32  bit  value  by  the  radix 
;to  extract  the  next  digit  for  the 
[forming  string. 

[is  the  value  zero  yet? 

[yes,  we  are  done  converting. 

[no,  divide  by  radix. 

[convert  the  remainder  to  an  ASCII  digit. 
;we  might  be  converting  to  hex  ASCII, 
[jump  if  in  range  0-9, 

[correct  it  if  in  range  A-F. 

[store  this  character  into  string. 

[back  up  through  string, 

[and  dp  it  again. 

[restore  sign  flag, 

[was  original  value  negative? 

;no,  jump 

[yes, store  sign  into  output  string. 


Dr.  Dobb's  Journal,  March  1985 


104 

239 


;back  to  caller 


bin4:  ret 

bin_to_aSc  endp 


General  purpose  32  bit  by  16  bit  unsigned  divide. 

Copyright  (C)  1984  Ray  Duncan 

This  must  be  used  instead  of  the  plain  machine  unsigned  divide 
for  cases  where  the  quotient  may  overflow  16  bits  (for  example. 


;  dividirlg  100, 

000  by  2)  .  If 

called  with  a  zero  divisor,  this 

;  routine  returns  the  dividend 

unchanged  and  gives  no  warning. 

;  Call  with  DX: 

AX  =  32  bit  dividend 

;  CX 

=  divisor 

;  Returns  DX: 

AX  =  quotient 

;  BX 

=  remainder 

;  CX 

=  divisor  (unchanged) 

divide  proc 

near 

;  Divide  DX:AX  by  CX 

jcxz 

divl 

;  exit  if  divide  by  zero 

push 

ax 

;  0:dividend  upper/divisor 

IIIOV 

ax  ,dx 

xor 

dx ,  dx 

div 

CX 

mov 

bx  ,ax 

;  BX  =  quotientl 

pop 

ax 

;  remainder l:dividend  lower/divisor 

div 

CX 

xchg 

bx , dx 

;  DX:AX  =  quotientl:quotient2 

divl:  ret 

divide  endp 

;  BX  =  remainder2 

End  Listings 


105 


Dr.  Dobb's  Journal,  March  1 985 


CP/M  EXCHANGE 


by  Robert  A.  Blum 


Steve  Russell  of  SLR  Systems,  the  au¬ 
thor  of  Z80ASM,  recently  sent  along  a 
disk  containing  a  problem  report  that 
he  had  sent  to  DR1.  The  disk  contained 
a  program  to  demonstrate  the  problem 
he  had  found  with  CP/M  Plus  and  the 
RSX  that  he  had  written  to  correct  the 
problem. 

While  installing  for  a  construction 
company  an  accounting  package  that 
was  written  in  a  high-level  language, 
Steve  encountered  inconsistent  results 
when  he  attempted  to  determine  the 
current  disk  file  sizes.  Although  the 
software  publisher  proved  to  be  of  little 
help,  Steve  did  learn  that  this  strange 
occurrence  seemed  also  to  appear  un¬ 
der  MP/M  but  never  with  CP/M  2.2 
and  that  the  disk  files  in  question  were 
being  accessed  with  random  I/O  BDOS 
calls. 

After  a  great  deal  of  probing  and 
prodding  into  how  CP/M  Plus  was  re¬ 
sponding  to  the  application  program's 
system  calls,  Steve  found  a  problem 
with  the  way  the  CP/M  Plus  file  system 
was  treating  random  read  errors.  This 
situation  was  compounded  further  by 
the  fact  that  errors  sometimes  were  not 
reported  at  all  and  that  incorrect  data 
was  being  placed  into  the  FCB. 

The  disk  I  received  contained  a  sam¬ 
ple  data  file,  257  sectors  (records)  in 
length,  and  a  program  FIND  that 
would  produce  the  problem.  When 
run,  the  FIND  program  showed  that 
the  random  record  logic  of  CP/M  Plus 
became  confused  on  the  second  consec¬ 
utive  random  read  to  an  unallocated 
sector  of  an  existing  extent  other  than 
extent  0.  However,  if  an  interceding 
valid  read  is  done  or  a  read  to  an  unal¬ 
located  record  of  an  unwritten  extent  is 
done,  the  problem  does  not  occur. 

The  FIND  program  (Listing  One, 
page  112)  tries  to  read  several  sectors 
from  the  file  XXX.  Each  read  attempt 
is  documented  by  displaying  the  record 
number  in  hex  and  the  return  error 


code  in  A.  The  FCB  contents  then  are 
dumped  to  find  out  what  is  happening. 

Follow  along  in  Figure  1  (page  109) 
as  each  read  is  documented.  The  first 
read  is  to  record  zero,  which  is  an  allo¬ 
cated  record  of  extent  zero;  it  correctly 
completes  with  a  zero  return  code  and 
valid  FCB  contents.  The  second  read  is 
to  record  number  257,  which  is  one 
record  beyond  the  last  record  written 
to  the  file.  The  return  code  for  this 
read  is  reported  correctly  as  one,  indi¬ 
cating  that  the  last  read  operation  was 
on  an  unallocated  record  of  a  written 
extent.  The  third  read  is  to  an  allocat¬ 
ed  record  of  extent  zero,  providing 
once  again  a  valid  error  return  code  of 
zero  and  correct  FCB  contents. 

The  error  occurs  on  the  second  of 
the  next  two  reads.  The  fourth  read  is 
to  record  number  500,  an  unallocated 
record  of  a  written  extent.  The  read  is 
flagged  correctly  as  an  error,  and  the 
FCB  contents  are  also  correct.  Now, 
the  error  occurs.  The  fifth  read  is  to 
record  number  260,  which  again  is  to 
an  unallocated  record  of  a  written  ex¬ 
tent.  This  time,  the  error  return  code  is 
reported  incorrectly  as  zero,  or  no  er¬ 
ror,  and  the  record  count  field  of  the 
FCB  incorrectly  contains  80h.  In  reali¬ 
ty,  both  the  return  code  and  the  record 
count  field  should  have  been  one. 

So,  the  problem  seems  to  happen 
only  when  two  consecutive  random 
reads  to  an  unallocated  record  of  a 
written  extent  are  attempted.  To  cor¬ 
rect  the  problem,  Steve  wrote  an  RSX 
( Listing  Two,  page  1  1 4)  to  monitor  the 
BDOS  function  calls  made  by  the  ap¬ 
plication  program.  When  a  random 
read  is  found,  the  return  error  code  is 
examined,  and  when  a  nonzero  error 
return  code  is  found,  a  dummy  read  to 
record  zero  of  the  same  file  is  done. 
This  interceding  read  keeps  the  incor¬ 
rect  error  return  code  and  FCB  con¬ 
tents  from  resulting,  should  the  same 
type  of  read  occur  a  second  consecutive 


time.  You  might  want  to  examine  Fig¬ 
ure  2  (page  1 10),  which  displays  the 
results  of  the  FIND  program  after  the 
installation  of  the  RSX. 

CP/M  Plus;  PIP  Patch 

Gary  Silvey  of  Digital  Research  re¬ 
cently  sent  along  a  patch  for  the  CP/M 
Plus  version  of  PIP  to  set  the  verify  op¬ 
tion  permanently  on,  as  1  described  for 
CP/M  2.2  in  the  November  issue  of 
this  column.  As  before,  DRI  does  not 
recognize  this  patch,  even  though  it 
comes  from  a  DRI  employee.  Also,  the 
patch  is  known  to  work  only  with  the 
specific  version  of  the  PIP  program 
mentioned  here.  Before  setting  out  to 
install  this  patch,  make  a  backup  copy 
of  your  disk  in  case  it  becomes  neces¬ 
sary  to  return  to  your  original  version 
of  PIP. 

Figure  3  (page  1 12)  lists  the  proce¬ 
dure  that  I  followed  to  install  the  verify 
patch  on  my  system.  The  first  step  is  to 
verify  that  your  PIP  program  is  version 
3.0;  this  is  the  only  version  of  PIP  with 
which  this  patch  is  known  to  work  suc¬ 
cessfully.  To  verify  your  version  num¬ 
ber,  load  PIP.COM  into  memory  with 
either  SID  or  DDT  and  display  memory 
between  21  Oh  and  2 1  fh;  the  version 
number,  which  should  be  3.0,  should 
begin  at  21Ch.  The  next  step  is  to 
change  the  single  byte  at  lOOlh  from 
1  Fh  to  37h.  The  final  step  is  to  save  the 
modified  program  back  onto  disk  under 
a  new  name  ready  for  testing. 

To  ensure  that  I  had  entered  my 
patches  correctly,  I  timed  how  long  it 
took  to  copy  the  same  file  with  an  unal¬ 
tered  version  of  PIP  using  the  verify  op¬ 
tion  and  again  with  the  newly  altered 
version  of  PIP.  The  two  times  were 
practically  identical,  which  was  verifi¬ 
cation  enough  for  me. 

Gary  also  sent  along  the  timings 
from  his  testing  of  the  changes;  these 
once  again  point  out  how  much  quicker 


Dr.  Dobb's  Journal,  March  1985 


108 

241 


disk  I/O  is  under  CP/M  Plus. 

For  your  information,  I  copied 
HELP.HLP  to  a  file  called  H  to  check 
out  the  PIP  version  3.0  patch.  Under 
my  nonbanked  version  of  CP/M  Plus,  it 
took  1 3:42  sec  for  the  copy  to  complete 


with  the  patched  PIP  and  21:71  sec 
with  unpatched  PIP.  On  my  banked 
system,  copying  the  same  file  with  the 
patched  and  unpatched  PIP  yielded 
times  of  8:00  and  1 8:24  sec,  respective¬ 
ly.  Using  the  unpatched  version  of  PIP 


on  the  banked  system  with  the  V  op¬ 
tion  yielded  18:25  sec.  DD| 

(Listing  begins  on  page  112) 

Reader  Ballot 

Vote  for  your  favorite  feature/article. 

Circle  Reader  Service  No.  1 96. 


READING  RECORD  0000,  ERROR  CODE  RETURNED  IN  A  IS  00 
36  HEX  BYTES  OF  FCB  CONTENTS  AFTER  ATTEMPTED  READ 
00  58  58  58  20  20  20  20  20  20  20  20  00  02  80  80 

06  07  08  09  OA  OB  OC  OD  OE  OF  10  11  12  13  14  15 

00  00  00  00 

READING  RECORD  0101,  ERROR  CODE  RETURNED  IN  A  IS  01 
36  HEX  BYTES  OF  FCB  CONTENTS  AFTER  ATTEMPTED  READ 
00  58  58  58  20  20  20  20  20  20  20  20  02  02  80  01 

16  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00 

01  01  01  00 

READING  RECORD  0064,  ERROR  CODE  RETURNED  IN  A  IS  00 
36  HEX  BYTES  OF  FCB  CONTENTS  AFTER  ATTEMPTED  READ 
00  58  58  58  20  20  20  20  20  20  20  20  00  02  80  80 

06  07  08  09  OA  OB  OC  OD  OE  OF  10  11  12  13  14  15 

64  64  00  00 

READING  RECORD  01F4,  ERROR  CODE  RETURNED  IN  A  IS  01. 
36  HEX  BYTES  OF  FCB  CONTENTS  AFTER  ATTEMPTED  READ 
00  58  58  58  20  20  20  20  20  20  20  20  03  02  80  00 

16  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00 

74  F4  01  00 

READING  RECORD  0104,  ERROR  CODE  RETURNED  IN  A  IS  00 
36  HEX  BYTES  OF  FCB  CONTENTS  AFTER  ATTEMPTED  READ 
00  58  58  58  20  20  20  20  20  20  20  20  02  02  80  80 

16  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00 

04  04  01  00 

READING  RECORD  0000,  ERROR  CODE  RETURNED  IN  A  IS  00 
36  HEX  BYTES  OF  FCB  CONTENTS  AFTER  ATTEMPTED  READ 
00  58  58  58  20  20  20  20  20  20  20  20  00  02  80  80 

06  07  08  09  OA  OB  OC  OD  OE  OF  10  11  12  13  14  15 

00  00  00  00 

READING  RECORD  0100,  ERROR  CODE  RETURNED  IN  A  IS  00 
36  HEX  BYTES  OF  FCB  CONTENTS  AFTER  ATTEMPTED  READ 
00  58  58  58  20  20  20  20  20  20  20  20  02  02  80  01 

16  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00 

00  00  01  00 

READING  RECORD  0104,  ERROR  CODE  RETURNED  IN  A  IS  01 
36  HEX  BYTES  OF  FCB  CONTENTS  AFTER  ATTEMPTED  READ 
00  58  58  58  20  20  20  20  20  20  20  20.02  02  80  01 

16  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00 

04  04  01  00 


READING  RECORD  0202,  ERROR  CODE  RETURNED  IN  A  IS  04 
36  HEX  BYTES  OF  FCB  CONTENTS  AFTER  ATTEMPTED  READ 
00  58  58  58  20  20  20  20  20  20  20  20  02  02  80  01 

16  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00 

02  02  02  00 

READING  RECORD  0104,  ERROR  CODE  RETURNED  IN  A  IS  01 
36  HEX  BYTES  OF  FCB  CONTENTS  AFTER  ATTEMPTED  READ 
00  58  58  58  20  20  20  20  20  20  20  20  02  02  80  01 

16  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00 

04  04  01  00 

Figure  1  Results  of  Random  Read  Before  RSX  Installed. 


109 


OAO 


Dr.  Dobb  s  Journal.  March  1985 


READING  RECORD  0000,  ERROR  CODE  RETURNED  IN  A  IS  00 
36  HEX  BYTES  OF  FCB  CONTENTS  AFTER  ATTEMPTED  READ 
00  58  58  58  20  20  20  20  20  20  20  20  00  02  80  80 

06  07  08  09  OA  OB  OC  OD  OE  OF  10  11  12  13  14  15 

00  00  00  00 

READING  RECORD  0101,  ERROR  CODE  RETURNED  IN  A  IS  01 
36  HEX  BYTES  OF  FCB  CONTENTS  AFTER  ATTEMPTED  READ 
00  58  58  58  20  20  20  20  20  20  20  20  00  02  80  80 

06  07  08  09  OA  OB  OC  OD  OE  OF  10  11  12  13  14  15 

00  01  01  00 

READING  RECORD  0064,  ERROR  CODE  RETURNED  IN  A  IS  00 
36  HEX  BYTES  OF  FCB  CONTENTS  AFTER  ATTEMPTED  READ 
00  58  58  58  20  20  20  20  20  20  20  20  00  02  80  80 

06  07  08  09  OA  OB  OC  OD  OE  OF  10  11  12  13  14  15 

64  64  00  00 

READING  RECORD  01F4 ,  ERROR  CODE  RETURNED  IN  A  IS  01 
36  HEX  BYTES  OF  FCB  CONTENTS  AFTER  ATTEMPTED  READ 
00  58  58  58  20  20  20  20  20  20  20  20  00  02  80  80 

06  07  08  09  OA  OB  OC  OD  OE  OF  10  11  12  13  14  15 

00  F 4  01  00 

READING  RECORD  0104,  ERROR  CODE  RETURNED  IN  A  IS  01 
36  HEX  BYTES  OF  FCB  CONTENTS  AFTER  ATTEMPTED  READ 
00  58  58  58  20  20  20  20  20  20  20  20  00  02  80  80 

06  07  08  09  OA  OB  OC  OD  OE  OF  10  11  12  13  14  15 

00  04  01  00 

READING  RECORD  0000,  ERROR  CODE  RETURNED  IN  A  IS  00 
36  HEX  BYTES  OF  FCB  CONTENTS  AFTER  ATTEMPTED  READ 
00  58  58  58  20  20  20  20  20  20  20  20  00  02  80  80 

06  07  08  09  OA  OB  OC  OD  OE  OF  10  11  12  13  14  15 

00  00  00  00 

READING  RECORD  0100,  ERROR  CODE  RETURNED  IN  A  IS  00 
36  HEX  BYTES  OF  FCB  CONTENTS  AFTER  ATTEMPTED  READ 
00  58  58  58  20  20  20  20  20  20  20  20  02  02  80  01 

16  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00 

00  00  01  00 

READING  RECORD  0104,  ERROR  CODE  RETURNED  IN  A  IS  01 
36  HEX  BYTES  OF  FCB  CONTENTS  AFTER  ATTEMPTED  READ 
00  58  58  58  20  20  20  20  20  20  20  20  00  02  80  80 

06  07  08  09  OA  OB  OC  OD  OE  OF  10  11  12  13  14  15 

00  04  01  00 


READING  RECORD  0202,  ERROR  CODE  RETURNED  IN  A  IS  04 
36  HEX  BYTES  OF  FCB  CONTENTS  AFTER  ATTEMPTED  READ 
00  58  58  58  20  20  20  20  20  20  20  20  00  02  80  80 

06  07  08  09  OA  OB  OC  OD  OE  OF  10  11  12  13  14  15 

02  02  02  00 

READING  RECORD  0104,  ERROR  CODE  RETURNED  IN  A  IS  01 
36  HEX  BYTES  OF  FCB  CONTENTS  AFTER  ATTEMPTED  READ 
00  58  58  58  20  20  20  20  20  20  20  20  00  02  80  80 

06  07  08  09  OA  OB  OC  OD  OE  OF  10  11  12  13_14  15 

00  04  01  00 


Figure  2 

Results  of  Random  Read  After  RSX  Installed. 


Dr.  Dobb's  Journal.  March  1985 


110 

243 


B>SID  PIP.COM 

CP/M  3  SID  -  VERSION  3.0 

NEXT  MSZE  PC 

END 

2300  2300  0100 

C8FF 

#D210 , 21F 

0210  20  33  20 

#S1001 

1001  IF  37 

1002  D2  . 

50  49  50  20 

56  45  52  53  20  33  2E  30  20  3  PIP  VERS  3.0 

#WPIPNEW . COM , 100, 2300 

0044h  recordfs 
#G0 

written. 

Figure  3 

Pip  verify  patch 

CP/M  Exchange  (Text  begins  on  page  108) 

Listing  One 

TITLE  DEMONSTRATE  FAULTY  RANDOM  RECORD  ERROR 
DFCB  EQU  5CH 


THE  SAMPLE  FILE  NAME  IS  RATHER  GENERIC,  'XXX' 
TO  RUN  THIS  PROGRAM,  JUST  TYPE  A>FIND  XXX 


START 

; FIRST  OPEN  THE  FILE 


LD 

LD 

CALL 

DID  IT  OPEN  ? 

INC 

RET 

OK,  MAKE  SURE 

XOR 

LD 


DE , DFCB 
C ,  OFH 
5 


A 

Z  ; NOPE ,  RETURN 

HIGH  BYTE  OF  RANDOM  RECORD  IS  0 
A 

( DFCB+35 ) , A 


LD  SP , MYSTACK 


FIRST,  LETS  SET  UP  A  STRING  OF  RECORD  NUMBERS  TO  READ 
WITH  A  RANDOM  READ  TO  SEE  IF  THEY  REACT  DIFFERENTLY 


LOOP1 


LD 

HL , REC0RD_TABLE 

; TABLE  OF  RECORD 

NUMBERS 

LD 

A , TABLE_LEN 

; LENGTH  OF  TABLE 

PUSH 

AF 

;  SAVE 

COUNT 

LD 

E, (HL) 

;  LOAD 

RECORD  #  DESIRED 

INC 

HL 

LD 

D, (HL) 

INC 

HL 

PUSH 

HL 

;  SAVE 

TABLE  POINTER 

EX 

DE  ,  HL 

LD 

DE , RECNUM 

.ENCODE  RECORD  NUMBER  TO 

HEX-ASCII 

CALL 

HEXWOUT 

LD 

(DFCB+33) , HL 

; SET  RECORD  NUMBER 

LD 

DE , DFCB 

LD 

C  ,  21H 

; RANDOM  READ 

CALL 

5 

LD 

DE, RESULT 

; ENCODE  VALUE  RETURNED  IN  A 

CALL 

HEXOUT 

CALL  PRINT  LINE 

; PRINT  LINE  ON  THE  CONSOLE 

POP  HL 

POP  AF 

; RESTORE  POINTER 

AND  COUNTER 

112 


Dr.  Dubb's  Journal.  March  I9S5 


OAA 


DEC 

JR 


A 

NZ , LOOP 1 


;  IF  MORE  RECORDS,  JUMP 


; FINISHED . 

JP 

0 

RECORD  TABLE 

DW 

0 

DW 

257 

;  READ 

DW 

100 

;  READ 

DW 

500 

;  READ 

DW 

260 

;  READ 

; FIRST  READ  RECORD  ZERO 
RECORD  257  (ONLY  0-256  EXIST ...  ERROR  REPORTED 
RECORD  100  (MAIN  PURPOSE,  BACK  TO  EXT  0) 
RECORD  500  (ONLY  0-256  EXIST ...  ERROR  REPORTED 
RECORD  260  (ONLY  0-256  EXIST ...  OOPS !  NO 


K!  ) 


DW 

0 

DW 

256 

DW 

260 

DW 

514 

DW 

260 

REPORTED) 

TABLE_LEN 

EQU 

HEXWOUT 

LD 

A ,  H 

CALL 

HEXOUT 

LD 

A,  L 

HEXOUT 

PUSH 

AF 

RRA 

RRA 

RRA 

RRA 

CALL 

HEX2 

POP 

AF 

HEX2 

AND 

OFH 

OR 

30H 

CP 

3AH 

JR 

C , HEX3 

ADD 

7 

HEX3 

LD 

(DE)  ,  A 

INC 

DE 

RET 

BACK  TO  VALID  EXT  INFO 
LAST  RECORD  OF  FILE,  FOUND 
READ  260,  ERROR  RETURNED 
READ  RECORD  514  ( "  "  " 
READ  RECORD  260  ( "  "  " 


NOTE,  UNWRITTEN  EXT 
THIS  TIME  ERROR 


( $-RECORD_TABLE ) / 2 


PRINTLINE 

LD  DE, RECLINE 

LD  C  ,  9 

CALL  5 


NOW  PRINT  OUT  THE  FCB 


LD 

HL , DFCB 

LD 

DE , FCBASCI I 

LD 

B,  36 

LOOP5 

LD 

A, (HL) 

INC 

HL 

CALL 

HEXOUT 

INC 

DE 

DJNZ 

L00P5 

LD 

DE, FCBASC 

LD 

C,  9 

JP 

5 

FCBASC 

DB 

ODH, OAH 

DB 

'36  HEX  BYTES  OF 

FCB  CONTENTS 

FCBASC I I 

DS 

36*3, 20H 

DB 

ODH, OAH, '$' 

RECLINE 

DB 

ODH, OAH, 'READING 

RECORD  1 

RECNUM 

DB 

1  ,  ERROR  CODE 

:  RETURNED  IN 

RESULT 

DB 

1  S  ' 

DS 

64 

MYSTACK 

END 

,  ODH , OAH 


End  Listing  One 

(Listing  two  begins  on  next  page) 


Dr.  Dobb’s  Journal,  March  1985 


113 

245 


CP/M  Exchange  (Listing  Continued,  text  begins  on  page  108) 
Listing  Two 


TITLE  RANDOM  READ  RSX  SOURCE  LISTING 


; START  OF  PREFIX 

DB 

0 , 0 , 0 , 0 , 0 , 0 

; SERIAL  NUMBER 

JP 

START 

NEXT 

JP 

$-$ 

PREV 

DW 

0 

REMOVE 

DB 

0 

NONBNK 

DB 

0 

RSXNAME 

DB 

'TRACE  EM1 

DB 

0,0,0 

;  START 

OF  RSX 

CODE 

START 

LD 

A  ,  C 

; PUT  BDOS  FUNCTION  CODE  IN  A 

CP 

2  1H 

; IS  IT  A  RANDOM  READ 

JR 

NZ ,  NEXT 

; SKIP  IF  NOT  RANDOM  READ 

START2 

DO  READ,  BUT  IF  IT  RETURNS  AN  ERROR  CODE  1 

SEEK 

SECTOR  0  BEFORE  GOING  ON  TO  CORRECT  POSSIBLE 

INACCURACY  OF  SUBSEQUENT 

RANDOM  READ 

LD 

(USERSAVE) ,SP 

; SAVE  USER  STACK  POINTER 

LD 

SP , MYSTACK 

; LOAD  A  LOCAL  STACK  POINTER 

PUSH 

DE 

; SAVE  FCB  ADDRESS 

CALL 

NEXT 

; ALLOW  REQUESTED  OPERATION  TO  COMPLETE 

THE  USER  REQUESTED  OPERATION  IS  NOW  COMPLETE 

CP 

1 

; WAS  THE  RETURN  CODE  DATA  UNAVAILABLE 

JR 

NZ , START8 

;  NO  -  BYPASS  OUR  FIX 

SAVE  ALL  THE  USER  INFORMATION  AND  PREPARE  FOR  SEEK  TO  SECTOR  ZERO 


EX 

( SP ) , HL  ; SAVE  HL  AND  LOAD  FCB  ADDRESS 

LD 

DE , 33  ; GET  INDEX  TO  RO  POSITION  OF  FCB 

EX 

ADD 

DE ,  HL 

HL ,  DE 

ADD  RO  POSITION  INDEX  TO  FCB  ADDRESS 

PUSH 

HL 

SAVE  ON  STACK 

XOR 

A 

PUT  ZERO  INTO  A 

LD 

C, (HL) 

PUT  FCB  RO  BYTE  INTO  C 

LD 

(HL) , A 

ZERO  FCB  RO  BYTE 

INC 

HL 

POINT  AT  R1  BYTE 

LD 

B, (HL) 

PUT  FCB  R1  BYTE  INTO  B 

PUSH 

BC 

SAVE  BC  ON  STACK 

LD 

(HL) , A 

ZERO  FCB  R1  BYTE 

INC 

HL 

POINT  AT  R2  BYTE 

LD 

B, (HL) 

PUT  FCB  R2  BYTE  INTO  B 

PUSH 

BC 

SAVE  BC  ON  STACK 

LD 

(HL) ,A 

ZERO  FCB  R2  BYTE 

LD 

C,  21H 

GET  RANDOM  READ  FUNCTION  CODE 

CALL 

NEXT 

EXECUTE  IT 

1 

l 

NOW 

RESTORE  ALL  THE  ORIGINAL  USER  INFORMATION 

t 

POP 

AF 

PULL  RO,  Rl,  AND  R2  INFO  FROM  THE  STAC 

POP 

BC 

* 

POP 

HL 

PULL  THE  FCB  ADDRESS  FROM  THE  STACK 

LD 

(HL) ,c 

PUT  THE  ORIGINAL  RANDOM  READ  INFO  BACK 

INC 

HL 

INTO  THE  FCB 

LD 

(HL)  ,B 

* 

INC 

HL 

* 

LD 

(HL) ,A 

* 

LD 

A,  1 

RESTORE  THE  ERROR  CODE 

POP 

HL 

CORRECT  STACK 

START8 

LD 

SP, (USERSAVE) 

RESTORE  USER  STACK  POINTER 

RET 

USERSAVE  DW 

0 

AND  BACK  WE  GO 

MYSTACK 

DS 

END 

64 

End  Listings 


114 


OAR 


Dr.  Dobb's  Journal,  March  1985 


SAVVY  PC,  Version  4.0 

Company:  Excalibur  Technologies 
Corporation,  800  Rio  Grande 
Blvd.  NW,  Albuquerque,  NM 
87104,  (505) 242-3333 
Computer:  IBM  PC  and  most 
compatibles 
Price:  $395 

Circle  Reader  Service  No.  131 
Reviewed  by  Steve  King 

SAVVY  PC  is  misrepresented  -Excali¬ 
bur  Technologies  advertises  its  product 
as  “the  artificial  intelligence  data 
base,”  but  the  data  base  manager  con¬ 
stitutes  only  a  small  part  of  the  total 
contents  of  the  SAVVY  PC  package. 
SAVVY  is  actually  a  complete  pro¬ 
gramming  language  unto  itself.  Ex¬ 
calibur  Technologies  seems  to  have  in¬ 
cluded  the  SAVVY  PC  data  base 
manager  more  as  an  example  of  the 
SAVVY  programming  language’s  ca¬ 
pabilities.  Don’t  get  me  wrong;  the 
SAVVY  PC  data  base  manager  is  as 
powerful  as  any  I’ve  tried,  including 
KnowledgeMan.  But  the  SAVVY  pro¬ 
gramming  language  is  the  heart  of  the 
whole  SAVVY  PC  package. 

Fourth  and  Fifth  Generation 
Languages 

The  mainframe  world  is  undergoing  a 
computer  language  revolution  that  par¬ 
tially  parallels  a  similar  change  in  the 
mini-  and  microcomputer  worlds. 
Fourth  generation  languages  are  re¬ 
placing  the  mainframe  world’s  liturgi¬ 
cal  high-level  programming  language, 
COBOL. 

From  a  microcomputer  world  view,  it 
appears  anachronistic  that  a  basically 
unstructured  language,  such  as  CO¬ 
BOL,  continues  to  enjoy  extensive  use 
for  applications  programming  (Of 
course,  the  microcomputer  world  also 
continues  to  suffer  with  Microsoft  BA¬ 
SIC  in  its  many  flavors.)  But  the  exis¬ 
tence  of  so  much  mainframe  COBOL 


code  makes  it  economically  unfeasible 
to  discard  the  language  in  favor  of  such 
structured  languages  as  Pascal  or  C, 
which  have  become  so  popular  for  mini- 
and  microcomputers.  However,  fourth 
generation  languages  (FGLs)  are  now 
helping  to  solve  this  mainframe  prob¬ 
lem:  they  allow  programmers  to  devel¬ 
op  application  programs  much  more 
rapidly  than  high-level  languages,  and 
moreover,  many  mainframe  FGLs  can 
access  COBOL  data  files. 

What  is  an  FGL?  It’s  the  procedural 
and  query  language  supplied  with  a 
data  base  management  system 
(DBMS);  examples  are  dBASE  II  or 
KnowledgeMan.  FGLs  probably  began 
as  macros  to  expedite  data  base  man¬ 
agement  procedures,  but  many  of  them 
have  evolved  into  fairly  complete  pro¬ 
gramming  languages.  Just  as  the 
dBASE  II  language  was  the  first  to 
gain  wide  use  and  popularity  in  the  mi¬ 
crocomputer  world,  the  SQL  (pro¬ 
nounced  “sequel”)  language,  devel¬ 
oped  by  IBM  as  part  of  the  SQL/DS 
DBMS,  was  the  first  to  gain  wide  use 
and  popularity  in  the  mainframe 
world.  The  KnowledgeMan  version  of 
SQL  is  modeled  after  the  IBM  main¬ 
frame  version. 

When  I  first  received  SAVVY  PC,  I 
expected  to  find  another  DBMS  with 
its  accompanying  FGL.  Instead,  I 
found  a  highly  structured  and  modular 
language  that  uses  spoken  English  ter¬ 
minology.  Excalibur  Technologies 
calls  SAVVY  “a  natural  language  pro¬ 
gramming  environment  and  set  of  tools 
for  solving  data  processing  problems 
[by]  using  natural  language  program¬ 
ming.”  The  Excalibur  folks  wrote  the 
SAVVY  PC  data  base  manager  in  the 
SAVVY  programming  language  (from 
here  on  referred  to  as  SAVVY).  SAV¬ 
VY  is  not  quite  a  true  “fifth  genera¬ 
tion”  language,  which  would  allow  a 
spoken  English  interface  with  a  com¬ 
puter  (“Hey,  computer,  baby,  look  in 


the  name  and  address  data  base  and 
get  me  the  phone  number  for  Dr. 
DooBy’s  ComPuter  JunkYard  and 
HamBurger  EmPorium  .  .  .”),  but  it’s 
as  close  to  that  goal  as  any  program¬ 
ming  language  I’ve  seen  yet. 

Pattern  Recognition 

Pattern  recognition  capabilities,  one  of 
the  foundation  structures  of  the  artifi¬ 
cial  intelligence  world,  allow  Excalibur 
Technologies  to  tout  SAVVY  PC  as 
“the  artificial  intelligence  data  base.” 
The  folks  at  Excalibur  structured 
SAVVY  around  their  Adaptive  Pattern 
Recognition  Processor  (APRP).  This 
4K  program  core  recognizes  the  com¬ 
mands  you  give  SAVVY  PC.  If  you’ve 
made  a  typing  error  or  used  improper 
syntax  with  a  command,  the  APRP  will 
either  correct  your  error  or  suggest 
changes  to  the  command  by  providing 
a  menu  of  possible  correct  choices. 

The  SAVVY  APRP  is  fast.  Excalibur 
claims  that  it  does  not  look  up  each 
word  in  the  SAVVY  dictionary  (more 
about  that  later)  as  you  key  entries  but 
uses  an  algorithmic  trick  to  provide 
immediate  response  to  entry  errors.  In 
fact,  development  of  the  APRP  preced¬ 
ed  the  SAVVY  language  and  SAVVY 
PC  DBMS. 

You  may  turn  off  the  APRP  and 
switch  SAVVY  from  the  free  mode  to 
the  literal  mode  at  any  time.  When  you 
write  a  program  with  SAVVY,  the 
manual  recommends  that  you  work  in 
the  literal  mode  so  the  APRP  won’t 
search  for  words  it  doesn’t  know.  Until 
I  got  used  to  SAVVY,  I  found  myself 
frustrated  several  times  when  I  forgot 
to  turn  off  the  APRP.  SAVVY  often  re¬ 
wrote  what  I  thought  was  a  new  word 
because  the  program  considered  my 
word  a  typing  error. 

The  second  contribution  that  the  ar¬ 
tificial  intelligence  world  has  made  to 
the  application  software  world  is  the 
natural  language  interface.  The  SAV- 


Dr.  Dobb’s  Journal,  March  1985 


116 

247 


VY  RETRIEVER  reportedly  allows  you 
to  query  a  SAVVY  PC  data  base  using 
plain  old  English.  I  had  hoped  to  de¬ 
scribe  how  RETRIEVER  works  and  to 
evaluate  it  for  this  review,  but  Excali- 
bur  didn’t  release  it  in  time  for  the 
Dr.’s  publication  deadline. 

The  Data  Base  Manager 

The  SAVVY  PC  package  comes  with  a 
thick,  IBM-sized,  detailed  reference 
manual/slide  case  for  SAVVY  and  a 
booklet  for  the  SAVVY  SAMPLER  to 
introduce  you  to  the  DBMS.  You  also 
receive  a  master  program  disk  and  the 
sampler  disk.  Both  disks  contain  the 
files  SAVVY.COM,  SAVVY.OVR,  and 
DATA  BASE. SVY. 

Excalibur  wrote  SAVVY  PC  in 
MMSForth  (this  despite  John  Dvorak’s 
statement  in  his  InfoWorld  column, 
“Inside  Track,”  that  no  decent  program 
was  ever  written  in  Forth).  SAVVY  PC 
shows  its  Forth  heritage  by  storing  data 
base  procedures,  report  formats,  and 
the  data  files  in  DATA  BASE. SVY.  Al¬ 
though  the  program  does  not  use  the 
DOS  environment,  DATABASE. SVY 
resides  on  a  standard  DOS  format  disk, 
and  SAVVY  PC  can  import  and  export 
standard  DOS  files.  When  you  write  a 
SAVVY  program  that  doesn’t  use  the 
data  base  manager,  SAVVY  PC  creates 
a  separate  storage  file  that  you  name 
(FILENAME.SVY). 

After  SAVVY  boots  and  presents  its 
sign-on  messages,  it  displays  the 
prompt:  “What  would  you  like  me  to  do 
now?”  You  may  either  enter  the  menu 
system  by  typing  MAIN  MENU  or  en¬ 
ter  direct  commands  to  SAVVY.  The 
data  base  manager  booklet  and  sampler 
disk  take  you  through  the  basics  of  us¬ 
ing  the  DBMS  and  generating  reports. 
Because  the  DATABASE.SVY  file  on 
the  sampler  disk  contains  almost  2 1 3K, 
you  barely  have  enough  disk  space  to 
finish  the  examples  before  running  out 
of  storage  room.  SAVVY  gives  you  ade¬ 
quate  warning  when  you  are  about  to 
run  out  of  disk  space.  It  also  provides 
the  JANITOR  command  to  perform 
general  disk  housekeeping  chores  and 
to  compact  your  data.  However,  you 
can  avoid  the  disk  space  problem  by 
copying  DATABASE.SVY  to  drive  B: 
and  modifying  the  DEMO.BAT  batch 
file  to  reflect  this  change. 

When  you  tell  SAVVY  PC  to  gener¬ 
ate  a  new  data  base,  the  program  pro- 

118 

248 


vides  a  blank  form  on  the  screen  and 
allows  you  to  structure  the  layout  of 
your  data  entry  screen  by  “painting” 
the  screen.  SAVVY  PC  also  guesses  at 
your  data  types  (i.e.,  ANSWER,  COM¬ 
PUTED.  DATE,  GENERAL,  INTE¬ 
GER,  MONEY,  NOTE,  NUMBER, 
PHONE,  SSN,  STATE,  or  ZIP)  when 
you  create  the  data  entry  screen.  Lat¬ 
er,  the  program  allows  you  to  clean  up 
things  by  detailing  data  types,  field 
length,  and  limits  for  data  entry.  You 
may  also  build  relationships  with  other 
data  files  and  specify  whether  data  en¬ 
tered  into  your  new  data  base  will  up¬ 
date  related  data  bases.  After  you 
complete  your  design  work,  SAVVY  PC 
takes  from  two  to  six  minutes  to  gener¬ 
ate  the  data  base.  All  SAVVY’s  activi¬ 
ties  scroll  past  you  as  it  works.  Al¬ 
though  not  documented  in  the  SAVVY 
manual,  if  you  press  <Ctrl><Num 
Lock>,  you  can  pause  SAVVY’s  activ¬ 
ities  to  determine  exactly  what  the  pro¬ 
gram  is  doing  to  create  your  data 
base — a  good  tool  to  assist  in  learning 
the  SAVVY  language. 

[A  warning  about  another  undocu¬ 
mented  feature:  <Ctrl> <Break> 
exits  SAVVY  in  a  hurry.  Once,  while 
reviewing  a  program  I  was  working  on, 
I  accidentally  pressed  <Ctrl> 
<Break>  instead  of  <Ctrl><Num 
Lock>.  After  some  disk  activity,  SAV¬ 
VY  dumped  me  back  to  DOS.  SAVVY 
had  closed  the  open  files,  so  no  harm 
was  done.  However,  I  experimented 
further  and  found  that  if  I  hit  <Ctrl> 
<Break>  while  SAVVY  was  building 
a  new  data  base,  all  my  work  would  be 
lost.] 

As  time  has  permitted  over  the  last 
six  months,  I  have  been  developing  a 
ranch  management  package  for  my 
wife,  who  is  a  horse  trainer.  I  had  been 
using  KnowledgeMan  (KMan),  which 
allows  much  quicker  and  easier  pro¬ 
gram  development  than  a  high-level 
language  does,  but  hadn’t  found  the 
time  to  design  simplified  data  entry 
forms.  KMan’s  traditional  data  base 
data  entry  format  reinforced  my  wife’s 
dislike  of  computers.  She  also  had 
trouble  phrasing  data  base  queries 
with  KMan’s  SQL  language.  After  her 
first  few  frustrating  attempts  with  the 
KMan  ranch  management  package, 
she  refused  to  use  the  program. 

When  I  received  SAVVY  PC,  I  de¬ 
cided  to  give  the  ranch  management 


project  one  more  try — but  with  SAV¬ 
VY  PC  instead  of  KMan.  SAVVY  PC 
simplifies  the  data  base  design  process 
(you  construct  the  entry  screens  as  you 
structure  the  data  base),  which  also 
speeds  up  the  whole  programming  ef¬ 
fort.  I  completed  the  SAVVY  PC  ver¬ 
sion  of  the  ranch  management  data 
base  in  less  time  than  it  took  to  finish 
the  KMan  version  only  partially.  After 
a  great  deal  of  coaxing,  my  wife  tried 
the  new  program.  She  was  pleasantly 
surprised.  Although  she  doesn’t  dislike 
computers  any  less,  at  least  now  she 
uses  the  data  base  I  designed  for  her. 

SAVVY  Programming 

SAVVY  comes  with  a  dictionary  of 
more  than  220  words.  The  dictionary 
does  not  contain  just  the  usual  data 
base  terms;  it  also  holds  all  the  avail¬ 
able  SAVVY  commands,  items,  tasks, 
and  functions.  When  you  write  a  SAV¬ 
VY  program,  you  use  existing  SAVVY 
words  to  create  new  words  for  the  dic¬ 
tionary;  i.e.,  you  may  extend  SAVVY 
as  you  would  extend  C  or  Forth.  Be¬ 
cause  SAVVY  words  may  contain  up  to 
80  characters,  you  can  be  fairly  de¬ 
scriptive  when  creating  new  terms. 

Commands  simply  represent  direct 
orders  to  SAVVY.  However,  the  APRP 
affects  the  way  you  interact  with  SAV¬ 
VY.  For  example,  after  you  give  the 
command  COPY  and  press  <Enter>, 
SAVVY  responds  with  “COPY  the  _  to 

_ ”  The  cursor  is  positioned  at  the  first 

underline  (underlines  are  displayed  as 
reverse  video  blocks  on  your  CRT).  Af¬ 
ter  you  type  the  name  of  the  item  you 
wish  copied  and  press  <Enter>,  SAV¬ 
VY  moves  the  cursor  to  the  second  un¬ 
derline.  Type  the  second  item  and 
<Enter>.  Items  may  be  either  con¬ 
stants  or  variables;  SAVVY  doesn’t  dif¬ 
ferentiate  between  the  two.  The  final 
result  could  indicate:  COPY  the  FIRST 
CONSTANT  to  LAST  VARIABLE. 

Tasks  and  functions  contain  groups 
of  commands  that  perform  specific  op¬ 
erations  (they  may  also  contain  other 
tasks  and  functions);  these  groups  pro¬ 
vide  SAVVY  with  its  modular  pro¬ 
gramming  approach.  Tasks  and  func¬ 
tions  are  identical  except  that  a  task 
stands  on  its  own  and  a  function  re¬ 
quires  operands  or  arguments  to  per¬ 
form  its  chores. 

For  example,  when  SAVVY  created 
the  ranch  management  data  base  pro- 

Dr.  Dobb's  Journal,  March  1985 


gram  for  my  wife,  I  had  specified  that 
the  horse’s  AGE  field  be  calculated.  My 
wife  wants  the  data  base  to  calculate 
the  horse’s  age  automatically  after  she 
enters  the  animal’s  date  of  birth.  (By 
convention,  all  horses  become  one  year 
older  at  the  first  of  each  year,  rather 
than  on  their  actual  birth  dates;  this 
custom  greatly  simplifies  the 
calculation.) 

While  watching  the  screen  as  SAV¬ 
VY  generated  the  ranch  management 
data  base,  I  saw  that  the  program  cre¬ 
ated  a  task  called  RANCH  MANAGE¬ 
MENT,  COMPUTE  THE  AGE.  After 
SAVVY  finished  creating  the  data 
base,  1  told  it  to  detail  the  RANCH 
MANAGEMENT  ,  COMPUTE  THE 
AGE.  Because  SAVVY  showed  that  the 
task  contained  only  the  line  “COPY  the 
‘  ’  to  AGE,”  I  used  the  SAVVY  Task 
Editor  to  create  the  actual  age  calcula¬ 
tion  task  as  follows: 

Task  RANCH  MANAGEMENT  , 

COMPUTE  THE  AGE 

1  COPY  the  DATE  OF  BIRTH  to 
BIRTHDATE 

2  TAKE  the  first  6  characters  from 
BIRTHDATE 

3  COPY  the  BACK  to  BIRTH  YEAR 

4  GET  DATE  and  time  into  TO¬ 
DAY’S  DATE 

5  TAKE  the  first  15  characters  from 
TODAY'S  DATE 

6  COPY  the  BACK  to  THIS  YEAR 

7  SUBTRACT  the  BIRTH  YEAR 
from  THIS  YEAR 

8  COPY  the  DIFFERENCE  to  AGE 
End. 

Each  time  you  enter  a  word  that  is 
not  in  the  SAVVY  dictionary  (such  as 
BIRTHDATE  or  BIRTH  YEAR),  the 
program  informs  you  that  it  does  not 
know  the  word  and  asks  if  you  wish  to 
define  it.  Answer  “yes”  and  continue 
to  enter  your  program.  SAVVY  auto¬ 
matically  numbers  the  lines.  Because 
no  task  or  function  may  be  more  than 
100  lines  long,  you  are  forced  to  write 
programs  in  modular  blocks  and  to 
structure  them  in  a  Forth-like  top 
down  fashion. 

In  the  above  example,  line  1  copies 
the  animal’s  birthdate  to  a  new  item. 
Because  you  enter  BIRTHDATE  in  the 
form  of  DD/MM/YY,  line  2  can  split  it 
into  the  FRONT  portion,  DD/MM/, 
and  the  BACK  portion,  YY,  to  obtain 


the  BIRTH  YEAR.  Lines  4  and  5  obtain 
the  TODAY’S  DATE  from  the  operat¬ 
ing  system;  the  GET  DATE  command 
also  retrieves  the  system  time.  Because 
this  command  uses  a  HH:MM:SS  DD/ 
MM/YY  format,  you  must  cut  the  TO¬ 
DAY’S  DATE  variable  after  the  fif¬ 
teenth  character  to  obtain  THIS  YEAR. 
Line  7  performs  the  calculation,  and 
line  8  copies  it  to  the  AGE. 

After  you  finish  writing  a  task  or 
function,  press  <Alt><Q>  and 
SAVVY  will  compile  it.  The  program 
informs  you  of  the  nature  and  location 
of  any  syntax  errors  that  the  task  or 
function  may  contain. 

Programming  in  SAVVY  is  similar  to 
programming  with  Forth.  Basically, 
you  extend  SAVVY  and  create  new 
tasks  and  functions  with  the  existing 
SAVVY  words,  then  SAVVY  compiles 
the  new  tasks  and  functions.  When  your 
program  runs,  SAVVY  interprets  the 
compiled  words  one  by  one.  But  unlike 
Forth,  SAVVY’s  terminology  is  easy  to 
remember  and  understand  because  it’s 
plain  old  English.  You  also  don’t  have 
to  worry  about  manipulating  Forth’s 
stack  or  reverse  Polish  notation. 

On  the  Negative  Side 

The  only  mathematical  functions  that 
SAVVY  contains  are  add,  subtract, 
multiply,  and  divide.  If  your  programs 
require  higher  math  functions,  you  will 
have  to  write  them  yourself. 

SAVVY  contains  no  way  of  adding 
comments  or  remarks  to  programs.  Be¬ 
cause  of  SAVVY’s  natural  language 
terminology,  I’m  sure  Excalibur  Tech¬ 
nologies  didn’t  consider  remarks  nec¬ 
essary.  But  remarks  often  make  it  easi¬ 
er  to  understand  long  tasks  or 
functions.  However,  SAVVY  lends  it¬ 
self  well  to  pseudocoding,  which  great¬ 
ly  simplifies  program  design. 

Like  its  parent  Forth,  SAVVY  makes 
no  provisions  for  arrays  and,  as  stated 
earlier,  doesn’t  use  a  stack  as  a  partial 
substitute  for  arrays.  However,  the 
program  contains  such  extensive 
string-handling  functions  for  character 
and  bit  manipulation,  you  probably 
won’t  need  arrays.  Unlike  BASIC’s 
RIGHTS,  MIDS,  LEFTS,  and  so  on, 
SAVVY’s  string-handling  commands 
are  easy  for  even  novice  programmers 
to  understand  and  use. 

As  massive  and  as  well  written  as 
SAVVY’s  documentation  is,  there’s 


just  not  enough  of  it.  You  must  watch 
SAVVY  build  data  bases  to  learn  the 
details  of  writing  programs  with  the 
language.  The  data  base  manager 
booklet  contains  only  416  pages  about 
extending  a  SAVVY-generated  data 
base.  Also,  the  manual  contains  only 
the  particulars  of  programming  with 
SAVVY  and  supplies  no  information 
about  integrating  programs  into  SAV¬ 
VY  PC-created  data  base  programs. 

A  member  of  Excalibur  Technolo¬ 
gies  informed  me  that  a  600+  page  tu¬ 
torial  for  an  earlier  version  of  SAVVY 
exists.  The  company  is  updating  the 
tutorial  to  meet  the  requirements  of 
the  present  version  of  SAVVY  and  will 
release  the  book  as  soon  as  possible. 

Where  to  Next? 

Because  SAVVY  also  contains  a  fairly 
complete  set  of  communications  com¬ 
mands,  a  friend  and  I  plan  to  write  a 
remote  bulletin  board  system  (RBBS) 
with  SAVVY.  Most  IBM  PC  RBBSs  use 
variations  of  the  Capitol  Computer 
Club  (CPC)  RBBS,  which  was  written 
in  BASICA.  I  have  seen  several  varia¬ 
tions  (A  -  D)  of  the  CPC  RBBS,  version 
1 2.3,  on  different  systems  and  have  also 
seen  announcements  that  version  13  is 
imminent.  An  RBBS  SYSOP  (SYstem 
OPerator)  recently  confided  in  me  that 
although  CPC  RBBS  has  been  evolving 
for  over  two  years,  it  still  contains  bugs. 
My  friend  and  1  believe  that  we  can  de¬ 
velop  a  SAVVY  RBBS  in  just  two  or 
three  months;  almost  any  FGL,  and 
particularly  SAVVY,  reduces  program¬ 
ming  time  extensively. 

I  have  pretty  much  stopped  pro¬ 
gramming  with  BASIC,  Turbo  Pascal, 
and  KnowledgeMan  and  switched  to 
SAVVY.  When  the  tutorial  becomes 
available,  I  believe  SAVVY  will  be¬ 
come  one  of  the  easiest  ways  to  teach 
novice  computer  users  about  the  black 
art  of  computer  programming.  When 
they  reviewed  SAVVY  PC  for  the  mas¬ 
sive  data  base  comparison  series  that 
appeared  in  PC  Magazine,  John  F.  and 
Barbara  E.  McMullen  stated  that  pro¬ 
gramming  in  SAVVY  PC  is  fun — and 
they  were  right. 

DDJ 


Dr.  Dobb’s  Journal.  March  1985 


120 

249 


OFINTEREST 


by  R.  P.  Sutherland 

Turing's  Man 

A  reading  of  J.  David  Bolter’s  book 
Turing’s  Man:  Western  Culture  in  the 
Computer  Age  has  put  our  issue  on  ar¬ 
tificial  intelligence  in  a  larger  context 
for  me.  Turing’s  Man  is  a  study  of  the 
impact  that  electronic  logic  machines 
are  having  upon  our  culture. 

Bolter  traces  the  history  of  what  he 
calls  “defining  technologies”  from  an¬ 
cient  Greece  until  today.  He  does  this 
by  showing  the  correspondence  be¬ 
tween  technologies  and  philosophical/ 
theological  metaphors.  He  points  out, 
for  example,  that  the  Greeks,  who 
were  craftsmen,  had  gods  who  were 
potters  and  spinners.  The  poet  who  de¬ 
scribed  the  Fates  as  spinning  the 
threads  of  human  lives  was  influenced 
by  the  fact  that  in  ancient  Greece  spin¬ 
ning  was  a  defining  technology.  Plato 
compared  the  physical  universe  to  a 
spindle.  Likewise,  during  the  Middle 
Ages  the  weight-driven  clock  provided 
a  new  metaphor  for  the  motions  of  the 
planets.  Descartes’  mechanistic  de¬ 
scriptions  of  the  world  were  compelling 
to  his  contemporaries  because  they 
were  familiar  with  clocks  and  gears. 
With  the  rise  of  the  steam  engine  in  the 
nineteenth  century,  the  earth  was  com¬ 
pared  to  a  large  heat  engine. 

In  the  twentieth  century’s  headlong 
rush  to  make  a  machine  think  like  a 
human,  we  have  defined  the  human  in 
terms  of  a  machine.  Arguing  for  a  syn¬ 
thesis  of  man  and  computer,  Bolter 
proposes  to  replace  the  term  “artificial 
intelligence”  with  “synthetic  intelli¬ 
gence.”  Bolter  concludes  that  Turing’s 
man  is  the  most  complete  synthesis  of 
the  human  and  the  technological  in 
Western  history,  and  he  predicts  that 
the  computer  as  a  defining  technology 
will  give  way  to  a  technology  that  we 
cannot  yet  imagine.  We  may  not  real¬ 
ize  Turing’s  1 950  prediction  by  the  end 
of  this  century,  but  the  fact  that  we  are 

122 

250 


thinking  along  these  lines  implies  that 
the  computer  as  a  metaphor  for  the 
way  we  understand  ourselves  is  firmly 
established. 

Bolter  has  graduate  degrees  in  the 
classics  as  well  as  in  computer  science. 
Turing’s  Man  (ISBN  0-8078-1564-0) 
is  published  by  The  University  of 
North  Carolina  Press,  Post  Office  Box 
2288,  Chapel  Hill,  NC  27514.  Reader 
Service  No.  101 . 


Al 

Solution  Systems,  the  publisher  of  Pro¬ 
log-86,  is  sponsoring  a  contest  for  pro¬ 
grammers  interested  in  artificial  intel¬ 
ligence.  The  purpose  of  the  contest, 
“Artificial  Intelligence  Concepts,”  is 
to  generate  material  to  help  Prolog 
programmers  and  to  promote  Prolog. 
Programmers  who  wish  to  enter  should 
develop  expert  systems  and/or  natural 
language  processing  systems  designed 
to  illustrate  methods  and  concepts  of 
Prolog  programming.  Applications 
with  a  strong  commercial  potential  are 
discouraged.  The  application  judged  to 
be  the  most  significant  will  receive  a 
cash  reward  of  $1000.00.  All  partici¬ 
pants  will  receive  diskettes  containing 
all  contributions  made  by  other  contes¬ 
tants.  Submit  applications  on  514-inch 
MSDOS  diskettes  to  Solution  Systems, 
335  Washington  Street,  Norwell,  MA 
02061,  accompanied  by  a  one-page  de¬ 
scription.  All  submissions  must  be  in 
by  April  30,  1985.  The  winners  will  be 
announced  in  June  1985.  For  details 
call  Solution  Systems  at  (617)  659- 
1  57  1 .  Reader  Service  No.  103. 

Sun  Microsystems  has  expanded  the 
use  of  its  workstations  by  adding  AI 
packages  to  its  Third  Party  Referral 
Program.  Quintus  Computer  Systems, 
Inc.,  Palo  Alto,  CA,  has  released  Quin¬ 
tus  Prolog,  which  includes  an  interface 
for  calling  C  programs.  Lucid,  Inc., 


Palo  Alto,  CA,  is  offering  Lucid  Com¬ 
mon  Lisp  for  developing  portable 
knowledge-based  applications  on  Sun 
Workstations.  The  Knowledge  Engi¬ 
neering  System  (KES)  from  Software 
Architecture  and  Engineering,  Inc., 
Arlington,  VA,  is  an  integrated  set  of 
utilities  designed  to  allow  newcomers 
to  the  field  of  AI  to  design  expert  sys¬ 
tems.  Smart  System  Technology, 
McLean,  VA,  offers  DUCK,  a  high- 
level  Lisp-based  applications  develop¬ 
ment  environment  for  designing  sys¬ 
tems  based  on  logic  reasoning.  JoAnn 
Kahn  is  the  manager  of  Sun’s  Third 
Party  Programs  and  can  be  reached  at 
Sun  Microsystems,  Inc.,  2550  Garcia 
Avenue,  Mountain  View,  CA  94043. 

Reader  Service  No.  105. 

A  Canadian  company  has  developed 
a  micro-version  of  MPROLOG,  which, 
it  says,  opens  the  door  to  low-cost  AI 
applications.  MPROLOG  (Modular 
Programming  in  Logic)  allows  a  pro¬ 
grammer  to  create  a  major  program 
for  a  mainframe  computer  on  a  micro¬ 
computer  by  working  with  modules. 
So,  by  working  on  an  IBM  PC,  one  can 
develop  an  A I  application  one  module 
at  a  time  and  execute  the  full  applica¬ 
tion  on  a  VAX  or  IBM  mainframe  or  a 
68000-based  machine.  MPROLOG  for 
microcomputers  is  $950.00  for  a  paid- 
up  license  or  $260.00  a  month  to  lease. 
For  further  information:  Logicware 
Inc.,  1000  Finch  Avenue  West,  Suite 
600,  Toronto,  Ontario  M3J  2V5  (416) 
665-0022.  Reader  Service  No.  107. 

Software 

“The  arcade  game  is  all  but  dead,”  ac¬ 
cording  to  Synapse  Software  President 
Jon  Loveless  who  has  just  released  two 
electronic  novels:  Essex  and  Mind-  | 
wheel.  I  haven’t  “read”  one  yet,  but 
they  are  supposed  to  put  the  user  in  the 
center  of  the  action  so  that  the  user’s 

Dr.  Dobb’s  Journal.  March  1985 


Dr.  Dobb's  Journal.  March  1985 


125 

251 


decisions  determine  the  outcome  of  the 
plot.  The  novels  are  to  be  marketed 
through  bookstores  as  well  as  software 
stores  for  less  than  $50.00  each  and  are 
available  for  IBM  PC,  Atari,  Apple  lie, 
Apple  lie,  Macintosh,  and  Commo¬ 
dore.  Synapse  Software  Corporation  is 
located  at  5221  Central  Avenue,  Rich¬ 
mond,  CA  94804  (415)  527-7751. 

Reader  Service  No.  109. 

Speaking  of  novels,  RACTER  is  a 
software  program  that  has  authored  a 
published  book:  The  Policeman's  Beard 
is  Half  Constructed.  The  book  features 
stories,  essays,  poems,  and  interviews 
created  by  RACTER.  A  sample  poem: 
“More  than  iron,  more  than  lead,  more 
than  gold  I  need  electricity./I  need  it 
more  than  I  need  lamb  or  pork  or  let¬ 
tuce  or  cucumber./I  need  it  for  my 
dreams.”  The  creators,  Tom  Etter  and 
Bill  Chamberlain,  claim  that  it  pro¬ 
duces  original  conversation  that  is  also 
grammatical,  without  the  use  of  stock 
phrases.  RACTER  is  available  for 
MSDOS  or  CP/M  for  $69.95  from  John 
D.  Owens  Associates,  Inc.,  12  Schu¬ 
bert  Street,  Staten  Island,  NY  10305 
(212)  448-6298.  Reader  Service  No.  111. 
The  Policeman’s  Beard  is  Half  Con¬ 
structed  (ISBN  0-446-38051-2)  is  pub¬ 
lished  by  Warner  Software/Warner 
Books,  666  Fifth  Avenue,  New  York, 
NY  1 0 1 03.  Reader  Service  No.  1 1 3. 

A  computerized  thesaurus  called 
Synonym  Finder  is  available  for 
MSDOS  running  WordStar  or  Multi¬ 
mate  and  CP/M  80  running  WordStar. 
Synonym  Finder  sells  for  $124.95 
from  Writing  Consultants  at  1 1  Creek 
Bend  Drive,  Fairport,  NY  1 4450  (716) 
377-0 1  30.  Reader  Service  No.  115. 

Word  Publishing  has  announced  the 
release  of  NARNIA,  a  video  game  based 
on  the  Chronicles  of  Narnia  by  C.  S. 
Lewis.  NARNIA  is  available  for  Apple 
II  and  Commodore  64  computers. 
Availability:  Waldenbooks  and  Com- 
puterland  stores,  or  send  $39.95  to 
Word  Inc.,  Waco,  TX  76703.  Reader 

Service  No.  117. 

Thoroughbred  Handicapper  II  is  a 

computerized  system  that  analyzes 
thoroughbred  horse  races  and  helps  to 
predict  the  winners.  The  handicapping 
system  is  based  on  a  statistical  analysis 
of  horse  races.  Thoroughbred  Handi¬ 
capper  II  is  available  for  the  IBM  PC 
family  for  $49.95.  Contact  The  Soft¬ 
ware  Bottling  Company  of  New  York, 


29-14  23rd  Avenue,  Long  Island  City, 
NY  1  1  105  (718)  728-2200.  Reader  Ser- 

vice  No.  119. 

A  brain  wave  to  electronic  speech 
system  for  the  ZX-81/TS-1000  and 
Apple  computers  permits  brain  wave 
control  over  robotic  devices.  We  can¬ 
not  verify  this  claim,  but  for  more  in¬ 
formation  write  to  Rosetronix,  P.O. 
Box  7464,  Chula  Vista,  CA  92012. 


Hardware 

The  Integral  Personal  Computer  from 
Hewlett-Packard  is  a  68000  Unix-based 
machine.  This  portable  computer  has  a 
built-in  ThinkJet  printer,  a  3'/2-inch 
(7 10K)  disc  drive,  a  9-inch  electrolumi¬ 
nescent  display,  and  a  full-size  key¬ 
board.  Price:  under  $5,000.00.  Hewlett- 
Packard,  P.O.  Box  10301,  Palo  Alto, 
CA  94303. 


The  Spectravideo  Bondwell  12  computer  runs  CP/M  2.2  on  a  Z80a  CPU.  The  price  of 
$995.00  includes  a  voice  synthesizer,  two  floppy  disk  drives,  and  bundled  software. 
Christopher  Chang.  President,  Spectravideo,  3300  Seldon  Court  #10,  Fremont,  CA 
94539  (415)  490-4300.  Reader  Service  No.  125. 


A  modular  robot  called  Tutor  is  available  as  a  kit  from  Cybot,  Inc.  for  $3,395.00.  The 
three  circuit  boards  used  to  control  the  Tutor  robot  are  designed  to  the  S-100  standard. 
Cvbot.  Inc..  733  7th  Avenue.  Kirkland.  WA  98033.  Reader  Service  No.  129.  DDJ 


- HUMAN  INIbKhALt  UfcSILN - 

SOFTWARE  TOOLS  FOR  ADVANCED  PROGRAMMERS 


#102  April  1985 


$2.95  (3.50  Canada) 


80286  Xenix  Three  Printer-taming  Programs 

a#  Turbo  Toolbox  Reviewed 


•-  .  ;-v.; - •  ‘V- ■ 

Dr.Dobb’s  Journal 

Editorial 

Editor-in-Chief  Michael  Swaine 

Editor  Randy  Sutherland 
Managing  Editor  Frank  DeRose 
Technical  Editor  Alex  Ragen 
Editorial  Assistant  Sard  Noah 
Contributing  Editors  Robert  Blum, 

Dave  Cortesi, 

Ray  Duncan, 

Allen  Holub 
Copy  Editor  Polly  Koch 
Typesetter  Jean  Aring 
Production 
Design  /  Production 

Director  Detta  Penna 
Art  Director  Shelley  Rae  Doeden 
Production  Assistant  Alida  Hinton 
Cover  Tom  Upton 
Advertising 

Advertising  Director  Stephen  Friedman 
Advertising  Sales  Walter  Andrzejewski, 
Shawn  Horst. 

Beth  Dudas, 

Michele  Beaty 
DDJ  Classifieds  Alice  Abrams 
Advertising  Coordinators  Lisa  Boudreau 
Jay  Horvath 
Circulation 

Fulfillment  Manager  Stephanie  Barber 
Direct  Response 

Coordinator  Maureen  Snee 
Promotions  Coordinator  Jane  Sharninghouse 
Circulation  Assistant  Kathleen  Boyd 
Administration 
Finance  Manager  Sandra  Dunie 
Business  Manager  Betty  Trickett 
Accounting  Coordinator  May  da  Lopez-Quintana 
Accounts  Payable  Denise  Giannini 
Billing  Coordinator  Laura  Di  Lazzaro 


M&T  Publishing.  Inc. 

Chairman  of  fhe  Board  Oimar  Weber 
Director  CF  von  Quad t 
President  Laird  Foshay 


Entire  contents  copyright  °  1985  by  M&T  Publishing, 
Inc.  unless  otherwise  noted  on  specific  articles.  All 
rights  reserved. 

Dr.  Dobb’s  Journal  (USPS  307690)  is  published 
monthly  by  M&T  Publishing,  Inc.,  2464  Embarcadero 
Way,  Palo  Alto.  CA  94303,  (415)  424-0600.  Second 
class  postage  paid  at  Palo  Alto  and  at  additional  entry 
points. 

Address  correction  requested.  Postmaster:  Send  Form 
3579  to  Dr.  Dobb's  Journal.  2464  Embarcadero 
Way,  Palo  Alto,  CA  94303.  ISSN  0278-6508 

Subscription  Rates:  $25  per  year  within  the  United 
States,  $44  for  first  class  to  Canada  and  Mexico,  $62 
for  airmail  to  other  countries.  Payment  must  be  in  U.S. 
Dollars,  drawn  on  a  U.S.  Bank.  For  subscription  prob¬ 
lems,  call:  outside  of  CA  1-800-321-3333;  within  CA  I- 
619-485-6535. 

Foreign  Distributors:  ASCII  Publishing,  Inc.  (Ja¬ 
pan),  Computer  Services  (Australia),  Computer  Store 
(New  Zealand),  Computercollectief  (Nederland), 
Homecomputer  Vertriebs  GMBH  (West  Germany), 
International  Presse  (West  Germany),  La  Nacelle 
Bookstore  (France),  McGill's  News  Agency  PTY  LTD 
(Australia),  Progresco  (France). 


People's  Computer  Company 

Dr.  Dobb's  Journal  is  published  by  M&T  Publishing, 
Inc.  under  license  from  People’s  Computer  Company, 
2682  Bishop  Dr.,  Suite  107,  San  Ramon,  CA  94583,  a 
non-profit,  educational  corporation. 


April  1985 
Volume  10,  Issue  4 


CONTENTS 


Dr.  Dobb's  Board  of  Referees 

Dennis  R.  Allison,  Stanford  University 

Ian  Ashdown,  Professional  Engineer 

Joe  A.  Barnhart,  Hewlett-Packard 

S.  M.  Bellovin,  Bell  Laboratories 

Robert  Blum,  DDJ  Contributing  Editor 

Patrick  Burnstad,  Lands  End  Computers,  IEEE,  SVCS 

Wayne  Chin,  Hewlett-Packard 

David  D.  Clark,  Pennsylvania  State  University 

David  E.  Cortesi,  DDJ  Contributing  Editor 

Keith  Coye,  PicoNet 

Mel  Cruts,  Trilogy 

Bob  Desinger,  Hewlett-Packard 

Ray  Duncan,  Laboratory  Microsystems,  Inc. 

Michael  T.  Enright,  Cal  Omega,  Inc. 

Z.  Z.  Fisher 

Jim  Fleming,  UNIR 

Angel  Gomez,  Tele  Comp,  Inc. 

Kim  Harris,  Forthright  Enterprises 
James  E.  Hendrix,  University  of  Mississippi 
William  P.  Hogan 

Allen  Holub,  DDJ  Contributing  Editor 
David  W.  Hopper,  Senes  Consultants  Ltd. 

John  R.  Johnson,  Professional  Microware,  Inc. 

James  Christopher  Johnston,  NASA  Lewis  Research  Center 

Michael  P.  Kelly,  Design  Software 

John  P.  Keys,  Microsoft 

David  Kirkland,  Baker  and  Botts 

Richard  G.  Larson,  University  of  Illinois  at  Chicago 

Ben  Laws,  North  Texas  State  University 

Mohamed  el  Lozy,  Harvard  University 

Patrick  Lynch,  Tandem  Computers,  Inc. 

Leonard  K.  Nicholson,  ADM, IEEE 
Donald  L.  Rastede, 

Richard  Relph,  ELXSI 

John  Rogers,  Fortune  Systems 

Doug  Rosenberg,  Westminster  Software 

Prof.  David  Ross,  University  of  Iowa 

Darryl  E.  Rubin,  Rolm  Corporation 

Joseph  Sharp,  Micro  Science  Associates 

Henri  Socha,  SochaLogical  Research 

Charles  T.  Springer,  Information  Appliance,  Inc. 

John  Taber,  IBM 

Scott  D.  Thomas,  Informatics  General  Corp. 

Allen  Tigert,  Kontron  Medical  Electronics 
Matthew  H.  Trask,  Phoenix  Computer  Products  Corp. 

Bob  Wheeler,  Custom  Hardware 
Charles  Wilde,  Aton  International,  Inc. 

Cover  Credit 

Tom  Upton  created  our  cover  this  month  with  the  help  of  MacVision,  a  graphic 
digitizer  from  Koala  Technologies  Corporation. 


4 


OU.A 


Dr.  Dobb's  Journal.  April  1985 


SOFTWARE  TOOLS  FOR  ADVANCED  PROG RAMMERS 


Dr.  Dobb’s  Journal 


ARTICLES 


MONTY  Plays  Scrabble: 
What  We  Can  Learn  about  Human  Factors 
Engineering  from  a  Game-Playing  Computer 

by  Terry  A.  Ward 

18 

Designers  of  software  can  make  it  easier  for  the  end  user 
to  interact  with  their  programs.  Some  principles  of  human 
factors  engineering  can  be  learned  from  a  small  computer 
that  plays  Scrabble  (Reader  Ballot  No.1 92). 

Shortcut  to  SCISTAR:  For  Prowriter  Users 

by  Amer  Nelson 

24 

How  to  print  special  characters  with  an  Osborne  1  / 
Prowriter  system  (Reader  Ballot  No.  1 93). 

fx80char:  A  Character  Editor  for  the  Epson 

FX-80 

by  David  D.  Clark 

28 

A  screen  oriented  editor  for  developing  alternate 
character  sets;  commands  for  downloading  the  new 
characters  to  the  printer  (Reader  Ballot  No.  1 94). 

A  Magic  Mushroom  for  the  Epson  Alice 

by  Gary  Palmer 

58 

An  anthropologist  prints  vertically  extended  phonetic 
symbols  for  transcriptions  of  the  stories  of  the  Coeur 
d'Alene  Indians  (Reader  Ballot  No.  195). 

Let's  Mouse  Around 

by  Vincent  and  Ronald  G.  Parsons 

74 

The  Microsoft  Mouse  with  Turbo  Pascal  (Reader  Ballot  No. 
196). 

COLUMNS 

Realizable  Fantasies 

by  Michael  Swaine  and  Bob  Albrecht 

12 

Liberating  the  essence  of  the  Mac  from  Apple’s  closed 
architecture  (Reader  Ballot  No.  190). 

Dr.  Dobb's  Clinic 

by  D.  E.  Cortesi 

14 

DR1  phases  out  unconfigured  versions  of  CP/M;  critique  of 
Modula-2;  shadowy  printing;  well-  and  ill-behaved 
functions  Reader  Ballot  No.  191). 

Software  Designer 

by  Mike  Swaine 

86 

Dave  Winer  of  Living  Videotext  and  Thom  Hogan  of 
Business  Software  discuss  human  factors  engineering  and 
software  design  (Reader  Ballot  No.  197). 

C  Chest 

by  Allen  Holub 

90 

Reasons  for  not  using  the  Shell  sort  in  K&R;  qsort.c,  a 
general  purpose  Quicksort  routine  (Reader  Ballot  No.  198). 

1 6- Bit  Software  Toolbox 

by  Ray  Duncan 

102 

ED/ASM-86;  a  TEE  filter  for  MSDOS  2.x;  MacAsm; 

80286  Xenix  3.0  (Reader  Ballot  No.  1 99). 

CP/M  Exchange 

by  Bob  Blum 

112 

A  patch  to  enable  PIP  v.  1.5  to  accept  null  command  lines 
from  a  submit  file;  a  stand-alone  $$$.SUB  file  generator 
improves  batch  procedures  (Reader  Ballot  No.  200). 

DEPARTMENTS 

Editorial 

6 

Letters 

8 

Reviews 

80 

SPSS/PC;  Borland’s  Turbo  Toolbox 

DDJ  Classifieds 

107 

Of  Interest 

122 

(Reader  Ballot  No.  201 ) 

Advertiser  Index 

128 

Dr.  Dobb's  Journal.  April  1985 


5 

255 


Changes  come  and  trip  all  your  traps.  You  subscribe  to  Micro  and  you  get 
Dr.  Dobb’s  Journal.  You  just  get  used  to  thinking  of  yourself  as  a  “serious 
computerist”  and  you  find  yourself  a  subscriber  to  a  magazine  “for  ad¬ 
vanced  programmers.” 

With  this  issue,  we  welcome  several  thousand  readers  of  the  sadly-defunct 
Micro  magazine  to  Dr.  Dobb’s.  Micro,  like  Microsystems  and  Microcomputing, 
died  last  fall  in  a  bad  season  for  many  technical  computer  magazines. 

I’m  sorry  about  Micro,  but  I’m  happy  to  see  this  influx  of  readers  serious  about 
the  6502  and  68000  microprocessors.  We’ve  been  publishing  more  and  more  68000 
material  recently,  but  aside  from  an  occasional  Commodore  64  piece,  we’ve  ne¬ 
glected  the  6502  programmer.  The  Micro  readers  present  Dr.  Dobb’s  with  an 
opportunity  to  address  an  audience  it’s  lost  touch  with  in  recent  years.  (For  an 
indication  of  the  kind  of  6502  material  we  have  published  in  the  past,  see  the  note 
at  the  end  of  the  “Realizable  Fantasies”  column  elsewhere  in  this  issue.) 
Welcome,  Micro  readers. 

Changes  have  come  to  Dr.  Dobb’s  as  well  in  the  past  year.  This  is  the  twelfth 
issue  of  the  magazine  published  under  the  M&T  banner  (the  hundred-and-sec- 
ond  in  all).  I  became  editor-in-chief  then  (and  thanks,  by  the  way,  to  all  those  I 
failed  to  thank  in  my  February  editorial),  and  I  instituted  some  new  features  in 
the  magazine,  including  our  first  operating-systems  issue  (on  Unix)  and  our 
unconventional  coverage  of  the  Mac  (see  our  January  how-to  on  home-fattening 
your  Mac  or  this  issue’s  Realizable  Fantasy  on  liberating  the  Mac). 

While  making  changes,  I’ve  tried  not  to  lose  track  of  what  Dr.  Dobb’s  was 
founded  to  do:  to  provide  software  tools  for  advanced  programmers.  In  1976, 
those  programmers  were  hackers  and  in  1985  they  tend  to  be  professional  soft¬ 
ware  designers,  but  after  attending  last  fall’s  Hackers’  Conference  I’ve  decided 
that  the  distinction  is  moot. 

Changes  come  thick  and  fast  in  the  microcomputer  industry,  but  in  the  thick  of 
it  all,  some  things  must  hold  fast.  Without  Micro,  without  Microsystems,  with¬ 
out  Microcomputing,  where  does  the  programmer  interested  in  8-bit  systems  go? 
We  promised,  in  our  May  and  October  issues,  not  to  abandon  the  8-bit  computer, 
and  we  haven’t.  We  promised  not  to  forsake  public-domain  and  low-cost  soft¬ 
ware,  and  we  haven’t.  (We’ve  also  pointed  out  that  purveyors  of  public-domain 
and  commercial  software  must  coexist.) 

But  there  are  changes  yet  to  come.  While  we  will  not  abandon  our  current 
readers,  we’ll  be  exploring  new  territory  in  1985,  ranging  beyond  the  fields  we 
know.  The  only  way  we  can  do  this  is  to  publish  more  material  that  is  of  broader 
applicability — not  by  lowering  the  technical  level,  but  by  giving  more  emphasis 
to  algorithms,  to  techniques,  to  portable  software,  and  less  to  machine-specific 
code.  I  claim  that  the  overall  effect  will  be  to  raise,  not  lower  the  technical  level  of 
the  magazine  without  making  it  harder  to  understand.  We’ll  see  if  I’m  right. 

We’ll  also  give  more  attention  to  the  craft  of  software  design  in  1985,  and  to 
the  aesthetics  of  design.  If  writing  utilities  produces  utilitarian  code,  then  I  think 
that  programmers  should  be  writing  efficiencies,  elegances,  beauties  in  addition 
to  utilities,  because  we  need  more  efficient,  more  elegant,  more  beautiful  soft¬ 
ware.  We’ll  encourage  that. 

And  yes,  I  deliberately  worked  into  this  editorial  the  themes  of  my  other  eleven 
editorials.  I’m  sorry;  I  learned  recursion  at  an  early  age  and  was  warped  for  life. 


Michael  Swaine 


Dr.  Dobb's  Journal,  April  1985 


LETTERS 


Chubby  Mac 

Dear  DDJ, 

I  would  like  to  congratulate  you  for 
the  excellent  January  [1985,  #99]  is¬ 
sue  of  Dr.  Dobb's  featuring  “Fatten 
Your  Mac”  by  Tom  Lafleur  and  Su¬ 
san  Raab.  Thanks  to  that  article,  I 
was  able  to  upgrade  my  Mac  for 
much  less  than  Apple’s  list  price:  I 
paid  $200  total  or  around  $11  per 
memory  chip. 

Having  never  seen  a  hardware 
technical  manual  for  the  Mac  that’s 
equivalent  to  “Inside  Macintosh”  for 
software,  I  found  the  diagnostics  part 
of  the  article  very  useful.  While  doing 
the  upgrade,  I  encountered  socket 
connection  problems  that  were  solved 
by  following  the  continuity  test  pro¬ 
cedure  mentioned. 

Do  you  know  what  dealers  or  cus¬ 
tomers  do  with  the  128K  boards,  for 
those  who  upgrade  their  boards  the 
official  Apple  way?  I  was  thinking 
that  if  I  decide  to  pursue  this  and  up¬ 
grade  Macs  for  friends  and  for  mon¬ 
ey,  I  will  probably  need  some  extra 
128K  boards  as  a  safety  net.  If  you 
know  people  who  have  a  128K  board 
that  they  are  going  to  throw  away,  let 
me  know. 

I  am  so  happy  to  have  a  5 1 2K  Mac 
now  that  I  decided  to  enter  a  1-year 
subscription  to  Dr.  Dobb’s.  Once 
again,  congratulations  to  Dr.  Dobb’s 
and  the  authors. 

Sincerely, 

Tho  AuTruong 
908-B  Menlo  Avenue 
Menlo  Park,  CA  94025 

PUBlic  Service 

Dear  DDJ, 

A  minor  correction  is  needed  to  our 
article  “CP/M  2.2  Goes  Public  (No¬ 
vember,  1984,  #97)”.  In  Listing  2, 
page  70,  the  PUBLIC. ASM  utility  to 


set  or  reset  the  PUBlic  file  attribute  bit 
contains  a  bug,  discovered  by  Greg 
Platt,  that  prevents  it  from  making 
very  large  files  PUBlic. 

Here’s  the  fix;  Update  the  version 
equate  to  read: 

vers  equ  1  $  1 

and  insert  the  following  code  in  the 
SAMEXT  routine  preceeding  its 
“ret”  instruction: 

rnz  ;  v  1.1 

inx  h  ;extent  is  0, 

check  overflow  (s2) 
ext. 

inx  h 
mov  a,m 
ani  7fh 

ret  ;end  of  SAMEXT 

routine 

PUBPATCH— the  BDOS  patch  that 
implements  PUBlic  file  processing — 
is  unaffected  by  the  bug. 

Sincerely, 

Bridger  Mitchell 
Derek  McKay 
Plu*Perfect  Systems 
Box  1494 

Idyllwild,  CA  92349 

Dear  DDJ, 

The  article  “CP/M  2.2  Goes  PUBlic” 
by  Mitchell  and  McKay,  in  your  No¬ 
vember  [1984,  #97]  issue  was  most 
informative  and  useful.  However,  the 
code  text  published  with  it  had  a  few 
problems.  Some  specifics  from  List¬ 
ing  One:  the  location  definitions 
CKFILPOS  and  SETSTAT  were  mul¬ 
tiply  defined  (to  be  sure,  the  defini¬ 
tions  were  the  same,  but  still  rather 
irregular);  ‘nxbyt:’  was  referenced 
both  as  itself  and  ‘nxtbyte’,  typo  or 
program  error  I  can’t  tell,  since  only 
the  first  six  characters  are  frequently 


used.  More  serious  was  the  use  of  a 
font  in  which  both  lower  case  ‘L’  and 
the  number  one  used  the  same  repre¬ 
sentation.  This  is  common  on  type¬ 
writers,  but  rare  on  computer  print¬ 
ers.  Line  253  is  definitely  ambiguous 
as  printed,  although  it  can  be  deci¬ 
phered  in  context. 

At  any  rate,  I  was  able  to  enter  the 
program  text,  make  the  necessary 
corrections,  translate  it  to  true  Z80 
code,  and  assemble  it  with  the 
Z80MR  assembler  from  MicroCornu- 
copia.  Installation  under  ZCPR3  re¬ 
quired  a  little  research  to  find  the 
correct  addresses,  but  they  were 
there.  Thus  I  no  longer  need  to  have 
long  text  files  in  the  same  user  area  as 
my  PW  or  WS  editor;  this  is  a  real 
convenience  in  the  rather  crowded  di¬ 
rectories  on  my  KayprolO.  My  sin¬ 
cere  thanks  to  the  authors! 

Gratefully 
Bonnell  Frost 
5559  Colt  Drive 
Longmont,  CO  80501 

Grep  Again 

Dear  DDJ, 

A  belated  thank  you  for  grep.c  [DDJ 
#96,  October  1984],  which  I  finally 
got  around  to  typing  in  and  compiling 
on  my  Osborne  1  with  C/80. 

C/80  Version  3.0  from  The  Soft¬ 
ware  Toolworks  will  not  compile 
grep.c  as  listed  because  it  has  no: 

a)  linker 

b)  typedef  statement 

c)  FILE  type 

d)  #defined  NULL 

e)  stdin 

f)  stdout 

g)  stderr 

h)  fgets 

i)  stdio.h 

j)  capability  to  call  a  function 
with  a  different  number  of 

(Continued  on  page  17) 


Dr.  Dobb's  Journal,  April  1985 


8 

PS7 


REALIZABLE  FANTASIES 


by  Mike  Swaine  and 
Bob  Albrecht 

Liberating  the  Mac 

The  Apple  Macintosh  embodies  a 
frustration  and  an  irony:  an  irony  for 
anyone  who  knows  the  history  of  the 
company  and  observes  that  it  is  IBM 
rather  than  Apple  that  is  selling  an 
open-architecture  machine;  a  frus¬ 
tration  for  anyone  who  knows  the 
sense  of  power  and  satisfaction  that 
comes  from  popping  the  cover  off  an 
Apple  II  to  plug  in  a  card  and  has 
come  up  against  the  closed  design  of 
the  Macintosh. 

After  setting  an  example  for  open 
design  with  the  Apple  II,  Apple  has 
created  in  the  Mac  an  appliance  com¬ 
puter  “for  the  rest  of  us,”  but  some  of 
the  brightest  people  feel  particularly 
excluded  from  that  “us.”  That’s  un¬ 
fortunate,  since  the  Mac  implements 
a  brilliant  user  interface  using  a  pow¬ 
erful  and  well-designed  microproces¬ 
sor.  But  for  experienced  program¬ 
mers,  that  interface  can  get  in  the 
way,  and  the  bulk  of  the  bandwidth  of 
the  microprocessor  is  swallowed  up  by 
the  graphics.  What  Apple  has  done  in 
producing  the  Mac  is  remarkable,  but 
one  of  the  things  it  has  done  is  to  make 
evident  all  the  possible  things  it  didn’t 
do.  Some  programmers  seem  to  feel 
that  the  Mac  has  too  much  potential 
to  be  left  to  Apple,  and  have  put  forth 
various  schemes  for  liberating  the  es¬ 
sence  of  the  machine  for  their  own 
version  of  “us.”  This  month’s  Realiz¬ 
able  Fantasies  are  all  schemes  for  the 
liberation  of  the  Mac. 

The  Hacker's  Mac 

Lee  Felsenstein  has  been  talking  for 
the  past  six  months  about  something 
called  “the  hacker’s  Mac.”  The  hack¬ 
er’s  Mac  is  not  in  essence  a  commer¬ 
cial  product  or  even  a  proposed  com¬ 
mercial  product,  but  a  project.  Lee’s 
intent  appears  to  be  to  engage  the 
imaginations  of  engineers  and  pro- 

12 


grammers  who  may  grudgingly  ad¬ 
mit  that  they  like  the  Mac,  but  who 
know  in  their  hearts  that  they  would 
have  done  it  better. 

The  hacker’s  Mac  is  a  consensus 
design  project  with  the  objective  of 
developing  a  computer  that  does  all 
that  the  Macintosh  does  and  more. 
Anyone  who  cares  to  contribute  ideas 
to  the  design,  may.  Clearly  that’s  not 
a  way  to  develop  a  commercial  prod¬ 
uct;  for  one  thing,  nobody  could  hope 
to  compete  commercially  head-on 
with  Apple  without  Apple’s  resources 
and  Apple’s  Macfactory.  Well,  sure. 
The  endeavor  may  be,  though,  a  way 
to  learn  the  design  of  the  Mac  so  inti¬ 
mately  that  you  can  find  ways  to  ex¬ 
tend,  improve  upon  the  technology. 
All  the  Mac  experts  a  year  from  now 
will  not  be  Apple  employees  and 
alumni.  Did  Burrell  Smith  and  Andy 
Hertzfeld  and  Bill  Atkinson  get 
something  more  than  money  out  of 
their  work  on  the  Mac?  Sure,  and 
Lee  seems  to  think  that  there’s  no 
reason  that  others  can’t  get  an  educa¬ 
tion  from  creating  a  Maclike  com¬ 
puter  on  their  own — or  with  some 
help  from  friends.  So  he’s  kicked  off 
the  hacker’s  Mac  project. 

As  Lee  has  pointed  out  to  me,  a 
monthly  magazine  is  an  inappropri¬ 
ate  medium  for  issuing  progress  re¬ 
ports  on  an  ongoing  project,  particu¬ 
larly  one  that  could  move  along  as 
quickly  as  this  one  could.  I  shall  re¬ 
port,  then,  only  that  Lee  introduced 
the  hacker’s  Mac  project  at  the 
Hacker’s  Conference  in  the  Marin 
headlands  last  fall  and  led  the  Home¬ 
brew  Computer  Club  in  sketching  out 
the  technical  desiderata  of  the  ma¬ 
chine  in  January.  It  is  February  as  I 
write  this;  suffice  it  to  add  that  you 
can  follow  the  progress  of  the  hack¬ 
er’s  Mac  project  on  line  by  calling 
Gordon  French’s  BBS  (the  French 
Connection)  at  408-736-6181.  By 


press  time,  this  board  should  have 
hacker’s  Mac  info;  you  can  also  learn 
more  about  the  project  by  sending  a 
self-addressed  stamped  envelope  to 
Lee  at  Golemics,  2600  10th,  Berkeley 
CA  94710. 

Free  the  ROM  64 

Steve  Jasik,  a  veteran  of  Control 
Data  and  Sorcim,  has  written  a  pro¬ 
gram  he  calls  MacNosy,  “the  disas¬ 
sembler  for  the  rest  of  us.” 

MacNosy  (Nosy  for  short)  is  an  in¬ 
teractive  disassembler  for  1M  Lisa 
systems  and  Fat  Macs.  According  to 
Steve,  it  lets  programmers  recover 
the  source  code,  or  a  reasonable  fac¬ 
simile  thereof,  for  any  Macintosh  ap¬ 
plication  program,  ROM  or  “code” 
type  resources  on  the  “System”  file. 
It  splits  programs  into  procedures 
and  referenced  data  blocks,  and 
maintains  journal  files  of  its  activity 
for  later  playback  or  trading  with 
your  friends.  Steve  brought  a  copy 
around  to  DDJ  and  demonstrated  its 
capabilities,  and,  whatever  Nosy’s 
strengths  and  weaknesses,  it  was 
clear  that  Steve,  like  the  hacker’s 
Mac  consensus  designers,  is  working 
to  liberate  the  Mac. 

Here’s  how  Steve  views  his  project: 
“Nosy  is  more  of  a  decompiler  than  a 
disassembler.  Its  purpose  is  the  re¬ 
covery  of  source  code  in  a  human- 
readable  format  that  reflects  the  in¬ 
tent  of  the  code’s  author.  To  achieve 
this  goal,  it  treats  the  program  it  is 
disassembling  as  a  tree  of  procedures. 
It  does  a  tree  walk  to  locate  the  re¬ 
gions  that  are  code;  the  remaining  re¬ 
gions  are  marked  as  data.  At  present 
it  does  not  gather  enough  informa¬ 
tion  to  determine  which  data  areas 
are  really  code  (like  procedures 
passed  as  parameters),  and  human 
help  is  then  needed  to  give  this  infor¬ 
mation  to  it.  One  then  repeats  the 
tree  search  and  continues,  until  the 


oco 


Dr.  Dobb's  Journal,  April  1985 


program  is  in  its  original  format,  or 
some  reasonable  representation.” 

Steve  sees  the  disassembler  as  ap¬ 
propriate  for  anyone  who  wants  to 
understand  68000  and  Macintosh 
programming  techniques  by  studying 
others’  code  and  for  developers  who 
want  to  “find  out  what  the  ROM  rou¬ 
tines  are  doing  to  or  for  them.”  He 
also  sees  it  as  a  tool  for  people  who 
want  to  remove  copy  protection  code 
from  programs  they  want  to  run  from 
a  hard  disk,  and  for  programmers  to 
find  out  how  easy  it  would  be  for 
someone  to  remove  copy  protection 
code  from  their  programs. 

You  can  get  the  current  version  of 
Nosy  for  $70  or  maybe  less  from  Free 
the  ROM  64  in  Menlo  Park, 
California. 

Blue  Light  Special 

Then  there’s  the  machine  everybody’s 
calling  the  Jackintosh:  Jack  Tramiel’s 
Atari’s  ST’s  version  of  who  the  rest  of 
us  are.  The  ST,  running  Digital  Re¬ 
search’s  Maclike  GEM  user  interface, 
will  be  sold  in  K- Marts  at  a  price  that 
will  take  desktop  icons  out  of  Yuppie- 
dom  for  the  first  time.  Ron  Jeffries  (of 
The  Jeffries  Report )  is  impressed 
with  GEM  and  with  the  ST,  and  seems 
to  think  that  the  machine  will  do  well. 
Will  it?  Will  icons  play  in  Ham- 
tramck?  Only  The  Marketplace 
knows  for  sure.  (And  is  it  true  that 
DR  I  is  convinced  that  MSDOS  ma¬ 
chines  represent  the  real  market  for 
GEM?  Sigh.) 

“Liberation”  is  a  slippery  word;  it 
has  taken  us  from  considerations  of 
open  vs.  closed  architectures  to  K- 
Mart  Blue  Light  Specials  in  the 
Computer  Department  and  Jack 
“business  is  war”  Tramiel  leading  a 
war  of  interface  liberation.  Anyway, 
although  Atari’s  marketing  strate¬ 
gies  are  a  different  kind  of  liberation 
of  the  Macintosh  from  reverse-engi¬ 
neering  the  machine  or  disassembling 
the  ROMs,  the  ST  is  evidence  that  the 
user  interface  that  Apple  liberated 
from  the  workstations  of  Xerox 
PARC  isn’t  waiting  for  the  Mac  to 
carry  it  to  the  heartland. 

Virtual  Slots 

But  is  all  this  Apple-tweaking  really 
necessary?  As  Tiny  BASIC  author 


Tom  Pittman  said  in  these  pages  two 
months  ago,  the  Mac  presents  us  with 
a  new  environment  for  hacking;  if  a 
few  years  ago  you  could  accept  the 
“closed  architecture”  of  integrated 
circuits  as  opposed  to  discrete  compo¬ 
nents,  why  not  accept  today  the  closed 
case  of  the  Mac  and  concentrate  on 
exploring  its  rich  software  environ¬ 
ment?  You  can’t  plug  boards  into  the 
Mac,  but  you  can  add  desk  accesso¬ 
ries.  And  external  enhancements. 

And  therein  (in  externals)  lies  the 
basis  for  Apple  management’s  recent 
claim  that  Apple  has  opened  the  Mac. 
Apple’s  new  LAN,  goes  the  argument, 
gives  the  Mac  “virtual  slots.” 

The  network  is  certainly  open.  Ap¬ 
ple  has  taken  the  lid  off  AppleTalk  for 
developers,  and  many  have  taken  ad¬ 
vantage  of  that  fact.  3Com,  for  exam¬ 
ple,  has  developed  an  AppleTalk-to- 
Ethernet  link.  The  protocols  for 
AppleTalk  are  all  public.  Apple  is 
making  the  multilevel  network  open 
at  all  levels,  in  the  hope  to  “spur  devel¬ 
opers  to  provide  innovative  products 
for  AppleTalk.”  It  seems  to  be 
happening. 

Do  virtual  slots  offset  what  some 
see  as  the  Mac’s  deficiencies  as  a  de¬ 
veloper’s  dream  machine?  Is  Apple 
the  true  liberator  of  the  Macintosh? 
Or  is  that  just  Apple’s  fantasy? 

Free-floating  Fantasy 

And  now  for  something  completely 
different.  Everybody  wants  to  define 
or  redefine  “hacking.”  Steven  Levy 
devoted  a  book  to  getting  a  handle  on 
the  term;  the  subsequent  Hacker’s 
Conference  wrestled  with  the  defini¬ 
tion;  old-time  programmers  wonder 
whether  the  term  has  been  irrenewa- 
bly  tarred  by  the  brush  of  illegal-en¬ 
try  pranks  and  mischief.  Perhaps  we 
need  to  liberate  the  word  from  defini¬ 
tion.  Undefine  it.  Perhaps  this  liber¬ 
ating  will  happen  as  a  matter  of 
course,  whatever  we  do.  Perhaps  lib¬ 
erating  the  word  “hacking”  is  some 
part  of  what  “hacking”  (or  “to 
hack”)  means. 

Consider  the  denumerable  set  of 
all  possible  meanings  of  the  term  “to 
hack.”  Call  the  cardinality  of  this  set 
n.  Proposition:  n  is  the  cardinality  of 
the  natural  numbers,  i.e.,  infinity  (in 


the  aleph-one  sense).  Defend  the 
proposition  by  enumeration. 

Well,  let’s  see.  ( 1 )  to  hack  is  to  un¬ 
define  “to  hack.”  (2)  to  hack  is  to  do 
more  with  less.  (3)  um,  reader  in¬ 
volvement  here  is  welcome. 

“Hackers  don’t  grow  older;  they  just 
get  lazy  or  preoccupied  or  enmeshed 
in  the  bottom  line.  What  was  done 
once  can  be  done  again.”  —  Laran 
Star  drake. 

Micro  Subscribers  Please 
Note: 

MacNosy  is  not  the  first  disassem¬ 
bler  for  an  Apple  machine  we’ve  pub¬ 
lished.  In  1976  we  printed  a  descrip¬ 
tion  and  a  listing  of  a  6502 
disassembler,  both  written  by  Steve 
Wozniak  and  Allen  Baum.  People  to 
whom  good  ideas  come  easily  tend  to 
be  free  with  their  ideas,  and  Woz, 
who  recently  left  Apple  over  philo¬ 
sophical  differences,  has  always  been 
a  believer  in  sharing  knowledge.  Over 
the  years  he  has  given  away  several 
pieces  of  6502  code  through  the 
pages  of  Dr.  Dobb’s ,  including  the 
disassembler,  floating-point  arithme¬ 
tic  and  conversion  routines,  a  (very 
simple)  number-guessing  game,  and 
BASIC  routines  for  renumbering  and 
appending. 

It’s  unlikely  that  we’d  reprint  the 
articles  in  Dr.  Dobb’s.  We  want  to 
run  current  6502  material,  and  these 
pieces  may  be  most  appealing  for 
their  historical  interest  (although  it’s 
instructive  to  read  Woz’s  code).  But 
we  could  put  together  a  reprint  pack¬ 
age  if  there’s  enough  interest.  We’d 
have  to  charge  enough  to  cover  the 
postage  and  printing  and  handling — 
probably  three  dollars.  But  we  could 
throw  in  Andy  Hertzfeld’s  Lazarus ,  a 
program  for  resurrecting  BASIC  pro¬ 
grams  on  the  Apple  II. 

If  you’d  like  to  have  such  a  reprint, 
write  to  The  Woz  Papers,  c/o  this 
magazine,  and  let  us  know.  If  interest 
is  great  enough,  we’ll  do  it. 

DDJ 

Reader  Ballot 

Vote  for  your  favorite  feature/article. 

Circle  Reader  Service  No.  1 90. 


13 


Dr.  Dobb’s  Journal,  April  1985 


ocn 


DRI  Drops  Homebrew 
Systems 

An  item  in  a  recent  BUSS  gave  us  a 
mild  panic.  It  seemed  to  say  that  Dig¬ 
ital  Research  had  withdrawn  CP/M 
Plus  from  sale.  We  called  some  peo¬ 
ple  we  know  in  Pacific  Grove  and 
found  that  the  news  wasn’t  that  bad. 
CP/M  Plus  is  alive;  it’s  homebrewing 
that’s  been  given  up  for  dead. 

DRI  used  to  sell  its  various  DOSses 
in  generic  form:  the  base  code,  un¬ 
configured  to  any  hardware,  needing 
a  complete  BIOS  to  run.  Lots  of  hob¬ 
byists  bought  CP/M  2.2  that  way;  we 
and  a  few  others  bought  CP/M  Plus 
in  the  same  form. 

No  more.  Today,  most  of  DRI’s  re¬ 
tail  sales  are  of  preconfigured  sys¬ 
tems  (for  the  IBM  PC,  mostly)  and  of 
languages  such  as  DR  LOGO,  which 
need  no  configuration.  The  generic 
packages  aren’t  moving,  our  source 
said;  they  burden  the  inventory  and 
bring  in  no  revenue.  So,  DRI  is  phas¬ 
ing  out  retail  sale  of  all  generic  sys¬ 
tems.  An  added-value  vendor  who 
wants  to  package  and  resell  a  DOS  in 
quantity  may  license  it,  of  course. 

But  sales  of  unconfigured  systems 
to  individuals  are  ending.  Wait,  we 
protested,  there  are  still  a  few  of  us 
who  want  to  roll  our  own.  What  about 
us?  Well,  DRI  doesn’t  mind  you  get¬ 
ting  a  copy  of  a  generic  system,  but 
they’re  determined  to  shed  the  inven¬ 
tory  burden.  They  are  “talking  to” 
third  parties  who  may  want  to  take 
over  the  sale  of  generic  systems  as  a 
sideline,  but  nothing  has  been  settled. 

If  you  think  you  might  ever  want  to 
buy  a  single  copy  of  CP/M  Plus  or 
CP/M  68K  or  Concurrent  CP/M  in 
generic  form,  write  to  the  marketing 
folks  at  Digital  Research  (P.  O.  Box 
DRI,  Monterey,  CA  93942)  and  tell 
them.  They  think  all  the  home- 
brewers  have  bought  PCs  and  retired. 


Criticism  of  Modula-2 

One  of  the  oldest  and  most  vigorous 
SIGs  sponsored  by  the  ACM  is  SIG- 
PLAN,  the  Special  Interest  Group  on 
Programming  Languages.  Its  month¬ 
ly  newsletter,  SIGPLAN  Notices ,  has 
carried  some  of  the  most  influential 
language  papers  ever  published;  yet 
because  it  isn’t  a  formal  journal,  it 
also  carries  some  of  the  funniest  and 
some  of  the  most  controversial  papers 
as  well. 

One  item  in  the  December  1984  is¬ 
sue  falls  into  the  latter  category. 
“Some  Concerns  About  Modula-2” 
by  David  V.  Moffat  of  North  Caroli¬ 
na  State  University  appears  to  be  in¬ 
tended  as  an  antidote  to  some  of  the 
gushier  praise  lavished  on  Modula-2 
of  late.  Some  of  you  may  not  have 
access  to  S1GPLAN  Notices.  Because 
we  wouldn’t  want  you  to  miss  out  on 
a  solid  linguistic  controversy,  we 
thought  we’d  summarize  Moffat’s 
points  here. 

His  first  point  is  that,  as  a  result  of 
its  relegating  all  I/O  to  procedures 
written  in  the  language  itself,  Mo¬ 
dular’s  I/O  syntax  is  very  low  level 
compared  to  other  languages.  It 
“forces  the  programmer  to  use  a  dif¬ 
ferent  procedure  for  each  type,  for 
each  number  of  parameters,  and  for 
each  default,”  Moffat  writes,  where¬ 
as  languages  that  contain  built-in 
I /O  statements,  however  inconsistent 
(think  of  Pascal’s  pseudo-procedure 
WRITE),  are  a  lot  easier  to  use. 

He  says  that  the  standard  I /O  mod¬ 
ules  are,  in  his  opinion,  “a  hodge¬ 
podge  of  oversights,  inconsistencies, 
duplicate  names,  and  hasty  patches.” 
And  he  gives  examples:  you  cannot 
write  real  numbers  to  the  terminal 
while  accessing  a  file  with  the  stan¬ 
dard  module  InOut;  the  I/O  modules 
export  many  similar  names,  “so  the 


simple  Pascal  . . .  statement  ‘write(s)’ 
becomes  ‘Terminal.  WriteString(s).’  ” 

Moffat’s  third  point  is  an  involved 
analysis  of  the  portability  of  modules 
as  object  code.  Because  of  the  linker’s 
version  checking,  and  because  any 
implementor  will  have  recompiled 
the  standard  I/O  modules  one  or 
more  times,  he  concludes  that  the 
modules  will  not  port,  which  fore¬ 
stalls  substantial  traffic  in  precom¬ 
piled  modules.  But  he  doubts  that 
distribution  of  modules  in  source 
form  will  work  either,  partly  because 
developers  are  reluctant  to  release 
source  but  also  “because  the  ‘stan¬ 
dard’  modules  are  not  really  stan¬ 
dard.  There  is  no  guarantee,  and  in¬ 
deed  no  real  expectation,  that  a 
procedure  in  one  version  of  a  module 
will  have  the  same  syntax  or  seman¬ 
tics — or  even  exist — in  another  ver¬ 
sion  of  that  ‘same’  module.” 

Moffat  notes  the  absence  of  built-in 
provisions  for  varying  strings,  for  sets 
large  enough  to  cope  with  the  alpha¬ 
bet,  and  for  high-precision  numeric 
types.  And  although  he  admits  that 
“all  of  these  types  can  be  simulated  in 
modules,  an  activity  that  is  too  often 
beside  the  point  of  the  program  to  be 
written,  although  a  hacker’s  delight,” 
their  absence  as  efficient  built-in 
types  and  the  presence  of  “lots  of  fea¬ 
tures  for  systems  programming” 
make  it  “plain  that  Modula  is  a  sys¬ 
tems  programming  language,  not  a 
general  purpose  language.” 

Because  the  “effective  size  of  the 
language  is  very  great”  (Moffat  notes 
40  reserved  words,  26  standard  iden¬ 
tifiers,  130  identifiers  in  the  standard 
modules,  and  “some  implementations 
provide  yet  200  more!”),  he  deems 
Modula-2  confusing  and  ineffective 
as  a  teaching  language.  Because  it’s 
neither  general  purpose  nor  good  for 
teaching,  he  concludes  that  “Mo¬ 
dula-2  is  not  the  successor  to  Pascal 


14 


Dr.  Dobb’s  Journal,  April  1985 


or  a  substitute  for  Pascal,”  as  many 
have  called  it.  He  points  to  UCSD 
Pascal  as  “a  genuine  extension  and 
improvement  of  Pascal.” 

We’d  enjoy  seeing  your  informed 
comments,  pro  or  con,  on  Modula-2 
and  will  print  any  that  seem  enlight¬ 
ening.  However,  if  you  feel  moved  to 
debate  with  Moffat  specifically,  you 
should  read  the  original  paper  and  di¬ 
rect  your  comments  first  to  S/GPLAN 
Notices ;  we’re  just  some  interested 
bystanders. 

Headless  No  More 

A  few  readers  wrote  to  commiserate 
with  us  regarding  the  decapitation  of 
our  Shugart  860,  dramatically  de¬ 
scribed  in  the  December  1984  issue. 
To  our  surprise,  they  didn’t  think 
much  of  the  Tandon  half-height  ei¬ 
ther.  Rex  Backus  of  St.  Helena,  CA, 
made  a  typical  remark:  “I  really  won¬ 
der  if  you  were  just  lucky  to  get  a 
working  848-2  with  only  one  try  or  if 
Tandon  is  doing  a  better  job  with 
those  drives  than  they  did  two  years 
ago.  ...  I  sent  two  drives  back  for  re¬ 
placement  before  1  got  a  pair  to 
work.”  The  answer  is  probably  yes, 
Tandon  has  improved;  their  latest 
drives,  we  hear,  are  quite  different 
from  their  first  efforts. 

However,  these  comments  made  us 
wonder.  How  many  other  people  are 
suffering  from  such  hardware  prob¬ 
lems?  More  to  the  point,  why  are 
they  keeping  it  a  secret?  That’s  what 
this  column  is  for;  we  love  to  collect 
anecdotal  reports  of  the  reliability  (or 
lack  thereof)  of  hardware  and  soft¬ 
ware.  How  are  the  Qume  and  Mitsu¬ 
bishi  drives? 

One  reader  offered  us  more  than 
sympathy.  Chuck  Schuetz  called  to 
say  that  when  he’d  worked  for  Shu¬ 
gart  he’d  had  a  hand  in  the  design  of 
the  860;  that  he  knew  exactly  what  we 
were  talking  about;  and  that  he’d  like 
to  fix  our  busted  drive.  He  took  the 
headless  drive  for  a  weekend  and  re¬ 
turned  it  working  perfectly. 

Chuck  told  us  a  lot  about  the  860. 
He  maintains  that  it  ended  up  as  a 
really  excellent  drive  in  its  final  ver¬ 
sions  but  allows  that  it  took  a  number 
of  MLCs  (machine-level  changes)  to 
reach  that  state.  For  example,  the  ear¬ 


lier  versions  included  a  small  boss  to 
force  an  intentional  bow  in  the  dis¬ 
kette  jacket;  this  boss  had  to  be 
ground  flat  in  later  versions.  In  fact, 
most  of  the  drive’s  problems  related  to 
the  positioning  of  the  diskette  jacket 
in  the  frame.  The  drag  and  varying 
speed  that  were  our  original  problems 
most  likely  were  due  to  a  maladjust¬ 
ment  that  let  the  eject  slider  bear 
against  the  corner  of  the  jacket  when 
the  drive  was  closed. 

Schuetz  is  willing  to  advise  other 


people  with  distressed  860  drives.  He 
can  get  spare  parts  at  reasonable  cost 
and  can  handle  a  limited  number  of 
repairs  as  his  spare  time  permits.  He’d 
prefer  your  initial  contact  with  him  to 
be  by  mail;  write  him  at  3675  Desoto 
Avenue,  Santa  Clara,  CA  9505 1 . 

Shadowy  Printing 

OK,  the  Intern  needs  more  help.  Our 
Diablo  1650,  sturdy  companion 
through  zillions  of  words,  is  getting 


Dr.  Dobb’s  Journal.  April  1985 


15 

261 


flaky.  The  figure  below  shows  the 
problem.  It  shows  lots  of  shadow- 
printed  Os.  As  you’ll  see  if  the  repro¬ 
duction  is  good,  the  amount  of  shadow 
varies  from  letter  to  letter.  Some  over¬ 
strikes  are  offset  a  little,  some  a  lot. 

The  problem  seems  to  be  a  slight 
random  variation  in  the  carrier’s  hor¬ 
izontal  position.  It  shows  the  worst  in 
shadow  printing,  but  it  can  also  be 
seen  as  a  slightly  “dirty”  look  in  pro¬ 
portionally  spaced  text.  The  problem 
is  not  in  radial  control  of  the  type- 
wheel,  because  it  can  occur  when  the 
typewheel  isn’t  turning  (as  in  the  fig¬ 
ure),  and  it  isn’t  a  typewheel  prob¬ 
lem,  because  it  affects  all  typewheels 
including  new  ones.  It’s  a  problem  of 
rapid,  incremental,  horizontal  move¬ 
ment;  it  probably  has  an  inertial  com¬ 
ponent,  since  running  the  printer  at 
half  speed  (300  baud  not  1200)  im¬ 
proves  the  output. 

We  took  it  to  a  local  Xerox  Ameri¬ 
ca  service  franchise.  They  replaced 
the  carriage  and  the  horizontal  drive 
motor.  They  claim  they  swapped  out 
all  the  boards.  At  our  urging,  they 
checked  the  power  supply  under  load. 
They  kept  the  printer  two  weeks. 
They  presented  a  large  bill  and  said, 
“There’s  nothing  wrong  with  it  now.” 

Baloney.  Shadow  printing  still 
looks  awful.  Are  there  any  devilishly 
good  printer  techs  out  there  who 
could  tell  us  why? 

Well-Behaved  Comments 

Back  in  October  we  outlined  a  method 
by  which  a  C  compiler  could  ( 1 )  de¬ 
tect  which  functions  absolutely 
couldn’t  be  recursive  and  (2)  allocate 


their  local  variables  in  common  pools 
of  static  storage.  Several  readers  were 
moved  to  comment  on  the  scheme. 

David  S.  Tilton,  whose  letter  last 
summer  started  the  whole  train  of 
thought,  found  a  hole  in  our  scheme. 
Although  we  set  out  to  identify  all  the 
well-behaved  functions  (WBFs),  we 
ended  up  identifying  only  one  of  them. 
Tilton  says,  “Consider  three  functions 
which  each  make  one  call.  A  calls  B,  B 
calls  C,  and  C  calls  B.  Clearly  B  and  C 
are  part  of  a  recursive  loop  and  are 
therefore  not  WBFs.  It  is  also  clear 
that  B  and  C  constitute  a  closed  set; 
the  call  from  A  to  B  cannot  be  part  of 
a  recursive  loop,  and  A  is  definitely 
not  recursive.”  But  our  method  would 
not  classify  it  as  a  WBF. 

In  part,  that’s  because  we  merged 
two  distinct  kinds  of  ill-behavior.  We 
viewed  as  “potentially  recursive” 
functions  that  called  themselves  or 
each  other  (functions  B  and  C  in  Til¬ 
ton’s  example)  and  also  functions  that 
called  external  or  pointer-based 
names.  A  compiler  in  principle  could 
recognize  the  limited  scope  of  the  for¬ 
mer  (although  neither  we  nor  Tilton 
can  think  of  a  simple  algorithm  to  do 
so),  but  it’s  a  peculiarity  of  C  that  the 
latter  behavior  is  intractable.  If  A 
calls  B,  B  calls  C,  and  C  is  declared 
external,  then  we  must  assume  that 
the  external  function  C  might  call  ei¬ 
ther  A  or  B  or  both — we  just  can’t  tell. 

James  Jones  of  Norman,  OK, 
pointed  out  that  the  method  might  be 
simpler  if  we  reversed  it,  searching  for 
the  ill-behaved  functions  (IBFs)  in¬ 
stead  of  the  well-behaved  ones.  A  di¬ 
rect  IBF  (DIBF)  would  be  a  function 
that  was  itself  objectionable — de- 


OOOOOOOOOOOOOOOOOOOOOOOOCXJOOOOOOOOOOOOOOOOOOOOOOOOOOOO 

OOOOOOOOOOOOOOOOOOCXJOOOOOOOOOOOOOOOOOOOOOCXJOOOOOOOOOOO 

0000000000000000000tx)0000000000000000000000cx)00000cx)00 

OOOOOOOOOOOOOOOOOCXJOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO 

OOOOOOOOOOOOOOOCXJOOOOOOOCXJOOOOOOOOOOOOOOOOOOOOOOOOOOOO 

0000000000000000000000000(x)00cx)00000000000000000000000 

OOOOOOOOOOOOOOOOOOOOOOOOOOOOOCXJOOOOOOOOOOOOOOOOOOOOOOO 


Figure 

The  irregular  shadowing  produced  by  the  Intern's  printer. 


dared  external,  called  itself,  or  called 
a  pointer.  Then  a  general  IBF  would 
be  any  function  that  called  a  DIBF  at 
any  level.  In  pseudo  Prolog: 

IBF(f)  if  member(f,DIBF). 

IBF(f)  if  CALLS*(f,g)  and 
IBF(g). 

Here  CALLS*  signifies  the  transitive 
closure  over  the  CALLS(a,b)  rela¬ 
tion.  We  can  find  the  transitive  clo¬ 
sure  in  order  n-squared  time,  Jones 
reminds  us,  using  Warshall’s  algo¬ 
rithm.  (The  final  phase  of  the  method 
we  described  bore  a  strong  resem¬ 
blance  to  Warshall’s  algorithm.) 
Jones  cites  David  Gries’  Compiler 
Construction  for  Digital  Computers 
(Wiley,  1972,  page  39)  as  a  source 
for  Warshall’s  algorithm,  but  heck, 
it’s  easy  to  describe. 

Suppose  i  and  j  are  both  integers 
between  one  and  some  maximum  n, 
and  suppose  that  you’ve  defined  a  re¬ 
lationship  R(i,j)  that  is  true  for  some 
pairs  (i,j)  and  not  others.  In  our  case, 
the  relationship  is  CALLS(ij),  and 
it’s  true  when  function  i  calls  function 
j.  We  can  express  the  complete  rela¬ 
tionship  in  the  form  of  a  matrix  of 
bits,  B,  such  that  B[i,j]  is  zero  where 
R(i,j)  is  false  and  one  where  it’s  true. 

We  really  want  not  CALLS(i,j)  but 
CALLS*(i,j),  the  transitive  closure  of 
CALLS:  the  relation  that  is  true  if 
function  i  calls  function  j  directly  or 
through  any  number  of  intermediate 
functions.  Warshall’s  algorithm  to 
convert  matrix  B  into  matrix  B*  (us¬ 
ing  pseudo-C  notation)  is: 

for(i  =  1;  i  <  =  n;  i  +  +) 
for(j  =l;j<  =  n;j++) 
if  B[j,i]  then 

B[j,*]l  =  B[i,*]; 

The  innermost  line  signifies  the 
bitwise  ORing  of  row  i  into  row  j;  it 
could  be  spelt  out  as: 

for(k  =  1;  k  <  =n;k  +  +) 
B[j,k]  I  =  B( i,k ]; 

However,  the  algorithm  is  n-squared 
only  when  the  rows  of  B  can  be  ORed 
as  single  objects,  preferably  with  a 
fast  assembly  subroutine. 

Jones  and  Jim  Howell  of  San  Jose, 


16 

262 


Dr.  Dohb's  Journal.  April  1985 


CA,  both  cortimented  that  the  Mo¬ 
torola  6809  CPU  shouldn’t  be  tarred 
with  the  same  brush  we  used  for  the 
8080;  the  6809  handles  stack-allocat¬ 
ed  variables  very  well  and  wouldn’t 
benefit  much  from  their  being  static. 
Howell  notes  that,  lacking  a  smart 
compiler,  C  programmers  can  create 
their  own  data  type 

#define  LOCAL  static 

and  use  it  in  the  functions  that  they 
know  to  be  nonrecursive: 

fun(drg) 

{ 

LOCAL  int  i,j: 

Then,  Howell  says,  “when  porting  to 
a  machine  that  can  efficiently  access 
variables  on  the  stack,  just  change 
one  line  to 

#define  LOCAL  auto 
or  even  to 

#define  LOCAL  /*  nothing  * / 

All  the  local  variables  are  now 
automatic.” 

Both  Howell  and  Brian  McKeon  of 
Saugus,  MA,  noted  that  our  method 
doesn’t  take  account  of  functions  that 
could  be  called  as  the  result  of  inter¬ 
rupts.  Paul  Canniff  of  King  of  Prus¬ 
sia,  PA,  commented  that  stack-allo¬ 
cated  locals,  especially  arrays, 
conserve  storage,  and  a  compiler  that 
makes  locals  static  may  produce  pro¬ 
grams  that  are  too  big.  Clearly,  any 
compiler  that  used  our  method  would 
have  to  provide  a  switch  to  turn  it  off. 
Alternatively,  the  compiler  could  take 
an  explicit  declaration  of  “auto”  as 
barring  static  allocation. 


DD| 


Reader  Ballot 

Vote  for  your  favorite  feature/article. 
Circle  Reader  Service  No.  191. 


(Continued  from  page  8) 

arguments  than  defined. 

To  compile  grep.c  with  C/80: 

In  TOOLS. H: 

1.  After  #define  NUL  0, 

add  #define  NULL  NUL 

2.  Add  #define  EOF  -1 

3.  Add  #define  TOKEN  struct 

token 

4.  As  explained  in  Section  8  of  the  C/ 
80  Version  3.0  documentation  add 
the  following: 

#define  FILE  int 
#define  stdin  fin 
#define  stdout  fout 
#define  stderr  0 
extern  int  fin,  fout; 

5.  In  the  typedef  statement  on  page 
58  delete  the  words  “typedef”  and 
“TOKEN”. 

In  TOOLS.C: 

1.  Delete  the  two  #include  directives 

2.  On  page  61  add  the  argument 
“boln”  to  the  omatchf  )  call.  The 
modified  line  should  read: 

while( * lin  &&  omatch(&lin, 
pat,  boln)) 

3.  On  page  64  change  the  expression 

(dstart-dest  <  maxccl) 
to 

(dest-dstart  <  maxccl) 

In  GREP.C: 

1.  Delete  the  line  #include 
“a:stdio.h” 

2.  Change  #include  “b:tools.h”  to 
#include  “tools. h” 

3.  Add  #include  “printf.c” 

4.  Add  #include  “tools. c” 

5.  On  page  77  change 

if(matchs(line,  exprv[j])) 
to 

if(matchs(line,  exprv[j],0)) 

6.  On  page  83  change 

bdos(  1 1 ) 
to 

bdos(  1 1 ,0) 

7.  At  the  end  of  grep.c  add  #include 
“STDLIB.C” 

Thank  you, 

Robert  C.  Briggs 
1337  Rossway  Court 
Los  Altos,  CA  94022 

DD| 


Dr.  Dobb’s  Journal,  April  1985 


17 

263 


18 

n  ca 


“Computers  evolved  from  scientific  superbrains  to  busi¬ 
ness  machines  to  household  appliances;  had  they  started 
out  as  video  games,  they  would  certainly  have  been  de¬ 
signed  with  the  user  interface  in  mind.” 


Talk  of  " computer  literacy" 
can  sometimes  be  a  mask  for  a 
botched  and  inhuman  design. 


The  quotation  above,  from  D.  Verne  Morland’s  arti¬ 
cle,  “Human  Factors  Guidelines  for  Terminal  De¬ 
sign”  ( Communications  of  the  Association  for 
Computing  Machinery,  26(7):  484-494,  July  1983)  is  the 
starting  point  for  our  case  study  of  human  factors  engi¬ 
neering  and  its  application  to  the  world  of  microcomputer 
software  design.  We  will  approach  this  investigation  by 
examining  a  popular  computer  game  and  seeing  how  hu¬ 
man  engineering  concepts  have  been  applied  to  make  it 
simple,  enjoyable,  and  useful.  We  will  also  look  at  some 
microcomputer  software  products  and  consider  how  and 
where  we  might  apply  these  same  concepts  to  them. 

Human  factors  engineering  is  that  branch  of  systems 
analysis  and  design  that  deals  with  the  interface  between 
the  computer  user  and  the  software/hardware  combina¬ 
tion.  On  the  hardware  side,  human  factors  engineering 
helps  designers  determine  where  they  should  place  special 
function  keys  on  the  keyboard,  what  color  monitor  is  least 
fatiguing,  and  so  on.  On  the  software  side,  it  helps  pro¬ 
grammers  to  design  program  menus,  or  choices  to  accom¬ 
plish  certain  tasks.  Finally,  human  factors  engineering 
addresses  the  user — problems  of  operator  error  (and  how 
to  handle  these  errors)  and  whether  a  computer  or  a  pro¬ 
gram  is  sufficiently  user  friendly. 

Although  human  factors  engineering  probably  should 
be  an  integral  part  of  any  computer  hardware  or  software 
design,  the  numerous  variations  of  keyboard  layout  and 
the  contorted  sequences  of  keystrokes  required  to  operate 
some  programs  would  indicate  that  human  engineering 
has  not  been  applied  uniformly.  For  every  powerful,  ac¬ 
cessible,  easy  to  use  system  available,  many  others  per¬ 
form  as  if  no  one  ever  tried  using  them  before  making 
them  available  for  sale. 


Scrabble  is  a  trademark  of  the  Selchow  &  Righter 
Company 

Terry  A.  Ward.  Academic  Computing  Services,  Universi- 
i  ty  of  Northern  Iowa,  Cedar  Falls,  I A  50614. 


Dr.  Dobb’s  Journal,  April  1985 


The  Product 

Let’s  look  at  a  popular  microcomputer-based  game  system 
for  playing  Scrabble.  For  those  unfamiliar  with  its  rules, 
Scrabble  is  a  crossword-puzzle  game  that  can  be  played  by 
two  to  four  people.  The  players  form  interlocking  words  on 
the  Scrabble  playing  board  using  letter  tiles  with  various 
point  values.  The  more  common  letters  such  as  I  or  E  are 
very  low  scoring  ( 1  point  each),  while  letters  such  as  Q  are 
worth  more  (in  this  case,  10  points).  Players  compete  by 
using  their  letters  and  the  various  bonus  squares  on  the 
board  to  maximize  their  score  while  minimizing  their  op¬ 
ponents’  opportunities. 

The  computer  variation  of  Scrabble  involves  MONTY, 
a  self-contained,  game-playing  computer  manufactured 
by  the  Ritam  Corporation.  Microcomputer  versions  of  the 
game  are  available  on  diskette  for  both  the  Apple  and  the 
Radio  Shack  computer  systems  and  have  a  similar  opera¬ 
tion.  Further  information  on  the  game  appears  at  the  end 
of  this  article. 

Basically,  the  self-contained  MONTY  (as  we’ll  refer  to 
the  game  from  now  on)  looks  like  an  oversized  calculator 
with  a  membrane  keyboard,  which  has  some  specialized 
characters,  and  a  small  4X8  LCD  display  window.  Fig¬ 
ure  1  (page  20)  shows  a  stylized  view  of  the  MONTY 
keyboard  and  display  area.  MONTY  measures  6  X  10 
inches  and  weighs  about  two  pounds.  Within  this  small 
frame  is  packed  a  formidable  Scrabble  challenger — and 
an  excellent  example  of  human  engineering. 

Tlie  Human  Elements 

Human  factors  engineering  has  certain  principles  or  axi¬ 
oms  that  are  applicable  to  any  computer  application, 
whether  it  be  an  extensive  payroll  program  or  a  game¬ 
playing  computer.  The  first  rule  or  axiom  is  to  identify 
your  system  users.  This  may  seem  obvious  when  stated  so 
baldly,  but  many  software  designers  neglect  to  do  this. 
Consider  any  number  of  word  processing  or  text  editing 
software  systems.  The  hardware  includes  a  keyboard  and 
a  video  display  screen,  and  a  great  many  software  prod¬ 
ucts  for  text  editing  refer  to  “screens”  of  information. 
However,  screens  are  a  convenience  only  to  the  hardware 
or  to  the  programmer.  Any  user  of  word  processing  does 
not  deal  with  screens.  Writers  write  pages  of  books,  secre¬ 
taries  write  pages  of  letters,  and  even  programmers  write 


Dr.  Dobb's  Journal,  April  1985 


pages  of  documentation.  The  system  is  oriented  toward 
programmer  or  hardware  conventions  that  have  no  mean¬ 
ing  to  the  word  processing  audience. 

Designers  also  fail  to  take  into  account  the  variety  of 
people  likely  to  use  a  computer  system.  A  program  written 
for  other  programmers  (such  as  Unix  or  CP/M)  can  be 
terse,  concise,  and  not  particularly  user  friendly.  But  the 
average  computer  user  who  desires  simply  word  processing 
capabilities  or  spreadsheet  functions  should  not  have  to 
deal  with  obscure  names  for  common  functions.  Programs 
for  these  users  must  provide  easier  access.  CP/M  shells 
(such  as  Unica  or  MicroShell)  and  menu-driven  systems 
(such  as  the  Morrow  or  Osborne  systems)  are  attempts  to 
match  established  programs  with  a  new  user  audience.  If 
the  program  is  intended  for  everyone  from  programmers  to 
the  company  vice  president,  it  must  provide  some  means  of 
accommodating  a  wide  spectrum  of  computer  experience 
and  computer  literacy. 

Furthermore,  software  should  make  complex  tasks  man¬ 
ageable.  Obviously,  a  trivial  program  may  need  little,  if 
any,  human  engineering.  Operating  real  programs,  howev¬ 
er,  is  often  sufficiently  complex  to  require  some  assessment 
of  the  human  element.  In  general,  any  application  program 
can  be  subdivided  into  manageable  tasks  for  the  human 


At 

Across 

B, 

C^3 

Da 

Down 

n 

H 

<G, 

H, 

Hint 

i> 

D 

K5 

L, 

Level 

Ms 

N, 

No 

Ot 

P3 

Pass 

Qio 

Quiet 

Rt 

s, 

Score 

T 

Ut 

V. 

VV< 

Wait 

Xa 

Exchange 

Y« 

Yes 

H 

(Blank) 

ENTER 

Tiles 

Figure  1 

MONTY  keyboard  layout:  A  4  x  8  character  LCD 
screen  provides  a  window  to  the  entire  Scrabble 
board.  The  commands  used  in  playing  the  game 
are  printed  in  contrasting  colors  to  make  them 
more  apparent.  The  point  value  of  the  letters  is 
also  reproduced  on  the  keyboard. 


operator.  For  example,  WordStar,  an  intrinsically  complex 
program,  has  a  mind-boggling  variety  of  commands,  but 
remains  a  top-selling  word  processing  program.  This  is  be¬ 
cause  WordStar  makes  information  available  in  the  form 
of  help  screens  to  anyone  who  faffs  to  remember  the  arbi¬ 
trary  keystrokes  required  for  operation;  sophisticated  users 
may  omit  the  help  screens.  This  on-line  help  alleviates  the 
problem  of  program  complexity.  As  we  will  see  in  our  case 
study  of  MONTY,  this  game  uses  the  keyboard  itself  as  a 
reminder  for  its  functions. 

Finally,  programs  should  weigh  the  consequences  of 
operator  error.  If  the  consequences  are  truly  catastrophic 
(such  as  file  deletion  or  program  abort),  the  program 
should  not  take  such  action  lightly.  Release  3.0  of  CP/M 
(also  known  as  CP/M  Plus)  has  instituted  automatic  re¬ 
logging  of  diskettes  to  compensate  for  the  common  user 
mistake  of  switching  disks  without  doing  a  control-C. 
When  you  do  that,  the  dreaded  “BDOS  ERROR”  appears 
and  the  system  must  be  restarted  or  rebooted.  In  general, 
programs  should  try  to  prevent  accidental  disasters  due  to 
operator  error.  MONTY  does  this  without  appreciably 
slowing  down  the  game-playing  process. 

Software  Design  Principles 

We  have  noted  that  our  first  general  axiom  is  to  consider 
the  human  elements  in  the  human-machine  interface.  The 
second  principle  of  human  engineering  is  to  design  the 
product  as  much  as  possible  to  meet  human  needs  for 
feedback,  consistency,  memory  aids,  simplicity,  and  so  on. 
We  will  now  consider  specific  rules  to  follow  when  design¬ 
ing  software. 

The  first  rule  is  to  provide  some  means  of  feedback  as 
to  the  program’s  current  status.  Whenever  the  computer 
is  doing  something  that  may  take  a  few  moments,  the  user 
should  be  aware  that  the  machine  is  still  working.  For 
example,  a  word-processing  program  will  often  appear 
totally  “dead”  when  it  is  doing  a  global  search-and-re- 
place  or  advancing  to  a  distant  portion  of  the  text.  This 
can  be  both  annoying  and  disconcerting;  novice  users  may 
wonder  as  time  passes  if  something  is  wrong.  A  simple 
display  of  a  flashing  asterisk  could  reassure  them  that  all 
is  well. 

The  second  rule  is  to  be  consistent  in  terminology  and 
program  design.  If  we  require  the  user  to  enter  informa¬ 
tion  using  the  Return  key,  we  should  refer  to  it  in  our 
instructions  as  the  Return  key — not  the  Carriage  Return 
or  the  Enter  key.  While  you  and  I  may  know  that  carriage 
return,  enter,  and  return  are  all  synonymous,  individuals 
new  to  computing  will  hesitate  before  assuming  that  the 
three  names  refer  to  the  same  key,  as  they  rightly  should. 

A  third  rule  is  to  minimize  the  memory  demands  placed 
on  human  operators.  We  should  not  expect  people  to  use 
our  programs  if  they  require  more  memorization  than 
they  are  worth.  The  spreadsheet  SuperCalc  makes  help 
available  throughout  the  program  for  users  who  forget 
the  exact  requirements  to  specify  a  format,  for  example. 
This  frees  them  to  handle  the  task  cf  problem  definition. 

The  fourth  software  design  rule  ’r  to  keep  the  program 
simple.  Programmers  are  notorious  preferring  the  lai 


20 


Dr.  Dobb’s  Journal.  April  1985 


est  and  most  complex  versions  with  “all  the  bells  and 
whistles,”  but  users  simply  want  to  get  the  job  done.  The 
program  Bank  Street  Writer,  while  not  in  the  same  league 
as  WordStar,  nonetheless  provides  usable  word  process¬ 
ing  capabilities  to  many  people.  I  have  even  seen  a  review 
of  Bank  Street  Writer  written  by  a  six-year-old  child  -an 
obvious  endorsement  of  keeping  it  simple! 

A  fifth  rule  is  to  match  the  program  to  the  operator’s 
skill  level.  Many  software  programs  assume  that  all  users 
are  equal:  everyone  must  venture  through  countless  help 
screens  or  menus  to  accomplish  even  the  simplest  task. 
For  example,  the  SCSS  conversational  statistics  package 
is  very  user  friendly,  but  you  must  answer  an  abundance 
of  questions  to  obtain  even  the  simplest  results.  To  plot 
two  variables  requires  close  to  two  dozen  instructions, 
while  a  similar  task  in  the  statistical  package  MINITAB 
requires  but  three  commands.  Providing  help  screens  on 
request  only  will  accommodate  both  novice  and  advanced 
statistical  users. 

Finally,  our  last  rule  for  software  design  might  be  para¬ 
phrased  as:  “The  user  is  always  right.”  Programs  should 
filter  and  correct  user  mistakes.  Often  user  error  simply 
indicates  poor  program  design.  For  example,  our  word  pro¬ 
cessor  requires  that  users  remember  that  the  Home  button 
really  means  Down  Arrow.  Anyone  who  uses  the  key  in¬ 
correctly  is  obviously  guilty  of  user  error,  but  the  program 
actually  invites  just  such  an  error.  Always  maintain  a  user 
or  operator  orientation.  We  are  writing  programs  for  peo¬ 
ple  to  use,  not  purely  academic  exercises. 

MONTY — A  Case  Study 

As  we  noted  above,  MONTY  is  a  hand-held  computing 
device  that  can  play  Scrabble  with  up  to  three  other  oppo¬ 
nents.  Let’s  look  at  the  design  of  this  computer  game  with 
an  eye  toward  the  first  axiom  of  human  factors  engineer¬ 
ing:  identify  your  audience. 

Obviously,  MONTY  is  designed  for  people  who  wish  to 
play  Scrabble.  Beyond  that,  the  audience  may  range  from 
computer  analysts  to  ten-year-old  children  to  adults  who 
are  totally  unfamiliar  with  computers.  I  know  individuals 
in  each  of  those  categories  who  have  successfully  played 
MONTY  with  only  minimal  instruction.  How  is  MONTY 
designed  to  appeal  to  this  wide  audience? 

To  begin,  the  keyboard  contains  virtually  all  the  com¬ 
mands  needed  to  play  the  game.  MONTY  commands  are 
superimposed  on  many  of  the  alphabetic  keys,  printed  in  a 
contrasting  color  to  distinguish  them  from  the  alpha  char¬ 
acters.  These  commands  are  fully  spelled  out:  one  may 
“exchange”  tiles  or  place  them  “across”  the  board.  This  is 
in  contrast  to  many  microcomputer  programs  that  would 
abbreviate  these  commands  as  “xchng”  or  “a.”  This  user- 
friendly  keyboard  allows  virtually  anyone  to  begin  play¬ 
ing  with  little  if  any  training.  MONTY  has  taken  into  ac¬ 
count  the  breadth  of  possible  users  and  the  diversity  of 
their  backgrounds  without  confusing  one  or  patronizing 
the  other. 

In  MONTY,  the  major  catastrophic  errors  are  ending 
the  game  prematurely,  exchanging  tiles  (and  thus  losing  a 
turn),  and  impossible  plays.  When  a  player  proposes  to 

Dr.  Dobb's  Journal,  April  1985 


end  the  game  or  exchange  tiles,  MONTY  asks  the  user  l 
again  if  he  or  she  is  sure  of  this  request.  Only  upon  being 
told  “yes”  does  the  program  implement  this  action.  When 
players  attempt  an  impossible  play  (such  as  placing  tiles 
where  they  cannot  legally  go),  MONTY  returns  them  to 
the  beginning  of  their  turn.  If  you  attempt  to  use  a  letter 
that  you  do  not  have,  MONTY  informs  you  of  this  and 
tells  you  which  letter  is  missing.  In  all  cases,  the  user  is 
placed  back  into  a  logical  position  from  which  to  try 
again.  This  is  in  contrast  to  many  computer  programs 
that  will  abort  and  place  you  in  an  unknown  environment 
(such  as  CP/M)  upon  operator  error. 

Obviously  MONTY  has  followed  the  first  axiom  of  hu¬ 
man  factors  engineering.  We  will  now  investigate  how 
well  our  game-playing  computer  has  utilized  the  six  rules 
encompassed  by  the  second  axiom:  design  the  product  to 
meet  human  needs. 

Provide  Feedback 

MONTY  can  take  up  to  three  minutes  to  find  a  word  and 
make  its  play.  A  counter  that  is  constantly  displayed  on  the 
screen  provides  feedback  while  the  program  is  calculating. 
When  the  counter  reaches  zero,  MONTY  plays  its  word. 
This  feedback  assures  the  opponent  that  all  is  proceeding 
normally.  Likewise,  requests  for  information  are  always 
accompanied  by  a  listing  of  expected  responses  (such  as 
yes/no  or  across/down).  This  feedback  ensures  that  the 
user  is  aware  of  what  is  going  on  with  the  program. 

Be  Consistent 

MONTY  always  requests  information  in  a  similar  format  or 
style  when  prompting  for  response.  An  inappropriate  re¬ 
sponse  is  always  greeted  with  a  beep,  and  the  request  is 
issued  again.  It  also  uses  the  natural  terminology  of  its  users 
rather  than  the  contrived  terminology  of  computerdom. 

Minimize  Memory  Demands 

MONTY  excels  in  this  particular  design  goal.  Embossing 
the  keyboard  with  the  commands  reduces  human  memory 
demands  to  the  bare  minimum.  By  replacing  human 
memory  requirements  with  good  software  design,  the  pro¬ 
grammer  frees  the  user  to  concentrate  on  the  task  at 
hand — beating  a  machine  at  Scrabble. 

Keep  It  Simple 

MONTY  is  totally  self-contained.  It  requires  that  the 
player  know  only  a  few  commands  in  addition  to  the  rules 
of  Scrabble.  This  is  in  contrast  to  much  of  the  currently 
available  microcomputer  software.  Programmers  have  a 
tendency  to  fill  any  available  storage  space  with  func¬ 
tions,  relevant  or  not.  The  range  of  options  is  simply  too 
great,  and  most  people  use  only  a  limited  subset  of  a  ma¬ 
chine’s  capabilities.  Limiting  the  options  to  those  most 
commonly  used  will  encourage  people  to  use  the  software 
rather  than  intimidate  them  with  an  instructional  manual 
the  size  of  a  telephone  directory. 

Match  the  User's  Skill  Level 

Here  is  an  area  where  much  of  the  available  microcomput- 

22 

267 


Level  A: 

frog 

suet 

Level  B: 

azo 

eta 

Level  C: 

viol 

averters 

Level  D: 

qintar 

(unit  of  currency) 

hegira 

(a  journey) 

wadi 

(a  normally  dry  stream  bed) 

Figure  2 

MONTY  offers  four  levels  of  play.  Levels  may  be 
increased  or  decreased  without  restarting  the 
game  or  program.  Listed  are  typical  words 
encountered  at  each  level  of  play. 


er  and  mainframe  software  succeeds.  MONTY  presents  all 
players  with  the  same  information  and  keyboard.  It  then 
accommodates  itself  to  player  proficiency  by  providing 
four  levels  of  play  (see  Figure  2,  above)  and  adjusting  the 
rewards  accordingly.  MONTY  rewards  particularly  good 
plays  with  a  brief  musical  selection  (such  as  the  “1812 
Overture”  or  “Hail  to  the  Chief”);  at  level  D,  50  points  per 
play  are  required  for  “Hail  to  the  Chief,”  while  at  lower 
levels  of  play  it  takes  a  less  impressive  score.  This  feature 
relates  to  the  sixth  and  final  design  principle. 


sons  people  complain  about  computers  and  their  associat¬ 
ed  software.  We  are  quite  often  to  blame:  many  programs 
are  not  easy  to  use.  Computer  illiteracy  often  reflects  pro¬ 
grammer  insensitivity  to  people’s  needs  and  desires.  Al¬ 
though  microcomputer  sales  are  still  quite  healthy,  I  won¬ 
der  whether  we  will  see  increasing  disenchantment  with 
software  that  doesn’t  work.  MONTY  and  other  well-de¬ 
signed  programs  can  provide  a  starting  point  to  enter  the 
world  of  software  engineering  for  human  beings. 

"MONTY  Plays  Scrabble" 

Manufacturer:  Ritam  Corporation 
P.  O.  Box  921 
Fairfield,  IA  52556 
(515)472-8262 

Format:  Apple  II,  II  Plus,  or  He;  Radio  Shack  TRS-80 
Model  1,  3,  or  4;  self-contained  version 
Price:  Apple,  $39.95;  Radio  Shack,  $34.95;  self-con¬ 
tained,  $1 29.95 

Description:  MONTY  has  a  basic  understanding  of  Scrab¬ 
ble  strategy  and,  combined  with  a  vocabulary  of  up  to 
44,000  words,  can  provide  a  formidable  opponent  to 
word  game  enthusiasts. 

DD| 

Reader  Ballot 

Vote  for  your  favorite  feature/article. 

Circle  Reader  Service  No.  1 92. 


Sustain  Operator  Orientation 

This  last  principle  is  by  no  means  the  least.  In  fact,  it  may 
be  the  most  important  of  the  half  dozen  presented.  Pro¬ 
grams  are  designed  for  people,  not  other  programmers. 
Unix,  for  example,  is  an  incredibly  powerful  software  oper¬ 
ating  system.  It  is,  however,  generally  quite  unsuited  for 
the  non-programmer.  CP/M  is  likewise  an  excellent  oper¬ 
ating  system,  but  with  its  PIPs  and  CON:s  and  LST:s  it  is 
designed  for  the  already  computer  literate.  This  is  perhaps 
one  of  the  great  appeals  of  BASIC  as  a  computer  language 
or  environment.  Everything  that  needs  to  be  done  can  be 
done  in  the  one  environment  of  the  BASIC  interpreter.  You 
write  your  programs,  you  test  them,  you  edit  them  and  you 
even  save  and  retrieve  them  from  one,  consistent  environ¬ 
ment.  This  is  in  contrast  to  the  typical  mainframe  situation 
where  you  must  always  know  exactly  where  you  are.  Edit¬ 
ing  commands  are  not  recognized  by  the  job  control  scan¬ 
ner  and  BASIC  commands  are  likely  to  result  in  bizarre 
error  messages  if  typed  in  at  the  wrong  moment.  In  my 
experience  as  a  user  consultant,  this  is  perhaps  the  major 
complaint  of  most  people  using  computers.  The  machine 
forgets  that  it  exists  for  the  sake  of  human  operators  and 
not  the  other  way  around. 

Conclusions 

This  venture  into  human  factors  engineering  via  a  game¬ 
playing  computer  should  give  some  insight  into  the  rea- 


Dr.  Dobb's  Journal,  April  1985 


23 


Shortcut  to  SCISTAR:  For 
Prowriter  Users 


by  Amer  W.  Nelson 


Thomas  and  Fletcher’s  excellent 
article,  “SCISTAR:  Greek  and 
Math  Symbols  with  Word¬ 
Star”  (DDJ,  August  1984),  taught 
me  what  I  needed  to  know  to  incorpo¬ 
rate  those  symbols,  as  well  as  super/ 
subscripts  and  underlining,  into  my 
writing.  The  real  advantage  of  Thom¬ 
as  and  Fletcher’s  method  is  their  use 
of  a  universal  command  set  common 
to  all  printers.  The  drawbacks,  as  I 
see  them,  are  that  MailMerge  must 
be  used  and  that  a  “hunt  and  peck” 
typist  like  me  requires  a  lot  of  time  to 
produce  files  like  SCI.CMD  and 
SCI.  DAT. 


The  shortcut  I  will  describe  has  the 
disadvantage  of  being  tailored  strict¬ 
ly  to  the  Prowriter.  Nonetheless,  I  be¬ 
lieve  that  experienced  users  who  un¬ 
derstand  Thomas  and  Fletcher  can 
make  those  changes  necessary  to  in¬ 
corporate  the  shortcut  in  their  own 
computer/printer  configuration. 

As  Table  1  (page  25)  shows,  I  have 
retained  or  only  slightly  modified 
many  of  Thomas  and  Fletcher’s 
WordStar  patches  for  the  Prowriter. 
1  elected  to  use  WordStar’s  own  bold¬ 
facing  (hence,  no  patch  for  that  fea¬ 
ture),  and  I  chose  pica  (10  CPI)  to  be 
my  standard  print  with  compressed 


Further  information  on  how  to  incorporate  Greek 
and  math  symbols  into  WordStar  files. 


These  drawbacks  caused  me  to 
search  for  a  shortcut  to  SCISTAR 
that  my  wife  and  I  could  use  on  our 
personal  Osbornel /Prowriter  sys¬ 
tem.  I  am  a  security  analyst  with  a 
small  investment  company,  and  my 
wife  is  an  EEG  technologist.  Because 
neither  of  us  produces  lengthy  scien¬ 
tific  papers,  our  use  of  Greek  charac¬ 
ters,  math  symbols,  and  super/sub- 
scripts  is  limited.  (I  use  an  occasional 
/3  to  denote  the  relative  volatility  of  a 
stock,  an  occasional  M|  in  reference 
to  the  money  supply,  and  a  few  super¬ 
scripts  for  footnotes;  my  wife  makes 
use  of  the  Greek  characters  a,  /?,  5,  k, 
H,  and  6  to  denote  brain  wave  pat¬ 
terns  and  fi,  the  symbol  for  ohms  of 
resistance.) 


Amer  W.  Nelson,  12030  36th  Ave., 
NE,  Seattle,  WA  98125. 


17  CPI  pitch  as  an  alternate.  I  used 
the  command  "PQ— related  to 
USR1: — to  enter  the  Prowriter’s  In¬ 
cremental  Print  Mode  before  typing 
special  characters  and  the  command 
'PR — related  to  USR4:  —to  return  to 
the  Prowriter’s  Logic  Seek  Print 
Mode  (see  the  model  8510A  user’s 
manual,  page  46). 

It’s  particularly  important  to  im¬ 
bed  your  "PQs  and  "PRs  between 
paragraphs.  When  the  Incremental 
Print  Mode  is  on,  printing  is  unidirec¬ 
tional,  but  when  operating  in  the 
Logic  Seek  Print  Mode,  printing  is 
bidirectional.  If  you  try  to  bounce 
back  and  forth  between  them  in  the 
middle  of  a  paragraph,  you  may  find 
one  line  printing  on  top  of  another. 
To  avoid  such  a  problem  and  to  con¬ 
serve  paper,  I  make  it  a  general  rule 
to  enter  the  Incremental  Print  Mode 
before  typing  any  paragraph  where  I 
intend  to  use  special  characters  (be 


24 


Dr.  Dobb’s  Journal,  April  1985 

269 


they  Greek,  math,  or  super/sub¬ 
scripts)  and  to  return  to  the  Logic 
Seek  Print  Mode  at  the  end  of  that 
paragraph.  Because  I  use  WordStar’s 
boldfacing  and  underscoring  fea¬ 
ture — the  latter  through  RIBBON: 
and  RIBOFF: — I  need  not  change 
print  modes  just  to  print  in  boldface 
or  to  underline. 

Table  2  (page  26)  contains  specific 
commands  that  produce  special  char¬ 
acters:  Greek,  math,  super/sub¬ 
scripts,  and  a  few  others.  Remember, 
however,  once  you  are  through  using 
one  of  the  special  characters  and  wish 
to  return  to  English  characters,  you 
must  type  a  “PE.  Thus,  to  print  some¬ 
thing  like  xr2,  you  would  type  “PW/ 
“PEr“PWQ“PE.  Similarly,  to  print  a 
chemical  representation  of  molecular 
composition  like  H2SQ4,  you  would 
type  H“PV“PWQ“PE“PVSO“PV 
“PWS“PE“PV. 

Also  remember  that  you  cannot 
mix  boldface  and  underlining  with 
special  characters  on  the  same  line. 
As  Thomas  and  Fletcher  point  out, 
the  Prowriter  will  print  an  alpha 
when  it  receives  a  20H. 

One  advantage  of  my  shortcut  is 
that  you  can  print  the  Greek  gamma 


without  jumping  through  hoops. 
Without  using  MailMerge,  you  can 
send  a  "  (22H)  as  a  variable.  More¬ 
over,  if  you’re  trying  to  type  eight  or 
nine  pages  with  only  a  few  special 
characters  imbedded  in  the  text,  you 
can  print  out  your  file  much  more 
quickly  in  the  bidirectional  mode,  us¬ 
ing  the  unidirectional  (Incremental 
Print)  mode  only  as  needed. 

Table  3  (page  27)  contains  some 
additional  information,  unrelated  di¬ 
rectly  to  this  article,  that  may  never¬ 
theless  be  of  interest  to  Osborne  1  us¬ 
ers.  If  you’re  interested  in  learning  C 
language  programming,  it  would  be 
nice  to  know  how  to  type  (and  print) 
a  {  or  a  }. 

This  works  too!  (Refer  to  page  29 
of  the  August  1984  Dr.  Dobb’s  Jour¬ 
nal.)  Typing  x  “PT  ‘PW  (space) 
“PE  “PT  (space)  *PWF  *PE 
(space)  "PW!Q  "PE  is  an  inequality, 
produces  the  following  print: 

xa  >  j(32  is  an  inequality. 

Nice,  huh? 

DD| 

Reader  Ballot 

Vote  for  your  favorite  feature/article. 

Circle  Reader  Service  No.  1 93. 


Incremental  Print 

USR1 : 

ESC[ 

02H,  1  BH.5BH  “PQ 

Logic  Seek  Print 

USR4:* 

ESC] 

02H,  1  BH,5DH  “PR 

Special  Characters  USR2: 

ESC& 

02H.1BH.26H  “PW 

ASCH  (English) 

USR3: 

ESC$ 

02H,  1  BH.24H  “PE 

Underscore  On 

RIBBON: 

ESCX 

02H.1BH.58H  “PY 

(toggle) 

Underscore  Off 

RIBOFF: 

ESCY 

02H,  1  BH,59H  “PY 

(toggle) 

Superscripting 

ROLUP: 

ESCr(LF) 

03H,  1  BH,72H,0AH  Use“PVSor 

& 

“PTS 

Subscripting 

ROLDOW:  ESCf(LF) 

03H,1BH,66H,0AH  (toggles) 

Normal  <CRLF> 

PSCRLF: 

ESCf(CR) 

05H,  1  BH,66H,0DH, 

<LF)(LF) 

OAH.OAH 

Initialization 

PSINIT: 

(CR)ESCT 

05H.0DH,  1  BH,54H, 

String 

12 

31FL32H 

Alternate  Print 

PALT: 

ESCQ 

02H,1BH,51H  17  CPI 

Standard  Print 

PSTD: 

ESCN 

02H,1BH,4EH  10  CPI 

*You  may  find  my  order  strange,  but  I  associated  “PR  with  Return  to  Logic 
Seek  and  “PE  with  English. 

(I  did  not  use  PSFINI:.  I  just  made  sure  that  I  was  in  Logic  Seek  Mode  at  the 
end  of  printing.) 

Table  1. 

Patches  installed  on  WordStar  disc  for  use  with  the  Prowriter 


25 

?7n 


Dr.  Dobb’s  Journal,  April  1985 


Type: 

‘PW<  SPACE  > 
‘PW! 

‘PW" 

‘PW# 

-pw$ 

*PW% 

‘PW& 

‘PW’ 

-PW( 

-PW) 

‘PW* 

‘PW  + 

‘PW, 

*PW- 

■PW. 

‘PW/ 

*PWO 

‘PW1 

‘PW2 

‘PW3 

‘PW4 

‘PW5 

‘PW6 

‘PW7 

"PW8 

‘PW9 

‘PW: 

‘PW; 

‘PW< 


To  get: 
a  (alpha) 

0  (beta) 

7  (gamma) 

5  (delta) 

f  (epsilon) 

f  (zeta) 

v  (eta) 

6  (theta) 

t?  (iota) 

k  (kappa) 

A  (lambda) 

m  (mu) 

v  (nu) 

«  (xi) 

o  (omicron) 

7T  (pi) 

P  (rho) 

s  (sigma) 

r  (tau) 

d  (upsilon) 

4>  (phi) 

X  (chi) 

1  (psi) 

«  (omega) 

A  (upper-case  delta) 

T  (upper-case  gamma) 

2  (upper-case  sigma) 

A  (upper-case  lambda) 

Q  (upper-case  omega) 


Most  upper-case  Greek  characters  can  be  printed  by  typing  normal  upper¬ 
case  English  characters:  upper-case  alpha  is  A,  upper-case  beta  is  B,  and  so 
on.  For  those  upper-case  Greek  characters  that  the  Prowriter  can’t  handle, 
like  pi  and  phi,  just  leave  two  spaces  and  do  them  by  hand. 

When  you  are  through  with  Greek  characters  or  any  other  special  charac¬ 
ters,  type  a  ‘PE.  For  instance,  to  print  <Pvk  a  8vk,  type  "PW43)"PE 
<space>  "PW(space)‘PE<space>  ‘PW#3)“PE. 

Other  available  symbols  (characters)  include  the  following: 


Type: 

To  get: 

-pw> 

V 

(square  root  sign) 

‘PW? 

□ 

(upper  square) 

‘PW@ 

t 

(arrow  up) 

‘PWA 

1 

(arrow  down) 

*PWB 

— 

(arrow  left) 

‘PWC 

— 

(arrow  right) 

"PWD 

+ 

(plus  or  minus) 

‘PWE 

(not  equal) 

‘PWF 

> 

(greater  than  or  equal) 

"PWG 

< 

(less  than  or  equal) 

‘PWH 

(approximates) 

Table  2. 

Special  Character  Commands 


26 


Dr.  Dobb's  Journal,  April  1985 


P71 


( Table  2  continued) 

Type: 

f 

To  get: 

APWI 

(dot  for  multiplication  sign) 

APWJ 

© 

(earth  symbol) 

"PWK 

00 

(infinity) 

“PWL 

(therefore) 

“PWM 

'h 

(one  half) 

APWN 

% 

(one  quarter) 

APWO 

o 

(degree  or  super  zero) 

APVAPWO 

0 

(sub  zero) 

APWP 

1 

(super  1) 

‘PV'PWP 

1 

(sub  1) 

'PWQ 

2 

(super  2) 

APVAPWQ 

2 

(sub  2) 

APWR 

3 

(super  3) 

APVAPWR 

3 

(sub  3) 

APWS 

4 

(super  4) 

APVAPWS 

4 

(sub  4) 

APWT 

5 

(super  5) 

"PV'PWT 

5 

(sub  5) 

'PWU 

6 

(super  6) 

APVAPWU 

6 

(sub  6) 

APWV 

7 

(super  7) 

APVAPWV 

7 

(sub  7) 

'PWW 

8 

(super  8) 

APVAPWW 

8 

(sub  8) 

APWX 

9 

(super  9) 

APVAPWX 

9 

(sub  9) 

APWY 

( 

(super  open  parenthesis) 

'PV'PWY 

(< 

(sub  open  parenthesis) 

APWZ 

i 

(super  close  parenthesis) 

APVAPWZ 

i 

(sub  close  parenthesis) 

APW[ 

+ 

(super  plus) 

*PV'PW[ 

+ 

(sub  plus) 

,‘PW\ 

- 

(super  minus) 

APVPW\ 

_ 

(sub  minus) 

APW] 

(super  period) 

APVAPW] 

(sub  period) 

APWA* 

• 

(super  asterisk) 

APVAPWA* 

• 

(sub  asterisk) 

APW_ 

/ 

(super  slash) 

APVAPW_ 

/ 

(sub  slash) 

*The  trailing  A  is  not  a  "control.' 

’  It  is  a  caret,  the  upper-case  6. 

Type: 

<CONTROL>  equal  sign  “  = 
<CONTROL>  comma 
<CONTROL>  period 
<CONTROL>  slash  7 


To  get: 

(a  back  accent  or  grave) 

{  (a  left-hand  brace) 

}  (a  right-hand  brace) 

(a  tHde  as  used  in  Spanish) 


The  left-  and  right-hand  braces  are  particularly  useful  to  those  interested  in 
learning  C  programming. 


Table  3. 

Some  Characters  not  on  the  Osbornel  Keyboard 


27 


Dr.  Dobb's  Journal,  April  1985 


fx80char:  A  Character  Editor  for 
Epson  FX-80  Printers 


by  David  D.  Clark 


When  Epson  announced  the  development  of  alternate  character 
FX-80/ 100  series  of  dot  sets  for  use  on  the  Epson  FX-80/ 100 
matrix  printers,  micro-  series  of  printers.  Written  in  the  Eco- 
computer  users  were  attracted  imme-  C  version  of  C  for  CP/M,  the  pro- 
diately  because  of  the  variety  of  de-  gram  provides  a  screen-oriented 
sirable  features  offered  at  a  low  price,  character  editor,  the  ability  to  read 
One  of  the  things  that  attracted  me  and  write  disk  files  containing  char- 
was  the  ability  to  redefine  the  stan-  acter  set  definitions,  and  commands 
dard  character  font.  Such  a  capabili-  to  download  new  character  sets  to  the 
ty  is  useful,  if  only  because  technical  printer, 
papers  almost  always  seem  to  require 
characters  that  your  printer  normally  Commands 

can’t  print.  After  fx80char  is  started,  it  displays 

The  FX-80  also  makes  a  good  draft  a  menu  of  commands.  To  select  a 
printer.  If  you  prepare  a  document  particular  command,  you  just  type 
for  printing  with  a  letter  quality  the  highlighted  character.  It  is  not 
printer  such  as  a  Diablo  or  NEC,  necessary  to  hit  a  <Ret>  after  typ- 
printing  early  drafts  can  be  tedious  ing  the  character.  This  section  dis- 
because  the  printers  are  so  slow;  the  cusses  each  of  the  commands  in  al- 
normal  character  set  of  a  dot  matrix  phabetical  order, 
printer,  however,  may  not  correspond  The  Activate  command  is  used  to 
to  the  characters  available  on  the  toggle  the  use  of  the  printer’s  ROM  or 
print  element  used  with  the  letter  RAM  character  set;  it  is  assumed  that 
quality  printer.  With  the  Epson,  you  the  ROM  set  is  active  when  the  pro¬ 
can  redefine  the  dot  matrix  charac-  gram  starts.  The  first  use  of  the  Acti¬ 
ve  most  underused  printer  feature  may  be  the 
redefinable  character  set.  This  tool  makes  the  feature 

more  usable. 

vate  command  causes  the  printer  to 
use  the  character  set  stored  in  RAM 
for  subsequent  printing.  Successive 
uses  of  this  command  will  toggle  be¬ 
tween  the  two  character  sets.  The 
program  always  displays  the  active 
character  set  after  executing  this 
command. 

The  Edit  command  is  used  to  cre¬ 
ate  or  alter  a  particular  character. 
When  you  enter  the  character  editing 
routine,  you  will  be  asked  for  the 
number  of  the  character  to  edit.  This 
number  is  simply  the  decimal  number 
Piscdiaway,  NJ,  08854.  I  associated  with  the  ASCII  character. 


ters  to  correspond  exactly  to  those  on 
the  print  element  of  the  slow  printer. 
In  my  case,  I  have  access  to  a  letter 
quality  printer  at  work  and  use  the 
FX-80  at  home.  By  redefining  the 
standard  characters,  I  can  get  a  rea¬ 
sonable  facsimile  of  how  the  docu¬ 
ment  will  look  when  printed  at  my  of¬ 
fice  while  still  working  at  home. 

The  program  described  in  this  arti¬ 
cle,  fx80char,  is  designed  to  aid  in  the 

David  D.  Clark ,  126  Birchview  Dr., 


Dr.  Dobb's  Journal,  April  1985 


28 

273 


A  number  is  requested  because  some¬ 
times  you  can’t  enter  the  desired 
character  from  the  keyboard. 

After  you  enter  the  number  of  the 
character,  the  character  itself,  as  cur¬ 
rently  defined,  is  displayed  on  an  en¬ 
larged  “easel”  for  editing.  You  can 
move  the  cursor  around  within  the 
easel,  as  well  as  “flip”  the  state  of  the 
individual  elements.  Elements  dis¬ 
played  in  reverse  video  represent  the 
pins  of  the  print  head  that  will  be 
fired  and  where  ink  will  appear  when 
the  character  is  printed. 

There  are  two  equivalent  groups  of 
movement  commands.  The  first  is 
similar  to  that  used  by  WordStar: 
Ctrl-E  moves  up,  Ctrl-D  moves  right, 
Ctrl-X  moves  down,  and  Ctrl-S 
moves  left.  You  can  use  Ctrl-F  to  flip 
the  state  of  the  element  upon  which 
the  cursor  rests.  The  second  allows 
you  to  use  a  numeric  keypad  for  edit¬ 
ing:  8  moves  the  cursor  up;  6  moves  it 
right;  2  moves  it  down;  4  moves  it  left; 
and  you  can  toggle  the  current  ele¬ 
ment  by  tapping  5.  The  two  groups  of 
keys  may  be  mixed  at  any  time. 

When  alterations  to  a  character 
are  complete,  typing  a  Q  or  Ctrl-Q 
updates  the  character  definition  and 
exits  the  editing  function.  During  the 
update  process,  the  character  is  con¬ 
verted  from  the  form  used  for  conve¬ 
nient  display  to  the  actual  form  used 
when  sending  definitions  to  the  print¬ 
er.  The  definition  is  checked  for  two 
types  of  errors:  characters  nine  ele¬ 
ments  high  and  characters  with  con¬ 
secutive  printing  of  horizontally  ad¬ 
jacent  dots.  Because  the  FX-80/100 
can  print  only  characters  that  are 
eight  or  fewer  cells  in  height,  defini¬ 
tions  with  nine  cells  are  not  allowed. 
Similarly,  although  eleven  horizontal 
positions  are  available,  the  FX-80/ 
100  can  print  no  two  consecutively.  If 
an  inadmissable  definition  is  detected 
during  the  conversion  process,  a  mes¬ 
sage  to  that  effect  is  displayed.  No 
further  action,  however,  is  taken:  the 
character  will  be  converted  but  the 
definition  will  not  print  correctly.  At¬ 
tempting  to  send  such  a  definition  to 
the  printer  will  produce  indetermi¬ 
nate  results. 

The  Initialize  command  is  used  to 
clear  the  buffer  that  holds  the  char¬ 
acter  definitions  in  the  program’s 


memory.  This  command  has  abso¬ 
lutely  no  effect  on  the  printer  or  on 
the  contents  of  its  memory;  it  simply 
inserts  housekeeping  information 
into  the  buffer  and  sets  all  definitions 
to  blanks.  This  command  gives  you  a 
“clean  slate”  to  work  with.  It  is  a 
good  idea  to  execute  this  command 
before  building  a  new  character  defi¬ 
nition  file  from  subsets  of  previously 
defined  files  on  disk  (see  the  Read 
command  below). 

The  Load  command  loads  the 
printer  RAM  with  the  contents  of  the 
printer  ROM.  It  does  not  transfer 
data  between  the  computer  and  the 
printer.  It  is  useful  primarily  to  check 
the  proper  functioning  of  the  print¬ 
er’s  RAM.  You  also  can  use  it  in  con¬ 
junction  with  the  Overlay  command 
to  replace  parts  of  the  standard  char¬ 
acter  font. 

The  Overlay  command  is  used  to 
redefine  all  or  part  of  the  printer’s 
character  set  using  definitions  from 
the  program’s  character  buffer.  This 
command  displays  a  menu  with  op¬ 
tions  to  redefine  all  characters,  a 
range  of  characters,  or  a  single 
character. 

If  you  request  that  a  range  of  char¬ 
acters  or  a  single  character  be  rede¬ 
fined,  you  must  select  the  limits  of 
the  range  or  the  single  character  as 
decimal  numbers,  just  as  in  the  Edit 
command.  If  the  selected  characters 
are  not  within  the  range  of  legal  char¬ 
acters  (0-225),  or  if  the  low  end  of  a 
range  is  greater  than  the  upper  end, 
an  error  message  is  displayed  and  no 
characters  are  changed. 

The  Print  command  is  used  to  print 
out  the  entire  character  set.  After 
you  have  Overlayed  the  printer’s 
RAM  character  set  with  newly  de¬ 
fined  characters,  use  this  command 
to  see  what  they  look  like.  Remem¬ 
ber,  to  be  printed,  redefined  charac¬ 
ters  must  be  Overlayed  in  the  print¬ 
er’s  RAM,  and  the  RAM  character  set 
must  have  been  Activated. 

This  command  exposes  one  of  the 
program’s  foibles.  Character  1 27,  the 
alternate  zero,  is  not  printed:  in  its 
place  is  a  blank.  I  couldn’t  figure  out 
how  to  print  the  alternate  zero  under 
program  control.  You  must  set  a 
switch  inside  the  printer  itself  (see 
your  printer’s  user  manual). 


29 

274 


Dr.  Dobb's  Journal,  April  1985 


The  Read  command  is  used  to  re¬ 
trieve  previously  defined  fonts  from 
files  on  the  disk.  You  first  are  asked 
for  the  name  of  the  file.  After  you 
enter  the  name,  a  new  menu  is  dis¬ 
played  giving  you  the  option  of  read¬ 
ing  all  of  the  characters,  a  range  of 
characters,  or  a  single  character  from 
the  file. 

If  you  select  to  read  a  range  of 
characters  or  only  a  single  character, 
you  again  are  asked  to  supply  the  lim¬ 
its  of  the  range  or  the  number  of  the 
character.  If  any  of  the  numbers  are 


outside  the  legal  range,  or  if  the  lower 
limit  exceeds  the  upper  limit,  an  error 
message  is  displayed  and  no  action 
takes  place.  If  the  program  cannot 
open  the  file  or  read  its  contents,  an 
error  message  to  that  effect  is  dis¬ 
played.  Check  the  spelling  of  the 
filename. 

As  mentioned  above,  if  you  intend 
to  create  a  font  by  building  it  up 
through  successive  Read  commands 
on  a  group  of  character  definition 
files,  invoke  the  Initialize  command 
before  you  start.  If  not,  the  newly 


built  definition  may  redefine  charac¬ 
ter  0  to  a  blank  or  possibly  crash  the 
program,  system,  and  printer.  In  ad¬ 
dition,  you  may  find  it  impossible  to 
Read  the  uninitialized  font  definition 
after  it  has  been  stored  on  disk. 

The  Write  command  is  used  to 
store  the  contents  of  the  character 
definition  buffer  into  a  file  on  the  disk. 
You  will  be  asked  to  enter  a  name  for 
the  file.  If  the  file  already  exists,  its 
contents  are  lost  when  the  new  file 
overwrites  it.  If  the  program  cannot 
create  the  file  or  detects  some  I /O  er¬ 
ror,  an  error  message  is  displayed.  It  is 
a  good  idea  to  make  sure  you  have 
enough  space  on  the  disk  for  all  the 
files  you  intend  to  create  before  you 
start  the  program.  If  the  program 
crashes  because  of  insufficient  disk 
space,  all  of  your  work  since  the  last 
Write  command  will  be  lost. 

The  Write  command  saves  the  en¬ 
tire  character  definition  buffer.  It 
cannot  operate  with  only  a  range  of 
characters  or  a  single  character. 

The  Quit  command  is  used  to  exit 
the  program.  Remember  to  save  any 
newly  created  or  altered  definitions 
with  the  Write  command  before  Quit¬ 
ting  or  you  will  lose  all  your  work. 

Using  the  Program 

Before  you  start  the  program,  be  sure 
that  the  switch  in  the  printer  that  de¬ 
termines  how  the  2K  buffer  will  be 
used  is  set  so  you  can  store  your  char¬ 
acter  definitions.  To  start  the  pro¬ 
gram,  type  fx80char  followed  by  a 
carriage  return.  You  can  also  enter  a 
filename  following  the  program 
name;  fx80char  will  assume  that  the 
file  is  a  character  definition  file  and 
attempt  to  read  it  into  the  program’s 
character  buffer. 

The  usual  sequence  of  actions  dur¬ 
ing  a  session  with  fxSOchar  involves 
Activating  the  printer’s  RAM  charac¬ 
ter  set,  Reading  a  character  defini¬ 
tion,  Editing  some  of  the  characters, 
Overlaying  the  new  characters  in  the 
printer,  and  Printing  the  new  charac¬ 
ter  set.  You  usually  repeat  the  Edit- 
Overlay-Print  cycle  several  times  dur¬ 
ing  a  session  as  you  make  changes  to 
the  character  definitions.  When  you 
are  finished,  you  probably  will  want  to 
Write  your  revised  character  defini¬ 
tions  to  a  file  for  permanent  storage. 


Dr.  Dobb’s  Journal,  April  1985 


30 

275 


When  you  want  to  use  a  different 
character  set  from  another  program 
(for  example,  to  print  a  draft  copy  of  a 
document),  run  fx80char,  Read  the 
new  character  set  from  a  file,  then 
Overlay  the  new  character  set.  After 
you  exit  from  fx80char,  the  altered 
character  set  will  be  used  for  printing 
until  you  turn  off  the  power  or  run 
fx80c har  again  and  replace  the  set. 

Some  of  my  friends  and  I  have  been 
using  this  program  for  some  time.  We 
have  yet  to  find  any  errors.  The  most 
common  mistake  we  make  seems  to  be 
Quitting  the  program  before  Writing 
revised  definitions  to  a  file. 

The  Program 

The  source  text  for  the  program  is  di¬ 
vided  among  five  files  written  in  the 
Eco-C  version  of  C  for  CP/ M.  These 
consist  of  the  header  file  FX80.H 
(Listing  One)  and  the  files 
FX80CH AR.C,  ACTIONS.C,  ED- 
CHAR.C,  and  PRNOUT.C  (Listings 
Two  through  Five,  respectively). 

FX80.H  (Listing  One,  page  34) 
contains  the  global  #define,  data 
structure,  and  variable  declarations. 
This  file  should  be  included  in  all  C 
source  modules  making  up  the  pro¬ 
gram.  In  addition,  one  and  only  one 
of  the  C  source  modules  must  define 
the  macro  MAIN  before  inclusion  of 
FX80.H.  This  defines  the  global  char¬ 
acter  buffer  pointer,  struct  chrary 
*chrs,  in  that  file.  All  other  files 
(those  without  the  macro  MAIN)  will 
compile  only  a  declaration  of  *chrs  as 
an  external  variable. 

struct  chrary  defines  the  structure 
of  the  elements  that  will  make  up  the 
program’s  global  character  buffer.  It 
is  fairly  straightforward.  The  chrnum 
member  contains  the  number  of  the 
character,  which  corresponds  to  the 
numbers  requested  in  the  Edit,  Over¬ 
lay,  and  Read  commands.  The  attrib 
element  contains  the  attribute  byte 
for  the  character  as  currently  de¬ 
fined:  the  program  uses  only  two  val¬ 
ues  for  this  byte  to  distinguish  be¬ 
tween  printing  with  the  upper  or 
lower  eight  pins  of  the  print  head. 
The  program  generates  no  spacing 
data  for  proportional  characters,  and 
ary  simply  contains  the  sequence  of 
bytes  needed  to  define  the  character 
to  the  printer. 


The  variable  chrs  is  a  pointer  to  a 
variable  of  type  struct  chrary.  When 
the  program  starts,  an  array  large 
enough  to  hold  NUMCH ARS  ele¬ 
ments  of  type  struct  chrary  is  allocat¬ 
ed;  chrs  points  to  the  first  element. 
The  variable  then  is  used  globally 
throughout  the  program. 

FX80CHAR.C  (Listing  Two,  page 
36)  contains  the  main  program  func¬ 
tion  and  miscellaneous  utility  rou¬ 
tines.  The  function  main(  )  attempts 
to  allocate  space  for  the  global  char¬ 
acter  buffer  and  to  assign  the  vari¬ 
able  *chrs  to  point  to  it.  If  the  alloca¬ 
tion  fails,  the  program  will  abort.  If 
the  allocation  succeeds,  the  program 
will  zero  (not  initialize)  the  character 
buffer.  The  character  buffer  is  allo¬ 
cated  dynamically  to  decrease  the 
size  of  the  code  file  (and  to  speed  up 
program  assembly  and  linkage). 

The  program  then  checks  to  see  if  a 
command  line  argument  has  been 
specified.  If  so,  the  program  inter¬ 
prets  the  argument  as  the  name  of  a 
character  definition  file  and  attempts 
to  read  it  into  the  global  character 
buffer.  When  all  initialization  is 
complete,  the  function  mainmenu(  ) 
is  invoked  to  handle  the  command 
loop. 

ACTIONS.C  (Listing  Three,  page 
42)  contains  most  of  the  functions 
that  actually  implement  the  com¬ 
mands  available  from  the  main  menu 
level.  These  are  all  pretty  obvious, 
many  simply  sending  sequences  of 
commands  to  the  printer. 

EDCHAR.C  (Listing  Four,  page 
50)  contains  the  character  editor 
function.  After  the  user  enters  the 
number  of  the  character  to  be  edited, 
the  element  is  “unpacked”  into  the 
static  external  variable  cary.  This  is  a 
two-dimensional  array  with  one  ele¬ 
ment  of  type  unsigned,  corresponding 
roughly  to  each  bit  in  a  variable  of 
type  struct  chrary.  Unpacking  the 
bits  into  this  variable  makes  the  edit¬ 
ing  process  much  simpler  and  faster 
because  individual  bits  of  the  charac¬ 
ter  definition  need  not  be  accessed. 
The  character  display  and  cursor 
movement  are  fairly  obvious.  At  the 
end  of  the  edit,  the  character  is 
checked  for  errors  in  definition  and 
repacked. 

PRNOUT.C  (Listing  Five,  page  56) 


32 

276 


contains  the  function  to  print  the 
character  set.  printall(  )  loops 
through  all  256  characters,  testing 
whether  the  character  falls  into  one 
of  five  classes.  If  it  is  a  normal  print¬ 
able  character  or  its  italic  counter¬ 
part,  the  character  simply  is  sent  to 
the  printer.  If  it  is  a  high-order  con¬ 
trol  character  or  something  other 
than  a  special  low-order  control  char¬ 
acter,  the  appropriate  series  of  com¬ 
mands  is  sent  to  the  printer.  If  the 
loop  counter  corresponds  to  charac¬ 
ter  127,  a  space  is  ^kipped.  If  it  is  a 
special  character,  as  determined  by 
the  function  isspecial(  ),  the  pspe- 
cial(  )  function  is  called.  (Special 
characters  correspond  to  things  like 
the  escape  and  carriage  return.)  This 
function  basically  does  a  brute  force 
translation  from  the  character  num¬ 
ber  to  the  commands  necessary  to 
print  a  graphic  version  of  the 
character. 

External  Functions 

fx80char  uses  several  external  rou¬ 
tines  that  are  not  members  of  the 
standard  library.  With  a  few  excep¬ 
tions,  these  are  used  to  control  the 
video  properties  of  the  terminal  upon 
which  the  program  is  running. 

The  video  control  functions  are 
from  my  C  function  library.  Most  pro¬ 
grammers  have  a  similar  library  con¬ 
taining  routines  to  access  functions 
available  on  their  particular  termi¬ 
nals.  The  functions  revon(  )  and  re- 
voff(  )  are  used  to  turn  on  and  off  the 
reverse  video  mode  of  the  terminal. 
The  gotoxy(column,  line)  function  is 
used  to  position  the  cursor  at  an  arbi¬ 
trary  location  on  the  screen,  clrline- 
(line)  clears  the  entire  line  that  is 
specified  as  the  function’s  argument. 
The  function  clreos(column,  line) 
clears  the  screen,  from  the  requested 
coordinates  to  the  end  of  the  screen, 
and  leaves  the  cursor  at  the  specified 
coordinates.'  Similarly,  clrscrn(  ) 
clears  the  entire  screen  and  leaves  the 
cuisor  at  the  home  position. 

My  video  routines  assume  that  the 
home  position  has  coordinates  of  (0, 
0).  Column  numbers  increase  to  the 
right  and  line  numbers  increase  as 
the  cursor  moves  down  the  screen. 
The  program  assumes  it  is  running  on 
a  terminal  with  24  lines  of  80  cot-  I 

Dr.  Dobb’s  Journal,  .npril  1985 


umns  each. 

The  program  also  uses  some  func¬ 
tions  from  the  Eco-C  standard  li¬ 
brary  that  are  not  available  in  the 
Unix  C  library.  The _ bdos(  )  func¬ 

tion,  which  provides  direct  access  to 
the  CP/M  BDOS,  is  used  for  raw  in¬ 
put  from  the  keyboard  and  output  to 
the  printer.  Similar  functions  are 
available  in  most  CP/M-based  ver¬ 
sions  of  C.  The  setmemf  )  function 
quickly  initializes  a  block  of  memory 
to  a  specified  value. 

One  additional  item  is  worth  noting 
here.  The  version  of  fopenf  )  used  in 
the  listings  does  not  use  standard  pa¬ 
rameters.  Unix  C  terminates  lines 
with  a  single  newline  character,  but 
CP/M  uses  the  carriage  return-line 
feed  (CR/LF)  pair  of  characters. 
Most  CP/M-based  implementations 
of  C  provide  an  automatic  translation 
of  CR/LF  to  LF  when  reading  text 
from  a  disk.  Conversely,  single  LF 
characters  are  translated  to  the  CR/ 
LF  sequence  when  writing  a  text  file. 
In  Eco-C,  the  mode  argument  is  de¬ 
clared  to  be  “binary”  by  appending 


the  character  “b”  to  the  standard 
mode  (see  functions  readfl(  )  and  wri- 
tefilef  )  in  Listing  Three).  This  con¬ 
vention  is  used  when  no  special  han¬ 
dling  of  CR/LF  sequences  is  desired. 

Conclusions 

If  you  are  looking  for  things  to  im¬ 
prove,  a  couple  of  places  in  the  pro¬ 
gram  are  ripe  for  diddling.  First,  the 
definition  of  struct  chrary  is  rather 
wasteful.  Declaring  the  attrib  and 
ary  fields  as  char  could  save  about 
half  the  space  now  required.  Another 
area  that  could  be  improved  is  the 
character-packing  function,  pack- 
it(  ),  in  the  file  EDCHAR.C  (Listing 
Four).  As  currently  implemented,  the 
program  will  not  create  characters 
that  can  be  proportionally  printed. 
This  is  due  to  the  way  packit(  )  as¬ 
signs  the  attribute  byte. 

It  would  also  be  simple  to  make  the 
program  a  little  more  forgiving  of 
user  errors.  You  could  have  the  pro¬ 
gram  prompt  users  if  they  try  to  Quit 
before  Writing  their  changes  to  disk. 
Another  good  idea  might  be  to  auto¬ 


matically  Initialize  the  character 
buffer  when  it  is  allocated. 

The  program  as  it  appears  in  the 
listings  does  not  use  any  printf(  )s. 
This  made  the  program  about  1.5K 
smaller  at  the  expense  of  a  couple  of 
clumsy  statement  sequences.  If  you 
make  changes  that  include  calls  to 
printf(  ),  you  also  might  want  to 
clean  up  some  of  those  statements. 

This  program  has  made  creating 
and  printing  alternate  characters  for 
the  FX-80  much  easier.  You  can 
change  characters  and  immediately 
see  the  results,  which  is  much  faster 
and  less  frustrating  than  writing  a 
program  to  send  a  fixed  series  of 
commands  to  the  printer. 

DDJ 

Reader  Ballot 

Vote  for  your  favorite  feature/article. 

Circle  Reader  Service  No.  1 94. 


fx80char  (Text  begins  on  page  28) 

Listing  One 


/* 

** 

fx80.h  —  global  header  file  for  fx80  character  font  editor 

*/ 

•ifndef 

VOID 

•define 

W>ID 

int 

♦endif 

♦define 

REVISION 

"7  April  1984" 

♦define 

TITLE 

"Character  Font 

Editor  —  FX-80  Vers.  1.0" 

♦define 

STATTJSLINE 

0 

/*  status  line  line  ntnrber  */ 

•define 

MENULINE 

4 

/*  line  at  which  menus  start  */ 

♦define 

PROMPTLINE 

20 

/*, line  for  prompts  */ 

♦define 

NAMELENSTH 

16 

/*  max  length  of  file  name  */ 

•define 

WIDTH 

11 

/*  width  of  character  cell  */ 

♦define 

HEIGHT 

9 

/*  height  of  character  cell  V 

♦define 

NUMCHARS 

256 

/*  number  of  possible  characters  */ 

♦define 

EOXX 

(unsigned)  30 

/*  left  edge  of  character  box  */ 

•define 

BOXY 

(unsigned)  5 

/*  top  of  character  box  */ 

♦def ine 

YOFF 

(BOXY  +  HEIGHT 

+  1) 

♦define 

BELL 

7 

•define 

ESC 

27 

♦define 

CIRLE 

5 

♦define 

CIRLX 

24 

♦define 

CTRLS 

19 

♦define 

CTRLD 

4 

•define 

ORLF 

6 

•define 

CTRIQ 

17 

( 

(Continued  on  page  36) 


Dr.  Dobb’s  Journal,  April  1985 


34 

277 


fx80char 
Listing  One 


(Listing  Continued,  text  begins  on  page  28) 


struct  chrary 
{ 


unsigned 

int 

chrnum; 

unsigned 

int 

attr ib; 

unsigned 

int 

ary (WIDTH) ; 

♦define 

ELSIZE 

(sizeof (struct  chrary) ) 

♦define  ARRAYSIZE 

(NUMCHARS*ELSIZE) 

♦ifdef 

MAIN 

/*  being  included  in  main  program  file  */ 

struct 

♦else 

chrary 

*chrs ; 

/*  pointer  to  global  character  array  */ 

/*  being  included  in  subsidiary  function  file  */ 

extern 

♦endif 

struct 

chrary 

‘chrs; 

Listing  Two 


/* 

“  fx80char.c  —  character  font  editor  for  Epson  FX80  printers. 

** 

“  This  program  can  be  used  to  create  and  alter  character  fonts 
“  for  downloading  to  Epson  FX80  and  FX100  printers. 

** 

“  David  D.  Clark 

“  7  April  1984 

*/ 

♦include  <stdio.h> 

♦def ine  MAIN 

♦include  <fx80.h> 


/‘notify  the  fx80.h  header  that  this  is 
the  main  program  file  */ 


main (argc,  argv) 
int  argc ; 

char  *argv [ 1 ; 

{ 

/*  allocate  character  buffer  area  */ 

if  ( (chrs  =  (struct  chrary  *)  calloctNUMCHARS,  ELSIZE))  ==  NULL) 
{ 

fputsfNo  roan  to  allocate  edit  buffer. \n",  stderr); 
exit(l) ; 

} 

/*  try  to  read  in  an  initial  font  file  if  requested  */ 
if  (argc  >  1) 

( 

readfl (argvll] ,  0,  NUMCHARS) ; 

) 

ma inmenu  0 ; 
clrscrnO; 
exit(0) ; 

} 


End  Listing  One 


(Continued  on  page  38) 


36 

278 


Dr.  Dobb' s  Journal.  April  1985 


fx80char  (Listing  Continued,  text  begins  on  page  28) 

Listing  Two 


/* 

**  mainmenu  —  outer  most  command  level.  This  is  the  actual 

**  command  p  ocessing  loop. 

*/ 

char  *mnulU  =  /*  menu  for  outer-most  command  level  */ 

{ 

"  —  Activate/deactivate  RAM  character  set.\n"/ 

"  —  Edit  the  currently  selected  character .\n'( 

"  —  Initialize  the  edit  character  array. \n", 
n  —  Load  printer  RAM  with  ROM  character  set.\n", 

"  —  Overlay  new  characters  in  printer. \n", 

"  —  Print  the  character  set.Xn", 

"  —  Read  character  file.Nn", 

"  —  Write  character  file.Xn", 

"  —  Quit.\n" 

>; 

char  *cmdl  =  /*  acceptable  user  responses  to  this  level  */ 

{ 

"AEILOPRMQ  " 

>/* 

int  ncmdl  =  9;  /*  number  of  acceptable  commands  */ 

VOID  mainmenu ( ) 

{ 

int  cmd;  /*  command  character  */ 

while  (TRUE)  /*  loop  forever  */ 

{ 

clrscrn  0 ; 
shortstat  0 ; 

cmd  =  domenu (mnul,  cmdl,  ncmdl); 
switch  (cmd) 

{ 

case  'A': 

activate  0 ; 
break; 

case  'E's 

edchar ( ) ; 
break; 

case  1 1 1 : 

inicharsO ; 
break; 

case  'L': 

loadramO  ; 
break; 

case  'O': 

overlay ( ) ; 
break; 

case  'P'; 

printall 0 ; 
break; 

case  'R': 

readfileO ; 
break; 

case  'W' : 

writef  ile  () ; 
break; 


Dr.  Dobb  s  Journal.  April  1985 


38 

279 


case  'Q': 

return? 


} 


} 


} 


/* 

**  The  following  functions  are  utilities  used  throughout  the  program. 

/* 

**  domenu  —  print  out  specified  menu  and  get  an  acceptable  response. 

V 

int  domenu  (mnu,  cmds,  ncmds) 

char  *mnu[], 

*cmds? 
int  ncmds ; 

{ 

int  c? 

unsigned  i ? 

fputs("\n\n\n",  stdout); 
for  (i  =  0;  i  <  ncmds?  i++) 

{ 

fputs("\t\t",  stdout)? 
revonO  ? 

putc (cmds  t i ] ,  stdout ) ? 
revoff  0  ? 

f puts  (mnu  [  i  ] ,  stdout )  ? 

) 

do 

{ 

c  =  (islcwer(c  =  mygetcO)  ?  toupper  (c)  :  c)? 
if  (index (cmds,  c)  =  NULL) 

{ 

c  =  NULL? 

putc  (BELL,  stdout)? 

} 

}  While  (c  ==  NULL)  ? 


) 


return  (c)? 


/* 

**  selchar  —  select  a  character  by  number. 

*/ 

unsigned  selchar 0 

{ 

char  charstr [6] ? 

gotoxy(0,  PROMPTLINE)? 
f  puts  ("character  nutiber?  ",  stdout)? 
if  (f gets  (charstr,  5,  stdin)  —  NULL) 

return  (32)  ?  /*  have  to  return  scmething  */ 

return  ((unsigned)  atoi (charstr) ) ? 

} 


/* 

**  pputc  —  put  a  character  to  the  printer. 

*/ 


M3  ID  pputc  (c) 

int  c? 

{ 

_bdos(5,  c)?  /*  send  char  directly  to  printer  */ 

) 


(Continued  on  next  page) 


39 

280 


Dr.  Dobb's  Journal,  April  1985 


f\80char  (Listing  Continued,  text  begins  on  page  28) 

Listing  Two 


/* 

** 

v 

int 

{ 

int 


} 


/* 

** 

V 

VOID 

{ 


} 


/* 

** 

*/ 

VOID 

char 

{ 


} 


/* 

** 

*/ 

VDID 

{ 


} 


/* 

** 

*/ 

VOID 

{ 


} 


mygetc  —  get  raw  keyboard  input. 


mygetc ( ) 
c; 

while  (He  =  _bdos(6,  OxOOff))) 
i 

return  (c)  ; 


/*  wait  for  a  character  */ 


pause  —  wait  for  the  user  to  type  a  character. 


pause  0 

fputs ("Type  any  character  to  continue.  ",  stdout); 
mygetc 0 ; 

putc('\n',  stdout); 


cantopen  —  print  error  message  when  unable  to  open  "file". 


cantopen(file) 

♦file; 


fputs ("Can't  open  ",  stderr); 
fputs (file,  stderr) ; 
putc('\n',  stderr); 
pause  0; 


filerr  —  print  error  message  after  file  i/o  error. 


filerrO 

fputs("\nFile  i/o  error\n",  stderr); 
pause  0 ; 


shortstat  —  print  short  status  line. 


shortstat  0 

clrline  (STATOSLINE)  ; 
fputs ("\t\t",  stdout) ; 

revon  0 ; 

fputs (TITLE,  stdout ) ; 
revoff  0 ; 


Dr.  Dobb’s  Journal.  April  i985 


( Continued  on  page  42) 


40 

281 


TXOUCndr  (Listing  Continued,  text  begins  on  page  28) 

Listing  Two 


/* 

**  longs  tat  —  print  a  long  status  line. 

V 

VOID  longs  tat  (charnum) 

int  charnum; 

{ 

char  curchar(6);  /*  ASCII  representation  of  charnum  */ 

cl r line  (STATUSLINE) ; 
revonO ; 

fputs (TITLE,  stdout ) ; 
gotoxy(42,  STATUSLINE); 
fputs ("Char.  No.  ",  stdout); 
itoa (cur char,  charnum) ; 
fputs (curchar,  stdout); 

gotoxy(58,  STATUSLINE); 
fputs ("Char  =  ",  stdout); 
revoff  0; 

if  (ispr int (charnum  &  0x7f)) 
if  (charmm  >  127) 

{ 

revonO ; 

putc (char nun,  stdout); 
revoff 0 ; 

) 

else 

putc (charnum,  stdout); 

else 

{ 

revonO ; 

fputs("Can't  show",  stdout); 
revoff  0 ; 

) 

)  End  Listing  Two 


Listing  Three 


/* 

**  actions. c  —  action  routines  for  character  editor 

V 

finclude  <stdio.h> 

♦include  <fx80.h> 


/* 

**  activate  —  activate  the  RAM  character  set. 

*/ 

static  ramact  =  FALSE;  /*  assume  ROM  set  active  at  start  of  program  */ 

VOID  activate  0 

t 

gotoxy (0,  FROMPTLINE); 
if  (! ramact) 

{ 


42 


OQO 


Dr.  Dobb’s  Journal,  April  1985 


pputc (ESC) ; 
pputc('% ') ; 
pputc (1) ; 
pputc  (0) ; 
ramact  =  TRUE; 

f puts ( "RAM  character  set  active. \n",  stdout); 

) 

else 

{ 

pputc (ESC) ; 
pputc ('%')  f 
pputc (0) ; 
pputc  (0) ; 
ramact  =  FALSE; 

fputs  ("ROM  character  set  active. \n",  stdout); 

} 

pause  0 ; 

) 


/* 

**  inichars  —  initialize  the  global  character  array. 

V 

WID  inichars  0 

{ 

unsigned  i; 

struct  chrary  *ptr ; 

gotoxy  (0,  PROMPTLINE)  ; 

fputs ("Initializing  character  array... \n",  stdout); 
setmemtchrs,  ARRAZSIZE,  0); 

for  (i  =  0,  ptr  =  chrs;  i  <  NUMCHARS;  i++,  ptr++) 
ptr->chrnum  =  i; 

pause  0; 

} 


/* 

**  loadram  —  load  ROM  character  set  into  RAM. 

*/ 

VOID  loadram 0 

{ 

gotoxy (0,  PROMPTLINE); 

pputc  (ESC) ; 

pputc  (';'); 

pputc  (0) ; 

pputc (0) ; 

pputc  (0) ; 

fputs ( "Pr inter  RAM  loaded  fran  ROM.\n",  stdout); 
pause  0 

) 


/*  menu  and  responses  for  the  overlay  and  read  commands  */ 

char  *mnu2 [ 1  = 

{ 

"  —  All  characters. \n", 

"  —  Range  of  characters. \n", 

"  —  Single  character. \n" 

); 

char  *cmd2  = 

{ 

"ARS" 

>? 


(Continued  on  next  page) 


Dr.  Dobb’s  Journal,  April  1 98 5  43 

283 


TXOUCnar  (Listing  Continued,  text  begins  on  page  28) 

Listing  Three 


int  ncmd2  =  3; 


/* 

**  overlay  —  overlay  the  characters  in  the  printer  RAM  with 

**  the  characters  in  chrs. 

*/ 

WID  overlay  ( ) 

{ 

int  cmd; 

unsigned  i,  rlo,  rhi; 

struct  chrary  *myptr ; 

clrscrn  0 ; 
shortstatO; 

and  =  domenu (mnu2,  cmd2,  ncmd2) ; 
gotoxy (0,  FROMPTLINE) ; 
switch  (cmd) 

{ 

case  'A': 

fputs ("Overlaying  entire  character  set...\n",  stdout); 
for  (i  =  0,  iryptr  =  chrs;  i  <  NUMCHARS;  i++,  nyptr++) 
defchar (myptr) ; 

break; 


case  'R'; 

clreos(0,  (PROMFILINE  -  1) ) ; 

fputs ("For  the  lew  end  of  the  range, \n",  stdout); 

rlo  =  selcharO; 

clreos(0,  (PROMP1LINE  -  1) )  ; 

fputs ("For  the  high  end  of  the  range, \n",  stdout); 
rhi  =  selcharO; 

define:  if  (rlo  >=  0  &&  rhi  <  NUMQJARS  as  rlo  <=  rhi) 

{ 

myptr  =  chrs; 

/*  skip  unwanted  characters  V 
while  (rryptr->chrnurr.  1=  rlo) 
nyptr++; 

for  (i  =  rlo;  i  <=  rhi;  i++,  nyptr++) 
defchar (myptr ) ; 

} 

else 

fputs ("Illegal  character  range. \n",  stderr); 

break; 
case  'S': 

rlo  =  rhi  =  selcharO; 
goto  define; 

) 

pause  0 ; 

} 


/* 

**  defchar  —  define  a  character  to  the  printer. 
*/ 

VOID  defchar  (p) 
struct  chrary  *p; 

{ 

unsigned  j ; 

pputc (ESC) ; 


(Continued  on  page  48) 


44 


Dr.  Dobb’s  Journal,  April  1985 


fx80char  (Listing  Continued,  text  begins  on  page  28) 

Listing  Three 


j 


pputc ('&'); 
pputc  (0) ; 
pputc (p->chrnum) ; 
pputc (p->chrnum) ; 

pputc  (p->attrib) ; 
for  (j  =  0;  j  <  WIDTH;  j++) 
pputc (p->ary  t j ] ) ; 


/* 

**  readfile  —  read  a  file  of  character  definitions. 

*/ 

VOID  readfile 0 
{ 

char  name  [NAMELEN3TH) ; 

int  end; 

unsigned  rlo,  rhi; 


gotoxy (0,  PROMPTLINE)  ; 

fputsCRead  what  character  file?  ",  stdout); 
fgets(name,  NAMELEtdH,  stdin); 
clrscrnO ; 

shortstatO ; 

cmd  =  danenu(imu2,  cmd2,  ncmd2) ; 
gotoxy  (0,  PROMPTLINE) ; 
switch  (end) 

{ 

case  'A'; 

readfl (name,  0,  NUMCHARS); 
break; 


case  'R': 

clreos(0,  (PROMPTLINE  -  1) ) ; 

fputs("For  the  low  end  of  the  range, \n",  stdout); 

rlo  =  selcharO; 

clreos(0,  (PROMPTLINE  -  1) ) ; 

fputs("For  the  high  end  of  the  range, \n",  stdout); 
rhi  =  selcharO; 

doread;  if  (rlo  >=  0  &&  rhi  <  NUMCHARS  &&  rlo  <=  rhi) 

readfl  (name,  rlo,  rhi) ; 

else 

fputs  ("Illegal  character  range. \n",  stderr); 

break; 
case  'S'; 

rlo  =  rhi  =  selcharO; 
goto  doread; 

) 

) 


/* 

**  readfl  —  do  the  actual  read  of  a  character  definition  file. 

**  Only  read  characters  fran  "lo"  to  "hi"  into  the  global  character 
**  buffer. 

*/ 

VOID  readfl  (name,  lo  hi) 
char  *name; 

unsigned  lo,  hi; 

( 

char  c,  *ptr ; 

unsigned  i; 

FILE  *fd; 

if  ( (fd  =  fopen  (name,  "rb") )  =  NULL) 


Dr.  Dobb  s  Journal,  April  1985 


48 

285 


{ 

cantopen  (name) ; 
return; 

) 

fputst "Reading...",  stdout); 

/*  skip  past  unwanted  characters  V 
for  (i  =  0,  ptr  =  (char  *)  chrs; 

(i  <  (lo*ELSIZE) )  &&  (  c  =  getc(fd) )  !=  EOF) ; 
i++,  ptr++ 

) 

;  /*  do  nothing  */ 

if  (lo  >  0  &&  i  <  (lo*ELSIZE  -  D) 
f ilerr  0 ; 

/*  new  read  the  characters  into  the  array  V 
for  ( 

i  =  (lo*ELSIZE);  /*  ptr  is  already  where  it  should  be  */ 
(i  <  (hi*ELSIZE  +  ELSIZE))  &&  ( (c  =  getc  (fd) )  1=  EOF); 
i++,  ptr++ 

) 

*ptr  =  c; 

if  (hi  >  0  &(,  i  <  (hi*ELSIZE  +  ELSIZE  -  1) ) 
f ilerr  0 ; 

fclose (fd) ; 

) 


/* 

**  writefile  —  write  a  file  of  character  definitions. 

V 

\)OID  writefile  0 

{ 

char  c,  *ptr ; 

char  raine  [NAMELEN3TH  ] ; 

unsigned  i; 

struct  chrary  *rnyptr  ; 

FILE  *fd; 


gotoxy (0,  PROMPILINE) ; 

fputs("Write  characters  to  what  file?  ",  stdout); 
fgets  (name,  NAMELEtCIH,  stdin); 
putc('\n',  stdout); 
if  ((fd  =  fopen (name,  "wb"))  ==  NULL) 

{ 

cantopen  (name) ; 
return; 

) 

fputs ("Writing. .. ",  stdout); 

for  (i  =  0,  ptr  =  (char  *)  chrs;  i  <  ARRAYSIZE;  i++,  ptr++) 
( 

if  (putc(*ptr,  fd)  =  EOF) 

{ 

f ilerr  0 ; 
fclose  (fd) ; 
return; 

) 

) 

fclose (fd) ; 

) 


End  Listing  Three 


(Listing  four  begins  on  next  page ) 


49 

286 


Dr.  Dobb's  Journal,  April  190? 


TXOUCfiar  (Listing  Continued,  text  begins  on  page  28) 

Listing  Four 


/* 

**  edchar.c  —  character  editing  functions  for  the  fx80 
**  character  editor  program. 

*/ 

♦include  <stdio.h> 

♦include  <fx80.h> 


/*  unpacked  version  of  the  character  being  edited  */ 
static  unsigned  cary [WIDTH] [HEIGHT] ; 


/* 

**  edchar  —  the  main  character  editing  function. 

*/ 

VDID  edchar ( ) 

{ 

int  c; 

unsigned  myx,  myy,  charnum; 

gotoxy  (0,  PROMPILINE  -  1) ; 
fputs ("Edit",  stdout) ; 
charnum  =  selcharO; 
clrscrnO  ; 
drwboxO ; 

longs tat (charnum) ; 
unpack (charnum) ; 
f lashit (charnum) ; 
myx  =  1; 
nyy  =  1; 

boxgotoxy (myx,  myy)  ; 

do 

{ 

switch  (c  =  (islower(c  =  nygetcO)  ?  toupper  (c)  :  c)) 
[ 

case  '8':  /*  up  */ 

case  CTOLE: 

if  (nyy  <  HEIGHT) 

boxgotoxy (myx,  ++myy) ; 

break; 

case  '2';  /*  down  */ 

case  CTCLX: 

if  (myy  >  1) 

boxgotoxy (myx,  — myy); 

break; 

case  1 4 ' :  /*  left  */ 

case  CIWjS: 

if  (myx  >  1) 

boxgotoxy ( — myx,  myy) ; 

break ; 

case  '6':  /*  right  */ 

case  CIRLD: 

if  (myx  <  WIDTH) 

boxgotoxy  (+-Hryx,  myy)  ; 

break; 


case  '5':  /*  toggle  pixel  */ 

case  CIRLF: 

if  (carytnyx  -  1)  liryy  -  1]) 

{ 

if  ( (nyx  -  1)%2) 


( Continued  on  page  52) 


50 


Dr.  Dobb's  Journal,  April  1985 


007 


TXOUtnar  (Listing  Continued,  text  begins  on  page  28) 

Listing  Four 


putc ('I',  stdout); 

else 

putcC  1 ,  stdout)  ; 
putc('\b',  stdout); 
cary  (irryx  -  1]  [nyy  -  1]  =  0; 

} 

else 

{ 

revonO ; 

putc  ( '  ' ,  stdout) ; 
revoff 0 ; 

putc ( ' \b 1 ,  stdout ) ; 
carytnyx  -  1]  tnyy  -  1]  =  1; 

) 

break ; 


case  'Q ' ;  /*  quit  V 

case  CIRIQ : 

c  =  'Q'; 
break; 


) 


) 

)  while  (c  1= 


default: 

putc  (BELL, 


■Q  ’> ; 


stdout) ; 


packit (charnum) ; 
clrscrnO ; 


/* 

**  drwbox  —  draw  the  character  easel. 

*/ 

VOID  drwbox 0 

{ 

unsigned  i,  j; 

gotoxy (BOXX,  BOXY) ; 
putc ( ' + ' ,  stdout ) ; 
for  (i  =  0;  i  <  WIDTH;  i++) 
putc ( 1 - ' ,  stdout) ; 
putc ( ' + 1 ,  stdout); 

for  (i  =  1;  i  <=  HEIGHT;  i++) 

{ 

gotoxy (BOXX,  BOXY  +  i) ; 
putc ('I',  stdout); 
for  (j  =  0;  j  <  WIDTH;  j++) 
putcC  ',  stdout); 
putc ('I',  stdout); 

) 

gotoxy (BOXX,  YOFF) ; 
putc('+',  stdout); 
for  (i  =  0;  i  <  WIDTH;  i++) 
putc('-',  stdout); 
putc('+',  stdout); 

) 


/* 

**  unpack  —  unpack  the  selected  character. 

V 

VOID  unpack (cnum) 

unsigned  cnum; 

{ 


52 


Dr.  Dobb's  Journal,  April  1985 


unsigned  i,  j,  nypack,  loj,  hij; 

struct  chrary  *myptr; 

for  (i  =  0;  i  <  WIDTH;  i++) 

for  (j  =  0;  j  <  HEIGHT;  j++) 
cary  til  [j]  =0; 

myptr  =  chrs; 

for  (i  =  0;  i  <  cnum;  i++) 
myptr++; 

if  (rryptr->attrib  &  128) 

{ 

loj  =  1; 
hij  =  HEIGHT; 

) 

else 

{ 

loj  =  0; 

hij  =  (HEIGHT  -  1)  ; 

) 

for  (i  =  0;  i  <  WIDTH;  i++) 

{ 

nypack  =  nyptr->ary [i] ; 
for  (j  =  loj;  j  <  hij;  j++) 

{ 

carytiHj]  =  nypack  &  1; 
nypack  »=  1; 

) 

} 

) 


/* 

**  packit  —  pack  the  selected  character. 

*/ 

TOID  packit (cnum) 

unsigned  cnum; 

( 

unsigned  i,  j,  mypack,  loj,  hij,  wantlo,  vranthi,  adjacent; 

struct  chrary  *myptr ; 

/*  move  pointer  to  appropriate  character  */ 
myptr  =  chrs; 

for  (i  =  0;  i  <  cnum;  i++) 
nyptr++; 

/*  determine  if  upper  or  lcwer  8  pins  requested  */ 
wantlo  =  0; 

for  (i  =  0;  i  <  WIDTH;  i++) 

wantlo  1=  carytiHO]; 

if  (wantlo) 

{ 

loj  =  0; 

hij  =  HEIGHT  -  1; 
myptr->attrib  =  11; 

} 

else 

{ 

loj  =  1; 

hij  =  HEIGHT; 

myptr- >attrib  =  139; 

} 

/*  pack  the  character  */ 
for  (i  =  0;  i  <  WIDTH;  i++) 

{ 

mypack  =  0; 

for  (j  =  loj;  j  <  hij;  j++) 

{ 

nypack  »=  1; 
if  (cary(i](j]) 

nypack  1=  128; 

J  (Continued  on  next  page) 


Dr.  Dobb's  JownaL  April  1985  53 

289 


rxoucnar  (Listing  Continued,  text  begins  on  page  28) 

Listing  Four 


myptr->ary(i]  =  nypack; 

} 

/*  determine  if  upper  8  pins  requested  */ 
wanthi  =  0; 

for  (i  =  0;  i  <  WIDTH;  i++) 

wanthi  1=  carytil  [  (HEIGHT  -  1)]; 

/*  determine  if  adjacent  pins  requested  */ 
adjacent  =  0; 

for  (i  =  0;  i  <  (WIDTH  -  1)  j  i++) 

{ 

if  (adjacent  =  (myptr->ary til  &  nyptr->ary(i  +  1])) 


/*  check  for  definition  violation  */ 
if  (adjacent  II  (wanthi  &&  wantlow)) 

{ 

gotoxy (0,  PROMPTLINE); 

fputsCThis  definition  is  illegal. \n",  stderr); 
pause ( ) ; 

} 

} 

/* 

**  flashit  —  draw  selected  character. 

*/ 

VOID  flashit  (cnum) 
unsigned  cnum; 

{ 

unsigned  i,  j; 


} 


for  (i  =  0;  i  <  WIDTH;  i++) 

for  (j  =  0;  j  <  HEIGHT;  j++) 
{ 


} 


boxgotoxy(i  +  1,  j  +1); 
if  (caryliHjl) 

{ 

revonO ; 

putc  ( 1  ' ,  stdout ) ; 
revoff  0 ; 

) 

else  if  (i%2) 

putc ( 1 1  1 r  stdout); 

else 

putc  ( '  ' ,  stdout) ; 


/* 

**  boxgotoxy  —  goto  x,y  position  in  character  editing  box. 

*/ 

VOID  boxgotoxy (x,  y) 

unsigned  x,  y; 

{ 

gotoxy (x  +  BOXX,  YOFF  -  y) ; 

} 


End  Listing  Four 


(Listing  five  begins  on  page  56) 


54 

290 


Dr.  Dobb’s  Journal,  April  1985 


IX  OULU  dr  (Listing  Continued,  text  begins  on  page  28) 

Listing  Five 


/* 

prnout.c  the  function  to  print  out  the  entire  character 
**  set  of  the  FX80  printer.  The  function  printallO  will  print  the 
**  characters  taking  into  account  special  handling  required  to 
**  print  characters  stored  in  the  locations  of  control  codes.  The 
**  only  character  not  printed  is  that  stored  in  127,  the  alternate 
**  zero.  I  can't  figure  out  how  to  do  it. 

V 


♦include  <stdio.h> 

♦include  <fx80.h> 


/* 

**  printall  —  print  the  entire  character  set. 
V 

VDID  printallO 
{ 

unsigned  i; 


} 


for  (i  =  0;  i  <  NUMCHARS;  i++) 

{ 

if  ( 1 (i%64) ) 

pputc  0\n'); 

if  (isprintfi  &  0x7f)) 
pputc  (i) ; 
else  if  (i  >*  128) 

{ 

pputc  (ESC)  ; 
pputc  ('6'); 
pputc (i); 
pputc (ESC) ; 
pputc  ( '7') ; 

) 

else  if  (isspecial(i)) 
pspecial(i); 

else 
{ 

if  (i  =  127) 

continue; 


/*  line  feed  every  64  characters  V 

/*  normal  Roman  or  Italic  V 
/*  high  order  control  code  */ 

/*  special  iow  order  control  code  V 
/*  low  order  control  code  V 

/*  swallow  it  */ 


pputc (ESC) ; 
pputc ( ' 1 1 ) ; 
pputc  Cl'); 
pputc (i) ; 
pputc (ESC) ; 
pputc ( ' I ' ) ; 
pputc  CO'); 

) 

) 

pputc('\n')  ? 

pputc ( 1 \n ' ) ; 


/* 

**  isspecial  —  return  TRUE  if  argument  is  low  order  control 

**  code  requiring  special  handling  to  print. 


V 


int  isspecial (i) 
unsigned  i; 

( 

return  ( (i  >• 


} 


7  &&  i  <» 
24  I  I  i  — 


15)  II  (i  >=  17  &«,  i  <=  20) 
27); 


I  I 


Dr.  Dobb's  Journal.  April  1985 


56 

291 


/* 

**  pspecial  —  print  control  codes  that  require  special  handling. 
V 

VOID  pspecial (i) 
unsigned  i? 

{ 

pputc (ESC) ;  /*  all  select  an  international  character  set  V 

pputc('R') ; 

switch  (i) 

{ 

case  7: 

pputc  (7) ; 
pputc  (91) ; 
break; 

case  8: 

pputc  (7) ; 
pputc (93) ; 
break; 

case  9: 

pputc (7) ; 
pputc  (92) ; 
break; 

case  10; 

pputc  (7) ; 
pputc  (124) ; 
break; 

case  11: 

pputc  (5) ; 
pputc (36); 
break; 

case  12: 

pputc  (7) ; 
pputc  (35) ; 
break; 

case  13: 

pputc (4) ; 
pputc (93) ; 
break; 

case  14: 

pputc  (4) ; 
pputc  (125) ; 
break; 

case  15: 

pputc  (1) ; 
pputc (92) ; 
break; 

case  17: 

pputc  (2) ; 
pputc  (126) ; 
break; 

case  18: 

pputc  (4) ; 
pputc (91) ; 
break; 

case  19: 

pputc (4) ; 
pputc  (123) ; 
break; 

case  20: 

pputc  (4) ; 
pputc  ( 92) ; 
break ; 

case  24: 

pputc  (2) ; 
pputc  (92) ; 
break; 

case  27: 

pputc (5) ; 
pputc  (124) ; 

) 

pputc (ESC) ;  /*  Return  to  USA  font  */ 

pputc ( 'R ' ) ; 
pputc  (0) ; 


End  Listings 


57 

292 


Dr.  Dobb's  Journal,  April  1985 


A  Magic  Mushroom  for  the 
Epson  Alice 

A  Practical  Program  for  Printing  Phonetic  Characters 


by  Gary  B.  Palmer 


I  am  using  an  Apple  II  Plus  micro¬ 
computer  and  an  Epson  MX- 100 
printer  with  Graftrax  Plus  to  print 
stories  of  the  Coeur  d'Alene  Indians 
in  their  native  language;  this  requires 
phonetic  characters.  Still  spoken  flu¬ 
ently  by  a  handful  of  elderly  Indians 
on  the  reservation  in  northern  Idaho, 
Coeur  d'Alene  belongs  to  a  family  of 
languages  known  for  their  difficult 
phonetic  systems.  Linguists  who  re¬ 
cord  Indian  languages  of  the  Ameri¬ 
can  Northwest  have  designed  a  char¬ 
acter  set  that  is  visually  elegant  but 
extremely  difficult  to  reproduce  on  a 
typewriter. 

Table  1  (page  59)  shows  the  Coeur 
d '  Alene  characters.  Entering  a  single 
phonetic  character  with  a  customized 
linguistic  element  may  require  the 
typist  to  use  as  many  as  six  key¬ 
strokes,  often  involving  keys  located 
in  awkward  positions.  With  phoneti¬ 
cally  complex  languages,  the  tedium 
and  frustration  of  keyboard  entry 
send  the  probability  of  errors  soaring. 


Coeur  d'Alenes  to  translate  the  story 
into  English.  I  use  the  word  processor 
Applewriter  II  to  enter  both  the  Indi¬ 
an  text  and  the  translation  into  the 
computer,  but  I  do  not  use  the  com¬ 
puter  to  translate  the  text. 

What  a  Linguist  Needs 

Automatic  printing  of  bilingual  texts 
on  the  Epson  MX  series  of  printers  re¬ 
quires  graphics  software.  Such  soft¬ 
ware  is  readily  available  for  the  Apple 
Macintosh,  which  prints  excellent 
characters  on  the  ImageWriter  dot 
matrix  printer.  Other  late  model  dot 
matrix  printers,  such  as  the  Epson  FX 
series  that  replaced  the  MX,  permit 
loading  of  user-defined  fonts.  This  is  a 
great  convenience  for  linguists,  but  if 
your  available  printer  belongs  to  the 
MX  series,  that  is  small  comfort.  Al¬ 
though  the  MX- 100  does  not  permit 
loading  of  fonts,  you  can  still  print 
user-defined  fonts  by  sending  graphics 
commands  to  the  printer. 

Linguists  have  a  great  interest  in 


An  exploration  of  phonetic  printing  and  rubberized 
fonts  for  the  Epson  MX- 100. 


To  those  of  us  working  with  Indian 
languages,  microcomputers  promise 
convenient  transcription,  rapid  pro¬ 
cessing,  and  accurate  printing. 

I  begin  on  the  reservation  by  re¬ 
cording  the  stories  on  audio  cassette 
tape.  After  transcribing  a  text,  using 
phonetic  symbols,  I  work  with  the 


Gary  B.  Palmer,  Center  for  Comput¬ 
er  Applications  in  the  Humanities, 
University  of  Nevada,  Las  Vegas,  NV 
89154. 


displaying  alternate  character  sets  on 
the  CRT  screen.  On  the  Apple  II,  this 
is  easily  accomplished  with  the  ap¬ 
propriate  80  column  card  or  with  a 
patch  to  the  word  processor’s  charac¬ 
ter  set  if  the  characters  are  created  by 
the  software.  But  when  you  wish  to 
produce  a  manuscript,  the  creation  of 
alternate  character  sets  on  the  printer 
becomes  an  issue.  It  is  important  to 
realize  that  the  printer  and  the  screen 
are  independent,  a  fact  that  is  not  at 
all  transparent  to  newcomers. 

Linguists  who  wish  to  switch  from 
on  ;  language  to  another  at  will,  mix- 


Dr.  Dobb's  Journal,  April  1985 


58 

293 


ing  codes  in  lines  of  text,  will  discover 
that  the  limitations  of  the  standard 
keyboard  require  them  to  select  a  set 
of  characters  to  represent  the  phonet¬ 
ic  symbols  for  text  entry:  each  key 
must  correspond  to  an  ASCII  code 
number — the  only  kind  of  signal  ac¬ 
ceptable  to  the  printer. 

Only  52  keys  are  available  for  al¬ 
phabetical  use  on  the  Apple  keyboard 
(ASCII  numbers  65  to  90  and  97  to 
122).  Moreover,  the  Apple  II  Plus 
will  send  only  the  first  128,  not 
enough  to  support  an  additional  con¬ 
current  set  even  if  the  new  set  shares 
many  symbols  with  the  standard  set. 
Because  the  control  codes  below  AS¬ 
CII  32  are  needed  to  drive  the  printer, 
and  because  standard  punctuation 
marks,  numerals,  and  underlining  are 
commonly  needed  in  the  text,  the  set 
of  characters  that  can  be  sacrificed  to 
provide  alternative  characters  is 
quite  small:  #,  $,  %,  &,  *,  +,  /,  <, 
=  ,  >,  [,  ],  *,  {,  !,  },  Of  these, 

several  (@,  [,  ],  ",  (,  1,  },  )  require 

awkward  combinations  of  shift  and 
control  characters  that  are  difficult 
to  remember  and  to  enter,  at  least 
with  Applewriter  II  Plus.  This  incon¬ 
venience  defeats  our  original  goal  of 
facilitating  manuscript  preparation. 

How  To  Get  It 

The  solution  lies  not  on  the  keyboard 
but  in  the  software.  In  the  text  file, 
the  standard  character  set  can  repre¬ 
sent  the  phonetic  set;  a  printer  driver 
written  in  APPLESOFT  BASIC  de¬ 
fines  the  phonetic  characters.  Code 
inserted  in  the  text  signals  character 
set  changes.  A  signal  to  change  sets 
can  be  entered  as  one  or  more  nonal- 
phanumeric  symbols,  and  each 
unique  signal  makes  available  at  least 
52  new  shapes.  Because  the  printer 
can  create  a  practically  unlimited 
number  of  shapes,  it  is  possible  to  ac¬ 
cess  many  alternate  character  sets 
from  a  single  text. 

A  special  problem  arises  if  a  char¬ 
acter  exceeds  the  standard  7/72-inch 
height.  Because  the  MX  series  lacks 
reverse  line  feeds  and  expanded  char¬ 
acter  matrices,  the  vertical  extension 
must  be  printed  before  the  remainder 
of  the  character/  This  means  the  up¬ 
per  story  of  every  extended  character 
on  a  line  must  be  printed  before  ad- 

59 


vancing  the  line  feed.  pared  to  performing  the  same  task 

The  program  described  in  this  arti-  with  a  modified  typewriter, 
cle  creates  vertically  extended  fonts 

and  generates  one  alternate  charac-  Convenient  Text  Entry 
ter  set  that  can  be  mixed  at  will  with  Entry  codes  must  be  easy  to  remem- 
English.  The  program  does  word  ber  and  to  type.  I  use  a  system  that 
wrap,  avoids  breaking  words  at  the  assigns  a  single  key  to  frequently  oc- 
end  of  lines,  numbers  pages,  and  per-  curring  phonetic  features,  such  as 
mits  variable  line  spacing.  Defini-  stressing  and  rounding.  This  avoids 
tions  are  entered  as  data  lines  (twelve  the  necessity  of  remembering  a  sepa- 
data  points/character).  rate  key  for  each  stressed  vowel  or 

To  produce  a  phonetic  symbol,  it  rounded  consonant, 
gets  one  character  at  a  time  from  the  The  system  requires  no  unusual 
text  file,  replacing  the  ASCII  number  substitutions  (for  one  who  under- 
with  a  string  of  graphics  commands,  stands  Coeur  d'Alene  phonetics);  it 
Each  string  of  code,  representing  a  does  not  resort  to  otherwise  little- 
phonetic  symbol,  is  placed  in  one  ele-  used  capital  Zs  ,  Qs,  numbers,  or  spe- 
ment  of  an  array  of  60,  the  length  of  a  cial  keys.  These  can  all  be  kept  for 
line.  Decoding  and  printing  the  their  normal  uses  or  for  character  set 
graphics  set  is  slower  than  printing  change  flags.  Table  2  (below)  lists 
English  but  still  very  practical  com-  some  representative  characters  from 


These  are 

the  Coeur  d'Alene  vowels:  a  e  a  i  i 

O  3 

u. 

These 

are  the 

Coeur  d'Alene  consonants:  b  c  c  H  J  d 

g" 

h  y 

k~  k- 

1  i  t  m 

»  »  » 
m  n  n  p  p  q 

*  *  *  »  r  w 

q  q~  q~  r  r  R  R  R~  R~  5  s 

9 

t  t 

9 

W  W 

X 

l 

|X 

x_w  y  y  ?. 

These  are 

English  vowels  often  found 

in 

Coeur 

d’ At  ene 

texts:  a  e 

a  i  i  o  o  u  n.  These  are 

Engl i sh 

consonants  often  found  in 

Coeur  d'Alene  texts:  A  8 

o. 

Any 

vowel 

can  be  printed  with 

stress,  as  in  the  following: 

/  / 

a  e 

/  /  / 

ail 

O  3  U  rt. 

Table  1 

Coeur  d'Alene  Phonetics 

CONSONANTS 

VOWELS 

Entry 

WPL 

Phonetic 

Entry  WPL  Phonetic 

c 

c 

c 

e  e 

E 

c' 

c' 

c 

e !  e ! 

/ 

E 

c 

c 

if 

E  E 

a 

C’ 

T 

£ 

i  !  i  ! 

i 

gw 

gW 

g“ 

I  I 

i 

* 

H 

? 

o  o 

3 

j 

j 

y 

o  o 

o 

kw 

kW 

k" 

u  u 

u 

1  ’ 

1  ’ 

i 

L 

L 

r 

q' 

q’ 

q 

qw 

qW 

T 

q’w 

q’W 

qw 

R 

R 

R 

R’ 

R’ 

Rw 

RW 

R~ 

FT  w 

R'W 

a- 

s  - 

s 

s 

s 

s 

* 

W 

W 

N 

XM 

xW 

Xw 

X 

X 

X_ 

" 

Xw 

XW 

x_~ 

Table  2 

Phonetic  Characters  for  the  Coeur  d'Alene 

Language,  in  Entry  Code,  after  Processing  by 

A  WPL  Filter  Program,  and  in  Final  Form. 

OOA 


Dr  Dnhh' s  Journal.  Anri I  1985 


the  set  of  Coeur  d'Alene  phonetics. 

All  entries  bear  some  resemblance 
to  the  actual  phonetic  symbols  that 
they  represent.  All  are  either  lower¬ 
case  letters,  capitals,  or  simple  dou¬ 
ble  keystrokes  called  “digraphs.” 
Speakers  of  Coeur  d'Alene  pro¬ 
nounce  several  phonemes  with  a 
catch  in  the  throat,  so  I  appended  the 
apostrophe  symbol  to  those  pho¬ 
nemes  for  all  such  glottal  sounds. 
Likewise,  several  phonemes  are  pro¬ 
duced  with  a  rounding  of  the  lips,  so  I 
appended  the  lower-case  w  for  all 
such  labial  sounds.  Because  the  sym¬ 
bols  are  easy  to  remember  and  the 
same  keyboard  pattern  serves  for 
both  languages,  text  entry  is  simple. 

Any  good  word  processor  permits 
similar  entry,  but  Applewriter  II  has 
another  useful  feature:  it  comes  with 
its  own  programming  language,  called 
Word  Processing  Language  (WPL), 
which  is  designed  for  string  manipula¬ 
tion.  When  preparing  long  text  files 
for  the  printer  driver,  I  use  a  WPL  fil¬ 
ter  to  convert  some  digraphs  into  sin¬ 
gle  characters.  It  also  prefixes  lines  of 


English  and  Coeur  d'Alene  with 
change  codes.  Table  2  shows  the  two 
transformations  required  to  produce 
phonetic  text  from  my  entry  code.  Al¬ 
though  not  strictly  necessary,  the 
WPL  filter  does  simplify  text  entry. 

Graphics  Fonts  on  the  MX- 100 

A  few  hours  of  experimentation  will 
disclose  the  difficulty  of  printing  text 
files  with  Epson  graphics  characters. 
Taking  pity  on  me,  a  colleague  called 
my  attention  to  the  Gutenberg  soft¬ 
ware,  which  he  thought  might  work. 
Having  just  stretched  the  department 
budget  to  purchase  a  computer  and 
printer,  however,  we  could  not  go 
back  again  for  an  unproven  software 
item  costing  over  $300.  For  those 
with  a  CP/M  card,  the  Fancy  Font 
program  would  serve  the  purpose,  but 
the  cost  made  this  option  unavailable 
to  us.  Not  wishing  to  risk  buying  soft¬ 
ware  of  uncertain  utility,  I  opted  for 
writing  a  printer  driver:  a  program  to 
act  as  a  filter  to  replace  ASCII  char¬ 
acters  with  graphics. 

Creating  special  fonts  requires 


some  basic  arithmetic.  On  the  MX- 
100,  English  consonants  normally  oc¬ 
cupy  up  to  7/72  inch  above  the  base¬ 
line  and  2/72  inch  below  (see  Figure 
1,  below).  The  remaining  3/72  inch 
in  each  normal  line  feed  of  12/72 
inch  provides  the  space  between  lines. 
All  vowels  extend  only  5/72  inch 
above  the  baseline  except  i,  which  ex¬ 
tends  7/72  inch.  Each  character  is 
printed  within  the  space  of  five  col¬ 
umns  of  dots.  A  trailing  sixth  column 
provides  spacing  between  letters. 

Standard  fonts  are  mapped  in  the 
printer,  and  when  the  printer  slot  is 
addressed  (normally  with  PR#1),  a 
single  ASCII  code  number  prints  a 
character.  For  example,  the  com¬ 
mand  PRINT  CHR$(65)  prints  A. 
The  Apple  printer  and  newer  Epson 
printers  such  as  the  FX-80  can  load 
user-defined  fonts  and  then  treat 
them  in  much  the  same  way  as  stan¬ 
dard  character  sets.  With  the  MX- 
100,  however,  alternate  fonts  require 
graphics  code  from  the  computer 
each  time  a  character  is  printed. 

On  the  MX-100,  each  column  in  a 


1 

2 

3 

4 

3 

0 

i 

2 

3 

4 

3 

6 

1 

1 

□ 

2 

■ 

□ 

3 

□ 

□ 

□ 

■ 

■ 

□ 

■ 

• 

4 

■ 

□ 

• 

t 

L! 

I 

3 

■ 

□ 

■ 

■ 

■ 

■ 

□ 

■ 

□ 

■ 

6 

■ 

□ 

■ 

■ 

■ 

■ 

E 

z 

a 

■ 

7 

■ 

K 

z 

z 

1 

■ 

■ 

■ 

□ 

■ 

■ 

■ 

8 

* 

r 

tv*  raw 

9 

m 

descenders 

10 

— 

It 

Haas;  Dermal 

1 2  revs  line-feed 

12 

boundary  brtvttn  characters 

Standard  Lower 

Figure  1 

-Case  t  and  y  on  the  Epson  MX- 

100 

Dr.  Dobb  s  Journal.  April  1985 


60 

295 


character’s  six-column  matrix  is  sent 
with  a  code  number.  Code  specifying 
the  shape  of  these  columns  must  be 
prefixed  by  a  graphics  command.  A 
high-density  mode  also  must  be 
called  to  generate  well-defined  char¬ 
acters;  because  this  mode  moves  the 
printing  head  only  half  a  column  at  a 
time,  the  graphics  command  creates 
twelve-column  characters.  Take  as 
an  example  the  command:  PRINT 
CHR$(27)  +  CHR$(76)  + 

CHR$(12)  +  CHR$(0).  CHR$(27) 
says  to  treat  the  following  characters 
as  commands  rather  than  ASCII  sym¬ 
bols.  CHR$(76)  calls  the  high-densi- 
ty  graphics  mode.  CHR$(12)  sets 
aside  twelve  columns  of  dots. 
CHR$(0)  signifies  that  less  than  256 
columns  follow. 

The  high-density  mode  produces 
definition  and  density  equal  to  that  of 
standard  fonts  printed  in  emphasized 
mode.  In  the  printer  driver  (see  List¬ 
ing  One,  page  66),  the  twelve  data 
points  necessary  to  define  the  overlap¬ 
ping  columns  of  a  phonetic  symbol  are 
provided  in  DATA  lines  1050  through 
1 1 70.  Each  data  point  represents  a 
column  seven  dots  high.  Figure  2 
(page  63)  illustrates  data  points  for 
the  graphics  character  “schwa.”  Al¬ 
though  the  Epson  MX- 100  manual 
describes  graphics  procedures,  it  does 
not  mention  the  use  of  the  high-densi- 
ty  mode  for  designing  attractive  fonts. 

The  complete  character  is  con¬ 
structed  by  concatenation.  As  the 
READ  G  statement  in  line  1000  finds 
each  data  point,  the  statement  G$(M) 
+  CHR$(G)  appends  it  to  the  string 
of  data  that  represents  the  twelve 
printed  columns.  Then  the  twelve  col¬ 
umns  in  G$(M)  are  appended  to  the 
graphics  command  GC$  in  the  state¬ 
ment  G$(M)  =  GC$  +  G$(M).  Final¬ 
ly,  the  command  PRINT  G$(M)  will 
print  the  phonetic  symbol.  However, 
this  command  is  never  given  explicit¬ 
ly,  because  the  value  of  G$(M)  is  first 
transferred  to  yet  another  array  ele¬ 
ment  that  represents  the  top  or  bot¬ 
tom  sector  of  a  character. 

Although  standard  characters  can 
Extend  as  high  as  7  rows  above  the 
jaseline,  a  graphics  column  can  be 
only  6/72  of  an  inch  high.  Likewise, 
the  graphics  column  extends  only  1  / 
72  of  an  inch  below  the  baseline, 

61 


compared  to  the  2/72  drop  of  the 
standard  j,  g,  or  y.  This  is  because  the 
Apple  II  Plus  can  address  only  seven 
bits;  it  prints  only  six  dots  above  and 
one  dot  below.  For  many  purposes, 
seven  rows  will  suffice,  but  if  one 
needs  taller  fonts,  there  is  a  solution. 

Vertical  Extension  of  Fonts 

Some  applications,  such  as  Chinese 
characters,  are  too  complex  for  a  sev¬ 
en-by-six  dot  matrix.  But  for  Epson 
Alices,  the  printer  driver  offers  a 
magic  mushroom:  smaller  line  feeds 
can  make  fonts  grow  infinitely  tall. 
Two  normal  lines  spliced  together 


make  one  tall  line,  and  ten  lines 
spliced  together  make  a  very  tall  line. 
Split  characters  are  aligned  horizon¬ 
tally  by  printing  lines  in  multiple  ar¬ 
rays  of  corresponding  elements  and 
are  joined  vertically  by  controlling 
line  feeds. 

In  the  Coeur  d'Alene  set,  diacriti- 
cals  signify  phonetic  features;  the 
most  frequent  is  the  stress  mark  on 
vowels,  represented  in  entry  code 
with  !.  If  the  MX- 100  supported  re¬ 
verse  line  feeds,  we  could  backspace 
with  PRINT  CHR$(8);,  send  a  one- 
half  reverse  line  feed,  print  the  char¬ 
acter,  and  send  a  half  line  feed  for- 


5QK 


Dr.  Dobb’s  Journal,  April  1985 


ward.  Because  reverse  line  feeds  are 
unavailable,  the  stress  mark  must  be 
printed  first,  followed  by  a  partial 
line  feed,  and  then  the  vowel. 

Similarly,  each  character  with  a 
vertical  extension  has  a  top  sector 
and  a  bottom  sector.  If  the  stress 
mark  on  top  is  T$(  1 ),  the  vowel  on 
the  bottom  is  B$(  1 ),  and  PLF$  repre¬ 
sents  a  partial  line  feed  command, 
then  PRINT  T$(1):PRINT 
PLF$:PRINT  B$(  1 )  will  print  the  full 
phonetic  symbol. 

I  designed  the  diacriticals  so  that  a 
partial  line  feed  of  4/72  inch  adjusts 
them  to  vowels  of  standard  height 
(see  Figure  3,  page  63).  The  com¬ 
mand  is  TF$  =  CHR$(27)  + 
CHRS(65)  +  CHR$(4)  (see  line  1240 
in  the  listing).  Following  the  escape 
character,  CHR$(65)  resets  the  line 
spacing  and  CHR$(4)  sets  the  spac¬ 
ing  at  four  rows  of  dots. 

To  avoid  a  cascade  effect,  the  pro¬ 
gram  prints  an  entire  line  of  diacriti¬ 
cals  first,  followed  by  a  partial  line 
feed,  and  then  a  line  of  base  charac¬ 
ters.  The  diacriticals  must  be  stored 
in  the  array  T$(X),  where  X  is  the 
character  counter.  Baseline  charac¬ 
ters  are  stored  in  the  array  B$(X).  If 
a  phonetic  symbol  lacks  a  diacritical, 
a  blank  must  be  printed  in  the  top 
sector  to  maintain  proper  alignment 
between  the  top  and  bottom  of  subse¬ 
quent  complex  symbols;  this  is  ac¬ 
complished  by  initializing  the  whole 
T$(X)  array  with  blanks  before  get¬ 
ting  a  line. 

The  code  for  a  stress  mark, !,  is  en¬ 
tered  into  the  text  file  after  its  vowel. 
We  then  send  the  corresponding 
graphics  mark  to  its  proper  destina¬ 
tion  over  the  vowel  by  placing  it  in 
T$(X-1)  rather  than  T$(X).  X  is 
decremented  by  one  so  the  next  char¬ 
acter  will  be  assigned  to  T$(X)  rath¬ 
er  than  T$(X  +  1)  (see  line  200  in 
the  listing).  The  same  procedure  is 
used  for  the  graphics  apostrophe  in 
line  250. 

When  vertically  extended  charac¬ 
ters  are  represented  by  a  single  char¬ 
acter  in  the  text  file,  both  the  top  and 
bottom  sectors  of  the  character  can  be 
assigned  directly  to  corresponding  ar¬ 
ray  elements.  For  example,  T  repre¬ 
sents  the  standard  c  with  a  diacritical 
cap  that  looks  like  a  v  with  a  nested  l 


apostrophe.  The  graphics  cap  is  as¬ 
signed  to  the  T$(X)  array  in  line  270, 
while  the  standard  Epson  c  goes  into 
B$(X)  (see  Figure  4,  page  63).  A  col¬ 
umn  can  be  raised  one  row  by  dou¬ 
bling  its  number.  To  raise  an  entire 
character  one  row,  double  the  data 
point  values  for  each  of  the  twelve 
columns. 

The  example  in  Figure  4  illustrates 
vertical  extension  by  setting  a  graph¬ 
ics  character  on  top  of  a  standard  font. 
It  would  work  just  as  well  in  reverse,  if 
that  produced  the  desired  shape,  or 
you  could  use  graphics  in  both  the  top 
and  the  bottom  sectors,  as  I  have  done 
with  the  character  “eth”  (see  Figure 
5,  page  63).  Overstriking,  superscript¬ 
ing,  and  subscripting  provide  addi¬ 
tional  possibilities  accessible  to  either 
the  top  or  bottom  sectors  in  this  flexi¬ 
ble  system;  however,  I  have  only  been 
able  to  achieve  overstriking  on  stan¬ 
dard  characters. 

The  Program 

Because  characters  consume  more 
memory  than  numerals,  text  process¬ 
ing  runs  more  slowly  than  calcula¬ 
tions.  You  can  accelerate  a  program 
by  placing  frequently  used  subrou¬ 
tines  near  the  beginning  and  by  stor¬ 
ing  string  constants  in  variables;  ini¬ 
tialization  routines  are  placed  near 
the  end.  Thus,  program  execution  first 
GOSUBs  to  line  850,  where  it  sets  the 
margins,  length  of  lines,  and  flags, 
then  it  dimensions  the  top  and  bottom 
sector  arrays,  T$(X)  and  B$(X). 

Beginning  with  line  980,  two  nest¬ 
ed  FOR/NEXT  loops  define  the  pho¬ 
netic  set.  The  inside  or  N  loop  begin¬ 
ning  on  line  990  reads  one  data  line  to 
build  a  graphics  string  by  concatena¬ 
tion;  each  data  point  represents  one 
high-density  column.  At  line  1030, 
the  column  descriptor  string  is  pre¬ 
fixed  with  the  graphics  command. 
The  outside  or  M  loop  beginning  with 
line  980  cycles  the  process  through 
all  the  data  lines. 

Commonly  used  sequences  of 
printer  commands  for  emphasis,  su¬ 
perscripting,  and  partial  and  normal 
line  feeds  are  assigned  to  string  vari¬ 
ables  from  line  1190.  This  segment 
also  defines  the  two  overstrike  char¬ 
acters  1  and  x. 

From  line  1320,  a  DOS  subroutine 


catalogs  the  text  diskette,  prompts 
for  the  name  of  the  text  file,  verifies 
the  name,  opens  the  file,  and  opens 
the  printer  port,  PR#1.  Notice  in 
lines  1500  and  1510  that  the  com¬ 
mand  to  the  printer  precedes  the 
PRINT  DS;“READ”;T$  command  to 
the  disk  drive.  Why  this  particular 
order  is  necessary  remains  one  of 
those  mysteries  of  the  silicon  cosmos, 
but  the  program  will  not  work  prop¬ 
erly  if  the  statements  are  reversed. 

After  the  catalog  has  been  listed 
and  the  file  name  entered,  the  pro¬ 
gram  prompts  for  the  desired  line 
spacing  with  a  default  value  of  0  for 
single  spacing.  The  IF/THEN  state¬ 
ment  at  line  1 500  rejects  spacings  less 
than  zero  or  greater  than  nine. 

Before  getting  the  first  character 
from  the  text  file,  the  program  per¬ 
forms  one  more  task:  a  subroutine  be¬ 
ginning  on  line  650  assigns  blanks  to 
all  elements  of  the  T$(X)  and  B$(X) 
arrays.  The  blanks  in  the  top  sector 
remain,  unless  replaced  by  diacriti¬ 
cals  or  partial  fonts,  to  guarantee 
proper  horizontal  alignment  of  dia¬ 
criticals  to  character  base  sectors. 

After  initialization,  execution  drops 
to  line  100  to  get  the  first  character 
(A$)  from  the  text  file  and  to  incre¬ 
ment  the  character  counter  X.  Be¬ 
cause  both  arrays  have  been  initial¬ 
ized  with  blanks,  a  blank  in  AS  does 
not  require  an  assignment;  execution 
is  routed  to  line  90,  which  checks  for 
line  length.  If  AS  is  not  a  blank,  lines 
120  and  130  check  whether  AS  is  a 
flag  for  English  (ENGS  =  “*”)  or  for 
Coeur  d'Alene  (CDAS  =  “  +  ”).  If 
AS  is  a  flag,  the  result  is  stored  in 
FLAGS  and  execution  returns  to  line 
100  to  get  another  character. 

Because  flags  are  not  printed,  the 
counter  is  decremented  by  one.  The 
flag  variable  in  line  1 50  directs  subse¬ 
quent  traffic.  When  FLAGS  is  En¬ 
glish,  AS  is  assigned  directly  to  an  el¬ 
ement  of  the  B$(X)  array  at  line  1 50; 
T$(X)  remains  a  blank,  as  initialized. 
When  the  flag  variable  switches  to 
Coeur  d'Alene,  execution  branches 
to  line  180  where  a  graphics  or  back- 
space-and-overstrike  sequence  corre¬ 
sponding  to  AS  is  assigned  to  B$(X). 

All  paths  to  the  printing  subrou¬ 
tine  pass  through  carriage-return  and 
end-of-line  checks.  If  AS  is  a  carriage 


Dr.  Dobb's  Journal,  April  1985 


62 

297 


63 


Dr.  Dobb  s  Journal.  Anri I  1985 


return,  the  end-of-line  variable  EOL 
is  set  as  the  current  value  of  X.  Be¬ 
cause  no  word  segment  remains  at 
the  end  of  the  line,  the  variable  SEG 
is  set  to  0.  Execution  branches  to  the 
printing  routine  at  line  410. 

If  a  line  contains  more  than  60 
characters,  the  check  at  line  90  causes 
a  branch  to  line  690;  there  a  reverse 
checking  procedure  looks  for  the  last 
blank  in  the  line  to  set  the  end-of-line 
variable.  It  also  determines  the  length 
of  the  trailing  word  segment  to  be  car¬ 
ried  over  to  the  next  line. 

When  execution  passes  to  the 
printing  subroutine  at  line  410, 
PRINT  SPC(LM);  prints  the  left  mar¬ 
gin.  The  FOR/NEXT  loop  beginning 
in  program  line  450  prints  the  top 
sector  diacriticals  contained  in 
T$(X).  Line  460  prints  the  partial 
line  feed  of  four  rows  and  increments 
the  line  feed  counter,  LF.  To  com¬ 
plete  a  line,  it  remains  only  to  print 
the  baseline  characters.  PRINT  BF$; 
resets  the  line  spacing  to  eight  rows. 
For  unknown  reasons,  the  eight-row 
line  feed  cannot  be  printed  with  a  sin¬ 
gle  print  command  as  is  done  for  the 
four-row  line  feed.  The  left  margin  is 
printed  again,  and  the  FOR/NEXT 
loop  beginning  at  program  line  500 
prints  the  baseline. 

The  assignments  made  in  the  loop 
at  line  610  transfer  the  line-final 
word  segment  to  the  beginning  of  the 
following  line  of  text.  The  loop  begin¬ 
ning  with  program  line  660  reinitia¬ 
lizes  the  remainder  of  the  two  line  ar¬ 
rays.  Subsequent  lines  set  X  to  the 
length  of  the  segment  and  reinitialize 
the  counters  FIRST  and  SEG. 

Now  the  program  cycles  back  to 
the  line  feed  check  in  line  80.  More 
arithmetic  is  needed.  The  length  of  a 
page  is  66  lines;  after  the  last  line,  we 
print  two  blank  lines,  a  line  number, 
and  six  more  lines  for  a  total  of  nine 
with  a  bottom  margin  of  six.  The  fig¬ 
ure  nine  represents  the  bottom  mar¬ 
gin  in  the  program.  At  line  780,  the 
assignment  FILL  =  66  —  BM  — 
LINES  says  to  subtract  nine  and  the 
line  count  from  66  lines.  After  the 
command  PRINT  NLF$  returns  the 
line  feed  status  to  normal,  the  fill 
lines  and  bottom  margin  are  printed. 
Line  800  centers  the  page  number, 
which  is  printed  in  line  810.  The  line 


feed  counter  is  reinitialized  to  the 
value  of  the  top  margin  (four),  and 
the  page  counter  PG  is  incremented 
by  one. 

Summary 

The  program  in  the  listing  prints  verti¬ 
cally  extended  phonetic  characters  on 
the  Epson  MX- 100  with  Graftrax 
Plus  by  storing  graphics  commands  in 
two  arrays  for  each  line.  Flags  insert¬ 
ed  in  the  text  file  route  ASCII  codes  to 
English  or  phonetic  program  seg¬ 
ments.  In  the  phonetic  branch,  the 
ASCII  codes  are  replaced  by  graphics, 
overstrike,  or  superscript  code  se¬ 
quences.  A  phonetic  symbol  may  com¬ 
bine  these  modes  with  standard 
characters. 

Formatting  features  include  mar¬ 
gins,  page  numbering,  word  wrap,  and 
variable  line  spacing.  English  and 
phonetic  fonts  mix  freely  within  lines 
of  text.  While  perhaps  not  as  flexible 
as  graphics  font  programs  that  print 
one  row  at  a  time,  a  program  such  as 
this,  which  overlays  whole  lines, 
should  print  more  rapidly — an  impor¬ 
tant  consideration  when  long  texts  are 
involved.  If  you  use  the  same  charac¬ 
ter  set  repeatedly,  the  once-only  effort 
of  programming  data  lines  is  minor 
compared  to  the  advantages  of  using  a 
familiar  word  processor  for  text  entry 
and  formatting. 

Although  these  graphics  routines 
might  be  unnecessary  with  new  print¬ 
ers  that  support  downloading  of 
fonts,  the  system  of  storing  partial 
characters  in  arrays  and  splicing  lines 
by  means  of  partial  overlays  is  adapt¬ 
able  even  to  these  printers.  Because 
the  program  stores  a  phonetic  char¬ 
acter  as  a  string  of  commands,  it  is 
also  adaptable  to  daisy  wheel  print¬ 
ers,  especially  those  that  support  re¬ 
verse  line  feeds.  A  Coeur  d'Alene  or¬ 
thography  similar  to  the  one  created 
on  the  Epson  could  be  printed  on  a 
daisy  wheel  printer  with  only  three 
physically  modified  characters.  The 
program  would  require  only  one  line 
array,  rather  than  the  two  arrays  re¬ 
quired  to  print  vertically  extended 
symbols  on  the  MX- 100. 

Although  many  Chinese  or  Kanji 
characters  would  fit  within  the  verti¬ 
cal  format  of  two  arrays,  large  and 
complex  fonts  of  Chinese,  Japanese, 


or  other  symbol  systems  may  require  I 
splicing  of  multiple  arrays.  You  can 
create  wider  characters  by  using  more 
than  twelve  columns  in  the  graphics 
command  prefix.  For  example,  a 
Kanji  character  could  probably  fit 
within  a  matrix  of  16  dot  rows  by  32 
high-density  columns.  Aside  from  the 
problem  of  graphics  design,  practical 
use  of  fonts  with  hundreds  or  thou¬ 
sands  of  characters  would  require  a 
different  system  of  text  entry  and 
character-to-keyboard  assignment. 

Once  you  understand  the  binary 
method  of  specifying  the  shape  of 
high-density  graphics  columns  in  data 
lines,  adaptation  of  this  program  to 
print  other  two-sectored  fonts  is  rela¬ 
tively  simple.  Owners  of  MX  series 
Epson  printers  with  an  interest  in  spe¬ 
cial  character  sets  may  find  it  essen¬ 
tial.  Modification  of  escape  sequences 
will  make  it  available  to  other  print¬ 
ers.  If  you  have  a  secret  urge  to  print 
Sanskrit,  runic,  or  Elf  script,  the  tools 
are  now  available.  Please  send  me  list¬ 
ings  of  your  data  statements. 

Postscript 

Listing  Two  (page  70)  contains  some 
of  the  more  commonly  used  special 
characters  of  the  International  Pho¬ 
netic  Alphabet  taken  from  Robert 
Albright’s  article  “The  International 
Phonetic  Alphabet”  (International 
Journal  of  American  Linguistics,  vol. 
24,  no.  1,  January  1958).  The  pro¬ 
gram  and  data  lines  can  be  substitut¬ 
ed  as  needed  into  the  sections  of  List¬ 
ing  One  titled  “ Assign  Fonts  To 
Array  Elements ”  and  “ Graphics 
Fonts."  The  choice  of  an  appropriate 
entry  character  for  each  graphics 
character  will  depend  upon  the  orga¬ 
nization  of  th?  user’s  orthography. 
Few  applications  would  require  all 
the  characters.  Avoid  the  numbers  7, 
8,  and  9  in  graphics  data  statements 
for  the  Epson  MX  printers;  they  re¬ 
sult  in  beeps  and  unpredictable 
graphics. 

DDJ 

(Listing  begins  on  page  66) 


Dr.  Dobb’s  Journal,  April  1985 


64 

299 


Magic  Mushroom  for  the  Alice 

Listing  One 


(Text  begins  on  page  58) 


10 

20 

30 

40 

50 

60 

70 

80 

90 

100 

110 

120 

130 

140 

150 

160 

170 

180 

190 

200 

210 

220 

230 

240 

250 

260 

270 

280 

290 

300 

310 

320 

330 

340 

350 

360 

370 

380 

390 


ONERR  GOTO  1630 
TEXT  :  HOME 

REM - 

GOSUB  850:  GOSUB  650:  REM  INITIALIZE 

REM - 

REM  GET  CHARACTER  AND  SET  LANGUAGE  FLAGS 

REM - - - 

IF  LINES  >  =  66  -  BM  THEN  GOSUB  760:  REM  END-OF-PAGE 

IF  X  =  LL  THEN  GOSUB  690:  GOSUB  410:  GOTO  80:  REM  EOL,PRINT,PG 
GET  A$:  X  =  X  +  1 
IF  A$  =  "  "  THEN  90 

IF  A$  =  ENGS  THEN  FLAGS  =  ENGS:  X  =  X  -  1:  GOTO  100 

IF  AS  =  CDA$  THEN  FLAGS  =  CDA$:  X  =  X  -  1:  GOTO  100 

IF  AS  =  CR$  THEN  EOL  =  X:SEG  =  0:  GOSUB  410:  GOTO  80:  REM  PRINT 

IF  FLAGS  =  ENGS  THEN  B$ (X)  =  AS:  GOTO  90 

GOSUB  180:  GOTO  90 

REM - - - 

REM  ASSIGN  FONTS  TO  ARRAY  ELEMENTS 

REM - 

IF  AS  =  "!"  THEN  :T$(X  -  1)  =  G$(7):X  =  X  -  1:  RETURN 

IF  AS  =  "E"  THEN  B$(X)  =  G$(2):  RETURN 

IF  AS  =  CHRS  (101)  THEN  B$ (X)  =  G$  (13) :  RETURN 

IF  A$  =  "W"  THEN  B$ (X)  =  SS$:  RETURN 

IF  A$  =  "H"  THEN  B$(X)  =  G$(l):  RETURN 

IF  AS  =  THEN  T$ (X  -  1)  =  G$(6):X  =  X  -  1:  RETURN 

IF  A$  =  "C"  THEN  B$  (X)  =  CHR$  (99):T$(X)  =  G$(5):  RETURN 

IF  AS  =  "T"  THEN  B$(X)  =  CHR$  (99) : T$ (X)  =  G$(4):  RETURN 

IF  AS  =  "L"  THEN  B$ (X)  =  LT$:  RETURN 

IF  AS  =  "S"  THEN  B$ (X)  =  CHR$  (115) :T$ (X)  =  G$  (5) :  RETURN 

IF  A$  =  "X"  THEN  B$ (X)  =  BX$:  RETURN 

IF  AS  =  "I"  THEN  B$ (X)  =  G$  (3) :  RETURN 

IF  AS  =  "D"  THEN  T$(X)  =  G$(9):B$(X)  =  G$(10):  RETURN 

IF  AS  =  "G"  THEN  B$ (X)  =  "0”  +  CHR$  (8)  +  RETURN 

IF  AS  =  CHRS  (106)  THEN  B$ (X)  =  A$:T$(X)  =  G$ (5) :  RETURN 

IF  AS  =  "N"  THEN  B$ (X)  =  G$  (8) :  RETURN 

IF  AS  =  CHRS  (111)  THEN  B$  (X)  =  G$ (11) :  RETURN 

IF  AS  =  "0"  THEN  B$ (X)  «  jGHRS  (111):  RETURN 

IF  AS  =  "U"  THEN  B$ (X)  =  G$  (12) :  RETURN 

B$ (X)  =  A$:  RETURN 


410  REM  PRINT 

420  REM  - 

430  REM  **  PRINT  TOP  SECTOR 

440  PRINT  SPC(  LM);:  REM  LEFT  MARGIN 

450  FOR  PR  =  1  TO  EOL  -  1:  PRINT  T$(PR);:  NEXT 

460  PRINT  TFS:  REM  4  ROWS  LINE-FEED 

470  PRINT  BFS;  :  REM  8  ROWS  LINE-FEED 

480  REM  **  PRINT  BOTTOM  SECTOR 

490  PRINT  SPC(  LM);:  REM  LEFT  MARGIN 

500  FOR  PR  =  1  TO  EOL  -  1:  PRINT  B$(PR);:  NEXT 

510  PRINT  : LINES  =  LINES  +  1 

520  REM  LINE-SPACING 

530  IF  S  =  0  THEN  GOTO  590 

540  PRINT  NLF$; 

550  FOR  L  =  1  TO  S 

560  PRINT  : LINES  =  LINES  +  1 

570  IF  LINES  >  =  66  -  BM  THEN  GOSUB  760:  GOTO  590:  REM  PAGINATION 

580  NEXT 

590  REM  **  CARRY  OVER  WORD  SEGMENT 
600  IF  SEG  =  0  THEN  650 
610  FOR  FIRST  =  1  TO  SEG 
620  TS (FIRST)  =  T$ (EOL  +  FIRST) 

630  B$ (FIRST)  =  B$ (EOL  +  FIRST) 

640  NEXT  FIRST 

650  REM  **  IN IT  ARRAYS 

660  FOR  X  =  FIRST  TO  LL:T$(X)  =  ”  ”:B$(X)  =  *  *:  NEXT 
670  X  =  SEG: FIRST  =  1:SEG  =  0:  RETURN 

680  REM  - 

690  REM  FIND  END-OF-LINE 

700  REM  - 

710  FOR  EOL  =  LL  TO  1  STEP  -  1 

720  IF  B$ (EOL)  =  "  *  THEN  SBG  =  LL  -  EOL:  RETURN 

730  NEXT  EOL 


(Continued  on  page  68) 


66 


Qnn 


Dr.  Dobb's  Journal,  April  1985 


Magic  Mushroom  for  the  Alice  (Listing  Continued,  text  begins  on  page  58) 
Listing  One 


740  EOL  =  LL  +  1:  RETURN 

750  REM  - 

760  REM  PAGINATION 

770  REM  - 

780  FILL  =  66  -  BM  -  LINES  :  PRINT  NLF$: 

790  IF  FILL  >  0  THEN  FOR  L  =  1  TO  FILL:  PRINT  :  NEXT 
800  PRINT  :  PRINT  :  CENTER  =LM+LL/2-3 

810  PRINT  SPC(  CENTER);"-  ";PG;"  FOR  L  =  1  TO  6:  PRINT  :  NEXT 
820  FOR  L  =  1  TO  TO:  PRINT  :  NEXT 
830  LINES  =  TM:PG  =  PG  +  1:  RETURN 

840  REM  - 

850  REM  INITIALIZE  VARIABLES  AND  DIMENSION  ARRAYS 

860  REM  - 

870  VTAB  10:  HTAB  12:  PRINT  "INITIALIZING" 

880  TO  =  4:BM  =  9:LINES  =  TO:PG  =1:  REM  TOP  AND  BOTTOM  MARG,  PAGE 
890  LM  =  10:RM  =70:  LL  =  RM  -  LM:  REM  MARGINS,  LENGTH  OF  LINE 
900  CDA$  =  "+":  ENG$  =  REM  COEUR  D'ALENE  AND  ENGLISH  FLAGS 

910  FLAGS  =  "+":  REM  INIT  TO  COEUR  D'ALENE 

920  DIM  T$ (LL) :  DIM  B$ (LL) :  REM  TOP-  AND  BOTTOM-OF-LINE  SECTORS 

930  RE>I - 

940  REM  GRAPHICS  FONTS 

950  REM  - 

960  DIM  G$ (13) :  REM  FONTS 

970  GC$  =  CHR$  (27)  +  CHR$  (76)  +  CHR$  (12)  +  CHR$  (0):  REM 
GRAPHICS  COMMAND 

980  FOR  M  =  1  TO  13:  REM  READ  FONTS 
990  FOR  N  =  1  TO  12:  REM  READ  COLUMNS 

1000  READ  G 

1010  G$(M)  =  G$(M)  +  CHRS  (G) 

1020  NEXT  N 

1030  G$(M)  =  GC$  +  G$(M) 

1040  NEXT  M 

1050  DATA  32,96,78,78,72,72,120,48,0,0,0,0:  REM  G$(l)  GLOTTAL  STOP 

1060  DATA  4,46,42,42,42,42,42,62,28,8,0,0:  REM  G$  (2)  SCHWA 

1070  DATA  0,0,0,0,32,62,62,2,2,0,0,0:  REM  G$(3)  IOTA 

1080  DATA  32,48,24,12,118,118,12,24,48,32,0,0:  REM  G$  (4)  EJEC  PALATAL 

1090  DATA  32,48,24,12,6,6,12,24,48,32,0,0:  REM  G$ (5)  PALATAL 

1100  DATA  0,0,0,0,80,80,96,96,0,0,0,0:  REM  G$(6)  EJECTIVE 

1110  DATA  0,0,0,8,24,48,96,64,0,0,0,0:  REM  G$(7)  STRESS 

1120  DATA  62,62,32,32,33,33,33,33,63,30,0,0:  REM  G$  (8)  NASAL  VELAR 

1130  DATA  0,0,8,40,56,24,12,12,8,8,0,0:  REM  G$(9)  ETH  TOP  SECTOR 

1140  DATA  28,62,34,34,34,34,38,62,60,0,0,0:  REMG$(10)  ETH  BOT  SECT 

1150  DATA  16,48,34,34,34,34,34,54,62,28,0,0:  REM  G$ (11)  OPEN  "O" 

1160  DATA  14,14,18,18,32,32,18,18,14,14,0,0:  REMG$(12)  LAX  MD-CNT  VL 
1170  DATA  20,62,42,42,42,42,42,42,0,0,0,0:  REM  G$ (13)  LOW-MID  "E" 

1180  REM  - 

1190  REM  PRINTER  COMMANDS 

1200  REM  - 

1210  CR$  =  CHR$  (13) 

1220  EM$  =  CHRS  (27)  +  CHR$  (69):  REM  EMPHASIS 

1230  OFFS  =  CHRS  (27)  +  CHRS  (70) :  REM  TURN  OFF  SUP/SUBSCRIPT 

1240  TFS  =  CHRS  (27)  +  CHR$  (65)  +  CHR$  (4):  REM  4  DOTS  LINE-FEED 

1250  BF$  =  CHRS  (27)  +  CHRS  (65)  +  CHRS  (8) :  REM  8  DOTS  LINE-FEED 

1260  NLF$  =  CHRS  (27)  +  CHR$  (65)  +  CHRS  (12) :  REM  NORMAL  LN-FEED 

1270  RS$  =  CHRS  (27)  +  CHRS  (64)  :  REM  RESET 

1280  SS$  =  OFFS  +  CHRS  (27)  +  "S"  +  CHRS  (0)  +  CHRS  (119)  + 

CHRS  (27)  +  CHRS  (72)  +  EM$:  REM  SUPERSCRIPT  COMMAND 
1290  BX$  =  CHRS  (27)  +  "-"  +  CHR$  (1)  +  CHRS  (120)  +  CHRS  (27)  + 

"-"  +  CHRS  (0) :  REM  BARRED  "X" 

1300  LT$  =  CHRS  (108)  +  CHRS  (8)  +  CHRS  (126):  REM  "L"  BKSP  TILDA 

1310  REM  - 

1320  REM  DOS 

1330  REM  - 

1340  D$  =  CHRS  (13)  +  CHRS  (4) 

1350  PRINT  D$; "MONI" 

1360  VTAB  10:  HTAB  10:  PRINT  "TURN  ON  PRINTER" 

1370  VTAB  11:  HTAB  10:  PRINT  "INSERT  TEXT  DISKETTE" 

1380  HTAB  10:  PRINT  "PRESS  ANY  KEY  TO  CONTINUE"; 

1390  GET  Q$:  HOME 
1400  PRINT  D$; "CATALOG" 

1410  INVERSE  :  PRINT  "RIGHT  DISKETTE?  Y  OR  N>  "; 

1420  GET  Q$:  IF  Q$  =  "Y"  THEN  PRINT  :  GOTO  1460 

1430  NORMAL  :  HOME  :  VTAB  12:  HTAB  10  (Continued  on  page  70) 


Dr.  Dobb's  Journal,  April  1985 


68 


Magic  Mushroom  for  the  Alice 

Listing  One 

(Listing  Continued,  text  begins  on  page  58) 


1440  PRINT  “INSERT  CORRECT  DISKETTE” 

1450  GOTO  1380 

1460  INPUT  "TEXT  FILE  NAME>  ";T$:  NORMAL 
1470  GOSUB  1550:  REM  GET  LINE-SPACING 
1480  PRINT  D$; "VERIFY  ”;T$ 

1490  PRINT  D$; “OPEN  ”;T$ 

1500  PRINT  D$;"PR#1“; 

1510  PRINT  D$; "READ  ";T$ 

1520  PRINT  EM$; 

1530  FOR  L  =  1  TO  TO  -  2:  PRINT  :  NEXT  :  RETURN 

1540  REM  - 

1550  REM  LINE-SPACING 

1560  REM  - 

1570  HOME  :  VTAB  8:  HTAB  10:  PRINT  "DEFAULT  SPACING>  0.  RETURN" 

1580  PRINT  :  HTAB  18:  PRINT  "SPACING>  ";:  INPUT  S$ 

1590  IF  S$  =  ""  THEN  S  =  0:  RETURN 

1600  IF  ASC  (S$)  <  48  OR  ASC  (S$)  >  57  THEN  1550 

1610  S  =  VAL  (S$) :  RETURN 

1620  REM  - 

1630  REM  TERMINATION 

1640  REM  - 

1650  IF  PEEK  (222)  =  5  THEN  EOL  =  X  +  1:  GOSUB  410:  GOSUB  760: 

PRINT  RS$:  PRINT  D$;"PR#0":  PRINT  "END  OF  FILE":  END 
1660  IF  PEEK  (222)  =  6  THEN  :  HCME  :  VTAB  8:  HTAB  12:  INVERSE  : 

PRINT  "FILE  NOT  FOUND":  FOR  T  =  1  TO  2000:  NEXT  :  NORMAL  : 
GOSUB  1380:  GOSUB  650:  GOTO  60 
1670  PRINT  RS$:  PRINT  D$;"PR#0" 

1680  PRINT  "DOS  ERROR  STATUS:  ";  PEEK  (222):  END 


1690 

1700 

1710 

1720 

1730 

1740 

1750 

1760 

1770 

1780 

1790 

1800 

1810 

1820 

1830 

1840 

1850 

1860 

1870 

1880 

1890 


REM - 

REM  PHONETIC  COEUR  D'ALENE 

REM  COPYWRITE  ©  ALL  RIGHTS  RESERVED 

REM 

REM  GARY  B.  PALMER,  ASSOCIATE  PROFESSOR 

REM 

REM  DEPT  OF  ANTHROPOLOGY  AND  ETHNIC  STUDIES 

REM  UNIVERSITY  OF  NEVADA,  LAS  VEGAS,  NV  89154 

REM 

REM  DECEMBER  31,  1983 

REM - 

REM  PRINTS  ENGLISH  AND  PHONETIC  COEUR  D'ALENE. 

REM  MIXES  CHARACTER  SETS  IN  SAME  LINE. 

REM  TEXT  CODE  CHARS:  "+"  =  CDA,  =  ENG. 

REM  CREATES  VERTICALLY  EXTENDED  FONTS  BY  SPLICING  TOO  ARRAYS. 
REM  DOES  WORD-WRAP,  PAGINATION,  AND  VARIABLE  SPACING. 

REM  ALL  LOWER  CASE  CHARS  EXCEPT  "J"  AND  "0"  PASSED  TO  PRINTER. 

REM  UPPER  CASE  A,B,F,J,K,M,P,Q,V,Y,Z 

REM  SPECIAL  CHARACTERS  C,D,E,G ,H, I ,L,N,0,S,T,U,W,X, ! , ' 

REM  REQUIRES  APPLE  ] [  PLUS  AND  EPSON  MX-100. 

REM  MARGINS:  4  TOP,  6  BOTTOM,  10  LEFT,  70  RIGHT 


End  Listing  One 


Listing  Two 


t  IF  A*  =  "any  char"  THEN  T*(X>  =  G*(M>:  B*(X)  = 

G*(M+1):  RETURN 

DATA  32,32,127,127,32,32,0,0,0,0,0,0:  REM  G*<M> 

DATA  0,0,12,14,2,2,2,0,0,0,0,0:  REM  G*(M+1) 

d  IF  A*  =  "any  char"  THEN  T*(X)  =  G*<M):  B*(X)  = 

G*(M+1):  RETURN 

DATA  0,2,2,2,2,2,2,126,126,0,0,0:  REM  G*(M) 

DATA  28,  30, 2, 2, 2, 2, 2, 30, 31, 1,  1,0:  REM  G*(M+1) 

i  IF  A*  =  "any  char"  THEN  T*<X>  =  G*(M>:  B*(X)  = 

G* (M+l ) :  RETURN 

DATA  0,2,2,62,62,0,0,0,0,0,0,0:  REM  G* (M) 

DATA  28,28,4,28,24,0,0,0,0,0,0,0:  REM  G*<M+1) 

(Continued  on  page  72/ 


70 


Dr.  Dobb's  Journal.  April  1985 


Magic  Mushroom  for  the  Alice 

Listing  Two 


(Listing  Continued,  text  begins  on  page  58) 


?  IF  A*  =  "any  char"  THEN  BS(X)  =  GS(M):  RETURN 

DATA  32,96, 78,78, 72, 72, 120, 48, 0,0, 0,0:  REM  GS(M> 
(See  also.  Listing  1,  lines  240,  1050)  ; 

n,  IF  A$  =  "any  char"  THEN  BS(X>  =  G*<M>:  RETURN 

DATA  28,28,32,32,28,28,33,33,31,30,0,0:  REM  GS(M) 

r\  IF  A*  =  "any  char"  THEN  B*(X)  =  GS(M):  RETURN 

DATA  62,62,32,32,32,32,62,31,1,1,3,0:  REM  6S(M> 

n  IF  A*  =  "any  char"  THEN  BS(X>  =  GS(M):  RETURN 

DATA  1,1,63,62,32,32,32,32,62,30,0,0:  REM  G*(M) 

O  IF  AS  =  “any  char"  THEN  BS(X)  =  GS(M>:  RETURN 

DATA  62,62,32,32,33,33,33,33,63,30,0,0:  REM  GS(M) 
(See  also  Listing  1,  lines  350,  1120) 

1-  IF  A*  =  "any  char"  THEN  BS(X)  =  CHR*(108)  +  CHR*(8> 

+  CHRS ( 126) 

(See  also.  Listing  1,  lines  280,  1300) 

h  IF  AS  =  “any  char"  THEN  TS(X)  =  GS(M>:  BS  <  X )  = 

GS (M+l ) :  RETURN 

DATA  30,30,2,2,2,2,2,0,0,0,0,0:  REM  G*(M) 

DATA  30,30,0,0,0,0,25,25,15,6,0.0:  REM  GS(M+1> 

l  IF  AS  =  "any  char"  THEN  TS(X)  =  GS(M>:  B*(X)  = 

GS (M+l ) :  RETURN 

DATA  64,64,127,127,0,0,0,0,0,0,0,0:  REM  GS(M) 

DATA  0,0,30,30,2,2,0,0,0,0,0,0:  REM  GS(M+1) 

X  IF  AS  =  "any  char"  THEN  BS(X>  =  GS(M):  RETURN 

DATA  2,6,12,24,56,108,70,66,0,0,0,0:  REM  GS(M> 

t  IF  AS  =  "any  char”  THEN  BS(X)  =  GS(M):  RETURN 

DATA  2,2,30,62,34,34,50,16,0,0,0,0:  REM  GS(M> 

C  IF  AS  =  "any  char"  THEN  TS(X)  =  GS(M>:  BS(X>  = 

GS (M+l > :  RETURN 

DATA  63,63,16,48,32,32,32,32,32,0,0,0:  REM  GS(M> 
DATA  14,15,1,1,3,3,0,0,0,0,0,0:  REM  GS(M+1) 

$  IF  AS  =  "any  char"  THEN  TS(X)  =  GS(M>:  BS(X)  = 

GS(M+1):  RETURN 

DATA  6,6,40,40,62,62,40,40,6,6,0,0:  REM  GS(M> 

DATA  16,16,10,10,30,30,10,10,16,16,0,0:  REMGS(M+1> 

P  IF  AS  =  "any  char"  THEN  TS(X)  =  GS(M>:  BS(X)  « 

GS(M+1):  RETURN 

DATA  30,30,16,16,16,18,30,12,0,0,0,0:  REM  GS(M> 

DATA  31,31,5,4,4,4, 28„ 24, 0,0, 0,0:  REM  GS(M+1) 

8  IF  AS  »  "any  char"  THEN  TS(X)  =  GS(M):  BS(X)  = 

GS<M+1>:  RETURN 

DATA  31,63,34,34,34,34,34,34,63,31,0,0:  REM  GS(M) 
DATA  12,14,2,2,2,2,2,2,14,12,0,0:  REM  GS ( H+ 1 ) 

d  IF  AS  =  "any  char"  THEN  TS ( X )  =  GS (M> :  BS ( X )  - 

GS (M+l ) :  RETURN 

DATA  0,0,8,40,56,24,12,12,8,8,0,0:  REM  GS(M) 

DATA  28,62,34,34,34,34,38,62,60,0,0,0:  REM  GS(M+1) 

j  IF  AS  =  "any  char"  THEN  BS(X)  =  GS(M>:  RETURN 

DATA  2,2,2,2,2,6,4,62,62,0,0,0:  REM  GS (M) 

f  IF  AS  =  "any  char"  THEN  TS(X)  =  GS(M>:  BS(X>  = 

GS(M+1):  RETURN 

DATA  12,12,10,10,10,10,12,4,0,0,0,0:  REMGS(M) 

DATA  62,63,41,41,41,40,56,16,0,0,0,0:  REM  GS(M+1) 

t  IF  AS  =  "any  char"  THEN  BS(X)  =  GS(M):  RETURN 

DATA  68,76,92,116,102,71,1,1,1,0,0,0:  REM  GS (M> 

/  IF  AS  =  "any  char"  THEN  TS(X)  =  GS(M>:  B(X>  = 


Dr.  Dnbb's  Journal,  April  1985 


72 

Finn 


G* (M+l ) :  RETURN 

DATA  0,0,0,6,14,24,16,16,0,0,0,0:  REM  G*(M> 

DATA  1,1,3,30,28,0,0,0,0,0,0,0:  REM  E*(«+l) 

3  IF  A*  =  "any  char"  THEN  B*<X>  =  G*<M>:  RETURN 

DATA  108,108,66,66,82,114,108,76,0,0,0,0:  REM  G*(M> 

C  IF  A*  =  "any  char"  THEN  B*(X>  =  G*(M>:  RETURN 

DAfA  1,29,63,34,34,34,34,34,34,0,0,0:  REM  G*(M) 

X  IF  A*  =  "any  char"  THEN  B*CX>  =  G*(M):  RETURN 

DATA  1,1,33,35,38,46,58,50,34,2,0,0:  REM  GI(M) 

C  IF  A*  =  "any  char"  THEN  B*(X>  =  G*(M):  RETURN 

DATA  56,125,69,69,71,71,68,68,0,0,0,0:  REM  G*(M) 

X  IF  A*  =  "any  char"  THEN  T*(X)  «  G*(M):  B*<X)  ■* 

G* (M+l ) :  RETURN 

DATA  32,48,25,15,6,6,15,25,48,32,0,0:  REM  B*(M) 

DATA  0,0,12,14,2,2,28,12,0,0,0,0:  REM  G*<M+1> 

*.  IF  A*  =  "any  char"  THEN  B*<X>  =  G*(M):  RETURN 

DATA  32,32,126,126,40,40,40,40,14,14,0,0:  REM  G*(M) 

<?  IF  A*  =  "any  char"  THEN  B*<X)  =  G*(M):  RETURN 

DATA  48,120,72,72,78,78,96,32,0,0,0,0:  REM  G*(M) 

fi  IF  A*  =  "any  char"  THEN  B*(X)  -  G*(M):  RETURN 

DATA  62,126,72,72,72,104,46,14,0,0,0,0:  REM  G*<M> 

1  IF  A*  =  "any  char"  THEN  B*<X>  »  G*(M):  RETURN 

DATA  112,112,16,16,16,16,127,127,1,1,0,0:  REM  G*<M> 

V  IF  A*  =  "any  char"  THEN  B*(X)  =  G*(M):  RETURN 

DATA  56, 60,6, 2, 34,38, 60, 56, O, O, 0j  O:  REM  G*(M) 

B  IF  A*  =  "any  char"  THEN  B*(X)  =  G*(M):  RETURN 

DATA  126, 126, 18, 18, 18, 18, 18, 126, 108,0,0,0: 

REM  G*<M> 

t  IF  A*  =  "any  char"  THEN  T*(X)  =  G*(M):  B*(X)  =■ 

G* (M+l > :  RETURN 

DATA  0,0,24,24,0,0,0,0,0,0,0,0:  REM  G*(M) 

DATA  16,18,62,62,18,16,0,0,0,0,0,0:  REMG*(M+1> 

All  the  remaining  vowels  are  produced  entirely  within 
the  bottom  line,  as  in  the  barred-u  character  which 
■fol  1  ows: 


V 

IF  A* 
DATA 

A 

DATA 

p 

DATA 

o 

DATA 

D 

DATA 

0 

D^TA 

* 

DATA 

a 

DATA 

DATA 

Ul 

DATA 

» 

DATA 

A 

DATA 

a 

DATA 

=  "any  char"  THEN  B*(X)  =  G*(M):  RETURN 
16,16,60,62,18,18,18,62,60,16,16,0:  REM  G*(M) 

28,29,35,38,46,58,50,98,92,28,0,0 

28,28,34,34,62,62,42,42,46,44,0,0 


16,48,34,34,34,34,34,54,62,28,0,0 
62,  t>2,  20,  54,34,34,34,34,62,28,0,0 
20,62,42,42,42,42,42,42,0,0,0,0 
36, 46, 42, 42,42,62,62, 42, 42 j  46, 44,0 
4,46,42,42,42,42,42,62,28,8,0,0 
32,60,30,42,42,42,42,42,58, 18,0,0 
60,60,2,2,60,60,2,2,60,62,2,0 


44,62, 18,18,62,44,0,0,0,0,0,0 


14,14,18,18,32,32,18,18,14,14,0,0 


28,62,34,34,34,34,54,20,62,62,0,0 


Let's  Mouse  Around 

A  Turbo  Pascal/Microsoft  Mouse  Sketching  Program  for  IBM  PCs 


by  Vincent  and 
Ronald  G.  Parsons 


The  program  in  the  listing  (page 
75)  is  fun  to  use  and  illustrates 
techniques  for  using  the  Micro¬ 
soft  Mouse  with  Turbo  Pascal.  The 
program  requires  the  IBM  Color/ 
Graphics  adapter,  a  Microsoft 
Mouse,  and  Turbo  Pascal. 

By  moving  the  mouse  around,  any¬ 
one  can  be  an  artist.  When  the  pro¬ 
gram  starts,  an  X-shaped  cursor  ap¬ 
pears  in  the  middle  of  a  blue  screen.  A 
line  is  drawn  on  the  screen  as  the 
mouse  is  moved  with  either  mouse 
button  depressed. 

Below  the  drawing  area  is  a  set  of 
options  for  changing  colors,  erasing 
portions  of  the  screen,  clearing  the 
screen,  and  quitting  the  program.  If 
the  mouse  cursor  is  placed  between 
the  [  ]  after  an  option,  the  option  is 
executed.  Choosing  “Back”  changes 
the  background  color,  “Palette”  se- 


the  program  to  redefine  the  mouse 
cursor  shape  and  the  position  of  the 
“hot  spot”  of  the  cursor.  The  hot  spot 
is  the  position  relative  to  the  upper 
left  corner  of  the  16  X  16  pixel  cur¬ 
sor  block  where  the  position  of 
the  cursor  is  defined.  The  call  to  this 
function  and  function  12  (Set  User- 
Defined  Subroutine  Input  Mask)  re¬ 
quires  slightly  different  parameters 
than  calls  to  the  other  mouse  func¬ 
tions.  The  fourth  parameter  of  func¬ 
tions  9  and  1 2  is  an  offset  of  an  array 
and  subroutine,  respectively.  The 
segment  address  of  this  array  or  sub¬ 
routine  must  be  passed  to  the  mouse 
interrupt  routine  in  register  ES.  We 
elected  to  make  the  segment  address 
a  global  variable  to  keep  the  proce¬ 
dure  parameters  for  the  rest  of  the 
functions  unchanged. 

If  you  want  to  define  a  new  cursor 


A  mouse  makes  drawing  with  a  computer  easy. 
Here's  a  program  that  makes  using  a  mouse  easy. 


lects  another  palette  of  colors  for 
drawing,  and  “Color”  changes  the  col¬ 
or  of  the  lines  to  be  drawn.  The  “Eras¬ 
er”  option  changes  the  cursor  to  a 
square  and  erases  the  portion  of  the 
drawing  area  in  the  square  when  a 
button  is  down.  The  “Clear”  option 
clears  the  drawing  area  and  “Quit” 
ends  the  drawing  session. 

Some  of  the  more  interesting  tech¬ 
niques  for  using  the  Microsoft  Mouse 
with  Turbo  Pascal  are  discussed  be¬ 
low. 

The  Microsoft  Mouse  function  9 
(Set  Graphics  Cursor  Block)  allows 


Vincent  and  Ronald  G.  Parsons, 
9001  Laurel  Grove  Dr.,  Austin,  TX 
78758. 


shape,  a  screen  and  cursor  mask  must 
be  defined  and  placed  in  two  contigu¬ 
ous  arrays  in  storage  and  a  pointer  to 
these  masks  passed  to  function  9. 
Several  different  cursor  shapes  are 
defined  in  the  Microsoft  Mouse  man¬ 
ual.  The  examples  are  given  for  BA¬ 
SIC.  However,  the  storage  order  of 
arrays  is  different  in  Turbo  Pascal 
than  it  is  in  BASIC.  Thus,  the  two- 
dimensional  array  defined  in  the  list¬ 
ing  has  the  parameters  in  reverse  or¬ 
der  from  the  equivalent  BASIC  arrays 
in  the  mouse  document.  The  horizon¬ 
tal  and  vertical  hot  spot  locations  are 
passed  in  m3  and  m4,  respectively. 
The  hot  spot  can  be  within  the  range 
of  — 16  to  16  from  the  upper  left  cor¬ 
ner  of  the  cursor  block. 

When  you  use  medium  resolution 


Dr.  Dobb’s  Journal,  April  1985 


74 


graphics  mode  with  Turbo  Pascal 
and  the  mouse,  the  x  coordinate  of 
the  mouse  must  be  divided  by  two  be¬ 
fore  the  Turbo  Pascal  Plot  or  Draw 
procedures  can  use  it.  Thus,  the  x  co¬ 
ordinate  is  calculated  as 

x  :=  m3  div  2; 

The  cursor  must  be  hidden  before 
you  modify  any  portion  of  the  screen 
containing  the  cursor.  Mouse  func¬ 
tion  2  (Hide  Cursor)  performs  this 
duty.  If  this  is  not  done,  the  portion  of 
the  modified  screen  behind  the  cursor 
block  will  disappear  when  the  cursor 
is  moved.  Functions  1  and  2  must  be 
used  in  pairs  because  these  functions 
increment  and  decrement  a  cursor 
flag  and  the  cursor  is  displayed  only 
when  the  cursor  flag  is  zero.  The  ini¬ 
tial  value  of  the  flag  is  -1 . 

Turbo  Pascal  permits  fast  develop¬ 
ment  of  programs,  and  the  Microsoft 
Mouse  makes  the  programs  easier  to 
use.  DD| 

Reader  Ballot 

Vote  for  your  favorite  feature/article. 

Circle  Reader  Service  No.  1 96. 

Let's  Mouse  Around  Listing  (Listing  Continued,  text  begins  on  page  74) 

l  Mouse_sketch  routine  for  Turbo  Pascal  and  Microsoft  Mouse  } 

{  Uses  redefined  cursor  and  Medium  resolution  graphics  in  color  ) 

{  by  Ronald  G.  Parsons  and  Vincent  L.  B.  Parsons  ) 

Program  Mouse_sketch; 

type  regpack  =  record 

ax, bx,cx,dx, bp, si ,di , ds.es, flags  :  integer; 
end; 

cursordef  =  arrayfO. . 1 ,0. . 15]  of  integer; 


var  oldx,oldy,x,y  :  integer; 

1,esreg,ml,m2,m3,m4  :  integer; 
cursorl ,cursor2  :  cursordef; 
background, palettenum, color  ;  integer; 
exit, eraser  :  Boolean; 


procedure  mouse  (var  ml , m2 , m3 ,m4: integer) ;  {Microsoft  Mouse  interrupt  routine) 

var  regset  :  regpack; 

begin 

with  regset  do 
begi  n 


ax  :«  ml 


(Set  registers  for  mouse  interrupt) 


bx  :=  m2 


cx  :=  m3 


dx  :»  m4 


If  ( ( ml *=9 )  or  (ml  =12 ) )  then  es:=esreg;  (Set  ES  for  functions  9  and  12) 
end; 

intr( 51  .regset);  (Perform  mouse  interrupt  51) 

with  regset  do 

begin 

ml  :=  ax;  (Set  parameters  on  return) 

m2  :=  bx; 
m3  :«  cx; 
ro4  :=  dx; 


(Continued  on  next  page ) 


75 


Dr.  Dobb  s  Journal.  April  1985 


Let's  Mouse  Around  Listing 

(Listing  Continued,  text  begins  on  page  74) 


{Screen  mask)  {Diagonal  Cross) 


end; 

end; 

procedure  initialize; 

begin 

cursorl[0,  0]:=$07e0 
cursorlCO,  1]:=$0180 
cursorlCO,  2 3 : =$0000 
cursorlCO,  3]:=$c003 
cursorl[0,  4]:=$f00f 
cursorlCO,  5]:=$c003 
cursorl[0,  6]: =$0000 
cursorlCO,  7]: =$0180 
cursorlCO,  8]:=$07e0 
cursorlCO,  9]:=$ ffff 
cursorl[0,10]:=$ffff 
cursorlCO.il ]:=$ffff 
cursorlCO, 12]:=$ffff 
cursorlCO, 13]:*$ ffff 
cursorlCO, 14]:*$ffff 
cursorlCO, 15]:=$ffff 

cursorlCl,  0]:=$0000 
cursorlCl,  l]:=$700e 
cursorlCl,  2]:=$lc38 
cursorlCl,  3]:  =$0660 
cursorlCl,  4]:=$03c0 
cursorlCl,  5]: =$0660 
cursorlCl,  6] : =$1 c38 
cursorlCl,  7]:=$700e 
cursorlCl,  8]: =$0000 
cursorlCl,  9]: =$0000 
cursorlCl. 10]:=$0000 
cursorlCl.il]:  =$0000 
cursorl  [1,12]:  =$0000 
cursorlCl  ,13]:  =$0000 
cursorlCl, 14]:=$0000 
cursorlCl , 15 ] : =$0000 


cursor2[0,  0]:=$0000 
cursor2[0,  1]:=$0000 
cursor2[0,  2]: =$0000 
cursor2[0,  3]:=$lff8 
cursor2[0,  4]:=$lff8 
cursor2[0,  5]:=$lff8 
cursor2C0,  6]:=$lff8 
cursor2[0,  7]:=$0000 
cursor2[0,  8]: =$0000 
cursor2[0,  9]:=$0000 
cursor2[0,10]:=$ffff 
cur sor2 [0,11 ] : =$  f f f f 
cursor2 [0,12]:=$ ffff 
cursor2[Q,13]:=$ffff 
cursor2[0,14]:=$ffff 
cursor2 [0,15]:=$ ffff 

cursor2[l,  0]:=$0000 
cursor2[l,  l]:=$7ffe 
cursor2[l,  2]: =$4002 
cursor2[l,  3]:=$4002 
cursor2Cl,  4]:=$4002 
cursor2[l,  5]:=$4002 
cursor2[l,  6]:=$4002 
cursor2[l,  7]:=$4002 
cursor2[l,  8]:=$7ffe 
cursor2[l,  9]:=$0000 
cur sor2[ 1,10]: =$0000 
cursor2[l ,11 ]:=$0000 
Cursor2[l  ,12]:  =$0000 
cursor2[l ,13]:=$0000 


{Cursor  mask) 


{Screen  mask)  {Eraser) 


{Cursor  mask) 


( Continued  on  page  79) 

76 


Dr.  Dobb  s  Journal,  April  1985 


o  r\~7 


Let's  Mouse  Around  Listing  (Listing  Continued,  text  begins  on  page  74) 


cursor2  [1,14]:  =$0000; 
cursor2[l ,15]: =$0000; 


background:=Bl ue; 
palettenum: =2; 
color:=3; 

GraphColorMode; 

GraphBackground( background);  (Medium  resolution  graphics  -  Blue  background) 

Palette! palettenum); 

era  ser:  =  false; 

exit:  =  false; 

ml:=0; 

mouse(ml,m2,m3,m4);  (Reset  mouse) 

ml : *1 ; 

mousetml , m2 , m3 ,m4 ) ;  (Show  cursor) 

ml: =9; 

m2:=7;  (Horizontal  hot  spot) 

m3:=4;  (Vertical  hot  spot) 


m4:=ofs(cursorl[0,0]); 

esreg:=seg(cursorl[0,0]);  (Set  pointer  to  cursor  definition) 
mouse(ml,m2,m3,m4);  (Set  graphics  cursor) 

oldx:=160;  (Center  of  screen) 

oldy:=100; 


gotoxy(l ,1 ); 

writer  Let' ' s  Mouse  Around  '); 

gotoxy(l,23); 

writeCBack  []  Palette  []  Color  []  Eraser  []'); 
gotoxyfl  ,24); 

write(  'Quit  []  Clear  []' ); 
draw(0,10,319,10,3); 
draw{319, 10,319, 169,3); 
draw(319, 169, 0,169,3); 
draw(0,169,0,10,3); 

GraphWindowfl, 0,317, 168);  (Limit  drawing  window) 
end;  (initialize) 


procedure  changeback; 
begin 

background: = background* 1 ; 
if  background>15  then  background:=0; 

GraphBackground( background) ; 
del  ay (300); 
end; 

procedure  changepalet.te; 
begin 

palettenum:=palettenum+l; 
if  pa1ettenum>3  then  palettenum:=0; 
palette!  palettenum) ; 
del  ay  (300); 
end; 

procedure  changecolor; 
begin 

color:=color+l; 
if  color>3  then  color:=0; 
del  ay (300); 
end; 

procedure  makeeraser; 
begin 

if  eraser  then 
begin 
ml:  =9; 

m2:=7;  (Horizontal  hot  spot) 

m3:=4;  (Vertical  hot  spot) 

m4 : =of s ( cursorl [0,0]); 

esreg:=seg(cursorl[0,0]) ;  (Set  pointer  to  cursor  definition) 
mousetml, m2, m3, m4 );  (Set  graphics  cursor) 

end 
else 
begin 


78 


Dr.  Dobb’s  Journal,  April  1985 


ml: =9; 

m2:»l;  {Horizontal  hot  spot) 

m3:=l;  {Vertical  hot  spot) 

m4:=ofs(cursor2[0,0j); 

esreg:=seg(cursor2[0,0]);  {Set  pointer  to  cursor  definition) 
mouse(ml,m2,m3,m4);  {Set  graphics  cursor) 

end; 

eraser:«not  eraser; 
del  ay  (300); 
end; 

procedure  checkoption; 
begin 

if  m2  <>  0  then 
begin 

if  ((m4  >  175)  and  (m4  <  183))  then 
begin 

if  ((m3  >  81)  and  (m3  <  103))  then  changeback; 

if  ((m3  >  273)  and  (m3  <  295))  then  changepalette; 

if  ((m3  >  433)  and  (m3  <  455))  then  changecolor; 

if  ((m3  >  609)  and  (m3  <  631))  then  makeeraser; 

end; 

if  ( (ro4  >  183)  and  (m4  <  191))  then 
begin 

if  ((m3  >  81)  and  (m3  <  103))  then  exit:*true; 

if  ((m3  >  273)  and  (m3  <  295))  then  initialize; 

end; 
end; 
end; 

begin 

initialize; 

while  not  (keypressed  or  exit)  do  {Exit  when  any  key  is  pressed) 
begin 
ml: =2; 

mouse(ml,m2,m3,m4);  (Hide  cursor) 

ml: *3; 

mouse(ml,m2,m3,m4);  {Get  mouse  position) 

{  if  m2  <>  0  then  begin  gotoxy(l ,1 );wri te(m3, '  ',m4,'  ');  end;  ) 

if  ( ( m4 <1 1 )  or  (m4>175))  then  checkoption  else 
begin 

x:=m3  div  2;  (In  medium  resolution,  x  coord,  must  be  divided  by  2) 

y:=m4; 

if  m2  <>  0  then 
begin 

if  eraser  then 
begin 

for  i :  =0  to  7  do  drawl  x+i  ,y,x+i  ,y+8,0); 
end 
else 
begin 

draw(oldx,oldy,x,y, color);  (Draw  when  mouse  button  is  pressed) 
end; 
end; 
end; 

oldx:«x; 

oldy:=y;  {Update  old  x/y  values) 

ml:»l; 

mouse! ml, m2, m3, m4);  {Show  cursor) 

end; 

TextMode(c80); 

end .  End  Listing 


Dr.  Dobb's  Journal,  April  1985 


79 

309 


Turbo  Toolbox,  Version  1 .0 

Company:  Borland  International, 
4113  Scotts  Valley  Drive, 
Scotts  Valley,  CA  95066 
Operating  System:  CP/M-80,  CP/ 
M-86,  MSDOS,  PCDOS 
Price:  $49.95 

Circle  Reader  Service  No.  143 
Reviewed  by  Karl  R.  Kachigan 

The  Turbo  Toolbox  by  Borland  for 
users  of  Turbo  Pascal  can  reduce  de¬ 
velopment  time  and  improve  the  por¬ 
tability  of  your  code.  It  includes  three 
tools:  Turbo-Access,  Turbo-Sort,  and 
GINST.  Turbo-Access  is  a  data-filing 
system  using  B-trees  that  allows  you 
rapid  access  to  large  amounts  of  data. 
Turbo-Sort  is  a  data-sorting  system 
that  uses  the  Quicksort  algorithm. 
GINST  generates  a  general  terminal 
installation  program  much  like 
TINST  for  Turbo  Pascal.  The  only  re¬ 
striction  on  the  tools  is  that  they  are 
compatible  only  with  Turbo  Pascal, 
version  2.0  (or  higher). 

After  using  all  the  tools,  I  can  com¬ 
fortably  say  that  any  user  of  Turbo 
Pascal  should  be  able  to  use  these 
tools  and  find  them  helpful.  At  first,  I 
expected  the  Turbo-Access  system  to 
be  too  complex  for  all  but  the  most 
advanced  programmers,  but  I  now 
believe  that  even  novice  program¬ 
mers  can  manage  it  once  they  go 
through  the  sample  programs. 
GINST  will  be  a  great  help  to  soft¬ 
ware  developers  who  would  like  their 
programs  to  use  Turbo’s  screen  con¬ 
trol  features  on  any  terminal. 

The  Toolbox  is  one  disk  containing 
16  files,  10  of  which  are  “toolbox” 
files  and  6  sample  programs  or  data 
files.  Source  code  is  provided  for  all 
tools  and  programs  except  the 
GINST-related  files.  GINST  is  the 
only  compiled  tool,  probably  because 
it  modifies  the  Turbo  Pascal  runtime 
code.  The  102-page  typeset  manual  is 


much  better  than  the  Turbo  Pascal, 
version  2.0  manuals — I  found  fewer 
typos  and  errors. 

At  $49.95,  the  Turbo  Toolbox  is 
less  of  a  bargain  than  Turbo  Pascal, 
but  once  you  use  one  of  the  tools,  you 
probably  will  find  it  worth  the  price. 
It  is  available  on  the  same  operating 
systems  as  Turbo  Pascal:  CP/M-80, 
CP/M-86,  MSDOS,  and  PCDOS.  In 
fact,  I  suspect  that  there  is  little  or  no 
difference  between  the  MSDOS  and 
PCDOS  versions;  they  ran  comfort¬ 
ably  on  both  my  HP- 150  and  IBM 
PC.  I  didn’t  test  the  CP/M  versions, 
but  because  the  manual  makes  no 
distinctions  in  the  Toolbox’s  perfor¬ 
mance  on  different  systems,  I  assume 
they  function  much  the  same  as  the 
MSDOS/PCDOS  version. 

These  tools  appear  to  be  useful  for 
both  the  hobbyist  and  the  software 
distributor.  Borland  graciously  has  al¬ 
lowed  developers  to  distribute  and/or 
sell  programs  whose  object  code  in¬ 
cludes  source  code  from  Turbo-Ac¬ 
cess,  Turbo-Sort,  and  the  terminal  in¬ 
stallation  program  generated  by 
GINST  and  to  paraphrase  the  installa¬ 
tion  section  from  the  Turbo  Pascal 
reference  manual  when  writing  their 
documentation. 

Turbo-Access 

I’m  not  sure  how  many  times  I’ve  had 
to  create  a  data  base  to  handle  a  lot  of 
information,  then  had  to  add  the  nec¬ 
essary  routines  to  find,  add,  delete, 
edit,  and  list  the  elements.  Turbo-Ac¬ 
cess,  a  data  base  system  that  you  can 
incorporate  into  your  programs,  is 
composed  of  four  source  files:  AC- 
CESS.BOX,  GETKEY. BOX,  ADD- 
KEY.BOX,  and  DELKEY.BOX.  These 
let  you  quickly  create  a  good-sized, 
custom  data  base. 

My  initial  fears  that  only  an  expe¬ 
rienced  programmer  need  delve  into 
Turbo-Access  arose  when  phrases 


like  B-trees,  index  files,  keys,  pages, 
and  tree  height  popped  up  in  the  tu¬ 
torial  section.  The  manual,  however, 
explicitly  states  that  you  don’t  need 
to  know  how  B-trees  work — just  how 
to  use  them.  The  tutorial  gives  a  nice 
overview  on  B-trees,  an  approach 
that  uses  keys  to  find  elements  in  its 
data  base. 

Turbo-Access  is  really  an  indexed 
sequential  access  method  (ISAM) 
data  base.  For  those  unfamiliar  with 
B-trees  or  ISAMs,  these  methods  or¬ 
ganize  data  in  a  manner  that  sepa¬ 
rately  stores  a  unique  key  (like  your 
street  address)  so  that  accessing  for  a 
specific  element  requires  only  a  few 
searches  of  the  key  directory.  This  is 
a  fast,  efficient  way  to  access  an  item 
from  a  large  data  base.  In  addition  to 
your  data  file,  you  generate  an  index 
file  that  includes  the  keys  organized 
in  whatever  manner  you  wish  to  treat 
the  data.  For  example,  if  you  want  to 
create  a  mailing  list,  you  could  use  an 
alphabetical  list  of  the  last  names  as 
your  key.  Or  you  may  want  to  catego¬ 
rize  these  addresses  by  state  or  zip 
code — these  too  could  be  keys. 

Just  what  is  involved  in  using  Tur¬ 
bo-Access?  Of  the  four  separate 
source  files,  you  always  must  include 
ACCESS.  BOX;  it  contains  all  the  pro¬ 
cedures  and  data  structures  to  set  up 
and  maintain  the  data  files  and  index 
files.  You  may  include  or  omit  the 
other  three  files  as  long  as  ACCESS- 
.BOX  comes  first.  GETKEY.BOX  pro¬ 
vides  the  searching  routines  for  the 
keys  in  the  index  files  and  the  data  in 
the  data  files.  ADDKEY.BOX  lets  you 
add  keys  and  data  to  the  index/data 
files,  and  DELKEY.BOX  lets  you  de¬ 
lete  keys  and  data  from  these  files. 

To  start  using  Turbo-Access,  first 
define  a  record  that  contains  all  the 
information  you  wish  to  store  per  ele¬ 
ment  in  your  data  base.  By  structur¬ 
ing  your  record  to  include  several 


80 


'Jin 


Dr.  Dobb's  Journal,  Apr'l  1985 


items,  you'can  use  any  of  those  kepis 
as  keys  in  indexing  your  data.  The 
key,  limited  to  less  than  256  charac¬ 
ters,  must  be  a  string.  Once  you  have 
defined  this-record,  decide  how  much 
data  you’ll  need  to  store  and  what 
you  want  your  keys  to  lool^  like.  This 
will  help  you  determine  the  constants 
that  Turbo-Access  will  need  to  set  up 
its  data  base.  The  six  constants  are 
data  record  size,  key  length,  page 
size,  order,  page  stack  size,  and  max 
tree  height.  You’ve  already  figured 
out  the  ‘first  two.  You  simply  guess 
the  page  size  and  page  stack,  given 
typical  operating  ranges  for  these. 
Order  is  half  the  page  size,  and  you 
can  compute  max  tree  height  easily, 
given  the  above  numbers.  I  was  sur¬ 
prised  to  see  how  little  effort  was 
needed  here  after  you  assume  the 
suggested  ballpark  figures. 

Finally,  determine  what  you  want 
to  do  with  your  data.  You  probably 
will  want  a  complete  program  to  find, 
add,  delete,  edit,  and  list  the  data;  the 
sample  programs  for  each  of  these 
tasks  should  get  you  going.  The  man¬ 
ual  suggests  that  you  include  in  your 
data  record  an  integer  variable  that 
you  can  use  for  status.  When  your 
program  stores  a  data  element,  you 
can  set  this  status  value  to  distinguish 
the  element  later  from  a  deleted  or 
unused  element.  This  is  helpful  for 
recreating  an  index  file  that  is  cor¬ 
rupt-  the  manual  even  gives  an  ex¬ 
ample  of  how  to  do  this.  Corruption 
of  index  files  is  the  biggest  problem 
with  ISAMs,  so  following  this  sugges¬ 
tion  is  a  good  idea. 

The  sample  program  BTREE.PAS 
is  a  fairly  extensive  mailing  list  pro¬ 
gram.  It  includes  the  capabilities  of 
finding,  adding,  editing,  deleting,  and 
listing  the  data,  with  a  nice  menu- 
driven  entry  mode  and  function  selec¬ 
tion.  BTREE.PAS  nicely  demon¬ 
strates  the  power  of  Turbo- Access.  It 
is  more  extensive  in  scope  than  the 
sample  programs  that  demonstrate 
the  separate  tasks  of  creating  an  in¬ 
dex/data  file  and  finding,  adding,  de¬ 
leting,  and  listing  the  data.  After  I 
used  these  simple  examples  to  put  to¬ 
gether  my  own  little  data  base,  the 
BTREE.PAS  example  showed  me 
some  nice  enhancements. 

What  are  the  limitations  of  Turbo- 

Dr  Dobb’s  Journal,  April  1985 


Access?  First,  if  you  need  to  include, 
all  of  your  source  files,  it  takes  a  while 
to  compile.  Second,  be  prepared  while 
debugging  your  program  to  know 
what  compiler  directives  the  B-tree 
modules  have  set.  Third,  although  a 
boolean  variable  “ok”  is  set  after 
many  operations  to  determine  if  a  task 
was  successful,  if  the  task  fails,  you 
have  little  indication  as  to  why.  Any 
error  that  Turbo-Access  can’t  handle 
becomes  a  fatal  error  and  bombs  you 
out  of  your  program;  you  are  left  with 
only  an  indication  of  the  error  code, 
record  number,  and  filename,  gener¬ 
ated  by  the  TAiocheck  routine.  I  sus¬ 
pect  you  could  modify  this  routine  to 
create  a  global  error  number  that 
could  be  tested.  However,  it  would  be 
nice  if  the  error  code  tests  available  in 
Turbo-Sort  were  available  here  so 
that  the  programmer  could  handle 
some  of  the  disk-related  errors  (e.g., 
disk  full  or  write-protected). 

Fourth,  the  MakeFile  routines 
used  to  create  index  and  data  files  de¬ 
stroy  any  existing  files  that  are  using 
the  same  filenames.  If  you  want  to 
test  automatically  whether  index  and 
data  files  already  exist,  you  must  try 
to  open  them  first.  Finally,  should 
you  wish  to  use  duplicate  keys  (and 
you  can  instruct  the  Turbo-Access 
routines  to  do  this),  you  must  do  ad¬ 
ditional  searches  to  find  the  data  you 
may  want.  Hence,  unique  keys  will 
simplify  your  data  base  searches. 

Turbo-Sort 

Many  times  I’ve  needed  to  sort  a  list 
of  items,  whether  numbers  or  strings, 
in  descending  or  ascending  order. 
Turbo-Sort  provides  a  simple,  effi¬ 
cient  method  to  do  so.  It  uses  the 
Quicksort  algorithm,  a  faster  ap¬ 
proach  than  the  time-honored  bubble 
sort.  It  allows  you  to  sort  data  from 
disk  and/or  memory  and  to  send  the 
results  to  disk  and/or  memory.  Why 
is  it  so  flexible?  You  get  to  write  the 
input  and  output  processing  proce¬ 
dures,  plus  the  procedure  that  deter¬ 
mines  the  sort  priority. 

Turbo-Sort  is  composed  of  three 
parts:  input,  sort,  and  output.  Bor¬ 
land  provides  the  code  to  sort,  while 
you  provide  ( 1 )  the  input  procedure 
Inp  that  puts  the  data  into  the  sort 
processing  memory,  (2)  the  output 


procedure  Outp  that  takes  the  sorted 
data  and  puts  it  somewhere  else,  and 
(3)  the  less  function  Less  that  deter¬ 
mines  if  one  element  is  less  than  an¬ 
other  element.  This  sounds  like  a  lot, 
but  by  copying  the  sample  programs, 
you’ll  quickly  be  up  and  running. 

The  Inp  procedure  takes  the  data 
you  wish  to  sort  and  puts  it  into  sort 
memory.  Turbo-Sort  uses  all  of  the 
existing  heap  space  to  store  your  data. 
If  there  is  insufficient  heap  space  (the 
program  can  sort  up  to  32,767  ele¬ 
ments),  Turbo-Sort  does  a  virtual 
memory  operation  and  partitions 
some  of  the  data  to  disk.  If  your  pro¬ 
gram  uses  the  heap,  it  must  allocate 
enough  heap  space  for  itself  before 
you  enter  Turbo-Sort.  The  minimum 
amount  of  memory  needed  to  sort  is 
three  times  the  item  size  (in  bytes)  or 
three  times  128  bytes,  whichever  is 
greater-this  isn’t  much  space.  The  Inp 
and  Outp  procedures,  in  essence, take 
or  get  your  data  from  the  sort  heap, 
which  is  managed  by  Turbo  Sort. 

The  nice  thing  about  being  in  con¬ 
trol  of  the  Less  procedure  is  that  you 
can  sort  on  multiple  keys,  sort  any 
type  of  item  (numeric  or  alphabetic), 
and  sort  in  ascending  or  descending 
order.  The  sample  programs  show  de¬ 
scending  sorts  on  numbers  (i.e.,  quan¬ 
tity  of  an  item,  stock  number,  etc.). 
You  can  get  ascending  sorts  by  chang¬ 
ing  the  <  sign  to  >  and  recompiling. 
Likewise,  if  you  wish  to  sort  a  data  list 
in  several  different  ways — say,  by 
quantity  then  by  name — you  can  fol¬ 
low  the  multiple  key  sample  program 
and  structure  the  Less  procedure  with 
a  case  statement.  One  thing  to  beware 
of:  Turbo-Sort  does  require  that  each 
element  of  your  data  be  at  least  two 
bytes  long. 

To  use  Turbo-Sort,  you  simply  in¬ 
clude  the  SORT.BOX  file  when  com¬ 
piling  your  program.  Turbo-Sort  for¬ 
ward  declares  the  Inp  and  Outp 
procedures  and  the  Less  function. 
Should  any  errors  occur,  Turbo-Sort 
returns  an  error  number  indicating 
what  is  wrong.  Of  the  six  error  codes, 
two  give  you  disk-related  errors  indi¬ 
cating  a  read  error  or  a  file  create  er¬ 
ror.  This  is  much  like  the  IoResult 
function  of  Pascal  that  lets  you  trap 
system  errors.  As  mentioned  before,  I 
wish  the  Turbo-Access  system  had 

81 

311 


provided  this. 

GINST 

This  tool  generates  a  terminal  instal¬ 
lation  program;  it  isn’t  the  installa¬ 
tion  program  itself.  It  needs  to  know 
the  name  of  your  program  so  that  it 
can  patch  the  proper  disk  file.  GINST 
produces  three  files:  a  code  file,  a 
data  file,  and  a  message  file.  Using 
this  tool  is  simple.  GINST  asks  you 
for  your  program  name,  then  the 
name  you  want  for  the  terminal  in¬ 
stallation  program.  That’s  it.  To  cus¬ 
tomize  your  program  for  a  new  ter¬ 
minal,  just  run  your  terminal 
installation  program. 

When  running  your  terminal  instal¬ 
lation  program,  you’ll  notice  that  it 
looks  similar  to  Turbo  Pascal’s  TINST 
program.  You  select  the  terminal  to 
use  from  a  list,  then  have  your  code 
updated  to  reflect  the  cursor  control 
sequences  and  screen  editing  com¬ 
mands  of  that  specific  terminal.  You 
can  also  modify  the  cursor  control  and 
screen  editing  sequences,  add  new  ter¬ 
minal  definitions  to  the  list,  and  delete 
selections  from  the  list. 

Although  I  heartily  approve  of  this 
new  tool,  I  do  have  a  few  complaints. 
First,  Borland  has  provided  an  IN¬ 
STALL. DOC  text  file  that  is  similar 
to  the  step-by-step  installation  of  the 
Turbo  Pascal  manual  for  use  in  your 
own  manual  or  for  distribution  with 
your  software.  Unfortunately,  the 
text  is  riddled  with  typos — as  if  some¬ 
one  hastily  transcribed  the  instruc¬ 
tions  from  the  manual  to  disk  but  did 
no  spelling  checks.  Second,  GINST 
will  not  let  you  abort  during  the 
prompt  asking  for  the  new  name  of 
your  installation  program.  It  would 
be  nice  if  a  control-C  were  acknowl¬ 
edged  here. 

Third,  on  the  MSDOS/PCDOS  ver¬ 
sion,  I  couldn’t  make  a  program  and 
its  new  installation  program  work  on 
both  my  MSDOS  machine  and  my 
IBM  PC.  If  I  compiled  a  program  on 
my  MSDOS  machine  then  generated 
an  installation  program,  it  would 
work  fine  on  that  machine.  But  when 
I  ported  both  object  code  files  and  the 
data  fifes  to  my  IBM  PC  and  tried  to 
run  the  installation  program,  oops!  no 
IBM  PC  installation  menu — just  the 
MSDOS  terminal  selections.  Howev¬ 


er,  if  I  recompiled  only  my  main  pro¬ 
gram  on  the  IBM  PC  then  ran  the  in¬ 
stallation  program,  voila!  the  IBM  PC 
monitor  selections  appear.  Obvious¬ 
ly,  GINST  inspects  some  part  of  the 
Turbo  Pascal  runtime  module  to  de¬ 
termine  what  compiler  (MSDOS  or 
PCDOS)  was  used  then  uses  the  ap¬ 
propriate  terminal  selections.  It  sure 
would  be  nice  to  have  one  object  file 
portable  to  both  the  IBM’s  screen  and 
a  normal  terminal  on  an  MSDOS  ma¬ 
chine.  I  suspect,  with  the  appropriate 
ANSI. SYS  file  under  PCDOS  2.0  (or 
higher),  I  could  add  the  IBM  to  my 
MSDOS  terminal  list,  but  that’s  not 
clean.  That  solution  also  doesn’t  do 
much  for  me  if  I’m  still  running 
PCDOS  1.1.  Oh,  well! 

Finally,  it  would  be  nice  to  custom¬ 
ize  the  GINST  terminal  data  file  for 
the  terminals  I  want  before  I  run  it 
and  generate  my  custom  installation 
program.  Unfortunately,  if  your  fa¬ 
vorite  terminal  (like  my  HP-150) 
doesn’t  appear  on  GINST’s  data  file, 
you  must  add  it  to  every  installation 
program  you  generate. 

Summary 

As  I  mentioned  at  the  start,  many  of 
us  can  use  these  tools.  In  general,  I 
found  Turbo  Toolbox  fairly  bug  free 
and  quite  usable,  except  for  the  prob¬ 
lems  noted.  The  folks  at  Borland  have 
produced  another  decent  product. 

SPSS/PC 

Company:  SPSS,  Inc.,  444  N. 

Michigan  Ave.,  Chicago,  IL 

60611 

Computer:  IBM  PC/XT,  AT,,  and 

compatibles 
Price:  $795.00 
Circle  Reader  Service  No.  xx 
Reviewed  by  Dr.  Steven  Anthony 
Sola 

Even  before  buying  my  microcom¬ 
puter,  I  dreamed  of  having  a  minia¬ 
ture  megalith  of  an  IBM  370  on  my 
desk,  at  my  command,  and  totally 
within  my  grasp.  I  wanted  it  all:  every 
interesting  language,  every  systems 


Copyright  ©  1984  by  Dr.  Steven  An¬ 
thony  Sola.  All  rights  reserved. 


tool,  and  especially  the  big  number- 
crunching  programs  like  SPSS  or 
BMDP.  The  operating  systems,  the 
languages,  and  the  applications  are 
here,  and  now  SPSS/PC  has  arrived. 
But  I  have  a  new  concern:  How  good 
is  this  micro  product? 

For  many  years  now,  Statistical 
Programs  for  the  Social  Sciences 
(SPSS)  has  been  the  standard  pack¬ 
age  that  most  graduate  students  have 
used  with  mainframe  resources  to  an¬ 
alyze  their  thesis  or  dissertation  data. 
Although  it  has  competition  from 
BMDP  and  SAS,  SPSS  is  more  than 
holding  its  own.  For  example,  in  the 
corporate  environment  as  represent¬ 
ed  by  the  annual  Datamation  survey 
of  mainframe  applications  packages 
(March  1984),  the  SPSS  batch  sys¬ 
tem  scored  better  than  its  group  aver¬ 
age  in  all  categories.  Perhaps  the  best 
test  of  quality  is  a  measure  of  the  pro¬ 
gram’s  reliability  and  validity.  Can  it 
be  trusted  to  process  the  data  of  a 
thesis  or  a  dissertation? 

Through  the  kind  cooperation  of  an 
associate,  Dr.  Jilisa  Snyder,  we  were 
able  to  evaluate  the  SPSS/PC  micro 
product  directly  against  the  SPSS 
mainframe  version  running  at  the 
State  University  of  New  York  at  Al¬ 
bany.  First,  we  ran  Jilisa’s  dissertation 
using  an  IBM  PC  as  a  terminal  under 
the  control  of  PC  TALK  III,  uploading 
the  data,  interactively  running  the 
SPSS  mainframe  programs,  and  final¬ 
ly  downloading  the  results.  We  then 
took  samples  of  the  most  arduous  sta¬ 
tistical  procedures  found  in  the  disser¬ 
tation  and  attempted  to  run  them  us¬ 
ing  the  SPSS/PC  micro  product.  The 
control  language  required  only  minor 
adjustment,  and  all  programs  ran 
without  mishap.  Finally,  we  compared 
the  output  of  each  package  using  side- 
by-side  windows 

The  results  were  spectacular  and 
reassuring:  out  of  over  156K  of  out¬ 
put  examined,  results  were  identical 
in  every  important  detail  to  the  main¬ 
frame  version.  Not  only  were  the  re¬ 
sults  accurate  to  their  scientific  preci¬ 
sion,  but  they  were  exact  even  in  their 
final  imprecise  digits  to  the  main¬ 
frame  version!  The  only  exceptions 
occurred  in  the  numeric  representa¬ 
tion  of  data  plots,  where  the  micro 
version  rounded  more  accurately 


82 


Dr.  Dobb  s  Journal,  April  1985 


than  the  mainframe  version.  Basing 
our  opinion  on  this  excellent  level  of 
performance,  we  are  reasonably  as¬ 
sured  that  the  SPSS/PC  system  is  at 
least  as  valid  and  reliable  as  the  SPSS 
mainframe  version. 

Of  course,  this  conclusion  must  be 
tempered  by  a  caveat.  We  assessed 
only  a  small  proportion  of  the  many 
possible  procedures  in  the  SPSS  pack¬ 
age,  principally  the  multiple  regres¬ 
sion  and  stepwise  multiple  regression 
procedures.  On  the  other  hand,  the 
data  used  was  all  too  real!  It  contained 
numerous  missing  values,  not  only  in 
the  independent  variables  but  also  in 
the  dependent  variables.  The  subsam¬ 
ples  were  unequal  in  size  and  dispro¬ 
portionate.  In  short,  the  data  was  the 
messy  and  ill-behaved  sort  so  typical 
of  the  social  and  behavioral  sciences. 

Is  it  possible  to  run  all  of  the  main¬ 
frame  package  on  the  micro?  Unfor¬ 
tunately,  no.  SPSS/PC  has  been  tai¬ 
lored  or,  more  accurately,  truncated. 
The  most  serious  omission  is  any 
form  of  multivariate  analysis  of  vari¬ 
ance;  the  MANOVA  procedure  is 
missing.  True,  the  complete  univar¬ 
iate  analysis  of  variance  procedure 
ANOVA  is  present,  but  this  proce¬ 
dure  is  far  from  sophisticated.  Other 
procedures  that  compare  groups  are 
acceptable  as  far  as  they  go,  but 
MEANS,  ONEWAY,  and  T-TEST 
simply  can’t  compare  with  MANOVA 
in  power  and  flexibility.  Also  missing 
is  any  form  of  canonical  correlation, 
discriminant  analysis,  logistic  regres¬ 
sion,  or  time  series  analysis. 

On  the  other  hand,  simple  descrip¬ 
tive  data  analyses  providing  means, 
standard  deviations,  and  the  like,  in¬ 
cluding  plots,  are  well  provided.  Par¬ 
ticularly  noteworthy  is  the  excellent 
implementation  of  cross-tabulation 
and  hierarchical  log-linear  models  for 
multi-way  contingency  tables.  Al¬ 
though  the  provision  for  pearson  cor¬ 
relation  and  nonparametric  statistical 
techniques  is  adequate,  there  is  no 
provision  for  rank-order  correlation 
nor  for  partial  correlation  analysis.  A 
hierarchical  cluster  analysis  is  nicely 
done,  but  the  cluster  memberships 
cannot  be  saved.  This  failure  to  save 
also  plagues  the  factor  scores  in  an 
otherwise  exemplary  procedure  of 
FACTOR.  Similarly,  casewise  residu¬ 


al  scores  cannot  be  written  out  in  the 
multiple  regression  procedure. 

Such  truncation  is  unwarranted  in 
this  otherwise  excellent  product.  The 
total  number  of  variables  is  limited  to 
200.  The  total  number  of  cases  osten¬ 
sibly  is  unlimited  but,  in  fact,  is  limit¬ 
ed  by  an  overall  maximum  of  64K 
workspace  for  data.  When  I  enquired 
as  to  why  this  might  be  so,  the  folks  at 
SPSS  muttered  some  mumbo-jumbo 
about  the  limitations  of  the  current 
crop  of  Fortran  compilers  for  the 
8088.  Come  come,  fellows!  It  should 
be  possible  to  overcome  this  64K 
curse.  As  an  additional  footnote, 
SPSS/PC  uses  the  Microsoft  Fortran 
compiler.  While  SPSS/PC  makes  use 
of  a  lot  of  overlays,  it  requires  at  least 
320K  and  uses  a  maximum  of  384K. 
Surely  using  another  256K  of  avail¬ 
able  storage  on  the  IBM  PC  wouldn’t 
hurt.  It  appears  that  SPSS  is  not  tak¬ 
ing  full  advantage  of  the  IBM  PC’s 
resources,  and  that  further  optimiza¬ 
tion  may  lead  to  a  better  product. 

Although  it  is  possible  to  run 
SPSS/PC  without  an  8087  numeric 
coprocessor,  it  is  silly  to  do  so.  After 
shelling  out  $795  for  SPSS/PC,  not 
spending  the  additional  $200  or  so  foi 
the  coprocessor,  which  greatly  speeds 
things  along,  just  isn’t  sensible.  A 
hard  disk  is  a  requirement,  not  only 
for  the  procedures  themselves  and  as¬ 
sociated  files,  which  take  up  approxi¬ 
mately  3200K,  but  also  for  the  tem¬ 
porary  files  used  during  a  program 
run. 

Transferring  data  out  of  SPSS/PC 
in  the  form  of  reports  is  helped  along 
by  a  report  writer;  however,  the  line 
length  is  limited  to  132  columns,  and 
there  is  no  printer  configuration  file 
to  customize  it  or  any  of  the  save 
commands  to  your  printer.  Transfer¬ 
ring  files  between  a  mainframe  and 
SPSS/PC  is  greatly  facilitated  by 
KERMIT,  a  neat  little  data  link  in¬ 
cluded  with  the  package.  KERMIT 
can  transfer  some  types  of  system 
files  with  error  checking,  as  well  as 
data  files.  Transferring  files  between 
your  favorite  spreadsheet,  data  base 
manager,  or  word  processor  and 
SPSS/PC  is  smooth  as  long  as  plain 
vanilla  ASCII  is  your  favorite  flavor. 
If  you  have  more  exotic  tastes,  you 
will  need  to  know  how  to  cook  in  a 


programming  language.  Unfortu¬ 
nately,  SPSS/PC  provides  no  recipes 
for  interfacing  with  their  SPSS  sys¬ 
tems  files. 

SPSS/PC  does  provide  the  most 
elaborate,  comprehensive,  well 
thought  out,  and  usable  documenta¬ 
tion  available  anywhere,  mainframe 
or  micro!  The  SPSS/PC  documenta¬ 
tion  is  specific  to  the  micro  user,  and 
the  built-in  statistics  guide  is  easy  to 
follow  and  genuinely  useful.  Illustra¬ 
tions,  examples,  and  reference  mate¬ 
rial  on  each  command  and  procedure 
abound.  The  typeset  documentation, 
which  weighs  in  at  several  pounds,  is 
well  organized,  indexed,  and  reads 
easily.  SPSS/PC  comes  with  a  nice  tu¬ 
torial  on  disk,  along  with  a  demo  pro¬ 
gram.  As  if  this  were  not  enough, 
SPSS/PC  also  provides  instantly 
available  on-screen  help  when  run¬ 
ning  interactively. 

Yes,  SPSS/PC  can  run  interactive¬ 
ly,  with  each  command  and  proce¬ 
dure  executed  when  entered.  Tradi¬ 
tional  batch  processing  is  also 
available.  Unfortunately,  SPSS/PC  is 
severely  hampered  by  the  lack  of  an 
on-line  editor.  If  you  make  a  mistake, 
for  example,  in  the  middle  of  a  lQng 
!  command  line  with  many  parame¬ 
ters,  well,  you  had  better  enjoy  typ¬ 
ing!  It  is  possible  to  get  a  directory 
listing  and  to  examine  or  erase  a  DOS 
file,  but  a  general  exit  and  return  to 
the  operating  system  doesn’t  exist. 
The  impact  of  this  becomes  more  ap¬ 
parent  when  you  realize  that  you 
must  completely  restart  the  whole 
package  each  time  you  use  your  fa¬ 
vorite  word  processor  to  edit  some 
commands. 

Which  brings  us  to  the  topic  of  the 
built-in  copy  protection  and  its  insid¬ 
ious  necessities.  Every  time  you  re¬ 
start  the  program,  you  must  have  the 
“key  diskette”  in  drive  A.  This  is  bad 
enough,  but  the  SPSS  ghouls  are  not 
through  with  you!  At  unpredictable 
times,  in  the  middle  of  a  procedure, 
the  thing  looks  for  the  key  in  drive  A. 
Woe  to  the  foolish  users  who  do  not 
replace  the  key  within  a  few  tries  into 
drive  A,  for  their  systems  will  come 
crashing  down!  Whatever  you  used  to 
have  in  drive  A  may  have  vanished 
into  bit  heaven.  For  the  typical  type 
(Continued  on  page  93) 


Ur.  Dobb's  Journal.  April  1985 


83 

313 


THE  SOFTWARE  DESIGNER 


The  Model  in  the  Machine  and 
the  Model  in  the  Mind 

by  Michael  Swaine 


How  do  you  design  software  for  peo¬ 
ple  rather  than  for  the  machine  or  for 
the  programmer?  How  do  you  make 
the  model  inside  the  machine  reflect 
the  model  in  the  user’s  head?  I  cor¬ 
nered  two  programmers  who  think 
particularly  hard  about  the  intended 
users  of  software,  and  they  talked 
about  human  factors  in  the  process¬ 
ing  of  words  and  ideas. 

Dave  Winer  is  the  president  of  Liv¬ 
ing  Videotext;  he  wrote  ThinkTank, 
the  outline  processor.  While  he  de¬ 
votes  a  lot  of  his  time  these  days  to 
meetings,  budgets  and  strategic  plan¬ 
ning,  he  says,  “what  I  am,  first  and 
foremost,  is  somebody  who  designs 
software.”  But  he’s  a  software  de¬ 
signer  who  uses  his  own  products  and 
listens  to  others  who  use  them. 

Thom  Hogan  identifies  himself 
here  as  a  writer.  As  Editor-in-Chief 
of  our  sister  magazine,  Business  Soft¬ 
ware,  he’s  that,  but  he’s  also  a  pro¬ 
grammer.  It’s  his  experience  as  a  user 
of  word-processing  programs, 
though,  that  gave  him  his  idea  for  ex¬ 
ponential  cursor  movement. 

Dave  Winer:  The  first  rule  I  try  nev¬ 
er  to  break  is  that  if  you  don’t  use  it 
yourself,  it  isn’t  going  to  be  an  inter¬ 
esting  product.  A  lot  of  software  de¬ 
signers  don’t  use  what  they  write.  I 
guess  we’re  fortunate  in  that  our 
product,  ThinkTank,  is  useful  in  a  lot 
of  different  functions,  so  it’s  easy  for 
us  to  say  that  everybody  in  the  com¬ 
pany  uses  the  product.  But  you’ve  got 
to  use  the  product:  we’ve  modified 
ThinkTank  many  times  over  several 
years,  and  those  modifications  have 
been  the  result  of  heavy  use  of  the 
product.  Talk  to  any  game  designer 
about  how  he  makes  a  game  enter¬ 
taining  and  he’ll  tell  you  the  same 
thing:  he  plays  the  game  himself,  and 
he  doesn’t  stop  until  it’s  entertaining 
for  him. 


But  it’s  hard  to  put  yourself  in  the 
user’s  frame  of  mind;  you’ve  got  to 
continually  cull  out  the  things  that 
make  you  too  specialized.  You  have 
to  work  at  seeing  things  the  way  a 
naive  user  sees  them,  and  you  have  to 
know  how  to  feel  the  little  jolt  when  a 
program  isn’t  right. 

There’s  a  term  we  use:  burning 
brain  cells.  Every  time  the  program 
does  something  that  you’re  not  ex¬ 
pecting  it  to  do,  every  time  it  sur¬ 
prises  you  just  a  little,  your  suspen¬ 
sion  of  disbelief  goes  away.  You’re  no 
longer  thinking  of  the  problem  that 
you’re  working  on,  but  about  why  the 
program  misbehaved  that  way.  It’s 
that  phenomenon  of  the  software  tak¬ 
ing  you  away  from  the  task  at  hand 
that  we  call  burning  brain  cells. 

The  concept  of  suspension  of  disbe¬ 
lief  is  very  important.  In  software  just 
as  in  the  movies  or  in  fiction  you  have 
an  audience,  and  you’re  trying  to  ma¬ 
nipulate  them,  to  influence  their  be¬ 
havior;  initially  with  a  program 
you’re  trying  to  relax  the  person  and 
invite  him  to  try  things.  The  suspen¬ 
sion  of  disbelief  develops  as  you  draw 
the  person  in  gradually:  first  he  tries 
the  cursor  keys,  say,  and  he  finds  that 
the  cursor  keys  move  the  cursor;  OK. 
Then  what?  Well,  people  make 
guesses;  and  you  as  a  software  de¬ 
signer  have  to  try  to  anticipate  their 
guesses.  To  a  certain  extent  you  do 
this  on  your  own,  but  eventually 
you’ve  got  to  try  it  out  on  real  people. 
We  found  with  ThinkTank  that  after 
the  cursor  keys,  people  were  reaching 
for  the  spacebar,  so  we  used  the  spa¬ 
cebar  to  step  through  the  command 
line.  So  you  second-guess  the  user, 
and  if  you  do  a  good  job,  you  build  up 
a  certain  amount  of  trust  on  the  part 
of  the  user;  then  over  time,  if  it  is  go¬ 
ing  to  be  a  successful  experience  for 
him,  he  will  have  suspended  his  disbe¬ 
lief.  He’s  now  no  longer  thinking 


about  the  software,  he’s  thinking 
about  the  mechanics  of  the  problem 
at  hand.  That’s  the  goal. 

One  technique  for  getting  there  is 
not  trying  to  force  your  structure  on 
the  user,  but  rather  trying  to  get  him 
to  recognize  his  own  structure  in  the 
machine.  One  of  the  reasons  people 
like  using  computers  is  that  it’s  neat  to 
crawl  around  in  there  and  discover 
things.  Well  it’s  neater  if  you’re  crawl¬ 
ing  around  discovering  things  that  you 
made  rather  than  things  that  some 
guy  in  Cambridge  made.  We  tried  to 
capture  a  human  structure  in  writing 
ThinkTank:  categorizing  and  subcate¬ 
gorizing  are  very  natural  to  the  hu¬ 
man  mind,  even  though  the  way 
ThinkTank  does  categorizing  is  very 
different  from  the  way  humans  do  it. 
We’ve  listened  to  users  and  in  the  next 
release  incorporated  features  like 
cloning  and  hoisting  and  the  use  of 
color,  all  things  that  make  the  model 
closer  to  the  model  the  user  has. 

Thom  Hogan:  I’m  a  writer.  I  spend 
a  lot  of  time  using  word  processors, 
and  I’ve  yet  to  find  a  word-processing 
program  that  doesn’t  somehow  inhib¬ 
it  my  writing  and  editing  style.  There 
are  so  many  needed  features  of  word 
processors  that  haven’t  been  imple¬ 
mented  yet  that  I  sometimes  wonder 
what  to  call  the  software  I  do  use. 
This  brief  note  describes  just  one  con¬ 
cept  that  has  yet  to  be  implemented 
in  any  word  processor. 

Cursor  control  is  out  of  control.  I 
just  got  the  latest  whiz-bang  word 
processor  and  I  find  that  I  can  now 
move  by  character,  by  word,  by 
phrase,  by  sentence,  by  paragraph,  by 
block,  by  screen  page,  by  actual  page 
and  by  section.  That’s  a  lot  of  cursor 
keys. 

Moving  around  in  a  document,  es¬ 
pecially  a  long  one  like  a  book  oi  soft¬ 
ware  manual,  should  not  be  a  many- 


86 

314 


Dr.  Dobb’s  Journal,  April  1985 


fingered  thing.  I  don’t  want  to  do 
nine  kinds  of  multiple-key  gymnas¬ 
tics,  I  don’t  want  to  move  my  hand 
from  the  home  row  of  keys  to  a  cursor 
pad  and  I  certainly  don’t  want  to  both 
move  my  hand  from  the  home  row 
and  hold  down  multiple  keys. 

The  cursor-control  diamond  in 
WordStar  is  reasonably  comfortable 
for  us  touch  typists;  I  can  Control-F 
and  Control-A  to  move  forward  and 
backward  a  word  at  a  time  almost  as 
fast  as  I  can  type,  but  then  I’ve  had 
several  books  worth  of  practice.  But 
WordStar’s  cursor  controls  are  inade¬ 
quate  for  moving  around  quickly  in  a 
large  document.  Here’s  a  piece  of  user 
data:  I  find  that  I  typically  move 
screen  page  by  screen  page,  then  nar¬ 
row  in  on  my  prey  using  word-by- 
word  moves,  with  maybe  a  few  up  and 
down  arrows.  With  a  word  processor 
that  makes  good  use  of  a  mouse,  I  can 
effectively  narrow  in  on  a  particular 
spot  on  a  screen,  but  it’s  less  useful  in 
larger  moves,  and  there’s  still  the  issue 
of  removing  my  hands  from  the  home 
row. 

All  of  which  leads  up  to  my  notion 
of  exponential  cursor  control.  I  only 
want  six  cursor  control  keys:  four  to 
move  one  character  at  a  time,  up, 
down,  right  and  left;  and  two  addi¬ 
tional  keys,  a  “power  forward”  and  a 
“power  backward”  key. 

My  definition  of  a  “power”  key  is 
one  that  moves  the  cursor  in  a  fixed 
direction  through  the  file,  but  that  in¬ 
creases  its  step  size  with  repeated 
presses  (or  if  you  hold  the  key  down 
long  enough  for  autorepeat  to  start). 
There  are  several  algorithms  possible 
for  a  power  key: 

Algorithm  1 :  time-dependent 

WHILE  power  key  held  down  DO 

WHILE  time  down  <  1  unit  DO 
move  cursor  one  word 

WEND 

WHILE  1  unit  <  time  down  < 

2  units  DO 

move  cursor  one  sentence 

WEND 

WHILE  2  units  <  time  down  < 

3  units  DO 

move  cursor  one  paragraph 

WEND 

WHILE  3  units  <  time  down  < 

4  units  DO 


move  cursor  one  page 
WEND 
WEND 

Algorithm  2:  context-dependent 

flag=l 

WHILE  previous  keypress  was 
power  key  DO 
CASE  OF  flag  = 

1 :  move  one  word 
test  for  boundary 
2:  move  one  phrase 
test  for  boundary 
etc. 

ENDCASE 

WEND 

The  test  for  boundary  sets  the  value 
of  flag  dependent  on  whether  or  not 
we’ve  moved  across  some  arbitrarily 
defined  boundaries  during  the  move¬ 
ment  of  the  cursor: 

phrase  boundary:  ,.)?!;: 
sentence  boundary:  .  ?  ! 
paragraph  boundary:  <cr><lf> 
page  boundary:  66  lines 
section  boundary:  section  marker 
With  either  approach,  the  effect  is 
the  same:  the  longer  the  power  key  is 
repeated,  the  greater  distance  it 
moves  and  the  greater  is  its  speed  of 
movement,  although  the  first  ap¬ 
proach  requires  that  the  user  hold  the 
power  key  down.  Each  approach  has 
its  advantages.  The  first  allows  you  to 
lift  your  finger  from  the  key  momen¬ 
tarily  to  get  your  bearings  and  then 
restart  at  a  slower  cursor  rate  when 
you  press  the  power  key  again.  The 
second  favors  key  tappers  who  use  the 
rhythm  of  the  keystrokes  to  reinforce 
what  they’re  doing.  Both  approaches 
require  programs  that  give  priority  to 
redisplaying  the  cursor  and  screen 
contents,  since  power  keys  would 
make  the  program  especially  sensi¬ 
tive  to  the  “understeer”  and  “over¬ 
steer”  problems  that  result  from  the 
display  not  keeping  up  with  the  cur¬ 
sor  movement. 

I’ve  described  moves  in  terms  of 
units  that  most  matter  to  me  these 
days:  words,  sentences,  paragraphs. 
Program  editors,  though,  could  be 
configured  to  use  boundaries  that 
make  sense  for  the  language  being 
used.  Boundaries  could  be  set  at 
keywords,  statement  boundaries,  loop 
structures  and  functions,  for  example, 


if  one  were  editing  C  programs.  Better 
yet,  give  me  an  editor  that  lets  me  pro¬ 
vide  definitions  for  boundaries. 

One  last  request  to  word-process¬ 
ing  programmers:  please  keep  my  fin¬ 
gers  on  the  keys.  Use  the  WordStar 
diamond  and  let  Control-A  and  Con¬ 
trol-F  be  the  power  keys;  I’ll  love  it.  I 
won’t  even  object  if  you  throw  in 
screen-up  and  screen-down  keys. 

Micro  Subscribers  Please 
Note: 

The  Software  Designer  is  a  column 
devoted  to  the  notion  that  the  activity 
of  programming  engages  one  in  a 
struggle  with  metaphors;  tha(  the 
only  worthy  goal  in  the  struggle  is  to 
be  a  wielder  of  metaphors,  a  creator 
of  metaphors,  and  not  an  unwitting 
tool  of  unexamined  metaphors. 
That’s  what,  I  claim,  distinguishes  a 
software  designer  from  a  mere  pro¬ 
grammer:  the  software  designer  cre¬ 
ates  new  metaphors  for  the  machine. 

A  computer  is,  after  all,  a  general 
purpose  device,  and  that’s  a  pretty 
ethereal  concept.  What  does  a  gener¬ 
al  purpose  device  do?  Well,  it .  . . 

You  see  the  point.  Just  to  tell  our 
friends  what  we  are  doing  with  what 
used  to  be  our  spare  time,  or  what  we 
do  for  a  living,  we  have  to  create  met¬ 
aphors.  All  the  metaphors  are  in  fact 
just  descriptions  of  software  prod¬ 
ucts:  the  Mac’s  metaphor  is  the  desk¬ 
top  and  VisiCalc  gave  us  the  spread¬ 
sheet  metaphor.  And  when  the 
metaphor  is  implemented,  the  com¬ 
puter,  the  general  purpose  device,  ac¬ 
quires  a  specific  purpose,  becomes, 
briefly,  an  electronic  desktop  or 
spreadsheet.  It’s  trivial  to  point  out 
that  the  computer  can  do  nothing  un¬ 
til  some  programmer  tells  it  what  to 
do.  But  the  computer  is  nothing  until 
the  software  designer  brings  into  the 
world  a  new  metaphor,  and  with  it  a 
new  intellectual  tool. 


DDJ 


Reader  Ballot 

Vote  for  your  favorite  feature/article. 
Circle  Reader  Service  No.  1 97. 


Dr.  Dobb’s  Journal,  April  1985 


88 

315 


C  CHEST 


by  Allen  Holub 


The  topic  of  this  month’s  column  is 
sorting  routines.  We’ll  look  at  how  the 
sort  routine  given  in  Kernighan  & 
Ritchie  works  and  talk  about  why  you 
shouldn't  use  it.  Then  we’ll  present  a 
general  purpose  Quicksort  routine, 
modeled  after  the  Unix  routine 
qsort(  ). 

Shell  Sort 

Many  C  programmers,  when  they 
need  a  sort  routine,  grab  the  one  out 
of  The  C  Programming  Language  (p. 
116).  The  routine  works,  it’s  short, 
why  not  use  it?  There  are  several  rea¬ 
sons:  it’s  not  documented,  it’s  slow, 
and  better  routines  are  available. 
Some  of  the  slowness  comes  from  us¬ 
ing  array  indexes  rather  than  point¬ 
ers  (an  array  index  usually  causes  a 
multiply,  while  incrementing  a  point¬ 
er  is  an  addition). 

Unfortunately,  the  speed  issue  runs 
deeper.  Just  about  every  program¬ 
ming  problem  presents  you  with 
trade-offs,  and  sorting  routines  are 
worse  than  most  in  this  respect.  Not 
only  are  the  usual  considerations  of 
code  size  versus  efficiency  a  factor, 
but  there  are  several  possible  ways  to 
go  about  sorting  an  array,  all  of  which 
behave  differently.  For  example,  with 
Quicksort,  the  time  required  to  sort  an 
array  can  change  radically  with  the 
kind  of  data  the  routine  is  presented. 
You  have  to  know  how  a  sorting  rou¬ 
tine  works  before  you  can  make  an  in¬ 
telligent  decision  on  whether  to  use  it 
in  a  particular  application. 

The  routine  in  K&R  is  a  Shell  sort. 
I  thought  for  years  that  Shell  sort  was 
named  after  a  shell  game;  anyone 
who  has  sat  down  with  the  K&R  code 
and  tried  to  figure  out  what’s  going 
on  can  understand  how  I  came  to  this 
conclusion.  As  it  turns  out,  the  algo¬ 
rithm  is  named  after  its  inventor, 
Donald  Shell,  who  developed  it  in 
1959.  A  Shell  sort  works  by  breaking 


up  the  sorting  problem  into  a  series  of 
smaller  problems.  For  example,  a  set 
of  eight  objects  to  be  sorted  is  broken 
up  into  four  subsets  of  two  objects, 
each  of  which  is  sorted  separately. 
The  eight  objects  are  then  reorga¬ 
nized  as  two  sets  of  four  objects; 
again  each  is  sorted  separately.  Fi¬ 
nally,  the  set  is  sorted  as  one  set  of 
eight  objects. 

The  rationale  behind  the  Shell  sort 
is  that  you  get  the  more  out-of-order 
parts  of  the  original  set  into  order 
very  quickly.  The  array  then  is  easier 
to  sort  on  the  next  pass.  What  char¬ 
acterizes  a  Shell  sort  is  the  reorgani¬ 
zation  into  smaller  sets.  Although  the 
algorithm  used  to  actually  sort  the 
items  in  each  subset  is  immaterial, 
the  behavior  of  this  algorithm  should 
get  better  as  the  set  it  is  working  on 
gets  closer  to  being  in  order.  A  bubble 
sort  does  this.  In  fact,  you  can  look  at 
Shell  sort  as  an  improved  bubble  sort. 

A  concrete  example  may  clarify 
the  process.  Let’s  start  with  a  set  of 
eight  objects: 

{9,  2,  1,7,  3,  8,  5,  4} 

We  break  this  up  into  four  subsets 
consisting  of  the  pairs  {9,3},  {2,8}, 
{1,5},  and  {7,4}: 

9  2  1  7  3  8  5  4 


Then  we  sort  each  pair  separately: 
3  2  1  4  9  8  5  7 


Next  we  break  up  the  set  into  two 
subsets  of  four  elements,  {3,  1,  9,  5} 
and  {2,  4,  8,  7}: 

3  2  14  9  8  5  7 


We  then  sort  these  separately: 


1  2  3  4  5  7  9  8 


Finally,  we  treat  the  input  as  one  set 
of  eight  elements  and  just  sort  it: 

12  3  4  5  7  8  9 


Now,  let’s  look  at  the  routine  in 
K&R  (a  slightly  modified  version  ap¬ 
pears  in  Listing  One,  page  94).  The 
outermost  for  loop  (line  6)  deter¬ 
mines  how  many  elements  will  be  in 
each  subset;  “gap”  is  the  distance  in 
the  array  between  two  elements  of 
the  same  subset.  In  the  above  exam¬ 
ple,  the  distance  between  9  and  3 
(which  compose  the  first  subset)  is  4; 
this  would  be  the  initial  value  of  gap. 
Each  time  through  this  first  for  loop, 
we  divide  gap  by  two,  which  effec¬ 
tively  doubles  the  number  of  ele¬ 
ments  in  a  subset. 

The  second  and  third  for  loops 
(line  7  and  8)  are  the  actual  sort  rou¬ 
tine.  The  algorithm  is  essentially  a 
bubble  sort,  although  the  situtation  is 
complicated  by  sorting  all  of  the  sub¬ 
sets  at  the  same  time.  You  can  best 
see  how  the  sort  works  by  reducing 
the  algorithm  to  handle  only  the  last 
case  (i.e.,  gap  =  =  1)  and  reversing 
the  sense  of  the  compare.  This  is  done 
in  Figure  1  (page  91 ). 

In  the  innermost  loop,  the  bubble 
sort  scans  along  the  unsorted  array 
looking  for  two  adjacent  elements  that 
are  out  of  order.  When  it  finds  these 
elements,  it  exchanges  them.  In  this 
way,  an  out-of-order  element  tends  to 
“bubble”  up  to  its  correct  place  in  the 
array.  However,  you  have  to  do  many 
comparisons  and  exchanges  to  get  it 
there  (worst  case  is  an  array  already 
sorted  but  in  reverse  order;  you  have 
to  make  N-l  exchanges  to  get  an  out- 
of-place  element  from  the  right  end  of 
an  N  element  array  all  the  way  to  the 
left  end).  This  inner  loop  must  be  exe¬ 
cuted  enough  times  to  guarantee  that 


90 

316 


Dr.  Dobb’s  Journal.  April  1985 


all  out-of-order  elements  find  their 
way  to  the  proper  position  in  the  ar¬ 
ray;  therefore,  a  bubble  sort  will  al¬ 
ways  use  0(n2)  comparisons.  In  the 
worst  case,  it  will  need  almost  as  many 
exchanges.  No  wonder  the  routine  is 
slow. 

You  can  now  see  how  the  Shell  sort 
strategy  helps  the  bubble  sort.  It 
takes  that  pathologically  out-of-place 
element  and  moves  it  to  the  front  of 
the  array  in  O(log  N)  exchanges, 
rather  than  in  N-l  exchanges.  You 
can  improve  the  behavior  of  a  Shell 
sort  by  fiddling  with  the  increments 
between  elements.  This  lets  the  vari¬ 
ous  passes  interact  with  each  other  in 
a  more  productive  fashion.  The  incre¬ 
ments  should  not  be  even  multiples  of 
each  other;  powers  of  two  are  actual¬ 
ly  among  the  worst  increments. 
Knuth  has  determined  that  a  good 
choice  of.  increments  is  1,4,  13,  40, 
121  ....  He  also  says  that  the  se¬ 
quence  1,3,7,  1 5,  3 1 , . . .  works  well. 
Using  this  latter  sequence  of  incre¬ 
ments,  a  Shell  sort  will  sort  an  N  ele¬ 
ment  array  in  OfN1 2)  time.  This  is 
better  than  the  0(N2)  time  required 
for  a  bubble  sort  but  not  much.  If 
you’re  interested,  the  gory  details  of 
this  analysis  are  in  Knuth’s  book  The 
Art  of  Computer  Programming ,  vol. 
3  (Addison  Wesley,  1973)  pages  84f. 

Quicksort 

In  the  general  case,  a  faster  sorting 
routine  than  the  Shell  sort  is  Quick¬ 
sort,  developed  by  C.  A.  R.  Hoare  in 
1962. 

Quicksort,  like  the  Shell  sort,  sorts 
an  array  in  place;  that  is,  it  sorts  by 
exchanging  elements  of  an  array 
rather  than  by  copying.  It  works  by 
selecting  an  arbitrary  element  of  the 
array  to  be  sorted  (called  the  “piv¬ 
ot”)  and  moving  that  element  to  its 
proper  position  in  the  sorted  array. 
As  it  is  doing  this,  it  arranges  the  ar¬ 
ray  so  that  all  elements  to  the  left  of 
the  pivot  are  less  than  the  pivot  and 
all  the  elements  to  the  right  are  great¬ 
er  than  the  pivot.  It  then  repeats  this 
process  recursively  on  the  two  halves 
of  the  array.  It  works  as  follows: 

( 1 )  Select  an  arbitrary  element  of  the 
array  to  be  sorted  (the  pivot).  It’s  eas¬ 
iest  just  to  lop  off  the  last  or  first  ele¬ 
ment  of  the  array  and  use  that  as  the 


pivot  (we  will  use  the  last  element  in 
this  example).  However,  if  the  pivot 
happens  to  be  the  largest  or  smallest 
element  of  the  array,  then  the  behav¬ 
ior  of  Quicksort  degrades  dramati¬ 
cally  (Quicksort  becomes  Slowsort). 
You  can  avoid  this  worst  case  scenar¬ 
io  by  taking  the  center  element  as  the 


pivot  or,  even  better,  by  taking  the 
median  of  three  elements.  This  extra 
work  will  slow  down  the  routine,  but 
it’s  worth  it  if  you  expect  to  be  sorting 
already  sorted  files. 

(2)  We  will  sort  using  two  pointers 
called  HIGH  and  LOW.  To  start  sort¬ 
ing,  make  HIGH  point  at  the  next  to 


for(  i  =  1  ;  i  <  argc  ;  i  +  +  ) 

for(  j  =  i  -  1 ;  j  >  =  0  ;  — j ) 

iff  strcmp(  argv[j],  argv[j  +  1]  >  0) 
exch(  agv[j],  argv[j  +  1] ) 

Figure  1 
Bubble  Sort 


Initial  Initial 


LOW  - j  HIGH  - - j  ^ _ Pivot 


Initial  file: 

80 

20 

40 

100 

110 

30 

10 

0 

160 

120 

130  90 

Looking: 

80 

20  40 

100 

110 

30 

10 

0 

1 60 

120 

130  90 

80 

20 

40 

100 

110 

30 

10 

0 

160 

120 

130  90 

80 

20 

40 

100  1 10 

30 

10 

0 

160 

120 

130  90 

80 

20 

40 

100  1 10 

30 

10 

0 

160 

120  130 

90 

80 

20 

40 

ioo  i  io 

30 

10 

0 

160  120 

130 

90 

80 

20 

40 

100  1 10 

30 

10 

0 

160 

120 

130 

90 

1st  exchange: 

80 

20 

40 

0 

110 

30 

10 

100  160 

120 

130 

90 

80 

20 

40 

0 

11030 

10 

1 00  1 60 

120 

130 

90 

80 

20 

40 

0 

11030 

10  100 

160 

120 

130 

90 

2nd  exchange: 

80 

20 

40 

0 

10  30 

110100 

160 

120 

130 

90 

80 

20 

40 

0 

10 

30 

110  100 

160 

120 

130 

90 

Pointers  cross: 

80 

20 

40 

0 

10 

30 

no  ioo 

160 

120 

130 

90 

HIGH  _ 

J 

t 

i  nw 

Exchange  pivot: 

80 

20 

40 

0 

10 

30 

90  100 

160 

120 

130 

110 

Partitioned: 

[80 

20 

40 

0 

10 

30] 

90  [100  160 

120 

130 

110] 

t 

t 

t 

t 

t 

t  • 

LOW 

HIGH 

Pivot 

LOW 

HIGH  Pivot 

Figure  2 
Quicksort 


Given: 

typedef  struct 

{ 

int  key,  elementl ,  element2,  etc; 

} 

ELEMENT; 

compare)  el ,  e2  ) 

ELEMENT  *e1,  *e2; 

< 

return)  e1->key  -  e2->key  ); 

} 

ELEMENT  array[10]; 

The  array  can  be  sorted  with: 

qsort)  array,  10,  sizeof(ELEMENT),  compare  ); 

Figure  3 

Sorting  an  Array  of  Structures 


Dr.  Dobb  s  Journal.  April  1985 


91 


last  element  of  the  array  and  LOW 
point  to  the  first  element  (the  last  ele¬ 
ment  is  the  pivot). 

(3)  Move  LOW  towards  the  last  ele¬ 
ment  of  the  array.  Stop  when  HIGH 
and  LOW  cross  or  when  the  object 
pointed  to  by  LOW  is  greater  than  the 
pivot. 

(4)  Move  HIGH  toward  the  first  ele¬ 
ment  of  the  array.  Stop  when  LIIGH 
and  LOW  cross  or  when  the  object 
pointed  to  by  HIGH  is  less  than  the 
pivot. 

(5)  If  the  two  pointers  have  not 
crossed,  exchange  the  objects  pointed 
to  by  HIGH  and  LOW. 

(6)  Repeat  steps  3,  4,  and  5  until  the 
pointers  cross. 

(7)  Exchange  the  pivot  and  the  object 
pointed  to  by  LOW.  This  puts  the  piv¬ 
ot  into  its  correct  place  in  the  array. 

(8)  The  array  is  now  partitioned  so 
that  all  elements  to  the  left  of  the  piv¬ 
ot  are  less  than  the  pivot  and  all  ele¬ 
ments  to  the  right  of  the  pivot  are 
greater  than  the  pivot.  Repeat  steps 
1-8  on  these  two  partitions  (not  in¬ 
cluding  the  pivot  in  either  partition) 
until  the  array  size  of  all  arrays  is  less 
than  or  equal  to  one. 

An  example  of  the  sorting  process 
is  given  in  Figure  2  (page  91).  The 
objects  pointed  at  by  HIGH  and  LOW 
are  underlined.  For  more  information 
see  The  Art  of  Computer  Program¬ 
ming,  pages  1 14f. 

The  biggest  problem  with  Quick¬ 
sort  is  its  sensitivity  to  the  input  data. 
Best  case  performance  (when  you  al¬ 
ways  select  the  median  as  the  pivot) 
requires  0(N  log  N)  comparisons 
and  0(1/6  log  N)  exchanges.  This  is 
quite  a  bit  better  than  0(N2).  The  av¬ 
erage  case  performance  is  still  pretty 
good:  0(2  In  2)  time  slower  than  the 
best  case.  Worst  case  performance 
(when  the  pivot  is  the  largest  element 
in  the  array)  is  another  matter.  In 
this  case,  one  partition  is  empty  and 
the  other  contains  the  balance  of  the 
array  (N-I).  N  (rather  than  log  N) 
partitioning  steps  must  be  made,  and 
the  algorithm  requires  0(N2)  time  to 
sort  the  array.  This  time  is  no  worse 
than  that  of  the  bubble  sort,  but  it’s 
no  better  either. 

A  general  purpose  Quicksort  rou¬ 
tine  is  presented  in  Listing  Two  (page 
94).  This  routine,  called  qsort(  ), 


works  like  the  Unix  routine  of  the 
same  name.  By  “general  purpose”  I 
mean  that  you  can  use  qsort(  )  to  sort 
arrays  of  anything.  You  can  sort  ar¬ 
rays  of  pointers,  of  integers,  of  struc¬ 
tures — of  anything.  The  advantages 
of  this  versatility  are  obvious.  The 
disadvantage  is  that  qsort(  )  will  run 
slower  than  a  routine  tailored  to  a 
particular  application. 

There  are  two  other  potential  prob¬ 
lems  with  this  particular  implementa¬ 
tion  of  Quicksort:  it  chooses  its  pivot 
in  an  unintelligent  way  and  it’s  recur¬ 
sive.  If  you  are  likely  to  be  sorting  al¬ 
ready  sorted  lists,  you  should  modify 
the  pivot  selection  to  take  a  median 
value  rather  than  just  grabbing  the 
last  element.  Handling  the  recursion 
is  trickier.  I’ve  tried  to  minimize  the 
average  case  recursion  nesting,  but 
the  worst  case  is  still  pretty  bad. 

In  particular,  if  you  are  sorting  an 
already  sorted  array  of  N  elements, 
there  will  be  N-2  levels  of  recursion. 
Each  level  is  passed  two  pointer-sized 
local  variables  on  the  stack.  Assum¬ 
ing  a  16-bit  pointer,  this  yields  eight 
bytes  per  level.  The  compiler  will 
probably  need  a  few  other  bytes  for 
the  return  address  and  frame  pointer, 
say,  1 2  bytes  total  (this  is  probably  an 
underestimate).  Sorting  a  1024-ele¬ 
ment  array  would  require  about  12K 
of  stack,  not  even  counting  the  space 
needed  for  the  array  itself.  The  moral 
is  to  use  this  routine  on  small  arrays. 
It’s  fast  but  it  eats  memory.  Best  case 
recursion  nesting  will  require  log2N 
levels,  which  is  better  but  still  not 
good.  If  someone  has  an  iterative  ver¬ 
sion  of  Quicksort,  send  it  to  me. 

The  versatility  of  qsort(  )  comes 
from  its  not  making  any  assumptions 
about  the  object  being  sorted.  Its  call¬ 
ing  syntax  is: 

qsort(  base,  nel,  width,  compare  ) 

char  *base; 

int  nel,  width; 

int  (*compare)  (  ) 

“Base”  is  the  base  address  of  the  ar¬ 
ray  being  sorted;  it’s  declared  as  a 
character  pointer  but  actually  can  be 
a  pointer  to  anything.  “Nel”  is  the 
size  of  the  array  in  elements  (as  com¬ 
pared  to  bytes).  “Width”  is  the  size 
of  one  element  in  bytes,  and  “com¬ 


pare”  is  a  pointer  to  a  comparison 
routine.  This  comparison  should  be¬ 
have  like  strcmp(  );  that  is,  when 
called  with 

(*compare)  (a,  b); 

where  a  and  b  are  pointers  to  two  ele¬ 
ments  of  the  array,  the  comparison 
routine  will  return  a  negative  number 
if  a  <  b,  zero  if  a  =  =  b,  and  a  posi¬ 
tive  number  if  a  >  b. 

The  main(  )  routine  in  Listing  Two 
(line  167)  shows  qsort  being  used  to 
sort  an  array  of  character  pointers. 
Figure  3  (page  91)  shows  it  being 
used  to  sort  an  array  of  structures 
where  the  key  field  contains  an  inte¬ 
ger  sort  key. 

Qsort(  )  itself  (Listing  Two,  line 
40)  is  an  access  routine.  The  actual 
sorting  is  done  by  rqsort(  )  (line  74), 
which  is  static  to  the  module  (i.e.,  it 
isn’t  externally  accessible).  Qsort(  ) 
first  copies  the  two  parameters  that 
won’t  be  modified  (width  and  com¬ 
pare)  into  two  global  variables  (line 
63;  comp  and  width  are  declared  on 
line  20).  Passing  them  as  parameters 
to  rqsort(  )  would  just  increase  the 
stack  usage  and  make  the  subroutine 
calling  overhead  higher.  Both  vari¬ 
ables  are  static,  so  you  don't  have  to 
worry  about  their  names  conflicting 
with  other  global  variables  in  your 
program.  I  have  deviated  a  little  from 
the  Unix  qsort(  )  by  defining  a  de¬ 
fault  comparison  routine  for  sorting 
argv-like  arrays  of  character  point¬ 
ers.  To  use  the  default  routine,  set  the 
compare  argument  to  0.  The  actual 
sorting  is  started  by  calling  rqsort(  ) 
(on  line  66). 

Rqsort(  )  is  passed  two  parame¬ 
ters,  “low”  and  “high”  which  point  at 
the  bottom  and  top  elements  of  the 
array  to  be  sorted.  They  have  the 
same  function  as  LOW  and  HIGH  in 
the  algorithm  description.  The  local 
variable  “pivot”  is  a  pointer  to  the 
pivot  (selected  on  line  91).  Note  that 
all  the  pointers  are  defined  as  charac¬ 
ter  pointers.  Because  characters  are 
of  size  1,  pointer  arithmetic  on  a 
character  pointer  is  plain  arithmetic. 
We  need  to  defeat  pointer  arithmetic 
because  we  don’t  know  the  size  of  the 
object  at  compile  time.  Adding  or 
subtracting  width  from  the  pointer 


92 


Dr.  Dobb  s  Journal.  April  1985 


will  advance  it  the  correct  number  of 
bytes.  The  comparison  routine  has  to 
know  what’s  being  pointed  to,  but  at 
this  level  we  need  only  the  size. 

The  two  while  loops  on  lines  95  and 
97  are  looking  for  out-of-place  ele¬ 
ments,  per  steps  3  and  4  of  the  algo¬ 
rithm.  Step  5,  exchanging  LOW  and 
HIGH,  is  done  on  lines  99-110. 
Again,  because  we  don’t  know  the  ob¬ 
ject  size  in  advance,  we  have  to  ex¬ 
change  the  two  objects  one  byte  at  a 
time.  We  look  for  the  pointers  cross¬ 
ing  at  line  1 1  3.  Step  7,  the  pivot  ex¬ 
change,  is  done  on  lines  1 18-124.  Be¬ 
cause  an  exchange  is  an  expensive 
operation,  “Low”  and  the  pivot  are 
exchanged  only  if  necessary.  On  the 
other  hand,  the  comparison  itself 
takes  time  too,  so  you  may  want  to 
pull  it  out  of  the  code. 

The  partitionirig  is  done  on  line 
131.  “Low”  is  advanced  to  exclude 
the  pivot  from  both  partitions  (it’s  al¬ 
ready  in  the  correct  place  in  the  sort¬ 
ed  array).  Finally,  step  8  (sorting  the 
two  partitions)  is  done  on  lines  131- 
145.  Several  tests  are  made  here.  The 
test  on  line  132  determines  which 
partition  is  smaller;  the  smaller  parti¬ 
tion  is  sorted  first.  This  practice  tends 
to  reduce  the  number  of  recursion 
levels.  The  tests  on  lines  134,  136, 
141,  and  143  are  looking  for  arrays 
with  one  or  fewer  elements  in  them; 
these  arrays  are  already  sorted.  Most 
Quicksort  algorithms  put  this  test  at 
the  top  of  the  algorithm,  but  this  adds 
an  additional  level  of  recursion,  and 
testing  at  the  bottom  doesn’t  add  all 
that  much  extra  code. 

Conclusion 

So,  there  are  two  sorting  routines. 
Which  you  should  use  depends  on  the 
application.  If  you  expect  to  sort  a  lot 
of  already  sorted  data,  the  Shell  sort 
will  work  in  0(N12)  time  while 
Quicksort  needs  0(N2).  A  Shell  sort 
is  also  not  the  memory  hog  that 
Quicksort  is.  On  the  other  hand, 
Quicksort  is  much  faster  on  random¬ 
ly  sorted  input,  even  though  it  eats 
stack  space. 

The  qsort(  )  routine  given  here  is 
more  versatile  than  the  shell  sort(  ) 
routine,  but  it  shouldn’t  be  too  hard 
to  modify  the  latter  to  be  as  general 
purpose  as  qsort(  ).  Also,  it’s  not  hard 


to  strip  the  versatility  out  of  qsort(  ) 
if  you  don’t  need  it,  improving  its  per¬ 
formance  for  a  specific  application. 

I’d  like  to  end  this  month  with  a 
request.  If  you  have  working  C  imple¬ 
mentations  of  other  sorting  algo¬ 
rithms,  send  them  to  me.  Perhaps  we 
can  do  a  follow-up  of  this  column  at 
some  future  date. 

The  O(N)  Notation 

The  big  O  notation  is  often  found 
when  analyzing  algorithm  efficiency. 
It  is  read  as  “order  of  .  .  .  .”  O(N)  is 
“order  of  N,”  0(N2)  is  “order  of  N 
squared,  “  and  so  on.  “O(X)  time” 
means  that  the  algorithm  will  per¬ 
form  in  time  proportional  to  X.  So, 
an  algorithm  that  sorts  an  array  in 
0(N2)  time  will  sort  the  input  data  in 
time  proportional  to  the  number  of 
input  elements  squared.  To  derive 
O(X),  you  look  at  the  algorithm  and 
extract  the  crucial  operations,  the 
ones  that  are  performed  most  often. 
In  the  case  of  sorting  algorithms,  this 
will  be  some  combination  of  compari¬ 
son  and  exchange  operations.  You 
then  figure  out  how  many  times  that 
operation  will  be  performed  on  a  giv¬ 
en  set  of  input  data.  Because  the 
main  use  of  the  big  O  notation  is  to 
compare  algorithms,  it’s  not  particu¬ 
larly  exact.  You’re  just  trying  to  ex¬ 
press  how  an  algorithm  performs  in  a 
general  sort  of  way.  So,  if  an  algo¬ 
rithm  really  takes  17  +  N2  opera¬ 
tions  to  do  something,  the  17  is  ig¬ 
nored  because  it  isn’t  particularly 
significant.  In  another  example. 
Quicksort  requires  N*log  N  compar¬ 
isons  and  l/6(log  N)  exchanges  in 
the  general  case.  The  1  /6  is  relatively 
insignificant,  so  we  usually  say  that 
Quicksort  will  work  in  0(N*log  N) 
time  in  the  general  case. 

DD| 


(Continued  from  page  83) 

of  system  hang  induced  by  these 
fiends  involves  keeping  drive  A  run¬ 
ning  an  infinite  length  of  time.  Can 
you  use  one  of  the  bit  copiers  at  least 
to  provide  yourself  with  some  insur¬ 
ance?  No,  they  don’t  appear  to  work. 
Will  SPSS  provide  you  with  a  backup 
disk?  No  way.  Be  prepared  to  wait 
about  a  week  and  to  shell  out  more 
money  when  your  key  goes  belly  up 
(and  they  all  eventually  do).  No,  this 
isn’t  the  middle  of  a  nightmarish  ad¬ 
venture  game  you’ve  warped  into — 
this  is  SPSS/PC’s  copy  protection! 
(While  we  are  on  the  subject  of  warn¬ 
ings:  don’t  buy  version  1.0.  Version 
1.1  corrects  a  number  of  significant 
oversights,  and  it  will  cost  you  an  ad¬ 
ditional  $100  to  upgrade.) 

Operationally,  SPSS/PC  purrs 
along  like  a  kitten.  It’s  fun  and  amus¬ 
ing  to  play  with,  and  it  appears  im¬ 
mune  to  fatal  operator  errors.  With 
an  8087  installed,  it  is  quite  frisky. 
Speed  is  not  a  major  problem.  SPSS/ 
PC  does  what  common  sense  and  its 
manual  says  it  will  do,  and  it  has  been 
extensively  debugged  and  carefully 
crafted.  Support  is  provided  by  tele¬ 
phone  during  regular  business  hours 
(you  pay  the  toll).  Technical  staff  are 
knowledgeable,  but  you  may  have  to 
wait  24  hours  before  they  have  time 
to  fully  answer  your  query.  There  is  a 
mainframe-oriented  users  group  but 
as  yet  no  micro  group.  SPSS/PC  will 
run  on  an  IBM  PC/XT,  the  AT,  and 
on  compatibles  such  as  the  Compaq, 
Tandy  2000,  and  TI  Professional. 

SPSS/PC  is  good,  and  it  has  brought 
me  closer  to  my  dream  of  having  that 
IBM  370  on  my  desk.  SPSS/PC’s  algo¬ 
rithms  are  fundamentally  sound,  it  is 
finely  crafted,  and  I. would  trust  it  to 
process  a  student’s  thesis.  You  won’t 
find  me  in  line  to  do  batch  processing 
nor  cursing  at  the  mainframe  for  being 
down  again.  I  have  my  micro  and  I 
almost  have  it  all! 

DD| 


93 

319 


L  C/ie5f  (Text  begins  on  page  90) 

Listing  One 


1 

shell ( 

argv,  argc  ) 

2 

char 

*argv[  ]  ; 

3 

int 

argc ; 

4 

{ 

5 

int  gap,  i,  j; 

6 

f or (  gap  =  argc/2;  gap  >  0  ;  gap  /=  2) 

7 

for(  i  =  gap;  i  <  argc;  i++  ) 

8 

Q 

f or (  j  =  i-gap;  j  >=  0 

;  j  - 

=  gap  ) 

10 

V 

if (  strcmp(argv[j], 

argv[ j+gap]) 

11 

break ; 

12 

exch (  &argv[j], 

Sarg 

v [ j  +  gap  ]  ); 

13 

) 

14 

) 

Listing  Two 

1:  linclude  <stdio.h> 


2 

•a 

/* 

* 

QSORT. C 

j 

4 

* 

Copyri 

ght  (c)  1984  Allen  I.  Holub 

5 

* 

$ 

All  rights  reserved. 

o 

7 

8 
9 

* 

♦ 

This  program  may  be 

coppied  for  personal,  non-profit  use  only. 

* 

Including  a  #define 

for  DEBUG  will  make  this  file  a  stand-alone 

10 

* 

program 

which  sorts 

argv  and  prints  the  result, 

along  with  all 

11 

* 

intermediate  stages. 

This  is  pretty  instructive 

if  you  want  to 

12 

* 

see  how 

the  quick  sort  works. 

13 

*/ 

14 

#i  f def 

DEBUG 

15 

static 

int 

Lev  =  0  ; 

/*  Recursion  level  */ 

16 

static 

int 

Maxlev  =  0; 

/*  Maximum  "  */ 

17 

#endif 

18 

t y pedef 

int  (  * 

PFI ) ( ) ; 

/*  pointer  to  a  function 

returning  int 

*/ 

19 

static 

PFI  Comp  ; 

/♦  Pointer  to  comparison 

routine 

*/ 

20 

static 

int  Width ; 

/*  Width  of  an  object  in 

bytes 

V 

21  :  /* 


*/ 


22 
23 
2  4 

25 

26 

27 

28 


int 

char 

( 


argvcmp(  sip,  s2p  ) 

**s 1 p ,  **s2p ; 

/*  Comparison  routine  for  sorting  an  argv  like  list  of  pointers 

*  to  strings.  Just  remove  one  level  of  indirection  and  call 

*  strcmp  to  do  the  comparison. 

*/ 


29:  #if def  DEBUG 


30 

31 

32 

33 

34 

35 

36 

37 

38 

39 

40 

41 

42 

43 

44 

45 

46 

47 

48 

49 

50 


#else 

#endif 

} 

qsort ( 
char 
int 
int 
{ 


register  int  rval; 

rval  =  strcmp(*slp,  *s2p)  ; 

printf ( "level  %d  :  a rgvcmp( <%s> , <%s> )  =  %d\n".  Lev , *s 1 p , *s2p  ,  r va  1 )  ; 
return(  rval  ); 

return(  strcmp(*slp,  *s2p)  ); 


base,  nel,  width,  compare  ) 

♦base ; 
nel,  width; 

( "“compare )()  ; 

/*  Perform  a  quick  sort  on  an  array  starting  at  base.  The 

*  array  is  nel  elements  large  and  width  is  the  size  of  a 

*  single  element  in  bytes.  Compare  is  a  pointer  to  a 

*  comparison  routine  which  will  be  passed  pointers  to  two 

*  elements  of  the  array.  It  should  return  a  negative  number 

*  if  the  left-most  argument  is  less  than  the  rightmost, 

*  0  if  the  two  arguments  are  equal,  a  positive  number  if 

*  the  left  argument  is  greater  than  the  right.  If  compare 


94 

320 


End  Listing  One 


Dr.  Dobb  s  Journal,  April  1985 


C  Chest  (Listing  Continued,  text  begins  on  page  90) 

Listing  Two 


51 

52 

53 

54 

55 

56 

57 

58: 

59: 

60: 

61 : 
62: 

63: 

64: 

65 

66 

67 

68 

69 

70 

71 ; 

72 

73 

74 

75 

76 

77 

78: 

79: 
80: 
81 : 
82: 

83 

84 

85 

86 

87 

88 

89 

90 

91 

92 

93 

94 

95: 

96: 

97 

98 

99 
100 
101 
102 

103 

104 


108 

109 

110 
111 


* 

* 

* 

*/ 


is  0  then  the  default  comparison  routine,  argvcmp 
(which  sorts  an  argv-like  array  of  prointers  to  strings), 
is  used. 


#if def  DEBUG 

pr i nt f ( "Sor t i ng  %d  element  array  of  %d  byte  elements  at  0x%x\n", 

nel ,  width ,  base  ) ; 


#endif 


print f ( "Comparison  routine  at  0x%x.  Unsorted  list:\n",  compare); 
ptext(  nel,  base  ); 


Width 

Corap 


width; 

(compare 


(PFI)0)  ?  Sargvcmp  :  compare  ; 


if(  nel  >  1  ) 

rqsort(  base,  base  +  ((nel-1)  *  width)  ); 

Ilf def  DEBUG 

printf("\nSort  complete,  list  i s : \ n " ) ; 
ptext(  nel,  base  ); 

pr in t f ( "Maximum  recursion  level  =  %d\n",  Maxlev  ); 

lendif 

) 


/*- 


static 

register  char  *low,  *high; 


rqsort(  low,  high  ) 


/* 

* 

*/ 

chfl  r 


static  char 
static  int 


Workhorse  function  called  by  the  access  routine,  qsort(). 
Not  externally  accessible. 


♦nlvot.  *  h  a  s  e  : 

*a ,  *b ; 
trap ,  i  • 


/*  Used  for  exchanges,  will  not  */ 
/*  need  to  retain  their  values  */ 
/*  during  the  recursion  so  they  */ 


/*  can  be  static. 


*/ 


#if def  DEBUG 

printf("New  pass,  recursion  .evel  %d\n",  Lev  ); 
if(  Lev  >  Maxlev  ) 

Maxlev  =  Lev; 


lendif 


base  =  low 
pivot  =  high 
high  -=  Width 


/*  Remember  base  address  of  array  */ 
/*  Partition  off  the  pivot  */ 


do 

( 


while(  low  <  high  &&  (*Comp)(low,  pivot)  <=  0  ) 
low  +=  Width; 

while(  low  <  high  &&  ( *Comp) ( high ,  pivot)  >=  0  ) 
high  -=  Width; 


lif def  DEBUG 

lendif 


if(  low  <  high  ) 

( 


/*  exchange  low  &  high  */ 


printf("lev  %d:  exchanging  high:<%s>  &  low:<%s>\n", 
Lev,*((char  **)high),  *(( char **) low) ) ; 

for(  b  =  low,  a  =  high,  i  =  Width  ;  — i  >=  0  ;) 

( 


105: 

tmp 

=  *b  ; 

/*  Exchange  *low  and 

106: 

*b++ 

=  *  a  ; 

107: 

*a++ 

=  trap  ; 

) 


)  while  (  low  <  high  ); 


112 

113 

114 

115 


lifdef  DEBUG 

print f ( "level  Zd:  Exchanging  pivot:<Zs>  &  low:<%s>\n", 

Lev,  *((char  **)pivot),  *(( char** ) low)  ); 

lendif 


Dr.  Dobb's  Journal,  April  1985 


96 

321 


C  Chest  (Listing  Continued,  text  begins  on  page  90) 

Listing  Two 


116 
i  1 7 
118 
119 
i  20 
121 

122 

123 

124 

125 

126 

127 

128 

129: 


143 

144 

145 

146 

147 

148 

149 

150 

151 

152 

153 

154 

155 

156 

157: 

158 

159 

160 


if(  low  <  pivot 
for(  b  * 
{ 


&&  (*Comp) ( low ,  pivot)  >  0  ) 
low,  a  =  pivot,  i  =  Width  ;  — i  > 


t  hi  p 
*bi-+ 
*3  +  + 


=  *b 


tmp 


0  ;) 

/*  Exchange  *low  and  *pivot  */ 


) 


lifdef  DEBUG 

printf ("\nDone  with  pass,  partially  sorted  list  =\n"); 
ptext(  ((pivot  -  base)/Width)  +  1  ,  base  ); 
pr intf ( "\n" ) ; 

++Lev  ; 


#endif 


low  +=  Width; 


130 

131 

if (  high  - 
( 

base  <  pivot  -  low  ) 

132 

if( 

low  <  pivot 

) 

133 

rqsort ( 

low,  pivot 

); 

134 

if( 

base  <  high 

) 

135 

136 

137 

138 

) 

else 

( 

rqsort( 

base,  high 

); 

139 

if( 

base  <  high 

) 

140 

rqsort( 

base,  high 

); 

141 

if  ( 

low  <  pivot 

) 

142 

rqsort( 

low,  pivot 

): 

) 

#i£def  DEBUG 
—  Lev 

Kendif 

) 

/*- 


/*  Test  routine  for  qsort.  compiled  if  DEBUG  is  ^defined 

#if def  DEBUG 


-*/ 

*/ 


static  ptext(  argc,  argv  ) 


int 

char 

( 


argc  ; 
**argv ; 


/* 

*/ 


Print  out  argv,  one  element  per  line, 
register  int  i; 


for(  i  =  1;  — argc  >=  0  ;  i++  ) 

printf("%2d:  %s\n",  i,  *argv++  ); 


161 

162 

163 

164 

165 

166 


main(argc,  argv) 
int  argc; 

char  **argv; 

( 

/*  Test  routine  for  qsort.  Sorts  argv  (less  the  first  element). 

*/ 


167:  qsort(  ++argv,  --argc,  sizeof(PFI)  ,  0  ); 

168:  ) 

169:  #end i f 


End  Listings 


98 

'WO 


Dr.  Dobb's  Journal,  April  1985 


16-BIT  SOFTWARE  TOOLBOX 


A  New  Assembler  for 
MSDOS 

Philip  Oliver  has  sent  us  a  copy  of  his 
new  product  ED/ASM-86  for  review. 
This  is  an  interactive  programming 
tool  that  combines  the  functions  of 
an  assembler,  line  editor,  linker,  and 
debugger  in  a  single  50K.  program. 
8087,  80186  and  80286  mnemonics 
are  fully  supported  for  assembly, 
tracing,  and  disassembly.  Macros, 
structures,  and  conditional  assembly 
directives  are  included.  This  is  an  in¬ 
teresting  product,  although  I  feel  that 
its  appeal  would  be  broader  if  it  were 
fully  source  compatible  with  the  Mi¬ 
crosoft  Macro  Assembler  and  if  it 
could  handle  standard  Microsoft 
Linker  object  files.  The  current  price 
of  ED/ASM-86  is  $100.00;  contact 
Oliver  Computing  Company  at  931 1 
Jutland  Court  #D,  Indianapolis,  IN 
46250  for  more  information. 

TEE  Filter  for  MSDOS  2 

Mr.  A.  K.  Head,  of  Melbourne,  Aus¬ 
tralia,  contributed  a  pair  of  MSDOS 
2.x  filters.  We  are  printing  the  first  of 
these,  called  TEE,  in  this  month’s  col¬ 
umn  as  Listing  One  (page  106).  TEE, 
a  standard  filter  in  Unix  systems,  is 
used  in  a  pipe  to  make  two  copies  of 
the  standard  input,  directing  one  to  a 
file  and  the  other  to  the  standard  out¬ 
put  (which  might  also  be  redirected 
into  a  file).  Where  you  normally 
would  use  a  filename,  here  you  can 
also  use  any  logical  device  such  as 
PRN:  or  CON:.  For  example,  you 
could  enter  the  command: 

DIR  !  SORT  !  TEE  C:SORTED.DIR 
This  would  direct  the  output  of  the 
directory  command  to  the  SORT  fil¬ 
ter  and  thence  to  TEE,  which  would 
send  one  copy  of  the  sorted  listing  to 
the  disk  file  C:SORTED.DIR  and  the 
other  copy  to  the  standard  output 


(console,  in  this  case).  This  version  of 
TEE  doesn’t  support  the  Unix  switch¬ 
es  -i  (ignore  interrupts)  or  -a  (ap¬ 
pend  to  previous  contents  of  a  file). 

80286  Feedback 

Matt  Robins,  of  Beverly  Hills,  Cali¬ 
fornia,  writes:  “I  am  writing  to  you 
concerning  two  and  a  half  errors  in 
your  November  1984  DDJ  column. 

“PCDOS  3.0  could  not  provide  the 
\  .  .  same  old  horrible  EDLIN  that  we 
first  met  in  PCDOS  1.0.’  In  fact,  as 
you  probably  know  by  now,  the 
PCDOS  2.0  version  of  EDLIN  is  a  sig¬ 
nificant  improvement  on  the  PCDOS 
1.1  version  that  I  started  out  with.  It 
boasts  40%  more  commands  (14  vs. 
10)  of  the  most  advanced  variety: 
Copy-line  and  Move-line  that  make 
programming  easy.  Additional  capa¬ 
bility  is  provided  by  the  use  of  +  and 
-  signs,  like  old  CP/M  ED  (which  was 
a  true  abomination). 

“What  I  particularly  like  about  the 
new  EDLIN  is  that  it  uses  the  DOS 
edit  mode  keys  FI  through  F4.  I 
couldn’t  live  without  this  capability! 
I’ve  enhanced  my  EDLIN  by  assign¬ 
ing  the  arrow  keys  and  Home,  End, 
PgUp,  and  PgDn  keys  to  ANSI  se¬ 
quences  that  execute  the  appropriate 
commands  in  EDLIN  (to  comply  with 
the  above  mentioned  keytop  defini¬ 
tions)  .... 

“Your  second  mistake  was  calling 
the  80286  ‘a  slightly  enhanced  8086.’ 
By  now  you  must  have  read  the  No¬ 
vember  BYTE  article  by  Paul  Wells 
that  correctly  states:  ‘.  .  .  at  the  same 
clock  speed  and  [when  you]  do  not 
take  advantage  of  any  80286  capabil¬ 
ities,  the  80286  achieves  250  percent 
of  the  8086’s  performance.’  It  does 
that,  partly,  by  having  five  processors 
on  board,  as  opposed  to  the  8086’s 
three.” 

I  agree  that  the  80286  in  protected 


mode  is  a  very  different  machine,  but 
I  still  maintain  that  you  don’t  gain 
much  more  by  using  an  80286  in  real 
mode  (as  PCDOS  3.0  does)  than  you 
could  get  just  by  cranking  up  the  clock 
on  a  normal  8086  and  using  16-bit 
memory  like  the  Compaq  DeskPro. 

Matt  goes  on  to  say:  “Finally,  a 
half-a-mistake  (maybe  only  a  matter 
of  taste)  is  your  saying  that  use  of  the 
multitasking,  virtual  memory,  and 
memory  protection  will  \  . .  wring  all 
of  the  available  computing  power  out 
of  the  80286.’  1  tend  to  think  of  com¬ 
puting  power  as  throughput,  and  the 
three  activities  mentioned  above  low¬ 
er  the  throughput,  not  facilitate  it. 
Put  another  way,  ‘computing  power’ 
is  benchmark  timings,  whereas  the 
three  activities  above  are  memory 
management  functions.  Being  a 
member  of  the  ‘one  [or  more]  proces¬ 
sors,  one  user’  school  of  thought.,  I  am 
interested  in  Unix  and  multitasking 
only  for  my  personal  use:  performing 
several  of  my  tasks  simultaneously.” 

More  Macintosh  Feedback 

Steve  Rabalais  writes:  “The  Macin¬ 
tosh  contains  a  rich  operating  system 
that,  if  used  right,  will  take  a  lot  of 
the  labor  out  of  programming.  Utili¬ 
ties  for  Floating  Point  (IEEE  Stan¬ 
dard),  File  Management,  Transcen- 
dentals,  etc.,  are  easily  accessed  from 
Assembly  Language.  The  Apple  peo¬ 
ple  supporting  the  development  work 
are  apparently  impressed  by  the  BIG 
NAME  programming  shops,  and  in¬ 
dependent  developers  are  left  to  their 
own  wits  (which  is  probably  a  good 
thing).  I  personally  do  not  depend  on 
phone  calls  to  solve  programming 
problems  and  prefer  to  ‘dig  it  out’  be¬ 
cause  this  is  the  best  way  to  learn.  1 
attempted  to  become  an  Apple  Certi¬ 
fied  Developer  but  failed  to  impress 
the  Marketing  and  Sales  people  with 


Dr.  Dobb’s  Journal,  April  1985 


102 

323 


their  requisite  pile  of  paperwork.  I 
purchased  my  Macintosh  and  Lisa 
‘over  the  counter’  and  began  pro¬ 
gramming  immediately.  I  have 
talked  to  Certified  Developers  that 
had  to  wait  months  to  obtain  their 
‘favored’  hardware  discounts.  I  do 
have  ‘INSIDE  MACINTOSH’  and 
have  found  it  indispensable  for  writ¬ 
ing  Mac  applications. 

“Although  I  do  not  have  the  offi¬ 
cial  Macintosh  Apple  Assembler,  I 
have  heard  horror  stories  to  the  effect 
that  it  requires  two  Macs.  I  use  an 
excellent,  inexpensive  Assembler 
called  MacAsm  from  Mainstay  that 
allows  me  to  program  complete  appli¬ 
cations  on  a  128K  Macintosh  with  a 
single  400K  disk  drive.  MacAsm  is  a 
two  pass  Macro  Assembler  and  is 
complete  with  a  Resource  Compiler 
and  Text  Editor.  I  am  writing  a  series 
of  Macintosh  CADD  (computer-aid¬ 
ed  design  and  drafting)  applications 
and  have  had  no  problems  using  this 
assembler.  1  have  found  it  fast,  effi¬ 
cient,  and  simple.  I  am  not  using  the 
Apple  Workshop  on  the  Lisa  to  de¬ 
velop  software  because  it  is  slow  and 
complicated  to  use  and  Apple  recent¬ 
ly  wanted  more  money  for  a  complete 
set  of  the  latest  updates. 

“I  use  the  Workshop  version  I  have 
to  help  learn  68000  assembly  lan¬ 
guage.  The  Workshop  contains  a 
Pascal  compiler  that  will  dump  the 
generated  assembly  code  into  a  text 
file.  I  inspect  the  code,  figure  out  how 
things  are  done,  and  then  rewrite  it 
(for  efficiency  and  clarity)  using 
MacAsm.  This  may  sound  like  cheat¬ 
ing,  but  I  find  that  I  am  gradually 
depending  less  on  the  Lisa  as  the 
learning  process  continues.  Macin¬ 
tosh  programming  in  assembly  lan¬ 
guage  is  hard  to  learn,  but  once  you 
have  learned,  it  is  easy  to  write  so¬ 
phisticated  applications.  Independent 
Developers  have  made  the  Apple  II 
successful  and  apparently  will  do  the 
same  for  the  Macintosh  in  spite  of  the 
roadblocks.  In  conclusion,  I  would 
say  that  the  Macintosh  is  a  superb 
combination  of  hardware  and  soft¬ 
ware,  and  a  proper  programming  en¬ 
vironment  can  be  had  at  a  reasonable 
price  .  .  .  .  Hopefully,  other  program¬ 
mers  will  read  this  and  know  that 
there  is  help  in  the  wilderness  for  pro¬ 


gramming  the  Macintosh.” 

Steve  also  contributed  a  program¬ 
ming  tip  for  the  68000,  which  ap¬ 
pears  as  Listing  Two  (page  1 10)  fol¬ 
lowing  this  column. 

80286  XENIX 

Speaking  of  Unix,  we  have  been  us¬ 
ing  80286  XENIX  3.0  for  the  PC/AT 
here  at  Laboratory  Microsystems  for 
about  two  months.  Getting  acquaint¬ 
ed  with  Unix  is  an  entertaining  but 
aggravating  experience.  Unix  devo¬ 
tees  claim  that  it  was  designed  by 
programmers  for  programmers  and 
increases  productivity  enormously, 
but  to  me  it  looks  like  it  grew  up  topsy 
turvy  out  of  the  late-night  efforts  of  a 
legion  of  graduate  student  hackers. 
No  doubt,  back  when  Ken  Thompson 
and  Dennis  Ritchie  invented  Unix,  it 
was  compact  and  elegant  (after  all,  it 
ran  on  a  PDP-7),  but  it  has  grown  into 
a  behemoth  that  needs  256K  of  RAM 
to  get  off  the  ground. 

The  “Adventure”  game  mentality  is 
seen  everywhere  in  the  system.  “You 
are  lost  in  a  twisty,  turning  maze  of 
cryptic,  poorly  indexed  software  man¬ 
uals.”  There  is  no  uniformity  of  syntax 
whatsoever,  the  commands  have  ob¬ 
scure  or  arcane  names  (grep,  Is,  cat), 
switches  are  case-sensitive  (for  in¬ 
stance,  a  lower-case  “s”  switch  and  an 
upper-case  “S”  switch  on  the  C  com¬ 
piler  do  vastly  different  things),  and 
the  screen  editor  vi  makes  WordStar 
look  positively  user  friendly. 

The  documentation  for  XENIX  is 
clearly  derived  from  the  original  Unix 
manuals,  even  though  it  has  been  fil¬ 
tered  through  the  technical  writers  at 
Microsoft  and  then  IBM.  One  often 
comes  across  unexpectedly  witty 
sayings,  such  as  (under  the  SPLINE 
command)  “a  limit  of  1000  points  is 
silently  enforced”  or  (in  the  terminal 
capabilities  library)  “Hazeltime  = 
brain  damage”  and  “Names  starting 
with  X  are  reserved  for  serious  distur¬ 
bances.”  Command  names  frequently 
have  a  high  cuteness  factor,  such  as 
“finger”  (report  names  of  active  us¬ 
ers),  “whodo,”  and  “stty  sane”  (used 
when  the  operating  system  loses  its 
mind  and  starts  spitting  garbage  out 
to  your  terminal). 

None  of  this  is  meant  as  a  criticism 


of  the  Microsoft  implementation, 
however,  because  the  boys  in  Belle¬ 
vue  seem  to  have  done  a  pretty  good 
job  of  implementing  System  III  on 
the  80286.  And  this  was  no  weekend 
fling,  either.  IBM  states  that  “the  to¬ 
tal  number  of  lines  that  migrated 
from  the  original  AT&T  source  code 
to  PC  XENIX  was  well  over  400,000 
lines.” 

The  performance,  even  with  a  cou¬ 
ple  of  extra  terminals  hung  onto  the 
system,  is  quite  reasonable — due 
mainly  to  the  PC/AT’s  very  fast  hard 
disk.  XENIX  3.0  runs  the  80286  in 
protected  mode  and  is  very  crash¬ 
proof  compared  to  PCDOS.  Any  at¬ 
tempt  by  your  program  to  access 
memory  not  specifically  allocated  to 
your  program,  or  to  execute  an  illegal 
or  privileged  opcode,  results  in  its  im¬ 
mediate  termination.  We  have  found 
that  as  you  develop  new  software  un¬ 
der  XENIX,  the  friendly  message 
“Memory  fault  core  dump”  will  be 
your  constant  companion. 

A  few  warnings  before  you  rush 
out  to  buy  a  PC/AT  and  XENIX. 
There  is  no  operating  system  support 
for  graphics  display  modes;  there  is 
only  very  primitive  support  for  the 
keyboard  (ALT-key  combinations 
can’t  be  read,  and  even  reading  the 
unadorned  function  or  arrow  keys 
must  be  done  through  writing  control 
strings  to  the  ANSI  driver);  and  the  C 
compiler  apparently  has  bugs  in  the 
“large  model”  and  the  optimizer,  as 
Microsoft  explicitly  warned  us  not  to 
use  those  features. 

Because  80286  XENIX  runs  in  pro¬ 
tected  mode,  direct  access  to  the 
hardware  is,  for  all  practical  pur¬ 
poses,  nonexistent  -you  can  forget 
about  your  carefully  optimized, 
memory-mapped  video  routines  and 
all  the  other  code  you’ve  painstaking¬ 
ly  created  to  make  your  PCDOS  ap¬ 
plications  look  flashy.  The  ROM 
BIOS  isn’t  available  either.  In  fact, 
your  program  can’t  even  tell  where  it 
is  running,  let  alone  where  anything 
else  is:  the  segment  registers  contain 
only  logical  selectors,  and  the  corre¬ 
sponding  physical  addresses  are  not 
visible.  Those  of  us  who  have  become 
spoiled  by  the  ability  on  micros  to 
reach  out  and  twiddle  bits  or'  ports 
wherever  we  please  are  out  of  luck. 


103 


Dr.  Dobb’s  Journal,  April  1985 


One  more  warning,  and  not  the  least: 
the  operating  system  requires  some 
15  Mbytes  of  hard  disk  space.  If  you 
are  planning  to  use  both  XENIX  and 
PCDOS  on  your  PC/AT,  best  to  plan 
ahead  by  buying  two  hard  disks. 

At  the  UN  I  FORUM  conference  in 
Dallas  that  we  attended  in  January, 
Microsoft  discussed  plans  to  upgrade 
80286  XENIX  for  compatibility  with 
AT&T’s  Unix  System  V.  Where  this 
will  leave  purchasers  of  IBM  PC/AT 
XENIX  is  not  clear.  The  PC/AT  ver¬ 
sion  is  being  sold  and  supported  di¬ 
rectly  by  IBM,  and,  as  we  have  found 


out  to  our  sorrow  with  other  products 
(such  as  the  Microsoft  Assembler), 
new  improved  versions  released  by 
Microsoft  hardly  ever  seem  to  mi¬ 
grate  down  the  pipeline  to  show  up  in 
IBM  distribution. 

But  however  you  slice  it,  the  re¬ 
lease  of  PC/AT  XENIX  is  a  signifi¬ 
cant  event  for  the  Unix  community. 
This  is  the  first  time  that  a  personal 
computer  powerful  enough  to  run 
Unix  with  decent  performance  has 
been  available  at  a  price  the  average 
small  businessman  would  be  willing 
to  pay;  the  added  prestige  of  the  three 


magic  initials  behind  it  (no,  I  don’t 
mean  AT&T)  may  be  sufficient  to  let 
it  travel  into  uncharted  regions  where 
no  Unix  has  gone  before.  The  total 
installed  base  of  Unix  and  Unix-de¬ 
rivative  systems  in  the  world  is  cur¬ 
rently  estimated  to  be  less  than 
100,000;  IBM  could  conceivably  dou¬ 
ble  or  triple  this  in  a  year  with  the 
right  kind  of  marketing  and  applica¬ 
tion  software.  It  should  be  interesting 
to  see  what  happens. 

DD| 


16-Bit 


Listing  One 


name  tee 

page  55,132 

title  'TEE  -  tee  junction  for  DOS  pipes' 


TEE  -  A  "tee"  junction  for  DOS  2.x  pipes 

after  the  fashion  of  the  Unix  function  TEE 

By  A.  Y .  tieiad,  6  Duffryn  Place,  Melbourne,  Australia  3142 
reformatted  and  error  handling  added  by  Ray  Duncan 

TEE  reads  from  the  standard  input  (redirectable)  and 
outputs  to  both  the  standard  output  (redirectable) 
and  a  file,  e.g.:  DIR  |  TEE  C:\MYDIR\FILE.EXT  |  SORT 


The  file  can  be  CON,  LPT1,  etc.  If  it  is  CON,  then 
keyboard  Control-S  pauses  the  display  as  usual. 


command 

equ 

80h 

buffer  for  command  tail 

buf len 

equ 

16384 

buffer  length,  alter  to  taste 

c  t 

equ 

0dh 

ASCII  carriage  return 

If 

equ 

0ah 

ASCII  line  feed 

ff 

equ 

0ch 

ASCII  form  feed 

eof 

equ 

01ah 

Erid-of-file  marker 

tab 

equ 

09h 

ASCII  tab  code 

blank 

equ 

20h 

ASCII  blank 

DOS  2.x  pre-defined  handles 

stdin 

equ 

0  0  00 

standard  input  file 

stdout 

equ 

0001 

standard  output  file 

stderr 

equ 

0002 

standard  error  file 

stdaux 

equ 

0003 

standard  auxilliary  file 

stdpr n 

equ 

0004 

standard  printer  file 

cseg 

segment 

para  public  'CODE' 

assume 

cs:cseg,ds:cseg 

org 

100H 

start  .COM  at  100H 

tee 

pr  oc 

far 

mov 

cl , ds : command 

get  length  of  command  tail. 

xor 

ch  ,ch 

address  of  command  string. 

mov 

si, offset  command+1 

mov 

di, offset  command+1 

teel: 

mov 

al  ,byte  ptr  [si] 

squeeze  out  leading  spaces. 

cmp 

a  1 , b lank 

jbe 

tee  2 

mov 

byte  ptr  [di ] ,al 

inc 

d  i 

(Continued  on  page  108) 


106 

325 


Dr.  Dobb’s  Journal,  April  1985 


16-Bi\ 

Listing 

tee2 : 


tee3 : 


tee4 : 

tee5 : 
tee6 : 

tee7 : 


tee 

nchar 

handle 

err  1 

errllen 
er  r  2 

108 

326 


(Listing  Continued,  text  begins  on  page  102) 


inc 

s  i 

r 

look  through  command  tail 

loop 

teel 

r 

until  it's  exhausted. 

mov 

byte  ptr  [di ] , 0 

} 

make  ASCIIZ  string. 

mov 

ah , 3ch 

i 

create  output  file. 

mov 

cx ,  0 

t 

attr ibute=0. 

mov 

dx,offset  command+1 

int 

2  lh 

jc 

tee6 

r 

can  1 1  create  file 

mov 

handle, ax 

9 

save  •  'ken  for  file. 

mov 

ah, 3fh 

r 

read  standard  input. 

mov 

bx , stdin 

mov 

cx , buf len 

lea 

dx , buf  f er 

int 

21h 

jc 

tee4 

r 

jump,  error. 

mov 

nchar , ax 

r 

save  length  of  read. 

cmp 

a  x ,  0 

r 

nothing  read  in? 

je 

tee4 

t 

end  of  file,  exit. 

mov 

ah ,  4  0h 

9 

write  to  file... 

mov 

bx ,  handle 

mov 

cx , nchar 

int 

21h 

jc 

tee7 

9 

jump,  write  failed. 

cmp 

ax , nchar 

j  ne 

tee7 

9 

jump,  write  failed. 

mov 

ah , 4  0h 

9 

now  write  to  standard  output.., 

mov 

bx , stdout 

int 

21h 

jc 

tee7 

9 

jump,  write  failed. 

cmp 

ax , nchar 

jne 

tee7 

9 

jump,  write  failed. 

jmp 

tee3 

9 

read  again  until  end  of  file. 

mov 

ah , 3eh 

9 

close  output  file. 

mov 

bx ,  handle 

int 

2  lh 

mov 

ax ,  4c00h 

9 

exit  with  return  code=0. 

int 

21h 

mov 

dx, offset  errl 

9 

print  "Can't  create  file". 

mov 

cx , err  lien 

mov 

ah , 40h 

9 

display  error  message  and  exit 

mov 

bx , stder r 

int 

21h 

mov 

ax , 4c01h 

9 

exit  with  return  code=l. 

int 

21h 

mov 

dx, offset  err2 

9 

print  "Disk  is  full". 

mov 

cx ,err21en 

mov 

ah , 4  0h 

9 

display  error  message  and  exit 

mov 

bx , stderr 

int 

2  lh 

mov 

ah , 3eh 

9 

close  output  file. 

mov 

bx , handle 

int 

2  lh 

mov 

ax , 4c02h 

9 

exit  with  return  code=2. 

int 

21h 

endp 

dw 

0 

9 

number  of  chars  actually  input 

dw 

0 

9 

token  for  output  file. 

db 

cr  ,  1  f 

db 

'  tee : 

Cannot  create 

file 

db 

cr  ,  1  f 

equ 

(this 

byte) - (offset 

errl 

db 

cr  ,  If 

db 

'  tee : 

Disk  is  full.' 

1 

( Continued  on  page  l 10) 


Dr.  Dobb’s  Journal.  April  1985 


Ib-I5l  T  (Listing  Continued,  text  begins  on  page  102) 

Listing  One 


err21en 

Gb 

equ 

cr  ,  1  f 

(this  byte)- 

(offset  err2) 

buffer 

equ 

this  byte 

;  data  is  read  here 
;  standard  input 

cseg 

ends 

end 

tee 

End  Listing  One 


Listing  Two 


Programming  Tip:  Jump  Tables  are  a  convenient,  efficient, 

and  general  way  of  directing  program  flow  based  on  a 
multiple  choice.  Here  is  an  implementation  of  a  Jump  Table 
for  the  68030. 

★ 

*  Register  D0  =  choice  index  from  0  to  N 

★ 


start 

equ 

* 

add .  w 

d0,d0 

move.w 

j table (pc ,d0 .w) ,d0 

jmp 

jtable  (pc,d0.w) 

j  table 

equ 

* 

data 

/ j  ump000-j  table 

data 

/j  ump001- j  table 

data 

/jump 00 2- jtable 

data 

(etc) 

(etc) 

(etc) 

/jump0 03- jtable 

jump000 

equ 

(code) 

* 

bra 

continu 

jump001 

equ 

(code) 

* 

bra 

continu 

j  ump  002 

equ 

(code) 

* 

bra 

continu 

jump003 

equ 

(code) 

* 

bra 

(etc) 

(etc) 

continu 

continu 

equ 

(code) 

* 

End  of  Listing  2 


tnd  Listings 


110 


Dr.  Dobb’s  Journal,  April  1985 


327 


Bill  Wilson  of  Pullman,  Washington 
wrote  several  months  ago  describing  a 
program  he  had  written  to  simplify 
batch  processing.  He  also  included  a 
patch  to  enable  PIP  to  accept  null 
command  lines  from  a  submit  file.  I 
am  sure  you  will  find,  as  I  did,  that 
both  are  most  useful  and  real  time- 
savers. 

The  patch  described  here  is  not  ap¬ 
proved  by  DRI;  therefore  you  should 
first  backup  your  distribution  version 
of  PIP  and  retain  it  for  use  in  the  un¬ 
likely  event  that  you  experience  a 
problem.  I  further  suggest  that  you 
exercise  extreme  caution  as  you  mod¬ 
ify  PIP  and  install  it  on  your  system. 
The  installation  steps  listed  here  are 
straightforward  and  shouldn’t  take 
more  than  a  few  minutes  to  enter;  but 
do  be  mindful  not  to  overwrite  any¬ 
thing  of  value. 

Listing  Two  (page  120)  is  the  step 
by  step  procedure  that  I  used  to  in¬ 
stall  the  null  line  patch  into  PIP  on 
my  system.  The  first  step  is  to  verify 
that  the  PIP  program  being  modified 
is  version  1.5.  This  is  the  only  version 
of  PIP  that  I  know  the  patch  to  work 
with  successfully.  To  verify  that  you 
have  the  right  version  of  PIP  load  PIP- 
.COM  with  SID  or  DDT  and  display 
memory  between  200h  and  240h.  At 
0233h  should  begin  the  version  num¬ 
ber,  which  must  be  1.5.  Next,  care¬ 
fully  enter  the  series  of  patches  listed. 
And  finally,  save  the  modified  pro¬ 
gram  back  onto  disk  under  a  new 
name  ready  for  testing. 

CP/M  Version  2.2 
Improved  Batch 
Procedures 

Batch  mode  automatic  sequential 
command  processing  under  CP/M 
2.2  using  SUBMIT  is  very  inconven¬ 
ient.  Especially  when  using  XSUB 
si  mply  to  pass  a  few  parameters  to  a 


program.  For  instance,  to  copy  a  few 
files  from  one  disk  to  another  re¬ 
quires  that  ED  or  some  other  text  edi¬ 
tor  first  be  used  to  build  a  disk  file  of 
the  desired  control  statements.  Then 
SUBMIT  must  be  run  to  build  yet  an¬ 
other  disk  file  of  the  very  same  con¬ 
trol  statements  but  in  a  format  suit¬ 
able  for  the  CCP  to  use.  The 
alternative  approach  is  to  enter  each 
control  statement  from  the  keyboard 
as  the  copy  program  asks  for  them. 
Which  is  fine  providing  not  too  many 
files  are  being  copied  or  you  have 
plenty  of  time. 

The  prospect  of  continually  being 
hampered  by  a  lengthy  process  when 
using  batch  procedures  prompted  me 
to  develop  a  stand-alone  $$$.SUB  file 
generator  called  BATCH.  A  source 
listing  for  BATCH  written  for  the 
Z80  is  given  in  Listing  One  (page 
114). 

The  BATCH  program  served  an¬ 
other  very  useful  purpose  as  well.  I 
was  in  the  process  of  developing  a 
CCP  replacement  in  conjunction  with 
a  Computer  Science  student  at  WSU 
that  was  to  have  a  built-in  SUBMIT 
file  generator.  Thus  it  was  important 
to  ensure  that  the  $$$.SUB  file  pro¬ 
cessor  within  the  CCP  replacement 
worked  properly.  Program  BATCH 
was  developed  for  this  purpose,  as 
well.  As  presented  here  it  may  be 
used  to  replace  the  combination  of 
ED  and  SUBMIT  for  simple  BATCH 
mode  command  processing. 

The  null  line  required  for  exiting 
from  PIP,  a  line  containing  a  CR  only, 
also  caused  us  a  problem  in  the  devel¬ 
opment  of  the  CCP  replacement.  It  is 
not  difficult  for  a  program  like 
BATCH  to  produce  a  null  line  in  a 
command  string,  but  the  processing 
of  a  null  line  by  the  $$$.SUB  file  pro¬ 
cessor  caused  problems.  That  is,  it  is 
most  difficult  for  the  $$$.SUB  file 
processor  to  distinguish  between  a 


null  line  intended  for  exiting  from 
PIP  and  one  to  designate  that  the  end 
of  the  $$$.SUB  has  been  found  with¬ 
out  a  lot  of  code  that  uses  an  exces¬ 
sive  amount  of  space. 

A  $$$.SUB  file  has  a  rather  tricky 
construction  with  one  command  per 
sector  stacked  in  reverse  order.  The 
$$$.SUB  processor  opens  the  SSS.SUB 
file,  goes  to  the  end  of  the  file,  reads  in 
the  last  sector,  decrements  the  record 
count  in  the  $$$.SUB  File  Control 
Block  (FCB),  closes  the  $$$.SUB  file, 
and  then  executes  the  command.  In 
other  words,  the  $$$.SUB  file  proces¬ 
sor  peels  commands  one  at  a  time 
from  the  end  of  a  $$$.SUB  file.  The 
crux  of  the  PIP  null  line  problem  is 
that  the  $$$.SUB  processor  quits  and 
erases  the  $$$.SUB  file  at  the  end  of 
the  file  or  if  the  character  count  is 
zero. 

In  order  to  make  XSUB,  PIP,  and 
the  CCP  replacement  function  effi¬ 
ciently  I  decided  to  take  a  stab  at 
modifying  PIP.  I  spent  an  evening  at 
home  disassemling  PIP  on  my  S-100 
buss  Z-80  based  system  running  un¬ 
der  CP/M  2.2.  I  have  a  very  good  dis¬ 
assembler  so  the  process  was  not  very 
difficult.  I  was  quite  amazed  by  the 
old  extra  code  for  paper  tape  han¬ 
dling  that  is  unused  but  still  retained 
within  PIP.  Needless  to  say,  I  was  not 
very  impressed  by  what  I  found  with¬ 
in  PIP.  However,  because  of  the  un¬ 
used  code,  it  was  simple  to  insert 
some  new  code  to  solve  the  null  line 
problem.  Extensive  testing  with 
BATCH,  XSUB,  PIP,  and  the  CCP  re¬ 
placement  indicated  that  the  fix 
worked  fine.  PIP  would  now  exit  on  a 
null  line  or  single  character  line.  The 
modified  version  of  PIP  would  only 
attempt  to  process  an  input  line  with 
two  or  more  characters. 

The  next  morning  while  shaving  it 
dawned  on  me  that  an  even  simpler  fix 
to  the  PIP  null  line  problem  was  possi- 


112 


R9R 


Dr.  Dobb's  Journal,  April  1985 


ble.  Since  it  was  Saturday  and  I  did 
not  have  to  go  to  work,  I  gave  my  new 
fix  a  try  with  the  same  results  as 
before. 

The  procedure  for  installing  the 
simple  PIP  fix  using  SID  is  shown 
here  in  Listing  Two.  PIP  uses  the 
buffered  line  function  for  input  and 


does  a  CPI  00  at  location  054Fh  to 
check  the  number  of  characters  en¬ 
tered.  The  JNZ  at  055  lh  causes  PIP 
to  exit  if  and  only  if  the  character 
count  is  zero.  That  is,  a  null  line  is 
required  to  exit  PIP.  A  simple  fix  in¬ 
volves  changing  the  CPI  00  to  a  SUI 
02  and  the  JNZ  to  a  JP.  PIP  will  now 


exit  if  the  character  count  is  less  than 
2.  This  modification  will  allow 
XSUB,  PIP,  CCP,  etc.,  to  all  work  in 
harmony. 

DD| 

Reader  Ballot 

Vote  for  your  favorite  feature/article. 

Circle  Reader  Service  IMo.  200. 


CP/M  Exchange  (Text  begins  on  page  112) 

Listing  One 


**  BATCH  version  2.1** 

A  program  to  generate  a  $$$.SUB  submit  file 
for  immediate  processing  under  CP/M  2.2  . 

--NOTES: 

1)  The  $$$ . SUB  file  is  created  on  disk  unit  A 
since  the  D.  R.  utility  XSUB  will  only  operate 
on  disk  unit  A.  If  BATCH  is  run  from  disk  B, 
for  example,  the  $$$.SUB  file  will  be  created 
on  disk  unit  A  and  then  processed  from  disk  A. 

All  files  referenced  in  the  submit  file  created 
by  BATCH  must  be  on  the  disk  unit  from  which 
BATCH  is  being  run  including  PIP  and  XSUB. 

2)  Use  DDT  to  Modify  PIP  as  follows  to  solve  the 
null  line  PIP  exit  problem:  change  053D  from 
2A  to  23,  0545  from  FE  to  D6,  0550  from  00  to 
02,  and  0551  from  C2  to  F2 .  Exit  DDT  by  entering 
GO,  and  then  do  a  SAVE  32  PIP.COM.  The  modified 
version  of  PIP  outputs  *t  as  an  input  request 
symbol  and  will  exit  on  a  null  or  single 
character  input  line. 

3)  Input  line  must  not  exceed  input  buffer  length 
which  is  128  characters. 

4)  Example  use  of  BATCH: 

>BATCH<RET> 

->XSUB/PIP/B : =A : XDIR . COM/X/DIR  A:/DIR  B : <RET> 
**  PROGRAM  DEFINITIONS  ** 


CR 

EQU 

ODH 

CARRIAGE  RETURN 

LF 

EQU 

0AH 

LINE  FEED 

BDOS 

EQU 

005H 

ADDR  CP/M  BDOS  FUNCTION 

ORG 

100H 

PROG  ORG 

START: 

LD 

HL , 00H 

ADD 

HL ,  SP 

GET  CP/M  STACK  POINTER 

LD 

(CPMSTK) , HL 

SAVE  IT 

LD 

SP, STACK 

SET  UP  NEW  STACK 

LD 

DE, PROMPT 

PTR  TO  SIGNON  MSG 

CALL 

PUTSTR 

SEND  STRING 

CALL 

SETDMA 

SET  DKS  BUF  ADDR 

CALL 

GETBUF 

GET  INPUT 

LD 

HL, INPBUF+1 

PTR  NUM  INP  CHRS 

LD 

A, (HL) 

GET  NUM  INP  CHRS 

LD 

C,  A 

SAVE  CHR  CNT  IN  C 

DEC 

A 

CK  IF  ANY  CHRS  IN  BUFF 

JP 

M , BCKCPM 

NO  CHRS,  BACK  TO  CP/M 

JP 

Z , BCKCPM 

NO  CHRS,  BACK  TO  CP/M 

LD 

A, OFFH 

GET  FF=START  OF  INPUT 

LD 

(HL) , A 

SET  CHR  CNT=FF 

LD 

A  ,  C 

GET  CHR  CNT  AGAIN 

INC 

A 

ADD  ONE  FOR  01  END  CHR 

;  — ADD 

A  TO  HL 

TO  FORM  ADDR  END  INP  STRING — 

CALL 

ADATHL 

LD 

(HL) ,01 

INDICATE  END  OF  INPUT 

; — SELECT  DISK  UNIT  A — 


( Continued  on  page  116) 


Dr.  Dobb’s  Journal,  April  1985 


114 


CP/MExchange 

Listing  One 


(Listing  Continued,  text  begins  on  page  112) 


CALL 

INIT 

CALL 

DELETE 

; DELETE  OLD  $$$.SUB 

CALL 

MAKE 

; MAKE  NEW  $$$.SUB 

;  SET  CURRENT  REC0RD=0 

LD 

HL, SUBFCB+32 

; HL=PTR  CUR  POS  IN  FCB 

XOR 

A 

LD 

(HL)  ,  A 

; — CONVERT  BUFFER  TO  UPPER  CASE  CHRS — 

SKIP  ANY  LEADING  BLANK  CHARS  IN  1ST  STRING — 

CALL 

CNVBUF 

LD 

HL, INPBUF+2 

; HL=PTR  INPUT  BUFF 

; --COUNT  CHRS  IN  INPUT  STRING 

AND  SET  HL  TO  END  — 

COUNT  CALL 

CNTCHR 

; — MAIN  COMMAND 

SCANNING  LOOP 

(RIGHT  TO  LEFT) — 

SCNLOP  LD 

A, (HL) 

; GET  CHR 

CP 

OFFH 

; CHECK  IF  DONE 

JR 

Z , DONE 

; QUIT ,  DONE 

LD 

C,0 

; BACKUP  TO  PREVIOUS  / 

SCNLP2  DEC 

HL 

; DEC  CHZ  PTR 

LD 

A, (HL) 

; GET  CHR 

INC 

C 

; INC  COUNT 

;--FF=BEGINNING 

(END)  OF  INPUT 

STRING  — 

CP 

OFFH 

; CHECK  IF  LAST  CMND 

JR 

Z , PUTCMN 

; PUT  LAST  COMMAND  IN  SUB  FILE 

; — /=BEG INNING 

(END)  OF  ONE  COMMAND  STRING — 

CP 

; CHECK  FOR  /  BETWEEN  COMMANDS 

JR. 

NZ , SCNLP2 

; LOOP  TILL  /  OR  FF 

; — PUT  COMMAND  INTO  SUBMIT  FILE — 
;  — USE  CP/M  DEFAULT  BUFFER  AREA— 


PUTCMN 

PUSH 

HL 

; SAVE  POINTER 

DEC 

C 

; DEC  FOR  /  CHR 

INC 

HL 

; SKIP  OVER  / 

LD 

A ,  C 

LD 

(  80H )  ,  A 

; PUT  LENGTH  IN  BUFFER 

LD 

DE,  81H 

CALL 

BLKMOV 

; MOVE  COMMAND  TO  BUFFER 

EX 

DE  ,  HL 

; SET  HL  TO  END  OF  COMMAND 

;  — ZERO 

REST 

OF  BUFFER — 

LD 

A,  127 

SUB 

A ,  C 

LD 

B,  A 

; SET  LOOP  COUNT 

LD 

C,0 

ZROLOP 

LD 

(  HL  j  ,  C 

INC 

HL 

DJNZ 

ZROLOP 

; — WRITE  ONE 

SECTOR — 

CALL 

WRITE 

; WRITE  BUFF  TO  SUB  FILE 

POP 

HL 

; GET  POSITION  POINTER  AGAIN 

INC 

A 

; CHECK  FOR  WRITE  ERROR 

JR 

NZ, SCNLOP 

; REPEAT  TILL  DONE. 

.■—DISK 

ERROR 

— 

LD 

DE , NSPMSG 

JP 

SERMSG 

;  — DONE 

BACK 

TO  CP/M — 

; — DO  A 

SYS  RESET  TO  START 

SUBMIT — 

DONE: 

CALL 

CLOSE 

JP 

OOOH 

— COUNT  THE  CHARS  IN  THE  COMMAND — 


;  MOVE 

HL  TO 

END  OF  COMMAND 

CNTCHR : 

LD 

C,  1 

;  INIT 

CNT=1 

CNTLOP : 

LD 

A,  (HL) 

CP 

ODH 

;  END 

IF 

CR 

RET 

Z 

CP 

OlH 

;  END 

IF 

CHR— OlH 

RET 

Z 

INC 

C 

INC 

HL 

JR 

CNTLOP 

; — ADD  A  TO  HL — 


(Continued  on  page  118) 


116 


Dr.  Dobb's  Journal,  April  1985 


CP/M  Exchange  (Listing  Continued,  text  begins  on  page  112) 

Listing  One 


ADATHL  ADD  A , L 

LD  L,  A 

RET  NC 

INC  H 

RET 

— BLOCK  MOVE — 

;  h:.=source,  de=destination,  c=nomber  of  chars 

BLKMOV  LD  B,OOH  ; ZERO  B 

LDIR 
RET 


; — CONVERT  BUFFER  TO  UPPER  CASE — 


CNVBUF 

LD 

HL, INPBUF+2 

LD 

D,H 

LD 

E,L 

.--SKIP 

LEADING 

BLANK  CHARS— 

SKPLOP 

LD 

A,  (HL) 

CP 

1  1 

JR 

NZ, CNVLOP 

INC 

HL 

JR 

SKPLOP 

CNVLOP 

CALL 

GETCHR 

LD 

(DE) ,A 

; MOVE  CHR 

INC 

DE 

INC 

HL 

CP 

OlH 

;  END  ? 

JR 

NZ , CNVLOP 

RET 

•--GET 

NEXT  CHAR  AND  TO  CONV 

UPPER  CASE — 

GETCHR 

LD 

A,  (HL) 

; GET  CHR 

CP 

07BH 

; ABOVE  z  ? 

RET 

NC 

CP 

061H 

; BELOW  a  ? 

RET 

C 

AND 

05FH 

; LOW  TO  UP 

RET 

■—GET 

BUFFERED 

LINE  OF  COMMANDS — 

GETBUF : 

LD 

DE, INPBUF 

; INP  BUFF  ADDR 

LD 

C,  10 

CALL 

BDOS 

RET 

1 --SET 

DISK  BUFFER  ADDRESS (DMA) — 

SETDMA 

LD 

DE , 080H 

; BUFF  ADDR 

LD 

C,  26 

CALL 

BDOS 

RET 

; — WRITE  CONTENTS  OF  BUFF  TO 

SUB  FILM— 

WRITE: 

LD 

DE, SUBFCB 

LD 

C,  21 

CALL 

BDOS 

RET 

■. — CLOSE  $$$ . SUB — 

CLOSE 

LD 

C,  16 

LD 

DE . SUBFCB 

CALL 

BDOS 

RET 

,’  — MAKE 

:  NEW  $$$ 

■SUB  FILE — 

MAKE: 

LD 

DE, SUBFCB 

LD 

C,  22 

;  MAKE 

CALL 

BDOS 

INC 

A 

; FF=NO  SPACE 

JP 

Z , NOSERR 

; NO  SPACE  ERROR 

RET 


; — DELETE  ANY  OLD  $$$.SUB  FILE — 
DELETE:  LD  DE , SUBFCB 

LD  C,19 


(Continued  on  page  120) 


>r.  Dobb's  Journal,  April  1985 


118 

331 


v-* /  /VI  CXtnange  (Listing  Continued,  text  begins  on  page  1 12) 

Listing  One 


CALL  BDOS 
RET 


; — INIT  AND  SELECT  DISK  A — 

INIT:  LD  C , 1 3  ; RESET 

CALL  BDOS 

RET 


— SEND  STRING  TO  TERMINAL — 

— DE  =  POINTER  TO  STRING  ADDRESS — 


PUTSTR 

LD 

C ,  9 

CALL 

BDOS 

RET 

.•—MESSAGES  — 

PROMPT 

DEFB 

CR , LF , 'BATCH  v2 . 1  Submit  File  Generator' 

DEFB 

CR, LF,  1  for  use 

with  XSUB  under  CP/M  2.2  .' 

DEFB 

CR,LF, 'Enter  Command  String  and  then  <RET>  . 

DEFB 

CR, LF, ' Separate  Each  Command  with  a  /  Symbol 

DEFB 

CR , LF , '/X  may 

be  used  to  exit  modified  versi< 

DEFB 

CR , LF , 

NSPMSG 

DEFB 

CR,LF,'Disk  Full — No  More  Space' 

DEFB 

CR,LF,07H,07H, 

'$' 

NDSMSG 

DEFB 

CR , LF ,' Directory  Full— No  More  Space' 

DEFB 

CR , LF , 07H , 07H , 

'$' 

NOSERR 

LD 

DE, NDSMSG 

; --SEND 

ERROR 

MESSAGE  AND  BACK 

TO  CP/M— 

SERMSG 

CALL 

PUTSTR 

BCKCPM 

LD 

HL, (CPMSTK) 

LD 

SP ,  HL 

; RESET  STACK  POINTER 

RET 

; BACK  TO  CP/M 

FILE  CONTROL  BLOCK-- 

SUBFCB 

DEFB 

OH 

; DRIVE  CODE 

DEFB 

'$$$  SUB' 

; FILE  NAME 

DEFB 

OH, OH, OH, OH 

DEFS 

17 

; --CP/M 

STACK 

STORAGE— 

CPMSTK 

DEFW 

00H 

INPUT  BUFFER  AREA — 

INPBUF 

DEFB 

128 

; BUF  LENGTH 

BUFCNT 

DEFB 

CH 

; CHRS  IN  BUF 

DEFS 

128 

STACK  AREA- 

•- 

DEFS 

20 

; SIZE  OF  STACK 

STACK 

EQU 

$ 

; BOTTOM  OF  STACK 

END 


End  Listing  One 


Listing  Two 

B>SID  PIP.COM 

CP/M  3  SID  -  Version  3.0 

NEXT  MSZE  PC  END 

IE00  .1 E00  0100  C8FF 

#0200,240 


0200 

20 

20 

20 

43 

4F 

50 

59 

52 

49 

47 

48 

54 

20 

28 

43 

29 

COPYRIGHT  (C) 

0210 

20 

31 

39 

37 

39 

2C 

20 

44 

49 

47 

49 

54 

41 

4C 

20 

52 

1979,  DIGITAL  R 

0220 

45 

53 

45 

41 

52 

43 

48 

2C 

20 

20 

50 

49 

50 

20 

56 

45 

ESEARCH,  PIP  VE 

0230 

52 

53 

20 

31 

2E 

35 

03 

01 

06 

01 

00 

24 

24 

24 

20 

20 

RS  1.5 . $$$ 

0240  20 
#S053D 
053D  2 A  23 
053E  CD  . 

#S054F 
054F  FE  D6 
0550  00  02 
0551  C2  F2 
0552  5E  . 

#WPIPNEW . COM ,100, 1E00 
003Ah  record(s)  written. 

#G0  r  .  . .  .. 

End  Listings 


120 


Dr.  Dobb's  Journal,  April  1985 


by  R.  P.  Sutherland 


Hardcopy 

Three  articles  in  this  issue  illustrate 
techniques  to  generate  various  non¬ 
standard  printed  characters.  Despite 
their  present  utility,  they  will  soon 
seem  curious  and  quaint.  The  prolifer¬ 
ation  of  the  Macintosh  and  Mac-alike 
machines  as  well  as  the  porting  of 
Donald  Knuth’s  TEX  to  micros  (in 
tandem  with  the  availability  of  laser 
printers)  will  result  in  easier  ways  of 
coaxing  two  and  three  dimensional 
graphic  concepts  into  hardcopy.  The 


Figure  below  shows  a  sample  of  out¬ 
put  f  ro  n  TgX  as  well  as  a  list  of  ven¬ 
dors  wno  carry  micro  implementa¬ 
tions. 


Publications 

Privacy  Journal:  An  Independent 
Monthly  on  Privacy  in  the  Computer 
Age  contains  the  seeds  of  the  antidote 
to  1984.  Subscriptions:  $89.00  per 
year,  $  1 09.00  overseas.  Contact  Kath¬ 
ryn  Ritter,  Privacy  Journal,  RO.  Box 


15300,  Washington,  D.C.  20003 
(202)  547-2865.  Reader  Service  No.  101. 

Apple  Access:  User’s  Guide  to  Ap¬ 
ple  Computer-Related  Periodical 
Literature  for  $19.95  is  a  reference 
for  Apple  literature.  Contact  Lee 
Webber,  Stony  Point  Publications, 
Box  4467,  Petaluma,  CA  94953  (707) 
778-8754.  Reader  Service  No.  103. 

The  Datapro  Complete  Guide  to 
Dial-Up  Databases  profiles  more 
than  a  thousand  accessible  data  bases 
in  over  200  subjects.  Contact  Karen 
Morrell,  Datapro  Research  Corpora- 


“small”  TgpC  implementations 

Computer 

Processor 

Contact 

Organization,  Address 

Hewlett-Packard  3000 

16-bit 

Lance  Carnes 

TeXdf,  163  Linden  Lane,  Mill  Valley,  CA  94941;  415-388-8853 

Hewlett-Packard  1000 

16-bit 

John  Johnson 

JDJ  Wordware,  Box  354,  Cupertino,  CA  95015;  415-965-3245 

DEC  PDP-H/44 

Plexus,  Onyx 

IBM  PC 

16-bit 

Z8000 

8086/88 

Dick  Gauthier 

TYX,  11250  Roger  Bacon  Dr.,  Suite  16,  Reston,  VA  22090; 
703-471-0233 

Apollo 

MC68000 

Thom  Hickey 

Bill  Gropp 

Pierre  Clouthier 

OCLC,  Box  7777,  Dublin  OH  43017;  614-764-6075 

Dept,  of  Computer  Science,  Yale  University,  Box  2158,  Yale 
Station,  New  Haven,  CT  06520;  203-436-3761 

COS  Information,  6272  Notre  Dame  West,  Montreal,  H4C  1V4, 
P.Q.;  514-935-4222 

Hewlett-Packard  9836 

MC68000 

aim  Crumly 

Hewlett-Packard,  Box  15,  Boise,  ID  83707;  208-376-6000  x2869 

Sun  Microsystems 

MC68000 

Jim  Sterken 

Rich  Furuta 

Textset,  Box  7993,  Ann  Arbor,  Ml  48107;  313-996-3566 

University  of  Washington,  Computer  Science,  FR-35,  Seattle, 
WA  98195;  206-543-7798 

Cyb 

MC68000 

Norman  Naugle 

Mathematics  Dept.,  Texas  A&M  University,  College  Station,  TX 
77843;  409-845-3104 

Apple  Macintosh,  Lisa 

MC68000 

Barry  Smith 

Dave  Kellero. 

Kellerman  &  Smith,  2343  SE  45th  Av.,  Portland,  OR  97215; 
503-232-4799 

Masscomp 

MC68000 

Bart  Childs 

Dept,  of  Computer  Science,  Texas  A&M  University,  College 
Station,  TX  77843;  409-845-5470 

Synapse 

MC68000 

Dick  Wallenstein 

Comoon,  5  Underwood  Ct.,  Delran,  NJ  08075; 

609-764-1720 

PERQ/ICL 

Jaap  van 't  Ooster 

Oce,  St.  Urbanusweg  43,  5900  MA  Venlo,  Holland 

IBM  PC,  XT,  AT 

8088,  80286 

Lance  Carnes 

TeXdT,  163  Linden  Lane,  Mil'  Valley,  CA  94941;  415-388-8853 

IBM  XT,  AT 

8088,  80286 

David  Fuchs 

DeDt.  of  Computer  Science,  Stanford  University,  Stanford,  CA 
94505 

IBM  XT,  AT 

8088,  80286 

Ronny  Bar-Gadda 

446  College  Av.,  Palo  Alto,  CA  94306;  415-326-1275 

Figure 


Dr.  Dobb' s  Journal,  April  198:- 


122 

333 


tion,  1805  Underwood  Boulevard, 
Delran,  NJ  08705  (800)  257-9406 
(in  New  Jersey  609/764-0100). 
Price:  $145.00.  Reader  Service  No.  105. 

Frank  Gaude’s  Z-System  Newslet¬ 
ter  for  ZCPR3  users  announces  a  Z- 
system  electronic  bulletin  board  at 
(213)  670-9465.  Echelon,  Inc.,  101 
First  Street,  Los  Altos,  CA  94022. 

Reader  Service  No.  107. 

MSX  World:  The  Newsletter  Dedi¬ 
cated  to  the  World  of  MSX  Comput¬ 
ers  is  published  by  MSX  World,  39  W. 
32nd  Street,  Suite  800,  New  York, 
NY  10001.  Reader  Service  No.  109. 


c 

A  demonstration  of  the  Safe  Runtime 
Analyzer  (Catalytix  Corporation)  is 
available  at  (617)  497-4829:  1200 
baud,  Unix  System  III,  login  is  safec, 
password  is  catalytix.  The  demon¬ 
stration  allows  one  to  upload  C  pro¬ 
grams  for  analysis  by  the  Safe  C 
Runtime  Analyzer,  twenty-four 
hours  a  day.  Contact  Janice  Tosi,  Ca¬ 
talytix  Corporation,  55  Wheeler 
Street,  Cambridge  MA  02138  (617) 
497-2160.  Reader  Service  No.  111. 

The  C  lib,  a  C  function  library,  pro¬ 
vides  over  200  routines  including 
functions  to  convert  floating  point 
numbers  from  MicroSoft  to  8087 
NDP  format  and  vice  versa.  Price: 
$145.00  from  Vance  Info  Systems, 
2818  Clay  Street,  San  Francisco,  CA 
94115  (415)  922-6539.  Contact:  Car¬ 
la  Radosta.  Reader  Service  No.  113. 

A  mathematics  library  compatible 
with  most  C  compilers  is  available  for 
$100.00  from  Micro  International, 
Box  47,  Route  36,  East  Fairfield,  VT 
05448  (802)  827-3827.  Contact:  Ber¬ 
nard  Hussels.  Reader  Service  No.  115. 


MSDOS 

A  special  version  of  Microsoft’s 
MSDOS  2.11  operating  system  is 
available  for  S-100  computers.  SB- 
86,  from  Lifeboat  Associates,  re¬ 
quires  a  CompuPro  System  Support  I 
card  with  G086  EPROM,  and  at  least 
64K  of  24-bit  addressable  RAM.  The 
installation  routine  supports  both  in¬ 
terrupt-driven  and  non-interrupt- 
driven  console  I /O.  The  price  of  SB- 


86  is  $275.00,  available  from 
Lifeboat  Associates,  1651  Third  Ave¬ 
nue,  New  York,  NY  10128  (212) 
860-0300.  Contact:  Doug  Barth. 

Reader  Service  No.  117. 

MasterForth  Version  1.0  provides 
Forth-83  for  the  IBM  PC  family. 
MasterForth  includes  an  8088  mac¬ 
ro-assembler  and  full  interface  to 
MSDOS  2.1.  Price  $125.00  from  Mi- 
croMotion,  12077  Wilshire  Boule¬ 
vard  #506,  Los  Angeies,  CA  90025 
(213)  821-4340.  Contact:  Linda 

Kahn.  Reader  Service  No.  119. 

MJ,  from  Mother  Jones’  Son’s 
Software  Corporation,  is  a  utility  that 
co-exists  with  MSDOS  and  permits 
one  to  open  a  window  and  reassign  a 
key  up  to  a  thousand  keystrokes.  Also, 
MJ  allows  one  to  speed  up  the  screen 
cursor  in  five  increments  and  to  ex¬ 
pand  the  keystroke  buffer  from  six¬ 
teen  characters  to  over  a  thousand. 
MJ  is  not  copy  protected  but  if  you 
abuse  your  backup  privilege,  you  for¬ 
feit  your  soul  to  the  author  of  the  pro¬ 
gram.  Price:  $30.00  for  object  pro¬ 
gram,  or  $70.00  for  object  program 
and  assembly  language  source  code. 
Mother  Jones’  Son’s  Software  Corpo¬ 
ration,  6310  Caballaro  Boulevard, 
Buena  Park,  CA  90620  (714)  522- 
7762.  Reader  Se -vice  No.  121. 

Microcomputer  owners  who  depre¬ 
ciate  equipment  for  tax  reasons  are 
often  approached  by  the  IRS  and 
asked  to  account  for  every  minute 
spent  on  the  computer.  TaxLog  is  a 
product  that  keeps  track  of  how 
much  time  one  spends  on  various  ac¬ 
tivities.  TaxLog  calculates  the  per¬ 
centage  of  tax  deductible  use  and 
prints  a  choice  of  reports  that  meet 
tax  code  requirements.  Taxlog  is  part 
of  Bellsoft’s  “Pop-Up”  line  of  desk 
tools.  TaxLog  costs  $39.95  from  Bell- 
soft,  2820  Northrup  Way,  Bellevue, 
WA  98004  (206)  828-7282.  Contact: 

Richard  Leeds.  Reader  Service  No.  123. 


Macintosh 

The  Public  Domain  Exchange  an¬ 
nounces  over  a  hundred  public  do¬ 
main  programs  for  the  Macintosh.  A 
listing  of  programs  is  available  for 
$2.00.  An  introductory  set  of  the 
three  most  popular  disks  and  a  cata- 


125 

334 


Dr.  Dobb's  Journal,  April  1985 


log  is  $28.00.  The  Public  Domain  Ex¬ 
change,  673  Hermitage  Lane,  San 
Jose,  CA  95134  (408)  942-0309. 
Contact:  Judy  Rosenthal.  Reader  Ser- 
vice  No.  125. 

PortaAPL  is  now  available  for  the 
Macintosh  (512K).  Price:  $275.00. 
Portable  Software,  60  Aberdeen  Av¬ 
enue,  Cambridge,  MA  02138  (617) 
547-2918.  Contact:  Richard  Smith. 

Reader  Service  No.  127. 


Hard  Disks 

For  a  year  now  we  have  been  trying 
to  wear  out  a  Davong  10  megabyte 
hard  disk  by  driving  our  Davong 
Multilink  system  (our  in-house  LAN) 
24  hours  a  day.  So  far  it  has  been  so 
reliable  that  we  have  become  careless 
about  backing  anything  up!  Davong 
now  has  an  86-megabyte  hard  disk 
for  the  IBM  PC.  Davong’s  hard  disk 
for  the  Macintosh  is  called  Mac  Disk. 
Mac  Disks  range  from  10  to  43  me¬ 
gabytes  and  come  with  software  that 
allows  the  user  to  configure  and 
maintain  multiple  volumes.  Contact 
John  Houthton,  Davong  Systems, 
Inc.,  217  Humboldt  Court,  Sunny¬ 
vale,  CA  94089  (408)  734-4900. 

Reader  Service  No.  1 29. 

Data  Technology’s  TeamMate  line 
of  hard  disks  includes  a  Kodak-based 
3.3  megabyte  5  1/4  inch  floppy.  I 
found  the  TeamMate  hard  disk  easy 
to  install  and  reliable.  The  Kodak 
floppy  can  be  used  to  back  up  the 
hard  disk  or  it  can  be  used  as  an  addi¬ 
tional  volume.  The  3.3  megabyte 
floppy  makes  sense  as  a  replacement 
to  the  AT’s  1.2  megabyte  drive  be¬ 
cause  it  more  than  doubles  the  capac¬ 
ity  and  it  can  write  48  TPI  floppies 
more  reliably  than  the  AT’s  drive. 
Data  Technology  Corporation,  2775 
Northwestern  Parkway,  Santa  Clara. 
CA  95051  (408)  496-0434.  Contact: 
Steve  Roberts  Reader  Service  No.  131. 


Single  Board  Computers 

ForthCard,  a  single  board  computer 
on  the  STD  bus  with  Forth  kernel 
built  in,  features  on-board  FJROM 
programming  and  supports  compila¬ 
tion  of  code  from  another  computer. 
ForthCard  is  $499.00  in  single  quan¬ 


Dr.  Dobb's  Journal,  April  1985 


tities.  An  OEM  ForthCard  is  also 
available.  Contact:  Ken  Arnold,  Hi¬ 
Tech  Equipment  Corporation,  9560 
Black  Mountain  Road,  San  Diego, 
CA  92126  (619)  566-1892.  Reader 

Service  No.  133. 

Motel  Computers  Ltd.  has  a 
68000/Z80  based  single  board  com¬ 
puter  called  CYPHER.  Motel  Com¬ 
puters  Limited,  174  Betty  Ann  Drive, 
Willowdale,  Ontario  M2N  1X6 
(416)  221-2340.  Contact:  Victor 

Pierobon.  Reader  Service  No.  135. 


Sundries 

Maynard  Electronics,  Inc.,  offers  a 
board  that  enables  an  IBM  PC  to  run 
programs  in  DOS  3.0  that  take  ad¬ 
vantage  of  extended  memory.  There 
are  no  software  drivers  required  and 
the  company  claims  the  system  runs 
as  fast  as  the  AT.  Maynard  Electron¬ 
ics,  430  Semoran  Boulevard,  Suite 
207,  Casselberry,  FL  32707  (305) 
331-6402.  Contact:  David  Knapp. 

Reader  ServiceNo.  141. 

The  Public  Domain  Users  Group  has 

expanded  to  include  Commodore  64 
users.  For  information  and  listings  of 
the  library  disks,  send  SASE  to  Public 
Domain  Users  Group,  P.O.  Box 
1442,  Orange  Park,  FL  32067.  Reader 

Service  No.  137. 

CominuniTree  Group’s  First  Edi¬ 
tion  (for  IBM  PC)  is  a  program  that 
lets  people  create  dial-up  information 
and  conferencing  services.  First  Edi¬ 
tion  can  be  used  on  PBX  and  PABX 
systems.  The  San  Francisco  Com- 
muniTree  Group  provides  an  on-line 
newsletter  and  update  service.  “The 
FIG  Tree,”  in  Hayward,  California,  is 
a  network  for  Forth  programmers 
(415)  538-3580.  First  Edition  is 
priced  at  $250.00.  CommuniTree 
Group,  1 1 50  Bryant  Street,  San  Fran¬ 
cisco,  CA  94103  (415)  861 -TREE. 

Reader  Service  No.  139. 

DD) 

Reader  Ballot 

Vote  for  your  favorite  feature/article. 

Circle  Reader  Service  No.  201 . 


126 

335 


Dr.  Dobb's  Journal 


Editorial 

Editor-in-Chief  Michael  Swaine 

Editor  Randy  Sutherland 
Managing  Editor  Frank  DeRose 
Technical  Editor  Alex  Ragen 
Editorial  Assistant  Sara  Noah 
Contributing  Editors  Robert  Blum. 

Dave  Cortesi. 

Ray  Duncan. 

Allen  Holub 

Copy  Editor  Rhoda  Simmons 
Typesetter  Jean  Aring 
Production 
Design  /  Production 

Director  Detta  Penna 
Art  Director  Shelley  Rae  Doeden 
Production  Assistant  Alida  Hinton 

Cover  Benoit  Mandelbrot 

Advertising 

Advertising  Director  Stephen  Friedman 
Advertising  Sales  Walter  Andrzejewski, 
Shawn  Horst, 

Beth  Dudas, 

Michele  Beaty 
DDJ  Classifieds  Alice  Abrams 
Advertising  Coordinators  Lisa  Boudreau 
Jay  Horvath 
Circulation 

Fulfillment  Mgr.  Stephanie  Barber 
Subscription  Mgr.  Maureen  Snee 
Book  Marketing  Mgr.  Jane  Sharninghouse 
Single  Copy  Sales  Mgr.  Kathleen  Boyd 
Administration 
Finance  Manager  Sandra  Dunie 
Business  Manager  Betty  Trickett 
Accounts  Payable  Supv.  Mayda  Lopez-Quintana 
Accounts  Payable  Asst.  Denise  Giannini 
Billing  Coordinator  Laura  Di  Lazzaro 


M&T  Publishing,  Inc. 

Chairman  of  the  Board  Otmar  Weber 
Director  C.  F.  von  Quadt 
President  Laird  Foshay 


Entire  contents  copyright  ®  1985  by  M&T  Publishing, 
Inc.  unless  otherwise  noted  on  specific  articles.  All 
rights  reserved. 

Dr.  Dobb’s  Journal  (USPS  307690)  is  published 
monthly  by  M&T  Publishing,  Inc.,  2464  Embarcadero 
Way,  Palo  Alto,  CA  94303,  (415)  424-0600.  Second 
class  postage  paid  at  Palo  Alto  and  at  additional  entry 
points. 

Address  correction  requested.  Postmaster:  Send  Form 
3579  to  Dr.  Dobb's  Journal ,  2464  Embarcadero 
Way,  Palo  Alto,  CA  94303.  ISSN  0278-6508 

Subscription  Rates:  $25  per  year  within  the  United 
States,  $44  for  first  class  to  Canada  and  Mexico,  $62 
for  airmail  to  other  countries.  Payment  must  be  in  US. 
Dollars,  drawn  on  a  U  S.  Bank.  For  subscription  prob¬ 
lems,  call:  outside  of  CA  1-800-321-3333;  within  CA  I- 
619-485-6535. 

Foreign  Distributors:  ASCII  Publishing,  Inc.  (Ja¬ 
pan),  Computer  Services  (Australia),  Computer  Store 
(New  Zealand),  Computercollectief  (Nederland), 
Homecomputer  Vertriebs  GMBH  (West  Germany), 
International  Presse  (West  Germany),  La  Nacelle 
Bookstore  (France),  McGill’s  News  Agency  PTY  LTD 
(Australia),  Progresco  (France). 


People's  Computer  Company 

Dr.  Dobb's  Journal  is  published  by  M&T  Publishing, 
Inc.  under  license  from  People’s  Computer  Company, 
2682  Bishop  Dr.,  Suite  107,  San  Ramon.  CA  94583,  a 
non-profit,  educational  corporation. 


May  1985 
Volume  10,  Issue  5 


CONTENTS 


Cover  Artist 

Our  cover  “Fractal  Dragon,”  is  the  creation  of  Benoit  Mandelbrot,  Professor 
of  the  Practice  of  Mathematics  at  Harvard  University.  The  most  conspicuous 
characteristic  of  this  dragon  is  that  the  eye  and  the  imagination  distinguish  in 
it,  in  addition  to  the  overall  shape,  an  infinity  of  smaller  features  of  every  size 
down  to  the  infinitesimal.  For  geometric  shapes  of  this  kind.  Professor  Man¬ 
delbrot  coined  the  term  “fractal.”  “Fractal  Dragon”  is  a  portion  of  “attrac¬ 
tor”  that  occurs  in  a  problem  of  dynamics.  The  cover  illustration  is  a  repro¬ 
duction  of  Plate  C5  of  Mandelbrot’s  book  The  Fractal  Geometry  of  Nature 
(Freeman  1982),  drawn  using  computer  programs  by  Mark  R.  Laff  and  V. 
Alan  Norton.  Copyright  1982  by  B.  B.  Mandelbrot. 

Referees  Who  Assisted  With  This  Issue 

Ted  Carnevale,  State  University  of  New  York  at  Stony  Brook 

Allen  Holub,  DDJ  Contributing  Editor 

Robert  Tripp,  Editor-in-Chief  of  the  late  MICRO 

James  Woomer,  Commodore-64  Guru  and  Gazebo  Builder 


See  Richard  Rylander's  " Solid  Shape  Drawing  on  the  Commodore  64"  page  50. 


4 

338 


Dr.  Dobb's  Journal,  May  1985 


SOFTWARE  T  POLS  FOR  ADVANCED  PROGRAMMERS 


Dr.  Dobb’s  Journal 


ARTICLES 


Using  Decision  Variables  in  Graphics 

Primitives 

by  Tom  Hogan 

Solid  Shape  Drawing  on  the  Commodore  64 

by  Richard  Rylander 


40  An  algorithm  that  uses  the  Decision  Variable  Method  for 
plotting  ellipses.  (Reader  Ballot  No.  193). 

50  Some  new  approaches  to  computer-generated  images  on 
small  systems.  Combining  elementary  shapes,  using  the 
polygon  mesh  technique,  creating  shading  effects.  (Reader 
Ballot  No.  194). 


A  Compiler  Written  in  Prolog 

by  G.  A.  Edgar 


84  A  compiler  for  VALGOL  1  written  in  Micro-PROLOG. 

(Reader  Ballot  No.  1 95). 


COLUMNS 


Dr.  Dobb's  Clinic 

by  D.  E.  Cortesi 

16 

A  wood  blocks  program  for  the  Atari;  keyboards  with  N- 
key  rollover;  DOS  services  and  device  drivers.  (Reader 
Ballot  No.  190). 

Realizable  Fantasies 

by  D.  E.  Cortesi 

22 

The  Resident  Intern  reflects  on  hackers,  radicals,  the 
microcomputer  revolution  and  the  role  that  DDJ  will  play 
in  fostering  that  revolution.  “Down  with  Cybercrud!” 

Reader  Ballot  No.  1 91 ). 

C  Chest 

by  Allen  Holub 

28 

A  general  purpose  command  line  processor.  (Reader  Ballot 
No.  192). 

1 6-Bit  Software  Toolbox 

by  Ray  Duncan 

118 

68000  square  root  routine;  MacFeedback;  MSDOS  device 
drivers;  Microsoft  Assembler  Bug  of  the  Month.  (Reader 
Ballot  No.  196) 

DEPARTMENTS 


Editorial 

6 

Letters 

8 

Reviews 

98 

Software  Toolworks’  C/80  and  C/80  Mathpack; 
AMPRO’s  Little  Board  and  Bookshelf  Computer;  New 
Generation  Systems’  Diskmaker  I;  Summit  Software’s 
BetterBASIC. 

DDJ  Classifieds 

97 

Advertiser  Index 

128 

Dr.  Dobb's  Journal,  May  1985 


5 

339 


EDITORIAL 


want  to  comment  on  what  others  have  been  saying  about  two  subjects  ger¬ 
mane  to  this  or  any  issue  of  DDJ:  graphics  and  copy-protected  disks. 

Frank  Gaude  regards  the  iconic  interface  of  the  Mac  and  its  imitators  as  a 
regression  to  preliterate  cave-scratchings.  In  his  Z-News  he  says,  “Long  ago  we 
used  hieroglyphs  to  communicate.  Then  after  a  struggle  an  alphabet  developed. 
Now  we  go  back  to  pictures:  icons.  How  many  glyphs  can  we  remember?” 

In  the  macaphony  of  the  current  iconolatry  this  cautionary  message  is  wor¬ 
thy  of  attention,  but  I  suggest  that  it’s  not  the  whole  story.  The  function  of 
software  is  to  realize  metaphors.  Icons  are  capable  of  doing  that  well  but  are 
no  help  in  stepping  outside  existing  metaphors  to  invent  new  ones;  no  help, 
that  is,  in  developing  new  software.  I  would  agree  that  icons  are  mnemonic  for 
users  and  superfluous  for  programmers  and  let  it  go  at  that,  but  for  the  fact 
that  I  have  recently  seen  a  prototype  of  a  system  that  takes  icons  a  step 
further,  a  system  in  which  actions  can  be  assigned  user-created  visual  tokens, 
and  can  be  combined  in  ways  that  visually  and  kinaesthetically  convey  the 
form  of  the  combination.  Active,  growing  icons. 

Furthermore,  while  the  Mac  icons  are  static  tokens  rather  than  elements  of 
a  true  language,  that  is  not  true  of  all  computer  graphic  aids  to  communica¬ 
tion.  French  researchers  are  currently  developing  the  means  for  transmitting 
sign  language  over  telephone  lines  at  commercial  data  rates.  Their  idea  is  to 
extract  from  a  sequence  of  video  frames  just  the  essential  components  of  a 
gesture  and  pass  those  along.  This  does  not,  I  should  make  clear,  mean  match¬ 
ing  the  gesture  to  some  template  from  a  database  of  acceptable  gestures; 
rather  it  means  producing  a  real-time  cartoon  version  of  the  gesturing  party. 
The  cartoons,  subject  to  idiomatic  and  dialectical  variations,  are  truly  linguis¬ 
tic  graphic  elements,  which  is  just  what  icons  aren’t. 

As  for  copy-protected  disks  .... 

“. .  .  imagine  yourself  buying  a  new  car.  You  have  narrowed  your  choice  to 
two  cars,  identical  in  all  respects  except  that  one  will  travel  100  mph  and  the 
other  has  a  governor  on  it  and  cannot  go  past  55.  Which  car  would  you 
buy?” — Michael  D.  Brown,  Central  Point  Software,  producer  of  Copy  II. 

“People  can  rest  assured  that.. .we  will  always  offer  versions  of  our  products 
that  are  not  copy-protected. “ — Philippe  Kahn,  Borland  International. 

“Companies  like  Multimate  and  Borland  have  the  right  idea.  Lotus, 
Ashton-Tate  and  MicroPro  are  the  bad  guys.” — Edward  Messerly,  SF  region¬ 
al  administrator  of  the  GSA. 

“. . .  a  growing  number  of  publishers  are  dropping  traditional  copy-protec¬ 
tion  methods,  which  they  conclude  have  created  ill-will  and  inconvenience  for 
users  ....  Among  the  publishers  who  have  abandoned  copy-protection 
schemes  is  MicroPro ....  Many  software  publishers,  however,  hope  to  see 
new  protection  methods  . . .  .”-  Time/Life  Access:Apple. 

“These  people  are  nutty.” — Edward  Messerly. 

Yes.  Something  is  nutty  when  a  vendor  can  brag  about  also  selling  non-broken 
goods.  It’s  really  simple,  isn’t  it?  Vendors  of  software  who  want  to  stay  ven¬ 
dors  of  software  must  find  ways  to  profit  from  their  efforts  without  breaking 
the  tools  they  sell.  The  ability  to  copy  files  and  disks  is  a  facility  I  bought  with 
my  hardware  and  operating  system.  To  try  to  sell  me  a  program  designed  to 
subvert  my  system  and  hamper  its  operation  is  to  try  to  enlist  me  in  vandalism 
against  my  own  property.  Even  granting  that  copy-crippling  is  a  morally  de¬ 
fensible  policy,  how  could  anyone  believe  that  it  was  commercially  viable  in 
the  long  run?  Nutty. 

Michael  Swaine 


6 

340 


Dr.  Dobb  s  Journal,  May  1985 


Anorexia  Cured 

Dear  DDJ: 

I  read  with  some  interest  the  article 
‘Fatten  Your  Mac’  that  appeared  in 
your  January  [1985,  #99]  issue  and 
forthwith  did  a  fattening  on  my  Mac¬ 
intosh.  There  are  a  number  of  points 
that  I  would  like  to  add  to  the  article, 
however.  Firstly,  a  fine  supplier  of  the 
256K  DRAM  chips  was  omitted.  1  re¬ 
fer  to  Microprocessors  Unlimited,  of 
Beggs,  Oklahoma,  (918)  267-4961.  I 
was  able  to  phone  them  late  Friday, 
and  the  chips  they  sent  out  on  Mon¬ 
day  arrived  here  (in  Seattle)  on  Tues¬ 
day.  I  was  quite  pleased  with  the  price 
and  the  service.  They  accept  Visa,  and 
they  even  sent  a  brief  note  explaining 
proper  handling  of  the  chips. 

Secondly,  it  is  not  necessary, 
though  it  may  be  desirable,  to  destroy 
the  64K  DRAM  chips.  I  desoldered 
the  original  chips  with  an  Ungar 
model  2000  desoldering  tool,  and  was 
able  to  remove  the  solder,  let  the 
board  cool,  then  pry  the  chips  from 
the  motherboard,  all  without  clipping 
a  single  lead.  This  damaged  two 
printed  circuit  traces  (pulled  them 
off  in  the  prying  process);  as  the  arti¬ 
cle  recommends,  there  is  a  safer  way 
to  proceed.  My  technique  cannot  be 
advised  in  the  absence  of  an  excellent 
desoldering  tool,  or  for  those  not  con¬ 
fident  in  their  abilitites  to  make 
printed  circuit  repairs.  The  article 
had  so  fired  my  enthusiasm  that  I 
yanked  out  the  64K  DRAMs  days  be¬ 
fore  the  256K  DRAM  circuits  ar¬ 
rived.  I  was  able  to  verify  the  repairs, 
and  the  removed  memory  chips,  by 
installing  sockets,  plugging  the  64K 
DRAMs  into  those  sockets,  and  using 
the  Macintosh.  The  nature  of  the  lift¬ 
ed  printed  circuit  traces  (they  showed 
copper  color  on  the  ICs  to  which  the 
traces  stuck)  made  detection  easy. 


and  no  faults  were  missed.  The  board 
worked  on  the  first  try. 

Thirdly,  the  multiplexer  can  be  in¬ 
stalled  either  piggybacked  on  another 
chip,  or  can  be  placed  on  a  separate 
board,  attached  on  the  seven  solder 
pads  referred  to  in  the  article.  Those 
seven  pads  include  all  logic  signals 
and  power  required  for  the  multiplex¬ 
er.  I  installed  wire-wrap  pins  in  those 
pads,  made  a  small  board  for  the 
multiplexer  from  .100-inch  hole-grid 
breadboard  material,  and  pushed  the 
small  board  over  the  wire-wrap  pins, 
wire-wrapping  the  connections  atop 
it  to  hold  it  in  place.  I  found  this  less 
tricky  than  the  piggyback  soldering 
job  would  have  been,  and  no  leads  of 
the  multiplexer  needed  to  be  bent. 

Fourthly,  the  multiplexer  recom¬ 
mended  is  74F253  or  74AS253;  while 
these  will  do  quite  well,  so  will  the 
74F153  or  74AS153;  which  are 
slightly  less  expensive;  the  only  dif¬ 
ference  is  the  nature  of  the  DISABLE 
function,  which  (in  this  application) 
is  always  unused.  All  pin  connections 
are  identical  to  the  ‘253. 

1  found  that  the  multiple-layer 
board,  having  extra  copper  in  the  in¬ 
terior  layers,  was  rather  more  diffi¬ 
cult  than  most  to  desolder;  I  had  to 
apply  more  heat  (use  a  higher  tem¬ 
perature  on  the  desoldering  iron) 
than  usual,  to  complete  the  solder  re¬ 
moval  within  a  safe  time.  I  did  not 
notice  any  fragility  of  the  printed  cir¬ 
cuit  (no  more  than  most,  at  any  rate), 
and  my  procedure  was,  as  I  have 
mentioned,  rather  stressful.  At  one 
point,  I  fumbled,  and  dropped  the 
board  onto  the  concrete  floor — there 
was  no  damage. 

Prices  were  better  than  I  expected, 
about  $220  total  cost  for  the  chips, 
and  that  figure  is  dropping  almost 
daily.  Time  required  was  almost  ex¬ 
actly  four  hours.  Since  Apple  charges 


about  six  hundred  dollars  more  for 
the  512K  Mac,  it  hardly  pays  to  buy 
one;  the  fine  folks  in  Cupertino  are 
willing,  in  effect,  to  pay  me  hand¬ 
somely  for  those  hours,  if  I  convert 
my  128K  instead. 

Instead  of  ‘Macintosh’  and  ‘Fat 
Mac’,  I  will  be  using  the  terms  ‘Mac¬ 
intosh’  and  ‘Anorexic  Mac’  in  the 
future. 

Sincerely  grateful, 

John  Whitmore 
FM-15  Physics  Dept., 
University  of  Washington 
Seattle,  WA  98195 

Going  PUBlic 

Dear  DDJ: 

PUBlic  files  have  been  used  success¬ 
fully  on  a  wide  variety  of  CP/M  com¬ 
puters  since  the  publication  of  our 
November  1984  [DDJ,  #97]  article 
“CP/M  2.2  goes  PUBlic.”  However, 
we  want  again  to  caution  readers  that 
PUBPATCH  must  be  installed  on  an 
unmodified  C P/M  2.2  BDOS! 

Several  users  have  discovered  that 
their  Heath  Co.  CP/M  2.2.03  has 
been  altered  with  a  patch  at  06E1 
and  0DEE-0DF3  (relative  to  the 
start  of  the  BDOS).  The  Heath  patch 
causes  the  BDOS  to  stop  building  the 
disk  group-allocation  vector  when  it 
encounters  the  first  directory  entry 
having  deleted  data  (E5)  codes  in 
both  bytes  0  and  1.  If  PUBPATCH  is 
then  installed  in  this  BDOS,  the 
symptoms  will  be  files  that  disappear 
from  the  end  of  the  disk  directory! 

Heath  users  with  this  problem 
should  patch  location  06E2h  back  to 
its  original  value  ‘06D2h’  (relative) 
before  installing  PUBPATCH. 

RELATIVE  ADDRESS  06E1’ 
ORIGINAL  2.2  BDOS  JZ  06D2’ 

HEATH  2.2.03  BDOS  JZ  0EEE’ 


Dr.  Dobb's  Journal.  May  1985 


8 

341 


Two  readers  have  reported  problems 
with  PUBLIC. ASM,  used  to  set  or  re¬ 
set  the  PUBlic  file  attribute  bit.  Up¬ 
dated  code  can  be  found  in  [Listing 
One  (page  13)].  The  first  fix  corrects 
a  bug  in  v.  1.0  that  prevents  PUBLIC 
from  setting  the  attribute  for  a  very 
large  file.  The  second  takes  care  of  a 
bug  in  the  standard  CCP  (but  not  in 
ZCPR),  which  fails  to  restore  the  de¬ 
fault  dma  address  before  continuing 
to  execute  a  SUBMIT  file. 

No  changes  are  required  to  PUB- 
PATCH  itself. 

Sincerely, 

Bridger  Mitchell,  Derek 

McKay 

Plu*?erfect  Systems 

Box  1494 

Idyllwild,  CA  92349 

Turbo  Bug 

Dear  DDJ: 

I  have  been  using  Borland  Interna¬ 
tional's  excellent  Turbo  Pascal  com¬ 
piler  for  several  months  now  (I  start¬ 
ed  with  version  1 )  and  I  think  it  is  by 
far  the  best  implementation  of  any 
language  I  have  every  seen.  The 
quick  compiler  and  built-in  editor 
have  reduced  the  pain  of  correcting 
typos  far  enough  to  let  me  appreciate 
the  virtues  of  Pascal.  In  the  process  I 
have  discovered  two  things  about  the 
language  that  may  be  of  interest  to 
others.  I  am  including  listings  of 
three  short  demo  programs. 

First,  the  “initialized  constants” 
mentioned  briefly  in  both  the  refer¬ 
ence  manual  and  the  Turbo  Tutor 
manual  are  really  initialized  static 
variables.  This  is  clear  from  a  close 
reading  of  either  manual,  but  it  is  nev¬ 
er  stated  in  so  many  words,  nor  do  any 
of  the  example  programs  demonstrate 
it.  Compiling  and  running  the  [pro¬ 
gram  in  Listing  Two  (page  13)]  will 
demonstrate  that  the  typed  constant  I 
in  the  procedure  ShowStatic  behaves 
exactly  like  a  static  variable  in  C. 

Compiling  and  running  the  pro¬ 
gram  [in  Listing  Three  (page  14)]  will 
demonstrate  a  problem  I  discovered 
while  trying  to  process  the  files  pro¬ 
duced  by  a  linker  cross  reference  utili¬ 
ty.  The  Trunc  and  Round  functions  in 
both  the  MSDOS  and  CP/M  versions 
of  Turbo  Pascal  produce  a  run  time 


12 


error  if  you  attempt  to  convert  a  real 
value  of  -32768  (8000H).  Since  this 
is  a  legal  integer  value,  and  right  in 
the  middle  of  the  range  of  hex  num¬ 
bers,  I  think  this  has  to  be  called  a 
bug. 

Listing  Four  (page  14)  shows  one 
way  of  programming  around  this 
problem.  It  works  just  fine  when  using 
real  numbers  and  integers  to  represent 
hex  numbers  between  0  and  FFFFH, 
but  it  would  not  be  very  useful  in  an 
application  that  requires  both  positive 
and  negative  real  numbers. 

Sincerely, 

William  Ames 

8720  Topanga  Cyn.  Blvd.  #8 

Canoga  Park,  CA  91304 

Borland  assures  us  that  this  bug  will 
be  fixed  in  Version  3.0  of  Turbo  Pas¬ 
cal.— Ed. 

Fast  Hartley  Transform 

Dear  DDJ, 

We  were  pleased  to  see  Ron  Ull- 
mans’s  letter  in  the  December  1984, 
issue  of  Dr.Dobb's  Journal  regarding 
the  Hartley  Transform  and  the  ac¬ 
companying  listing  of  a  software  pro¬ 
gram.  We,  too,  consider  its  advan¬ 
tage  to  be  great. 

Your  readers  may  wish  to  know 
Stanford  University  has  proprietary 
rights  on  this  technology,  developed 
by  Professor  Ronald  N.  Bracewell. 
Patent  rights  are  pending. 

Use  of  the  software  without  a  li¬ 
cense  may  be  an  infringement  of  those 
proprietary  rights.  However,  a  license 
under  reasonable  terms  to  the  technol¬ 
ogy  is  available  from  Stanford. 

Incidentally,  we  have  called  this  de¬ 
velopment  the  “Bracewell”  transform, 
in  recognition  of  Professor  Bracewell’s 
fundamental  contribution. 

Sincerely, 

Lisa  Kuuttila 
Consulting  Associate 
Office  of  Technology 
Licensing 

Stanford  University 

DDJ 


Dr.  Dobb's  Journal,  May  1985 


Letters  (Text  begins  on  page  8) 
Listing  One 


j  UPDATE  PATCHES  FOR  PUBLIC. ASH 

vers  equ  1$2  jupdate  PUBLIC. ASH  version  number 

fix  1.  Insert  the  following  code  in  the.  'SAMEXT'  routine 
;  immediately  proceeding  its  'ret'  instruction. 

rnz  ;  v  1.1 

inx  h  ;  extent  is  0,  check  overflow  (s2)  ext. 

inx  h 

mov  a  ,tn 

ani  7Fh 

! 

ret  ;  end  of  SAHEXT  routine 


;  fix  2.  Insert  the  following  macro  statement  at  the  label  'xit:' 


xit:  dobdos  dmafn.BOh  [restore  default  dma  for  SUBHIT  under  ccp. 

;  v  1.2 

lspd  ustack 
ret 


Listing  Two 


TURBO  PASCAL  Program  Lister,  Copyright  1983  Borland  International 
Listing  of:  STATIC. PAS 


program  Test St  at i cVan abl es ; 

<  This  program  demonstrates  that  the  "tyoeo  constants"  of  Turbo  Pascal  > 

<  are  really  static  variables.  Each  time  the  main  loop  calls  BhowStatic 

<  the  typed  constant  1  is  incremented  by  .1.  When  BhowStatic  is  called  > 
{  again  the  incremented  value  is  written  to  the  CRT.  > 

var 

Ch  :  Char; 

I  :  integer; 

procedure  BhowStatic; 

<  This  prints  and  then  increments  the  "constant"  I  > 
const 

I  ;  integer  =  (?; 


ben  i  n 

Wr i t  e 1 n  < I )  ; 

I  :=  I  +  1 : 
end  ; 

function  UseHeap(L  :  integer)  :  integer: 

■C  This  is  included  to  demonstrate  that  the  tyoed  constant  above  is  not  ) 

<  distroyed  when  another  procedure  uses  the  heap  or  stack.  > 

var 

I  :  integer; 

J  :  '“•integer; 

beg  l  n 

j  :  =  — L  ; 

new ( J )  ; 

J'  :  =  L  5 
L  L  +  1? 
if  L  <5  then 

L  «=  UseHeap(L) ; 

D ispose ( J ) ; 

UseHeao  : “  L„ 
end  ; 


End  Listing  One 


> 


(Continued  on  next  page ) 


Dr.  Dobb’s  Journal,  May  1985 


13 

343 


L  euers  (Listing  Continued,  text  begins  on  page  8) 

Listing  Two 


repeat 

read (Kbd,  Ch )  ; 

I  s=  UseHeapdTU? 
ShowSt at ic ; 

Until  Ch  =  #13; 
end. 


Listing  Three 


TURBO  PASCAL  Program  Lister',  Copyright  1983  Borland  International 
Listing  of:  CNVR7 1 . PAS 


program  Real  To  Integer 1 s 

<  This  program  can  be  used  to  demonstrate  a  problem  in  > 

<  converting  real  numbers  to  integers.  > 
var 

Nmbr  :  rea 1 j 
I  :  integers 

begin 

Read  In (Nmbr)  ; 

{  if  Nmbr  *  3£ 76,8.0  the  following  produces  a  runtime  error  > 
if  (Nmbr  >  65535.0)  or  (Nmbr  (  0)  then 

<  I  am  only  interested  in  the  range  of  4  digit  Hex  numbers  > 
Writeln(’  overflow  error’ ) 
else  begin 

if  Nmbr  <  3£76B. 0  then 
I  :=  Trunc(Nmbr) 
e  1  se 

I  :  =  Trunc(Nrnbr  —  65536.0)  ; 

Wr iteln(I)  : 
end  ; 
end. 


Listing  Four 


TURBO  PASCAL  Program  Lister.  Copyright.  1383  Borland  International 
Listing  of:  CNVRT£. PAS 


program  Real To Integers ; 

f  this  shows  one  way  around  the  bug  dernost rated  in  version  .1  > 

var 

Nrnbr  s  real  ; 

I  :  integer: 


bea  i  n 

Read  1 n ( Nmbr )  ; 

•C  3S768.  0  is  converted  correctly  by  the 
if  (Nmbr  >  65535.0)  or  (Nmbr  (  0)  then 
<  I  am  only  interested  in  the  range  of 
Writeln(’  overflow  error’ > 
else  begin 

if  Nrnbr  <  3276,8.0  then 
I  :  =  T  r  unc  ( Nrnbr ) 
el  se 
beg  l n 

I  : =  Trunc (Nmbr  —  65535.0) ; 

I  s  ==  I  —  Is 
end  ; 

Wr i teln(I) ; 
enci ; 
end. 


following  code  > 

4  dioit  Hex  number's 


> 


14  Dr 

1AA 


End  Listing  Two 


End  Listing  Three 


End  Listing  Four 

Dobb's  Journal,  May  1985 


Welcome,  Microans 

It  seems  that  DDJ  has  picked  up  a 
batch  of  new  readers,  the  erstwhile 
subscribers  to  the  late,  lamented  Mi¬ 
cro.  Let’s  recap  the  purpose  of  this 
column  for  their  benefit.  It’s  sup¬ 
posed  to  be  “a  place  for  the  display  of 
techniques  and  discoveries.”  That’s 
what  we  said  the  first  time  it  ap¬ 
peared,  in  May  1981.  Also,  “We’d 
like  to  tell  of  unobvious  uses  for  stan¬ 
dard  utilities.  We  want  to  uncover  er¬ 
rors  in  documentation,  to  warn  peo¬ 
ple  away  from  pitfalls,  and  to  show 
off  those  'Eureka!'  moments  that 
make  systems  work  rewarding.” 

For  a  long  time,  most  of  the  contri¬ 
butions  to  the  Clinic  involved  CP/M 
and  the  Z80.  Nowadays,  we  hear 
more  from  MSDOS/8086  users. 
Well,  stand  back,  Zilog  lovers;  make 
room,  Intel-lects!  Now  we  have 
among  us  people  who  understand 
real  computers  like  the  6502,  the 
6809,  and  the  68000  and  real  operat¬ 
ing  systems  like  OS9,  SOS,  and  PRO- 
DOS.  We  can  barely  wait  for  the  neat 
contributions  they’ll  be  sending  in. 
Soon. 

Wood  Blocks 

As  bait  for  the  Micro  readers  (hey, 
folks,  we  really  like  reader  contribu¬ 
tions  here),  we  present  our  wood 
blocks  program  for  the  Atari.  It’s  a 
little  thing  (see  Listing  at  right)  that 
we  ran  up  one  day  while  experiment¬ 
ing  with  the  start,  select,  and  option 
keys.  These  three  keys  control  the 
three  lowest  bits  of  a  byte  in  the  Atari 
address  space.  The  program  samples 
that  byte,  and  each  time  the  combi¬ 
nation  of  operated  keys  changes,  it 
emits  a  heavily  damped  klonk  at  a 
different  pitch.  Play  it  by  mashing 
down  all  three  keys  and  then  sort  of 


working  your  fingers  around. 

The  simulation  of  damped  tones  is 
pretty  good.  Experiment  with  the 
damping  factor  and  with  the  expres¬ 
sion  that  generates  a  note  value  from 
the  key  combination. 

Roll  Over,  Keyboard 

We’ve  an  interesting  letter  here  from 
Charles  Davis,  who  lives  in  a  town 
with  the  delightful  name  of  Flower 
Mound,  Texas.  Besides  submitting 
some  throughput  timings  for  an  Atari 
system  (at  last!),  he’s  been  studying 
keyboards: 

“Perhaps  you  could  survey  another 
area  of  concern.  I  am  either  a  very 
good  typist  or  a  very  sloppy  one!  I 
need  a  keyboard  with  what  used  to  be 
called  ‘N-key  rollover.’  These  are 
hard  to  find.  The  current  generation 
of  computers  and  terminals  does  not 
seem  to  supply  this  feature.  Most 
marketing  and  sales  people  ...  are 
unaware  of  the  feature  and  therefore 
the  sales  literature  does  not  mention 
it,  even  when  it  is  present. 

“A  survey  of  the  popular  computers 
and  terminals  with  reference  to  roll¬ 
over  would  be  interesting  to  me  and,  I 


think,  to  many  of  your  readers.  My 
procedure  for  checking  rollover  is  to 
depress  several  keys  in  sequence,  hold¬ 
ing  each  until  all  are  depressed,  then  I 
lift  them  all  in  the  same  order.  I  ob¬ 
serve  what  happens  on  the  screen. 
There  seem  to  be  three  types  of  re¬ 
sponse: 

•  the  first  N  keys  (type  1 ) 

•  the  last  N  keys  (type  2) 

•  only  the  first  and  last  keys 
(type  3) 

The  major  variable  is  how  big  N  is. 
Rarely  must  I  find  a  helper  with  more 
fingers.  One  terminal  manufacturer 
admitted  that  their  terminals  had  N- 
key  rollover  and  that  N  was  supposed 
to  be  5,  but  some  key  sequences  did 
not  work  right  so  they  would  only 
guarantee  N  to  be  3.  This  points  up 
the  weakness  of  my  procedure:  I  do 
not  do  an  exhaustive  check  and  some 
problem  characters  may  be  missed. 
Without  knowledge  of  the  key  matrix 
and  the  scanning  algorithm,  it  would 
take  too  long  to  find  the  problems.” 

Davis  sent  along  the  results  from 
some  terminals  he’d  tested  (see  Table 
1  on  page  17).  However,  our  first  at- 


10  REM  W00DBL0X  FOR  ATARI,  D.E.  CORTESI 
20  KEYBYTE=53279 
:  DAMPING  =  0.29 
30  SAMPLE=  PEEX  (KEY  BYTE) 

:  IF  SAMPLE=  BUTTONS  THEN  GOTO  30 
40  BUTTONS =SAMPLE 

:  PI  TCH  =  1  2*  (8-SAMPLE) 

:  V0LUME= 1 5 

50  SOUND  0, PITCH, 12, VOLUME 

:  VOLUMEr  VOLUME-  VOLUME*DAMPI  NG 
:  IF  VOLUME  >=  1  THEN  GOTO  50 
60  SOUND  0,0,0, 0 
:  GOTO  30 


Dr.  Dobb’s  Journal.  May  1985 


16 


Terminal 

Response 

Rollover 

ADDS  Regent  20 

type  1 

N  >  10 

ADDS  Viewpoint 

type  1 

N  >  10 

Appollo  DN600  Workstation 

type  1 

N  >  10 

Atari  computers 

type  3 

n.a. 

CIT  10  IE 

type  1 

N  =  2 

Daisy  Logician  Workstation 

type  1 

N  >  10 

Hazeltine  1410 

type  3 

n.a. 

Heath/Zenith  1 9  and  29 

type  4 

N  =  2 

HP-85 

type  3 

n.a. 

HP-9816 

type  1 

N  >  10 

IBM  PC/XT 

type  1 

N  >  10 

Perkin-Elmer  Bantam  550 

type  1 

N  =  3 

Soroc  IQ-1 20 

type  3 

n.a. 

Televideo  950 

type  1 

N  -  2 

Wyse  50 

type  1 

N  =  3 

Table  1 

Charles  Davis'  survey  of  the  number  of  keys  that  can  be 

"rolled  over”  on  different  terminals.  Types  are: 

1 ,  first  N  keys; 

2,  last  N  keys;  3,  first  and  last  only;  4,  first  N  keys  then 

garbage. 

Command 

Code 

Type  of 
Driver 

Operation 

0 

either 

initialize 

1 

block 

media  (sic)  check 

2 

block 

build  Bios  Parameter  Block 

3 

either 

IOCTL  input  call 

4 

either 

input 

5 

character 

nondestructive  input  no  wait 

6 

character 

input  status  check 

7 

character 

input  flush 

8 

either 

output 

9 

either 

output  with  verify 

10 

character 

output  status 

11 

character 

output  flush 

12 

either 

IOCTL  output  call 

13 

either 

notification  of  file-open 

14 

either 

notification  of  file-close 

15 

either 

IOCTL  query:  removable  disk? 

Table  3 

The  command  codes  to  which  an  MSDOS  device  driver  must 
respond,  omitted  from  IBM's  DOS  Technical  Reference  man¬ 
uals.  Codes  13,14  and  1  5  are  new  with  PCDOS  3.0. —Codes  3, 
1 2  and  1  5  are  passed  only  to  drivers  that  indicate  IOCTL 
support  in  their  Device  Header  Attribute  word. 


17 


Dr.  Dobb's  Journal,  May  1985 


tempt  to  test  a  keyboard  produced  a 
new  result  that  you  should  watch  out 
for: 

•  the  first  N  keys  plus  a  garbage 
key  (type  4) 

This  is  the  H19  problem  we  reported 
here  in  August  1982;  it  results  from  a 
lack  of  blocking  diodes  in  the  key  ma¬ 
trix.  Heath  hasn’t  learned  anything 
since,  either.  On  our  Heath/Zenith 
29,  holding  L,  I,  and  then  S  produces 
“LIQS”  on  the  screen.  We  have  to  be 
very  precise  typing  the  word  “list”  or 
it  comes  out  “liqst.”  Worse,  some 
combinations  produce  false  function 
keys.  The  combination  E,  I,  then  P 
generates  a  false  down-arrow  (es- 
cape-B)  sequence,  for  instance.  So 
the  H /Z29  is  an  N  =  2  machine,  even 
though  for  some  key  combinations  it 
will  pass  Davis’  test  as  a  type  1  with 
much  higher  N.  Incidentally,  the 
good  combinations  occur  on  the 
home  row.  That  suggests  that  you 
might  apply  Davis’  test  on  the  top  or 
bottom  rows. 

Charting  DOS 

We’ve  been  boning  up  on  MSDOS 
and  PCDOS  lately,  in  preparation  for 
writing  a  book  and  we’ve  compiled 
the  chart  of  DOS  services  that  ap¬ 
pears  in  Table  2  (page  19)  as  a  study 
aid.  For  each  possible  Int  21  service  it 
shows  the  request  number  in  decimal 
and  hex,  the  releases  that  support  it, 
and  a  capsule  description  of  the  ser¬ 
vice.  The  services  are  organized  into 
groups  by  function.  The  groups  and 
the  services  within  the  groups  are 
generally  in  ascending  order  by  re¬ 
quest  number,  but  functional  rela¬ 
tionships  are  given  precedence. 

The  idea  is  that  you  can  look  for 
the  service  you  need  by  scanning  a 
small  group  of  related  ones.  The  cap¬ 
sule  descriptions  are  just  enough  to 
differentiate  between  similar  func¬ 
tions.  Having  found  the  service  you 
need,  you  can  access  its  detailed  de¬ 
scription  in  your  DOS  manual 
through  its  request  number. 

Our  study  of  PCDOS  has  been  im¬ 
peded  no  small  amount  by  the  poor 
quality  of  the  IBM  DOS  manuals,  es¬ 
pecially  those  for  DOS  3.0.  For  one 


Dr.  Dobb's  Journal,  May  1985 


example,  the  documentation  on  ser¬ 
vice  56/ 38h  (get  or  set  country-de¬ 
pendent  information)  is  so  badly 
done  that  we  honestly  cannot  make 
sense  of  it.  If  you  are  a  reader  who 
can  write  a  comprehensible,  accurate 
description  of  the  DOS  3  version  of 
service  38h,  please  do  so  and  send  it 
to  the  Clinic. 

For  another  example,  the  chapter 
on  device  drivers  omits  any  specifica¬ 
tion  of  the  command  codes  to  which  a 
device  driver  is  supposed  to  respond. 
In  the  DOS  2. 1  manual  you  could  still 
cull  the  essential  information  from 
the  comments  of  a  sample  program. 
In  DOS  3.0,  the  sample  program  has 
been  dropped  from  the  manual.  The 
book  says  just  enough  that  you  can 
infer  that  command  codes  3,  4,  8,  9, 
and  12  specify  input  or  output,  but 
there  is  no  way  you  can  tell  which 
code  calls  for  what  action! 

Fortunately,  the  missing  informa¬ 
tion  can  be  found  in  Microsoft’s  ver¬ 
sion  of  the  technical  manual,  from 
which  we  constructed  Table  3  (page 
17).  Although  some  command  codes 
are  specific  to  character  or  block  de¬ 
vices,  it  seems  wisest  for  all  drivers  to 
expect  all  command  codes.  They  can 
treat  the  unwanted  ones  as  null  oper¬ 
ations,  or  they  can  reject  them  with 
the  “unknown  command”  error  code. 


DD| 


Reader  Ballot 

Vote  for  your  favorite  feature/article. 
Circle  Reader  Service  No.  1 90. 


18 

■5A7 


Service  number  in  decimal  and  hex 

Appears  in  which  releases 
Functional  group 

Capsule  description 


0/00 

1/2/3 

program  cntrl 

end  program  (requires  PSP  *CS) 

49/31 

-/2/3 

program  cntrl 

end,  stay  resident,  AL=ret,  DX  =  size 

76/4C 

—  /2/3 

program  cntrl 

end  program,  AL=ret 

38/26 

1/2/3 

program  cntrl 

make  daughter  PSP  at  (DX:00) 

75/4B 

— /2/3 

program  cntrl 

(AL=0)  load,  run  daughter  from  path  *DS:DX 

77/4D 

-/2/3 

program  cntrl 

get  AH  =  term  code,  AL  =  ret  of  daughter 

75/4B 

-/2/3 

program  cntrl 

(AL=3)  load  DS:DX  — >path  as  overlay 

98/62 

— / — /3 

program  cntrl 

get  BX  =  paraddr  of  my  PSP 

48/30 

—  /2/3 

system  info 

get  DOS  version  number 

42/2A 

1/+/3 

system  info 

get  CX  =  year,  DH  =  mo,  DL=day  (2,3:AL=dow) 

44/2C 

1/2/3 

system  info 

get  time  as  CH:CL:DH.DL 

51/33 

-/2/3 

system  info 

(AL=00)  get  Break  switch  in  DL 

53/35 

~/2/3 

system  info 

get  int(AL)  vector  to  ES  +  BX 

56/38 

-IH+ 

system  info 

(AL=00)  get  country-dependent  info 

72/48 

~/2/3 

system  info 

(BX  =  FFFF)  get  size  available  RAM  to  BX 

89/59 

— / — /3 

system  info 

get  extended  error  to  AX,  BH,  BL,  CH 

37/25 

1/2/3 

system  control 

set  int(AL)  vector  to  (DS  +  DX) 

43/2B 

1/2/3 

system  control 

set  date  from  CX,  DX 

45/2D 

1/2/3 

system  control 

set  time  from  CH:CL:DH.DL 

51/33 

/2/3 

system  control 

(AL=01)  set  Break  switch  from  DL.O 

56/38 

— / — /3 

system  control 

(??)  set  country-dependent  info 

72/48 

-12/3 

system  control 

allocate  BX  paragraphs 

73/49 

~/2/3 

system  control 

return  RAM  from  ES  =  paraddr 

74/4A 

— /2/3 

system  control 

resize  ES  =  paraddr  to  BX  paras 

1/01 

1/2/3 

kbd/handle  0 

get  key:  wait,  echo,  break-check 

6/06 

1/2/3 

kbd/handle  0 

(DL=FF)  get  key:  noecho,  nowait,  nobreak 

7/07 

1/2/3 

kbd/handle  0 

get  key:  wait,  noecho,  nobreak 

8/08 

1/2/3 

kbd/handle  0 

get  key:  wait,  noecho,  break 

10/0A 

1/2/3 

kbd/handle  0 

buffered  line  input  with  editing 

11 /0B 

1/2/3 

kbd/handle  0 

test  if  key  available  (break) 

12/OC 

1/2/3 

kbd/handle  0 

flush  buffered  keys,  then  do  (AL) 

2/02 

1/2/3 

scrn/handle  1 

put  DL  with  editing  and  break-check 

6/06 

1/2/3 

scrn/handle  1 

(DL  <  >  FF)  put  DL,  noedit,  nobreak 

9/09 

1/2/3 

scrn/handle  1 

iterate  DOA  2  over  *DS:DX  to  $ 

3/03 

1/2/3 

aux/handle  3 

get  byte  from  COM1 

4/04 

1/2/3 

aux/handle  3 

put  byte  to  COM1 

5/05 

1/2/3 

prt/handle  4 

put  byte  to  LPT1 

25/19 

1/2/3 

disk  info 

get  AL= default  drive 

47/2F 

— /2/3 

disk  info 

get  Disk  Transfer  Address  to  ES:BX 

27/IB 

1/  +  /3 

disk  info 

get  FAT  *DS:BX  (2,3:  only  1  st  byte  of  FAT)  .  . 

28/1 C 

-12/3 

disk  info 

.  .  and  CX  =  bytes/sec  AL= sec/unit  DX  =  units/disk 

54/36 

-72/3 

disk  info 

BX=free  units,  CX/DX/AL  as  for  1 C 

84/54 

—/2/3 

disk  info 

get  write-verify  switch  to  AL.O 

Table  2 

Chart  of  DOS  Services  (Continued  on  next  page) 


19 


Dr.  Dobb’s  Journal.  May  1985 


(Continued) 

■ 

13/0D 

1/2/3 

disk  control 

reset  disk  system 

14/OE 

1/2/3 

disk  control 

select  drive,  return  count  of  drives 

26/1  A 

1/2/3 

disk  control 

set  Disk  Transfer  Address  =  DS:DX 

46/2E 

— /2/3 

disk  control 

set  write-verify  switch  from  AL.O 

15/OF 

1/2/3 

fcb  control 

open  file  from  FCB 

16/10 

1/2/3 

fcb  control 

close  file  from  FCB 

17/11 

1/2/3 

fcb  control 

scan  current  directory  for  first  match 

18/12 

1/2/3 

fcb  control 

scan  current  directory  for  next  match 

19/13 

1/2/3 

fcb  control 

erase  files  matching  FCB 

22/16 

1/2/3 

fcb  control 

make  new  file  from  FCB 

23/17 

1/2/3 

fcb  control 

rename  file  from  FCB 

35/23 

1/2/3 

fcb  control 

get  size  of  file  to  fcb 

36/24 

1/2/3 

fcb  control 

set  relrecno  from  current  file  position 

40/28 

1/2/3 

fcb  control 

(CX  =  0)  trunc  or  stretch  file  to  size 

41/29 

1/  +  /3 

fcb  control 

parse  filespec  *DS:SI  into  fcb  *ES:DI 

20/14 

1/2/3 

fcb  i/o 

read  next  record  to  DTA 

21/15 

1/2/3 

fcb  i/o 

write  DTA  to  next  record 

33/21 

1/2/3 

fcb  i/o 

read  record  at  direct  address 

34/22 

1/2/3 

fcb  i/o 

write  record  to  direct  address 

39/27 

1/2/3 

fcb  i/o 

block  read  CX  records  to  DTA 

40/28 

1/2/3 

fcb  i/o 

(CX  >  0)  block  write  CX  records  from  DTA 

57/39 

~/2/3 

directories 

make  directory  for  path  *DS:DX 

58/3A 

— /2/3 

directories 

erase  directory  at  end  of  path  *DS:DX 

59/3B 

—  /2/3 

directories 

set  current  path  as  path  *DS:DX 

71/47 

—  /2/3 

directories 

get  current  path  of  DL  =  drv  to  DS:DI 

63/3F 

-/2/3 

file  i/o 

read  CX  bytes  from  BX  =  handle  to  DS:DX 

64/40 

-/2/3 

file  i/o 

write  CX  bytes  to  BX  =  handle  from  DS:DX 

60/3C 

—  /2/3 

file  control 

create/trunc  file  (path  *DS:DX,  CX  =  attr) 

91/5B 

1-/-/3 

file  control 

create  new  or  fail  (path  *DS:DX,  CX  =  attr) 

90/5A 

—  /  —  /3 

file  control 

create  unique  file  (path  *DS:DX,  CX  =  attr) 

61/3D 

-/2/+ 

file  control 

open  file  (path  *DS:DX,  AL=  access  type) 

62/3E 

—  /2/3 

file  control 

close  file  (BX  =  handle) 

65/41 

—  /2/3 

file  control 

erase  file  (path  *DS:DX) 

66/42 

—  /2/3 

file  control 

seek  (BX  =  handle,  AL=method,  CX  +  DX  =  offset) 

67/43 

-/  2/3 

file  control 

(AL=0)  get  CX  =  attr  of  file  path  *DS:DX 

67/43 

-/  2/3 

file  control 

(AL=  1 )  set  attr  of  file  path  *DS:DX  to  CX 

69/45 

—  /2/3 

file  control 

create  a  duplicate  file  handle 

70/46 

— /2/3 

file  control 

force  a  handle  to  be  a  duplicate 

78/4E 

—  /2/3 

file  control 

search  1  st  match  to  path  *DS:DX 

79/4F 

/2/3 

file  control 

search  next  match  to  path  *DS:DX 

86/56 

—  /2/3 

file  control 

rename/move  path  *DS:DX  to  path  *ES:DI 

87/57 

-/2/3 

file  control 

(AL=0)  get  CX  =  time,  DX  =  date  of  path  *DS:DX 

87/57 

~/2/3 

file  control 

(AL=  1)  set  CX=time,  DX  =  date  of  path  *DS:DX 

92/5C 

— / — /3 

file  control 

(AL=0)  at  CX  +  DX  in  BX  =  hdl,  lock  SI  +  DI  bytes 

92/5C 

— / — /3 

file  control 

(AL=  1 )  release  bytes  locked  by  prior  call 

Table  2 

Chart  of  DOS  Services 

Dr.  Dobb's  Journal,  May  1985 


20 


REALIZABLE  FANTASIES 


Dr.  Dobb  is  a  Subversive 

by  D.  E.  Cortesi 


The  publishers  of  DDJ  recently 
threw  a  party  to  celebrate  the  1 00th 
issue  of  the  magazine.  It  was  a 
friendly  affair,  with  lots  of  sincere 
speeches  about  how  important  DDJ 
has  been  and  about  how  it  still  has  a 
great  role  to  play.  There  was  a  lot  of 
talk  about  "hackers:”  what  they  ac¬ 
complished;  where  DDJ  stood  in  re¬ 
lation  to  them;  where  they  might 
have  gone  now. 

they  were  expressed,  the  sentiments 
seemed  incomplete  to  me.  Something 
was  being  overlooked,  but  I  couldn’t 
figure  out  what  it  was  until  /  was 
halfway  home.  Then  /  lay  awake 
working  out  the  speech  that  ought  to 
have  been  given,  but  wasn’t.  And  this 
is  it ... . 

We’ve  heard  a  lot  about  “hackers” 
in  the  last  few  minutes.  I  would  like 
to  protest  the  use  of  that  word.  I  un¬ 
derstand  why  it’s  being  used.  It’s  be¬ 
cause  nobody  can  think  of  the  right 
word  to  describe  the  people  who 
founded  DDJ  and  the  people  who 
read  and  profited  from  it  in  its  earli¬ 
est  days.  We  know  they  weren’t  part 
of  the  establishment;  we  know  they 
didn’t  play  a  conventional  role  in  the 
society  of  their  day.  We  have,  per¬ 
haps,  an  uneasy  feeling  that  we  aren’t 
quite  like  them  and  that  they 
wouldn’t  find  us  very  interesting — 
even  though  perhaps  we  were  them 
just  a  few  years  ago. 

So  we  scratch  around  in  our  vocab¬ 
ularies,  searching  for  the  right  word 
to  describe  these  departed  people, 
and  the  best  we  can  come  up  with  is 
“hacker.”  Well,  it’s  the  wrong  word. 

The  right  word  is  . . .  revolution¬ 
ary.  Subversive.  Bomb-throwing  an¬ 
archist. 

To  me,  the  hacker  state  is  a  frame 
of  mind  in  which  one  is  turned  inward 
on  oneself  and  the  machine:  one’s 


spiritual  third  eye  rolls  up  into  the 
head.  The  people  who  birthed  DDJ 
and  personal  computing  in  general 
were  quite  the  opposite.  Their  eyes 
were  turned  outward  on  society — and 
twinkled  with  mischief. 

They  were  bent,  quite  consciously, 
on  overthrowing  the  established  order. 
It  was  to  be  a  nonviolent  overthrow, 
one  conducted  if  possible  with  whimsy 
and  good  humor,  but  it  was  to  be  an 
overthrow,  a  toppling,  nonetheless. 

The  central  tenet  of  this  jolly  revo¬ 
lution  was  (and  still  is)  the  belief  that 
if  we  put  enough  computers  in  the 
hands  of  enough  people,  wonderful 
changes  will  take  place.  What  the 
wonderful  changes  are  . .  .  well,  that 
varies  with  the  year  and  the  person 
you’re  talking  to.  But  that  they  will 
be  wonderful,  as  well  as  radical  in  the 
deepest  and  best  sense  of  the  word,  no 
computer  subversive  doubts.  I  cer¬ 
tainly  don’t. 

The  founder  of  the  movement  and 
its  only  theorist,  its  Marx  or  Che  Gue¬ 
vara  if  you  like,  is  Ted  Nelson.  His 
book,  Computer  Lib,  was  written  in 
reaction  to  computers,  their  employ¬ 
ers,  and  their  professional  priesthood 
as  those  things  existed  in  the  early  70s. 
It  was  a  protest:  “Either  computer 
systems  are  going  to  go  on  inconven¬ 
iencing  our  lives,  or  they  are  going  to 
be  turned  around  to  make  life  better,” 
and  then,  in  caps,  a  battle  cry:  “COM¬ 
PUTER  POWER  TO  THE  PEOPLE! 
DOWN  WITH  CYBERCRUD!” 

And  to  make  sure  we  didn’t  miss 
the  point,  there  was  the  symbol  of  the 
clenched  fist  on  the  cover.  I  hope  I’m 
not  the  only  one  here  who  remembers 
when  the  word  Lib  and  the  upraised 
fist  were  potent,  dangerous  symbols. 
That  Nelson  borrowed  both  for  his 
manifesto  indicates  how  serious  he 
was. 

I  don’t  know  if  the  People’s  Com¬ 


puter  Company  admitted  to  subver¬ 
sive  intentions  in  public  or  not.  Un¬ 
fortunately  for  me,  I  was  out  of  the 
country  during  its  glory  days.  I  was 
aware  of  micros  and  an  early  sub¬ 
scriber  to  DDJ,  but  I  was  only  a  fellow 
traveler  to  the  revolution,  not  a  revo¬ 
lutionary  myself. 

But  whether  the  intentions  were  ad¬ 
mitted  or  not,  I’m  positive  that  they 
were  there.  No  one  who’s  spent  any 
time  with  Bob  Albrecht,  or  with  Ra¬ 
mon  Zamorra,  who  founded  Compu- 
terTown  USA!,  could  have  any  doubt 
that  these  people  are  motivated  by  a 
deep  love  of  mischief.  Nothing  pleases 
them  better  than  a  well-toppled  apple 
cart  or  a  well-rocked  boat. 

Another  revolution  that  I  missed 
out  on  was  the  birth  of  the  acid  cul¬ 
ture  in  the  early  60s.  But  I  suspect 
that  the  spirit  of  People’s  Computer 
Company  had  a  lot  in  common  with 
the  spirit  of  Ken  Kesey’s  Merry 
Pranksters,  and  with  that  of  the  Dig¬ 
gers  and  the  various  communards  of 
the  60s.  They  all  felt  that  they’d  sto¬ 
len  the  fire  of  the  gods  and  that 
they’d  damn  well  better  set  as  many 
blazes  as  they  could  before  the  gods 
came  to  take  it  back. 

After  all,  what  could  possibly  be 
more  subversive  than  training  chil¬ 
dren  to  operate  the  tools  of  the  estab¬ 
lishment,  without  at  the  same  time 
inculcating  the  establishment’s  line 
of  thinking?  But  that  was  what  Al¬ 
brecht,  Allison,  et  al.,  were  doing.  I 
don’t  know  if  this  is  what  was  specifi¬ 
cally  in  their  minds  or  not.  But  con¬ 
sider.  You  have  all  had  the  following 
experience.  You  program  something 
in  BASIC;  you  find  that  the  program 
doesn’t  work  because  of  the  inevita¬ 
ble  bugs;  then,  after  you  pick  them 
off,  you  find  that  it  still  doesn’t  work 
because  the  limited  accuracy  of  bina¬ 
ry  floating  point  has  turned  your  nu- 


22 


Dr.  Dobb’s  Journal,  May  1985 


merical  results  into  oatmeal.  Nobody 
who  has  been  through  that  can  ever 
look  at  their  bank  statement  in  quite 
the  same  way  again.  Or  their  phone 
bill.  Or,  if  there  is  a  live  connection 
between  one  side  of  their  brain  and 
the  other,  can  they  ever  feel  quite  the 
same  about  a  proposal  for  an  antibal- 
listic  missile  system  as  they  might 
once  have  done  before  their  personal 
encounter  with  computers  in  the  raw. 

Albrecht  and  company  used  time¬ 
sharing  terminals  to  start  indoctri¬ 
nating  children  and  susceptible 
adults,  but  then  the  micro  came 
along.  The  earliest  micros  were  piti¬ 
ful,  but  to  the  eyes  of  a  computer  sub¬ 
versive  they  appeared  to  be  the  sword 
of  liberation.  A  computer  you  could 
hold  in  your  hand! 

For  a  parallel,  try  to  imagine  that 
Gutenberg  had  never  invented  his 
printing  press;  that  the  only  way  for 
books  to  be  reproduced  was  for  them 
to  be  handwritten  by  scribes.  Of 
course,  only  the  wealthy  and  the  gov¬ 
ernment  can  afford  to  pay  scribes  and 
buy  vellum,  so  the  only  thoughts  that 
circulate  in  book  form  are  the  ones 
acceptable  to  the  powers  that  be. 
Then  somebody  invents  the  typewrit¬ 
er:  a  cheaper,  faster  way  of  being  a 
scribe,  one  that  anybody  can  learn. 
So  you  start  a  counterculture  book 
factory,  teaching  people  to  use  type¬ 
writers  to  duplicate  unapproved  liter¬ 
ary  works. 

And  then  somebody  walks  in  the 
door  with  the  latest  invention,  the 
Xerox  copier.  And  just  that  simply, 
your  revolution  has  been  won;  now 
it’s  just  a  matter  of  mopping  up. 

In  the  case  of  the  computer  revolu¬ 
tion,  the  mopping  up  is  still  going  on. 
The  micro  has ,  in  fact,  hurt  the  com¬ 
puter  bureaucrats,  the  people  who 
would  try  to  make  you  swallow  an  in¬ 
convenience  on  the  grounds  that  the 
computer  wouldn’t  let  them  be  help¬ 
ful.  These  days  they  find  it  a  lot  hard¬ 
er  to  say  “the  computer  won’t  let  me” 
because  so  many  clients  are  likely  to 
answer  “Why  not?  My  computer 
would.”  There  really  is  a  lot  less  these 
days  of  what  Nelson  called  cyber- 
crud,  and  we  can  credit  that  to  the 
wide  popular  exposure  to  micros. 

But  there  is  a  lot  more  to  be  done. 
We’ve  spread  computers  all  over  the 

Dr.  Dobb's  Journal.  May  1985 


place  and  still  have  no  millenium,  nor 
anything  remotely  like  it  There’s 
been  no  great  liberation  of  minds  or 
spirits.  The  revolution  has  been  as¬ 
similated,  just  as  the  automotive,  sex¬ 
ual,  drug,  and  free  speech  revolutions 
were  assimilated  before  it,  and  the 
feminist  revolution  is  being  assimilat¬ 
ed  now:  they  come,  they  pass,  things 
are  different  but  not  very  much  so. 
Surely  by  now  the  wonderful  thing 
should  have  happened.  Surely  by  now 
somebody  should  have  written  the 
program  that  will  set  us  all  free?  But 
no. 

One  reason  for  this  failure  is  that 
the  computer  did  not  turn  out  to  be  a 
sword  that  could  be  placed  in  any¬ 
one’s  hand.  It  isn’t  a  sword  at  all,  nor 
anything  like  a  simple  edged  or  point¬ 
ed  weapon.  For  complexity  it’s  more 
like  a  whole  battalion  of  soldiers.  If 
you  tell  some  poor  shmuck  to  start 
wielding  one,  you  put  him  in  the  posi¬ 
tion  of  a  private  promoted  instantly 
to  general  and  asked  to  take  over 
management  of  a  battle. 

To  change  metaphors  nimbly  in 
midstream,  we  have  made  the  belated 
discovery  that  the  computer  priest¬ 
hood  that  we  deposed  was  not  entire¬ 
ly  a  group  of  fakes;  they  had  more 
than  mumbo  jumbo  going  for  them. 
True,  they’d  made  public  access  to 
the  machines  difficult,  or  allowed  it 
to  be  difficult.  But  now  that  we  have 
our  own  machines,  we  find  out  that 
there  were  good  reasons  for  that.  It 
turns  out  that  quite  possibly  the  most 
difficult  thing  you  can  do  with  a  com¬ 
puter  is  to  make  it  be  truly  helpful.  If 
you  are  in  a  hurry  for  an  answer,  you 
will  find  it  a  lot  easier  to  program  the 
machine  to  take  its  input  in  rigid,  un¬ 
forgiving  formats,  and  to  give  cryp¬ 
tic,  insulting  error  messages,  than  to 
make  it  flexible  and  friendly  and  for¬ 
giving. 

In  fact,  we  find  that  in  putting  on  its 
happy  face,  the  machine  uses  up  so 
much  capacity  that  it  has  precious  lit¬ 
tle  left  in  which  to  do  any  useful  work. 

Partly  this  is  a  real  effect.  It  truly 
is  difficult  to  make  a  computer  be 
helpful,  and  there  are  deep,  unsolved 
problems  in  the  way  of  making  that 
happen.  But  in  part,  I  think  that  the 
revolutionaries  were  a  bit  hasty  in  ex¬ 
iling  those  priests.  They  have  a  genu- 


|  ine  science,  computer  science,  that 
deals  with  the  elegant  and  efficient 
application  of  machine  power.  All  too 
little  of  that  science  is  being  applied 
today.  The  result  is  that  we  have  ma¬ 
chines  with  512K  of  storage  that 
can’t  keep  up  with  the  demands  of  a 
single  user.  Damn  it,  we  used  to  sup¬ 
port  twenty  APL  users  on  a  single  360 
model  40  with  1 28K;  we  considered  a 
370/158  with  512K  a  big  machine, 
suitable  for  use  by  a  middle-sized 
company  with  half  a  dozen  program¬ 
mers  online  to  TSO  or  VM/370.  The 
big  macro  assembler  for  the  IBM  370 
needs  a  128K  partition  to  run  in. 
That’s  the  assembler  program  plus  all 
the  tables  and  work  areas  and  buffers 
it  uses.  The  macro  assembler  for  the 
IBM  PC  can  barely  load  itself  into 
128K,  and  it  can’t  assemble  anything 
of  useful  size  until,  you  give  it  256K. 
In  short,  there  has  been  a  major  loss 
of  efficiency  in  moving  from  the  soft¬ 
ware  that  the  priests  built  for  the  cor¬ 
porate  mainframes  to  the  software 
that  we  are  putting  on  personal  com¬ 
puters  today. 

As  a  good  revolutionary,  I  must  be¬ 
lieve  that  computer  use  is  for  every¬ 
body,  and  I  really  do.  But  program¬ 
ming  the  computers  so  they  can  be 
used  is  almost  certainly  a  job  for  spe¬ 
cialists. 

The  question  that  confronts  us  now 
is:  whose  specialists  are  they  going  to 
be?  Will  they  be  the  tools  of  the  new 
computing  establishment,  working 
for  the  fiscal  advantage  of  a  company 
like  IBM  or  AT&T  or  Microsoft,  or 
for  the  political  advantage  of  a  nation 
like  Japan  or  France? 

Or  will  they  be  grass-roots  benefac¬ 
tors  like  so  many  who  supplied  the 
software  we  have  now — and  who  of¬ 
ten  supplied  it  through  the  pages  of 
DDJ1  I  would  like  it  to  be  the  latter.  In 
fact,  I  propose  to  you  that  what  DDJ 
has  always  done  is  to  support  and 
train  and  encourage  our  grass-roots 
systems  programmers.  And  that  is 
still  the  best,  and  most  truly  radical, 
thing  that  it  can  do  in  the  future.  I’m 
delighted  to  see  that  its  editors  agree 
with  me.  Proof  that  they  do  is  in  their 
publishing  Richard  Stallman’s  “GNU 
Manifesto”  ( DDJ  #101,  March 
1985),  an  outrageous,  revolutionary 
challenge  quite  in  the  old  vein. 


23 

351 


My  own  best  writing  for  DDJ  has 
been  inspired  by  a  species  of  the  Rob¬ 
in  Hood  ethic:  a  desire  to  steal  ideas 
from  the  well-funded  and  put  them 
into  the  heads  of  the  unfunded.  I  en¬ 
joyed  writing  my  recent  article  on 
Prolog  for  just  that  reason.  Prolog 
and  the  ideas  behind  it  had  been  the 
property  of  a  few  academics,  and 
these  ideas  were  rapidly  being  co-opt¬ 
ed  by  the  Japanese  for  their  Fifth 
Generation.  But  DDJ  gave  me  a 
chance  to  snatch  those  same  notions 
and  spread  them  around  everywhere, 
making  them  accessible  to  everybody 
who  can  grasp  them.  Of  course  the 
real  revolutionaries  are  the  people  in 
London  who  publish  Micro-PROLOG 
and  the  ones  in  this  country  who  are 
selling  a  version  of  DEC- 1 0  Prolog  for 
the  IBM  PC.  The  ideas  wouldn’t  be 
much  use  without  the  cheap  software 
that  implements  them,  and  that 
wouldn’t  be  of  any  use  without  the 
cheap  micros  to  run  it  on. 

So  these  processes  all  feed  back  on 
themselves  and  get  richer  and  deeper. 
But  the  continuation  of  this  revolu¬ 
tion,  at  least  its  continuation  as  a  rev¬ 
olution  and  not  as  a  captive  toy  of  the 
corporations  and  the  advertisers,  de¬ 
pends  on  getting  ever  more  sophisti¬ 
cated  ideas  and  tools  into  the  hands 
of  creative  people,  so  that  they  can 
keep  putting  ever  more  sophisticated 
programs  back  into  ours. 

Notice  how  the  ante  goes  up  on  ev¬ 
ery  round.  The  level  of  sophistication 
is  escalated  every  time  through  the 
loop.  The  Mac’s  internal  toolkit,  and 
its  competitive  followers  Windows 
and  GEM,  are  vastly  harder  to  use 
well  than  the  old  CP/M  BDOS  func¬ 
tions.  Graphics,  AI,  and  speech  rec¬ 
ognition,  in  their  different  ways,  re¬ 
quire  the  exercise  of  all  the 
algorithmic  tools  of  computer  sci¬ 
ence.  And  it  takes  very  abstruse,  very 
mathematical  tools  to  analyze  these 
massive,  sophisticated  programs  and 
prevent  them  from  overflowing  the 
capacities  of  personal  hardware, 
which  really  are  quite  limited.  The 
experience  of  mainframers  has  been 
that  your  hardware  is  always  an  or¬ 
der  of  magnitude  too  small  for  the 
software  you’d  like  to  run.  There’s  no 
indication  that  this  law  has  been  re¬ 
pealed  for  micros.  We  will  be  squeez¬ 


26 


ing  quarts  into  pint  bottles  forever, 
and  it  takes  sound  theoretical  insight 
to  do  that  well. 

I  should  add  that  the  rule  that  the 
capacity  of  the  hardware  always  falls 
short  of  the  needs  of  the  software  just 
might  be  repealed  by  highly  parallel, 
multiple-micro  systems.  Two  things 
about  such  systems:  first,  they  are 
right  on  the  fringe  of  computer  sci¬ 
ence  and  the  academics  haven’t  the 
foggiest  idea  how  to  build  good  soft¬ 
ware  for  them;  and  second,  they  are 
ideally  suited  for  hobbyist-level,  ga¬ 
rage  experiments. 

But  this  is  why  the  face  and  con¬ 
tents  of  DDJ  have  been  changing. 
Whether  the  editors  and  contributors 
knew  it  or  not,  they’ve  been  trying  to 
prepare  themselves  to  discuss  and  un¬ 
derstand  computers  at  the  more  so¬ 
phisticated  level  permitted  by  the 
second  generation  of  micros.  But 
there’s  been  no  change  in  who  is  do¬ 
ing  the  discussing,  nor  in  their  essen¬ 
tially  subversive  motive,  which  is  to 
snatch  the  tools  of  the  establishment 
and  apply  them  in  the  public  domain. 

It’s  that  mischievous,  subversive 
motive  that  has  kept  me  writing  for 
DDJ.  That,  and  the  belief  I  still  hold 
that,  somewhere  out  there,  fingers  on 
the  keyboard  of  an  Apple  or  Osborne, 
is  a  fifteen-year-old  kid  who  is  capable 
of  writing  the  program  that  will  turn 
the  world  upside  down.  The  proper 
role  of  DDJ  is  to  make  sure  that  that 
kid  has  free,  unfettered  access  to  the 
ideas  and  the  software  tools  that  she 
needs  in  order  to  write  it. 

DD| 

Reader  Ballot 

Vote  for  your  favorite  feature/article. 

Circle  Reader  Service  No.  191. 


orro 


Dr.  Dobb's  Journal,  May  1985 


Command  Line  Processing 


by  Allen  Holub 


A  command  line  switch  is  an  argu¬ 
ment  usually  preceded  by  a  ‘  —  ’  that 
modifies  the  way  in  which  a  program 
works.  Just  about  every  C  program  I 
write  uses  command  line  switches, 
and  all  these  programs  have  to  pro¬ 
cess  these  switches  one  way  or  anoth¬ 
er.  For  a  long  time,  I  wrote  a  differ¬ 
ent  little  subroutine  for  each 
program,  modifying  the  routine  to 
deal  with  the  idiosyncrasies  of  a  par¬ 
ticular  program.  After  writing  the 
same  subroutine  about  a  million 
times  (and  making  the  same  off-by- 
one  error  with  argc  every  time),  I 
thought  that  there  had  to  be  a  better 
way.  All  my  processing  routines  were 
essentially  the  same  in  structure;  only 
the  names  of  the  command  line 
switches  were  different.  A  general 
purpose  command  line  processor  was 
clearly  what  1  needed. 

Looking  around  in  the  literature  for 
something  that  did  the  job,  I  found  the 
“Argum”  package,  which  appeared  in 
Anthony  Skjellum’s  C/Unix  column 
in  DDJ  (#70,  August  1982).  The  pro¬ 
gram  was  well  written  and  did  what  I 
needed,  but  it  had  several  problems. 
First  of  all,  it  did  a  lot  more  than  I 
needed  and  the  extra  functions  added 
extra  code.  Because  this  package  was 
going  to  be  included  in  every  program 
I  wrote,  a  reduction  in  scope  seemed 
to  be  a  good  idea.  Another,  related, 
problem  was  the  internal  tables  used 
by  Argum.  They  were  created  at  run¬ 
time  (as  compared  to  compile  time). 
This  added  both  extra  code  and  extra 
execution  time  to  any  program  that 
used  Argum.  Finally,  Argum  had  no 
convenient  way  to  deal  with  errors  on 
the  command  line.  In  view  of  these 
problems,  I  decided  to  write  my  own 
processor,  a  description  of  which  fol¬ 
lows.  This  is  by  far  the  most  frequent¬ 
ly  used  subroutine  in  my  standard 
library. 


Getargs(  ) 

The  command  line  processor  is  actu¬ 
ally  a  package  of  subroutines.  Access 
to  these  routines  is  through  a  single 
procedure,  called  getargs(  ).  The  rou¬ 
tines  are  table  driven.  In  every  pro¬ 
gram  you  have  to  declare  a  table  in 
which  the  various  command  line 
switches  are  described.  Getargs(  )  re¬ 
moves  the  switches  from  the  com¬ 
mand  line  as  it  works.  This  means 
that  position-sensitive  arguments 
(e.g.,  a  switch  that  applies  to  all  files 
that  follow  it  on  the  command  line, 
but  not  to  any  files  that  precede  it) 
are  not  supported.  I  don’t  use  this  sort 
of  switch  very  often,  and  I  got  tired  of 
having  to  skip  past  arguments  that 
had  already  been  processed  to  get  to  a 
filename.  The  arguments  are  evaluat¬ 
ed  left  to  right,  so  interaction  be¬ 
tween  PROC  type  switches  (see  be¬ 
low)  is  possible. 

When  getargs(  )  finds  an  error  on 
the  command  line,  it  prints  to  stderr  a 
list  of  all  legal  switches,  along  with  a 
brief  description  of  what  those  switch¬ 
es  do  and  their  default  values.  After  it 
prints  the  error  message,  getargs(  ) 
terminates  the  program  with  an 
exit(  1 )  call.  This  closes  any  open  files 
and  returns  to  the  operating  system. 

Command  Line  Switch  Formats 

Command  line  switches  all  must  take 
the  form: 

—  <character>  [  <number>  1 
<string>  ] 

That  is,  all  arguments  start  with  a 
minus  sign;  each  switch  is  identified 
by  a  single  letter  that  follows  the  mi¬ 
nus  sign  immediately  (no  intervening 
spaces);  the  letter  may  be  followed  by 
an  optional  number  or  string,  again 
with  no  spaces  between  the  character 
that  serves  as  the  switch  identifier 


and  the  corresponding  number  or 
string.  Switches  may  be  combined, 
provided  that  the  argument  types  al¬ 
low  this  combination.  For  example, 
the  command  line: 

program  —a  —  b  1 23  — c 
can  also  be  written  as: 

program  —  abl23c 

However,  if  getargs(  )  expects  a 
string  to  follow  the  switch  identifier 
character,  then  it  assumes  that  the 
rest  of  the  argument  is  part  of  the 
string.  You  have  to  be  careful  when 
combining  string  type  command  line 
switches  with  other  types. 

Using  Getargs(  ) 

To  use  getargs(  )  you  must  do  two 
things:  (1)  set  up  a  table  to  tell  the 
routine  what  kind  of  command  line 
switches  to  expect;  (2)  call  the  rou¬ 
tine  itself  somewhere  early  in  the 
main(  )  module.  The  file  getargs.h 
(Listing  One,  page  32)  contains  the 
#defines  and  typedefs  needed  to  cre¬ 
ate  the  command  switch  descriptor 
table.  This  table  is  an  array  of 
structures: 

typedef  struct 

{ 

unsigned  arg  :  7  ; 

unsigned  type  :  4  ; 

int  "“variable  ; 

char  *errmsg  ; 

} 

ARG; 

The  arg  field  is  a  single  character 
that  identifies  the  switch  on  the  com¬ 
mand  line.  In  the  following  descrip¬ 
tions  this  character  is  represented  as 
<switch>.  The  type  field  may  take 
any  one  of  five  values,  all  of  which  are 


Dr.  Dobb's  Journal,  May  1985 


28 

n.«M 


#defined  in  getargs.h.  The  behavior 
of  getargs(  )  will  vary  according  to 
the  value. 

INTEGER  switches  take  the  form 

—  <switch>  <number> 

Numbers  preceded  by  Ox  are  hex,  by 
0  without  the  x,  octal.  All  others  are 
decimal.  The  number  is  terminated 
by  any  character  not  legal  in  the  indi¬ 
cated  radix.  Any  characters  that  fol¬ 
low  are  assumed  to  be  additional 
switches.  The  int  sized  variable  point¬ 
ed  to  by  the  variable  field  of  the  ARG 
structure  will  be  set  to  the  value  of 
the  <number>.  If  the  <number> 
isn’t  typed  on  the  command  line, 
^variable  will  be  set  to  0. 

BOOLEAN  switches  will  cause 
some  action  based  on  the  presence  or 
absence  of  the  indicated  switch  on  the 
command  line.  If  the  switch  is  pre¬ 
sent,  then  the  int  pointed  to  by  the 
variable  field  is  set  to  1,  otherwise 
*variable  is  not  modified. 

CHARACTER  switches  take  the 
form 

—  <switch>  <character> 

When  the  switch  is  found  on  the  com¬ 
mand  line,  then  *variable  is  set  to  the 
character  immediately  following  the 
<switch>  character. 

STRING  switches  take  the  form 

—  <switch>  <string> 

When  the  switch  is  found  on  the  com¬ 
mand  line,  the  character  pointer 
pointed  to  by  the  variable  field  is  set 
to  point  at  a  string  consisting  of  all 
characters  following  (but  not  includ¬ 
ing)  the  <switch>  character  up  to 
the  end  of  the  current  argument  (not 
to  the  end  of  the  command  line).  In 
the  case  of  combined  switches  in  a 
single  argument,  the  STRING  argu¬ 
ment  must  be  the  last  one  because  all 
following  characters  will  be  consid¬ 
ered  part  of  the  <string>.  When  de¬ 
fining  a  STRING  switch  in  the  table, 
be  sure  to  cast  the  variable  into  an 
integer  pointer.  See  Listing  Three 
(page  38),  line  15,  for  an  example. 

PROC  switches  take  the  form 

—  <switch>  <anything> 


This  works  like  the  STRING  switch  in 
that  all  characters  following 
<switch>  up  to  the  end  of  the  cur¬ 
rent  argument  will  be  part  of  the 
<anything>.  However,  the  variable 
field  is  a  pointer  to  a  subroutine  that 
is  called  indirectly  as  soon  as  the 
switch  is  encountered  on  the  com¬ 
mand  line.  A  pointer  to  <anything> 
is  passed  to  this  subroutine  as  a  single 
argument.  An  example  of  such  a  sub¬ 
routine  is  given  in  Listing  Three,  line 
19.  It  is  the  responsibility  of  the 
called  subroutine  to  parse  <any- 
thing>  as  appropriate. 

The  errmsg  field  is  used  to  print  an 
error  message  if  an  undefined  switch 
is  found.  An  example  of  the  format  is 
shown  in  the  Figure  (page  30).  This 
message  was  generated  when  the 
command  line  “argtest  —  x”  was  giv¬ 
en  to  argtest. 

Listing  Three  (if  you  haven’t  real¬ 
ized  it  by  now)  is  an  example  of  how 
to  use  getargs(  ).  Lines  5-9  are  decla¬ 
rations  of  the  objects  that  are  used  in 
the  variable  fields  of  the  switch  de¬ 
scriptor  table.  The  initial  values  of 
these  variables  remain  unchanged  if 
the  switch  is  not  encountered  on  the 
command  line  at  runtime.  The  table 
itself,  Argtab,  is  declared  on  lines  10- 
17.  The  main(  )  routine  calls  get- 
args(  )  on  line  35.  The  rest  of  main(  ) 
just  prints  the  command  line,  both 
before  and  after  the  getargs(  )  call,  so 
you  can  see  how  getargs(  )  strips  pro¬ 
cessed  switches  out  of  argv. 

Getargs(  )  itself  starts  on  line  98  of 
Listing  Two  (page  32).  It  is  passed 
four  arguments:  argv,  argc,  a  pointer 
to  the  switch  descriptor  table  (tabp), 
and  the  size  of  this  table  in  elements 
(not  bytes).  The  routine  returns  a 
new  value  of  argc,  and  argv  is  com¬ 


pressed,  i.e.,  all  entries  containing 
command  line  switches  are  removed 
from  it.  The  for  loop  on  line  1 1 2  pro¬ 
cesses  argv  one  element  at  a  time. 
Nargv  points  into  argv  and  strips  pro¬ 
cessed  switches.  Nargv  is  initialized 
to  point  at  argv[l]  because  argv[0] 
can’t  possibly  contain  a  command 
line  switch  (it  holds  the  program 
name).  If  no  leading  minus  sign  is 
found  in  the  argument,  *argv  is  cop¬ 
ied  to  *nargv  and  both  pointers  are 
advanced.  In  addition,  nargc  (new 
argc)  is  incremented.  If  the  argument 
does  begin  with  a  minus  sign,  it  is 
processed  as  a  command  line  switch. 
In  this  case  *argv  is  not  copied  to 
*nargv,  and  argv,  but  not  nargv,  is 
advanced.  This  effectively  eliminates 
the  argument  containing  the  switch 
from  the  argv  array. 

Findarg(  )  (Listing  Two,  line  44)  is 
used  by  getargs(  )  to  see  if  an  argu¬ 
ment  is  in  the  table.  It  performs  a  lin¬ 
ear  search.  Because  the  table  is  usu¬ 
ally  fairly  small,  it  seemed  as  if  the 
extra  code  needed  for  a  binary  search 
wasn’t  justified.  Findarg(  )  takes 
three  arguments:  c  is  the  switch  iden¬ 
tifier  character  being  searched  for; 
tabp  and  tabsize  are  the  same  param¬ 
eters  as  were  passed  to  getargs(  ). 

Setarg(  )  (Listing  Two,  line  9)  is 
called  when  a  command  line  switch  is 
found  in  the  table.  Argp  is  a  pointer 
into  the  table,  as  returned  from  find- 
arg(  ).  Linep  is  a  pointer  into  the  argv 
entry  that  is  being  processed.  Casts 
were  used  to  make  this  routine  as 
transportable  as  possible. 

Pr_usage(  )  (Listing  Two,  line  57) 
is  used  to  print  the  error  message 
shown  in  the  Figure  when  an  illegal 
command  line  switch  is  found.  It  just 
spins  through  the  table,  printing  the 


Illegal  argument  <x>.  Legal  arguments  are: 

(value  is  FALSE) 

(value  is  .) 

(value  is  0) 

(value  is  <doo  wha>) 


Figure 

Error  Output  from  Program  in  Listing  Three 


-b 

—  c<c> 

—  n<num> 

—  s<str> 

—  p<str> 


boolean  argument 
character  argument 
integer  argument 
string  argument 
procedure  argument 


30 


Dr.  Dobh's  Journal,  May  1985 


current  contents  of  the  object  pointed 
to  by  the  variable  field  as  well  as  the 
message  given  in  the  errmsg  field. 

The  final  routine  in  the  package  is 
stoi(  )  (for  string  to  integer,  Listing 
Four,  page  38),  which  is  used  to  pro¬ 
cess  INTEGER  switches.  Stoi(  )  is  a 
fancy  version  of  the  standard  library 
routine  atoi(  )  (described  on  page  58 
of  Kernighan  &  Ritchie).  Stoi(  )  can 
handle  numbers  in  base  8,  10,  or  16. 
If  code  size  is  a  real  issue,  you  may 
want  to  remove  the  code  that  process¬ 
es  octal  numbers  (Listing  Four,  lines 
45-53).  Another  major  difference  be¬ 
tween  stoi(  )  and  atoi(  )  is  that  stoi(  ) 
is  passed  a  pointer  to  a  character 
pointer.  That  is,  it  is  passed  the  ad¬ 
dress  of  the  pointer,  which  in  turn 
points  into  the  string  to  be  processed. 
This  extra  level  of  indirection  lets  you 
modify  the  character  pointer  itself  to 
point  past  the  end  of  the  number  be¬ 
ing  processed.  If  you  use  atoi(  ),  you 


have  to  go  through  the  string  twice, 
once  to  extract  the  number  and  once 
more  to  skip  past  the  characters  that 
represent  that  number. 

Conclusion 

Several  additions  could  be  made  to 
getargs(  ),  at  the  cost  of  an  increase 
in  complexity  and  code  size. 

•  A  common  command  line  function 
is  to  open  or  close  a  file  in  a  partic¬ 
ular  mode  and  to  abort  if  the  file 
can’t  be  opened.  At  present  I’m  do¬ 
ing  this  with  a  PROC  type  com¬ 
mand  line  switch,  but  you  could 
add  another  argument  type,  whose 
variable  field  points  to  a  FILE 
pointer.  Alternately,  variable  could 
point  to  a  structure  that  included 
the  FILE  pointer,  an  open  mode,  a 
pointer  to  an  error  processing  rou¬ 
tine,  and  a  default  file  name. 

•  Another  nice  feature  would  be  to 
mark  an  argument  if  its  presence 
is  required  on  the  command  line. 


You  could  accomplish  this  by  add¬ 
ing  additional  types  (i.e.,  REQ 
_INTEGER),  or  by  adding  anoth¬ 
er  field  to  the  ARG  structure.  An 
error  message  would  be  printed  if 
the  argument  wasn’t  found  on  the 
command  line. 

•  Getargs(  )  depends  on  initializers 
to  set  default  argument  values.  If 
your  compiler  doesn’t  support  ini¬ 
tializers,  you  can  add  a  default  val¬ 
ue  field  to  the  ARG  structure. 

•  I  have  violated  my  smallness  rule 
by  using  stoi(  )  to  process  numeric 
arguments.  If  you  expect  only  dec¬ 
imal  numbers,  you  may  want  to 
replace  stoi(  )  with  atoi(  ).  How¬ 
ever,  stoi(  )  is  a  useful  routine  in 
its  own  right. 

I’m  sure  that  you  can  think  of  other 
bells  and  whistles.  I’ve  been  using  get- 
args(  )  for  a  of  couple  years  now  and 
am  satisfied  with  it  in  its  existing 
state.  Defining  the  way  the  command 
line  looks  and  then  getting  switches 
from  it  is  now  a  painless  process,  ddj 


C  Chest  (Text  begins  on  page  28) 

Listing  One 


1 

/* 

Getargs . h 

Typedefs  and 

defines  needed  for  getargs 

2 

*/ 

3 

# define 

INTEGER 

0 

4 

#d  e  f ine 

BOOLEAN 

1 

5 

#de  f ine 

CHARACTER 

2 

6 

#de  f i ne 

STRING 

3 

7 

#de  f ine 

PROC 

4 

8 

Q 

typedef 

l 

struct 

10 

l 

unsigned 

arg  :  7  ; 

/*  Command  line  switch 

*/ 

1 1 

unsigned 

type  :  4  ; 

/♦  variable  type 

♦/ 

12 

int 

♦variable  ; 

/♦  pointer  to  variable 

*/ 

13 

char 

♦errmsg  ; 

/*  pointer  to  error  message 

♦/ 

14 

) 

15 

ARG; 

Listing  Two 


Is  /*  GETARGS.C  Command  line  argument  processor  for  C  programs 

2:  * 

3:  *  (C)  Copyright  1985,  Allen  I.  Holub.  All  rights  reserved. 

4:  *  This  program  may  be  copied  for  personal,  non-profit  use  only. 

5:  ♦/ 


End  Listing  One 


6:  ^include  <stdio.h> 

7:  ^include  <getargs.h> 


8:  typedef  int 


(*PFI)(); 


9 

static 

char 

*setarg(  argp,  linep  ) 

10 

ARG 

♦argp ; 

1 1 

char 

♦linep ; 

12 

( 

13 

/♦ 

Set  an  argument,  argp  pc 

>ints  at 

14 

* 

corresponding  to  *linep 

Return 

15 

* 

past  the  argument  being 

set . 

16 

♦/ 

17 

++linep 

18 

swi tch ( 

argp->type  ) 

the  argument  table  entry 
linep,  updated  to  point 


Dr.  Dobb' s  Journal.  May  1985 


32 


case  INTEGER: 

*argp->variable  =  stoi(  Slinep  ); 
break ; 


case  BOOLEAN: 

*argp->variable  =  1; 
break ; 


case  CHARACTER: 

*argp->variable  =  *linep++  ; 
break ; 

case  STRING: 

♦(char  * *  ) a r g p-> va r ia bl e  =  linep  ; 
linep  = 
break ; 


case  PROC: 

(*  (  PFI ) ( ar gp->var iable )  )(  linep  ); 
linep  = 
break ; 


default: 


fprintf (stderr, "INTERNAL  ERROR:  BAD  ARGUMENT  TYPE\n") ; 
break ; 


returnf  linep  ); 


static 

int 

ARG 

( 


static 

ARG 

int 

( 


ARG  *findarg(  c,  tabp,  tabsize  ) 

c,  tabsize; 

♦tabp ; 

/*  Return  pointer  to  argument  table  entry  corresponding 

*  to  c  (or  0  if  c  isn't  in  table). 

*/ 

for(;  — tabsize  >=  0  ;  tabp++  ) 
if(  tabp->arg  ==  c  ) 
return  tabp; 

return  0 ; 

pr_usage(  tabp,  tabsize  ) 

♦tabp ; 
tabsize ; 

/*  Print  the  argtab  in  the  form: 

*  -<arg>  <errmsg>  (value  is  <*variable>) 


for(;  — tabsize  >=  0  ;  tabp++  ) 

( 

switch(  tabp->type  ) 

( 

case  INTEGER: 

fprintf (stderr,  "-%c<num>  %-40s  (value  is  ", 

tabp->arg,  ta bp->er rmsg ) ; 
fprintf(stderr ,  "%-5d)\n",  * ( ta bp->var iable )  ); 
break ; 

case  BOOLEAN: 

fprintf(stderr,"-%c  %-40s  (value  is  ", 

tabp->arg,  tabp->errmsg)  ; 
fprintf (stderr,  "%-5s)\n",  *( tabp->variable) 

?  "TRUE":  "FALSE"); 

break ; 

case  CHARACTER: 

fprintf(stderr,  "-%c<c>  %-40s  (value  is  ", 

tabp->arg,  tabp->errmsg ) ; 
fprintf (stderr ,  "%-5c)\n",  *( tabp->var iable )  ); 
break ; 

case  STRING: 

fprintf (stderr,  "-%c<str>  %-40s  (value  is  ", 

tabp->arg,  tabp->errmsg) ; 

(Continued  on  page  36) 


1Z.F. 


Dr.  Dobb's  Journal,  May  1985 


r 


I  (Listing  Continued,  text  begins  on  page  28) 

Listing  Two 


87 

88 
89 


fprintf(stderr, 
break ; 


"<%s>)\n", 

♦(char  **)tabp->variable) ; 


90 

91 

92 

93 

94 

95 

96 

97 


case  PROC: 

f print f ( s tderr  ,  "-%c<str>  %-40s\n", 

tabp->arg,  tabp->errrasg) ; 

break ; 


Idefine  ERRMSG  "Illegal  argument  <%c>.  Legal  arguments  are:\n\n" 


98 

99 
100 
101 
102 

103 

104 

105 

106 
107 


int 

int 

char 

ARG 

( 


getargs(argc ,  argv,  tabp,  tabsize  ) 
argc,  tabsize  ; 

♦♦argv  ; 

♦tabp  ; 

/*  Process  command  line  arguments.  Stripping  all  command  line 

*  switches  out  of  argv.  Return  a  new  argc.  If  an  error  is  found 

*  exit(l)  is  called  (getargs  won't  return)  and  a  usage  message 

*  is  printed  showing  all  arguments  in  the  table. 

*/ 


108 

109 

1 10 


register  int 
register  char 
register  ARG 


nar  gc 

♦♦nargv,  *p 
*argp 


111 

112 

113 

114 

115 

116 

117 

118 

119 

120 
121 


nargc  =  1  ; 

for(nargv  =  ++argv  ;  — argc  >  0  ;  argv++  ) 
( 

i f (  ♦♦argv  ! =  ' - '  ) 

( 

*nargv++  =  *argv  ; 
nargc++; 

) 

else 

( 

p  =  (*argv)  +  1  ; 


122 

123 

124 

125 

126 

127 

128 

129 

130 

131 

132 

133 

134 

135 

136 


while(  *p  ) 

( 

if(argp  =  findarg(*p,  tabp,  tabsize)) 
p  =  setarg(  argp,  p  ); 

else 

( 

f print f ( s tderr ,  ERRMSG,  *p  ); 
pr_usage(  tabp,  tabsize  ); 
exi t (  1  )  ; 

) 

) 

) 

) 

return  nargc  ; 


End  Listing  Two 


Listing  Three 


1  : 
2: 

/* 

*/ 

ARGTEST . C  Test 

program  for  getargs. 

3: 

^include 

<stdio .h> 

4: 

linclude 

"getargs . h" 

5: 

int 

boolarg  =  0; 

/*  Variables  used  by  argtab  */ 

6: 

int 

chararg  =  ' . ' ; 

7: 

int 

intarg  =  0; 

8: 

char 

♦strarg  =  "doo  wha" 

; 

9: 

extern 

proc()  ; 

10: 

ARG 

Argtab [  ]  = 

(Continued  on  page  38) 

Dr.  Dobb’s  Journal,  May  1985 


36 

357 


C  Chest  (Listing  Continued,  text  begins  on  page  28) 

Listing  Three 


11 

( 

12 

(  ’b\ 

BOOLEAN, 

&boolar g , 

"boolean  argument" 

13 

{  ’f. 

CHARACTER, 

Schararg , 

"character  argument 

14 

(  'n\ 

INTEGER, 

Sintarg , 

"integer  argument" 

15 

(  's'. 

STRING, 

(int  *)&strarg. 

"string  argument" 

16 

17 

{  V. 

); 

PROC, 

(int  *)&proc, 

"procedure  argument 

18 

Idefine  TABSIZE 

(  s i zeof ( A 

rgtab)  /  sizeof(ARG)  ) 

). 

), 

). 

), 

) 


19 

20 
21 
22 

23 

24 


proc(  str  ) 
char  *str; 

pr i nt f ( "Inside  procedure  called  by  -p  command  line  switch,  "); 
pr in t f ( "st r ing  =  <%s>\n",  str  ); 

) 


25: 

main(argc,  argv) 

26: 

int 

argc  ; 

27: 

char 

**argv ; 

28: 

t 

29: 

register  int 

i ; 


30 

31 

32 

33 

34 


printf("Argc  ==  %d .  ",  argc); 
printf("Cmd  line:  argtest  "); 

for(  i  =  1  ;  i  <  argc  ;  printf("%s  ",  argv[i++])  ) 
printf("\n")  ; 


35: 


argc  =  getargs(  argc,  argv,  Argtab,  TABSIZE)  i 


36 

37 

38 

39 

40 

41 


printf("Argc  ==  %d.  ",  argc); 
printf("Cmd  line:  argtest  "); 

for(  i  =  1  ;  i  <  argc  ;  printf("%s  ",  argv[i++])  ) 

printf("\n") ; 


End  Listing  Three 


Listing  Four 

1:  /*  ST0I.C  More  powerful  version  of  atoi. 

2:  * 

3:  *  Copyright  (C)  1985  by  Allen  Holub.  All  rights  reserved. 

4:  *  This  program  may  be  copied  for  personal,  non-profit  use  only. 

5:  */ 


6:  Idefine  islower(c)  (  'a'  <=  (c)  &&  (c)  <=  'z'  ) 

7:  Idefine  toupper(c)  (  islower(c)  ?  (c)  -  ('a'  -  'A')  :  (c)  ) 


8 

int 

stoi( instr) 

9 

register  char 

**i n  s t  r ; 

10 

( 

11 

/* 

Convert 

string 

to  integer.  If  string  starts  with  Ox  it  is 

12 

* 

interpreted  as 

a  hex  number, 

else  if  it 

starts 

with  a  0  it 

13 

* 

is  octal 

,  else 

it  is  decimal 

.  Conversion 

stops 

on  encounterii 

14 

* 

the  first  character  which  is 

not  a  digit 

in  the 

indicated 

15 

* 

radix  .  *instr 

is  updated  to 

point  past  the  end 

of  the  number 

16 

*/ 

17 

register 

int 

num  = 

0  ; 

18 

register 

char 

*str 

19 

int 

sign  = 

1 

20 

str  =  *instr ; 

21 

while(*str  ==  * 

'  II 

♦str  ==  ’ \t 

| |  *str  == 

'  \n '  ) 

22- 

str++  ; 

23: 

i f (  *  st r 

_ *  , 

) 

24: 

( 

25: 

sign  =  - 

1  ; 

26: 

str++ ; 

38 


OCQ 


Dr.  Dobb's  Journal,  May  1985 


27: 


28 

29 

30 

31 

32 

33 

34 

35 

36 

37 

38 

39 

40 

41 

42 

43 

44 

45 

46 

47 

48 

49 

50 

51 

52 

53 

54 

55 

56 

57 

58 

59 

60 
61 


if(*str  ==  '0') 

( 

++st  r ; 

if  (*str  ==  'x'  ||  *str  ==  'X'  ) 

( 

str++ ; 

while(  ('0'<=  *str  SS  *str  <=  '9')  | 

('a'<=  *str  SS  *str  <=  'f')  | 

(' A'<=  *str  &&  *str  <=  'F')  ) 

{ 

nun  *=  16; 

nun  +=  ('0'<=  *str  &&  *str  <=  '9')  ? 

*str  -  'O'  : 

toupper (*str )  -  'A'  +  10  ; 

str++ ; 

) 

) 

else 

{ 

while(  'O'  <=  *str  &&  *str  <=  '7'  ) 

( 

nun  *=  8; 

num  +=  *str++  -  'O'  ; 


) 

else 

( 

while(  'O'  <=  *str  &&  *str  <=  '9'  ) 

( 

num  *=  10; 

num  +=  *str++  -  'O'  ; 

) 


62 ;  *instr  =  str; 

63:  return(  num  *  sign  ); 

64:  } 


End  Listings 


Dr.  Dobb’s  Journal,  May  1985 


39 

359 


Using 

Decision  Variables 
in  Graphics 
Primitives 

by  Tom  Hogan 

"Graphics  algorithms"  is  not  a  listing 
you'll  find  in  the  Collected  Algorithms  of 
the  ACM,  but  graphics  programming 
presents  unique  problems  to  the 
software  developer. 


Picture  a  vast  subterranean  cavern  packed  with 
robed,  grey-bearded  men.  A  small  platform  stands 
at  one  end.  Billows  of  purple  smoke  appear  and  out 
of  the  mist  steps  a  tall  man  with  white  hair.  He  speaks  and 
his  voice  fills  the  cavern. 

“Welcome,  brethren.  Tonight  we  shall  consider  how  to 
guarde  our  Realme  of  Graphix  from  the  uninitiated  who 
would  learn  our  Mysteries  and  divulge  them.  We  will 
speake  of  Master  Bresenham,  who  revealed  to  us  the  Se¬ 
crete  of  ye  Mystik  Circle.” 

Does  the  idea  of  graphics  wizards  jealously  guarding 
their  secrets  seem  strange?  I  can  tell  you  that  it  didn’t 
seem  so  to  me  when  I  searched  the  literature  for  a  routine 
that  would  map  ellipses  and  couldn’t  find  one. 

Light  in  the  Tunnel 

Thanks  to  a  tip,  I  learned  about  Fundamentals  of  Inter¬ 
active  Computer  Graphics  by  James  D.  Foley  and  An- 
dries  Van  Dam  (Addison  Wesley,  1982).  It  contains  an 
algorithm  for  plotting  a  circle  using  a  decision  variable 
(based  on  Bresenham’s  algorithm).  I  used  the  decision 
variable  method  (hereafter  referred  to  as  the  DV  method) 
to  derive  the  algorithm  for  plotting  ellipses  that  is  found 
in  Listing  One  (page  45). 

The  DV  Method  Generalized 

Although  the  DV  method  was  used  only  to  plot  an  ellipse, 
it  can  be  used  to  plot  other  conic  sections  as  well.  In  fact, 
the  DV  method  can  be  used  to  generate  any  well-behaved 
curve  that  can  be  expressed  as  G(x,  y)  =  0. 

The  DV  Method  Compared 

Speed  is  important  in  graphics  applications.  The  DV 
method  is  fast  because  it  adds,  shifts,  subtracts,  and  mul¬ 
tiplies  integers,  taking  much  less  time  than  would  be  re¬ 
quired  by  floating-point  calculations. 

An  obvious  method  for  plotting  an  ellipse  is  to  calculate 
each  point  independently  by  incrementing  x  in  unit  steps 
and  calculating  y  using  square  roots.  This  method  is  slow 
and  generates  a  curve  that  is  nonuniform  when  the  slope 
of  its  tangent  line  falls  below  —  1  (see  Figure  1,  page  41 ). 

In  contrast  to  the  preceding  method,  the  DV  method 
does  not  calculate  points  independently.  Instead,  each 
succeeding  point  is  mapped  with  reference  to  the  point 
previously  mapped.  Furthermore,  this  method  guarantees 
that  succeeding  points  are  adjacent.  Therefore,  the  gener¬ 
ated  curve  is  more  uniform  than  the  curve  generated  by 
calculating  points  independently  (see  Figure  2,  page  41 ). 

Reduce  the  Number  of  Sample  Points 

Eight  adjacent  points  surround  any  previously  mapped 
point.  In  order  to  use  the  DV  method  to  map  the  curve, 
you  must  reduce  the  number  of  sample  points  from  eight 
to  two.  The  sign  of  the  decision  variable  can  only  be  posi- 


Tom  Hogan,  C  Source,  12801  Frost  Road,  Kansas  City, 
MO  641 38. 


40 


Dr.  Dobb’s  Journal,  May  1985 


tive  or  negative.  The  two  possibilities  correspond  to  the 
two  sample  points. 

Take  the  case  of  an  ellipse  mapped  only  in  the  first 
quadrant.  Points  1,  2,  3,  4,  and  6  (see  Figure  3,  page  42) 
can  be  discarded  immediately  when  mapping  from  Point 
A  to  C  (see  Figure  4,  page  42),  because  x  never  decreases 
and  y  never  increases  as  the  ellipse  is  mapped  in  that 
direction.  Points  1  through  3  would  require  y  to  increase, 
while  points  1,  4,  and  6  would  require  x  to  decrease. 

This  reduces  the  choice  to  three  points:  5,  7,  and  8. 
Which  two  are  chosen  as  the  set  of  sample  points  depends 
on  the  slopes  of  the  line  segments  joining  them  and  the 
last  point  plotted:  the  slope  of  the  tangent  line  must  fall 
within  the  range  of  the  slopes  of  the  two  line  segments  for 
the  region  plotted.  The  slopes  of  the  line  segments  of  any 
two  points  must  approach  the  slope  of  the  tangent  line  as 
closely  as  possible.  Thus,  a  set  comprising  Points  5  and  7 
would  be  excluded  because  it  would  violate  this  require¬ 
ment  in  the  region  close  to  B  in  Figure  4.  There  remain 
the  possible  combinations  5  and  8  and  7  and  8.  Points  5 
and  8,  corresponding  to  Points  S  and  T  in  Figure  5  (page 
42),  can  be  said  to  comprise  set  A.  Points  7  and  8,  corre¬ 
sponding  to  Points  T  and  U  in  Figure  5,  can  be  said  to 
comprise  set  B. 

Changing  Sets  of  Sample  Points 

From  Point  A  to  B  (Figure  4),  set  A  is  used  because  the 
slope  of  the  tangent  falls  within  the  range  delimited  by  the 
slopes  of  F5  and  FT  (Figure  5).  The  decision  variable  is 
initialized  to  its  value  at  Point  A  (see  Equation  9  or  10  in 
the  Table  on  page  44).  The  quantities  to  be  added  to  it  are 
based  on  the  sample  points  in  set  A  (Equations  6  and  8). 

When  the  slope  of  the  tangent  line  reaches  —  1  (at  Point 
B  in  Figure  4),  set  B  is  used  (see  Figure  6,  page  42).  The 
value  of  the  decision  variable  is  recalculated  (Equation  1 1 ) 
at  Point  B  for  set  B.  The  quantities  that  are  added  to  the 
decision  variable  are  now  based  on  set  B  (Equations  1 3  and 
15). 

Expansion  on  General  Applications 

Please  note  a  key  feature  of  the  DV  method:  the  choice  of 
the  sample  points  depends  on  the  slope  of  the  tangent  line 
and  the  direction  of  its  change.  A  curve  can  be  broken  up 
into  sections  for  plotting,  based  on  the  range  of  the  slope 
of  the  tangent  line  in  each  section.  The  method  is  useful 
for  a  broad  spectrum  of  curves. 

Example:  The  Ellipse 

The  initial  value  of  y  depends  on  the  position  of  the  major 
axis.  If  it  is  vertical,  y  is  Rm,  half  the  length  of  the  major 
axis.  If  horizontal,  y  equals  Rm  multiplied  by  the  aspect 
ratio.  The  positions  of  the  major  and  minor  axes  depend  on 
the  aspect  ratio.  If  it  is  less  than  one,  then  the  major  axis  is 
horizontal.  If  greater  than  one,  it  is  vertical. 

Long  variables  were  used  in  this  routine  instead  of  inte¬ 
gers  or  doubles  in  order  to  provide  the  widest  range  of 
values  for  the  aspect  ratio  and  Rm  while  retaining  reason¬ 
able  plotting  speed.  Integers  limited  the  ellipse’s  size  too 
much,  while  doubles  were  too  slow. 


Figure  1 

Circle  generated  by 
calculating  points  independently. 


Figure  2 

Circle  generated  by 
the  Decision  Variable  Method. 


Dr.  Dobb's  Journal,  May  1985 


41 

361 


The  lower  limit  for  the  aspect  ratio  is  0.004  and  Rm 
must  not  exceed  1 54.  Otherwise,  the  values  of  some  of  the 
long  variables  will  overflow. 

This  algorithm  takes  advantage  of  the  symmetry  of  an 
ellipse.  The  ellipse  is  symmetric  about  the  x  and  y  axes, 
and  about  its  center.  Therefore,  an  ellipse  need  only  be 
mapped  in  the  first  quadrant.  The  other  three  quadrants 


are  easily  plotted. 

Clipping  is  done  by  the  routine  in  Listing  Two  (page 
48).  The  algorithm  assumes  medium  resolution.  DD) 

(Listings  begin  on  page  45) 

Reader  Ballot 

Vote  for  your  favorite  feature/article. 

Circle  Reader  Service  No.  1 93. 


1 

2 

3 

• 

• 

• 

4 

R-1 

5 

• 

• 

• 

6 

7 

8 

• 

• 

Figure  3 

• 

Last  point  plotted  with  eight  adjacent  points. 

42 


Dr.  Dobb's  Journal.  May  1985 


The  well  known  equation  of  an  ellipse  is: 

di+i  =  «(Yi_i  -  I)2  -F/3(X,_i  +  2)2  -a/3 

«Y2  +  /SX2  — a/3  =  0. 

+  a(Yj_i  -  2)2  +  /3(Xj-,  +  2)2  —  a/3. 

Eq.  1. 

di+i  =  «(Yi_,  -  I)2  +  a(Yj_i  -  2)2 

+  20(Xj_i  +  2)2  —  2 a/3.  Eq.  7 

Therefore  define  an  error  term  D(Pj)  such  that  at  any  sample 

point  Pi  along  the  curve: 

Define  AT,  the  difference  between  d,+  i  and  dj  when  Tj  is 
chosen: 

D(Pj)  =  aY2  +  /3X2  -a/3. 

Eq.  2 

At  =  d,+i  —  dj. 

Define  two  points  S,  and  J,  relative  to  Pj_i  such  that  Pi  will 

At  =  2/3(Xj„i  +  2)2  -  2/3(Xj„,  +  I)2 

be  selected  from  one  of  these  two  points  (see  Figure  5). 

+  a(Yj_i  -  2)2  —  aY2_,. 

Define  a  decision  variable  di  such  that: 

Aj  =  4^Xj_i  -f-  6 (3 —  4or Y j _  i  +  4a.  Eq.  8 

di  =  D(Si)  +  D(Tj). 

Eq.  3 

Now  the  value  of  d,  for  the  initial  point,  which  is  located  on 
the  Y-axis,  must  be  determined.  Rm  is  half  the  length  of  the 

Notice  that  if  the  curve  passes  midway  between  Si  and  T, 

major  axis.  If  the  Y-axis  is  the  major  axis,  the  initial  point  is 

di  =  0.  Further  notice  that: 

the  point  P(0,  Rm).  Therefore: 

1 )  if  the  curve  passes  above  the  midpoint,  di  <  0,  and 

dj  =  2aR&  -  2aRm  +  a  +  2/3-2a,8.  Eq.  9 

2)  if  the  curve  passes  below  the  midpoint,  dj  >  0. 

If  the  X-axis  is  the  major  axis,  the  initial  point  is  the  point 

Therefore: 

P(0,  (/3/a) X Rm  ).  Therefore: 

1)  if  dj  <  0,  we  choose  Sj,  and 

di  =  (2/3 2/a)  X  R&  -  2/3Rm  +  a  +  2/3-  2a$.  Eq.  10 

2)  if  dj  >  =  0,  we  choose  Tj. 

Consider  now  the  slope  of  a  line  tangent  to  the  ellipse.  Be- 

The  quantity  to  add  to  dj,  depending  on  whether  Sj  or  T  is 

ginning  with  the  original  point,  it  can  be  guaranteed  that  the 

chosen,  must  be  determined.  The  value  of  dj  for  Sj  and  T, 

slope  of  the  tangent  line  will  always  decrease  while  the 

must  first  be  determined: 

ellipse  is  being  mapped  in  the  first  quadrant.  Therefore, 
once  the  slope  of  the  tangent  line  reaches  —  1 ,  it  will  never 

dj  =  D(Sj)  +  D(T  i). 

be  greater  than  —  1 .  Also,  it  may  be  seen  that  from  the 

di  =  aY?_,  +  /3(Xj-i  +  I)2  -a/3  +a(Y^,  -  I)2 

point  where  the  slope  of  the  tangent  line  reaches  —  1 

+  /3(Xj—  i  +  I)2— a/3. 

throughout  the  rest  of  the  first  quadrant,  for  any  point  Pj—  i 

dj  =  aY?_,  +  a(Yj_i  -  I)2  +  2/3(Xj_i  +  I)2 

mapped,  Tj  will  be  closer  than  Sj.  At  some  point,  the  ellipse 

-  2a/3. 

Eq.  4 

will  diverge  enough  from  the  tangent  line  of  slope  —  1  for  a 
point  Ui  (see  Figure  6)  to  be  closer  to  the  ellipse  than  T,.  It 

Determine  di+i  if  Sj  is  chosen: 

will  be  sufficient  to  check  the  new  set  of  points  (T  and  Uj) 
from  the  point  where  the  tangent  line's  slope  becomes  —  1 . 

di+1  =  aY?_,  +  /3(Xj-i  +  2)2  -a/3  +a(Yi_,  -  I)2 

dj  must  be  recalculated,  as  must  At  and  Au-  Calculate  dj: 

+  /3(Xi--,  +2)2  -  a/3. 

di+1  =  aY?_,  +  a(Yi_,  -  I)2  +  2/3(Xj_1  +  2)2 

d,  =  D(Tj)  +  D(Uj). 

—  2  a/3. 

Eq.  5 

dj  =  a(Yj_,  -  I)2  -F  /3(Xj_i  +  I)2  —a/3 

+  a(Yj_i  -  I)2  +  /3X?_,  -  a/3. 

Define  As,  the  difference  between  dj+ 1  and  dj  if  Sj  is  chosen: 

dj  =  2a(Yj_i  -  1)2  +  /3XtL, 

-F /3(X,_i  +  I)2  —  2 a/3.  Eq.  11 

As  =  di+i  ~  di. 

As  =  2/3(Xi--|  +  2)2  -  2/3(Xi-i  +  I)2. 

Determine  di+i  if  Tj  is  chosen: 

As  =  2/3(2Xj_  i  +  3). 

As  =  4/3X,-i  +  6/3. 

Eq.  6 

dj+,=2a(Yi+,-2)2  +  /3(XI-,  +  1)2 

-F  /3(Xj_i  -F  2)2  —  2a/3.  Eq.  12 

Now  calculate  the  quantity  to  be  added  to  d,  if  T,  is  chosen: 

Define  AT  as  the  difference  between  d,  and  di+i  if  Ti  is 

di+1  =  a(Yj-i  -  I)2  +  /8(Xi_,  +  2)2  -a/3 

chosen: 

Table 

Derivation  of  the  algorithm  to  map  an  ellipse. 

Dr.  Dobb's  Journal,  May  1985 


44 

363 


At  —  dj+i  —  dj. 

The  point  where  the  slope  becomes  less  than  —  1  must  be 

=  2a(-2  Yi-,  +  3)  +  j3(4Xi_,  +  4). 

Eq.  13 

determined.  Finding  the  first  derivative  of  the  ellipse  pro¬ 
duces  the  equation: 

Determine  d,+ 1  if  Uj  is  chosen: 

aYY'  =  — /3X.  Eq.  16 

di+,  =  2a(Yj-1  -  2)2+  0X?_, 

+  /3(Xi_1  +  1)2  -2ap. 

Eq.  14 

When  the  slope  is  required  to  be  less  than  —  1 ,  the  relation 

Define  Au  as  the  difference  between  di+i 

and  dj  if  Uj  is 

aYC/SX  Eq.  17 

chosen: 

determines  the  point  at  which  this  occurs.  This  concludes 

Au  —  dj+i  —  dj. 

the  derivation  of  the  algorithm  for  mapping  an  ellipse. 

=  2«(-2Yj_,  +  3). 

Eq.  1 5 

Decision  Variables  (Text  begins  on  page  40) 
Listing  One 


/*  compiled  11/28/84  under  Lattice  C  Compiler  ver.  2.13 
* 

*  ellipse. c 

*  by  Tom  Hogan 

*  Version  2.0 

*  Copyright  1984 

*  by  C  Source  Inc. 

*  All  rights  reserved. 

*  Permission  is  granted 

*  for  unlimited 

*  personal,  non- 

*  commercial  use  only. 

* 

*  For  answers  to  questions, 

*  please  write  to: 

* 

*  Tom  Hogan 

*  C  Source  Inc. 

*  12801  Frost  Road 

*  Kansas  City,  MO  64138 

* 

*  If  you  would  rather  call, 

*  our  number  is  (816)  353-8808. 

* 

*  Please  call  between  9  AM 

*  and  5  PM  CST,  Monday  thru 

*  Friday.  Calls  can  only 

*  be  returned  on  a  collect  basis. 

*/ 

tfdefine  ERROR  0 
tfdefine  SUCCESS  1 

#def ine  MAX_COL  319 
^define  MAX_R0W  199 
^define  MIN_C0L  0 
^define  MIN_R0W  0 

/*  a  macro  used  in  the  erroi — checking  code  */ 
tfdefine  inrange(a,x,b)  ((a)  <=  (x)  &&  (x)  <=  < b> > 

/*  the  bounds  of  the  following  array  can  be  set  for  different  * 

*  graphics  modes;  this  array  is  used  in  the  assembly  algorithm  * 

*  Listing  2  to  write  individual  pixels.  */  (Continued  on  next  page) 


45 


<~>f>  A 


Dr.  Dobb's  Journal,  May  1985 


Decision  Variables 

Listing  One 


(Listing  Continued,  text  begins  on  page  40) 


int  CRT_BNDSC]  =  <.  MAX_C0L,  MAX_R0W,  MIN_C0L,  I1IN_R0W  >; 

/*  r_sub_m  is  half  the  length  of  the  major  axis  */ 

ellipse(x,  y,  r_sub_m,  color,  aspect) 
int  x,  y,  r_sub_n,  color; 
double  aspect; 

{ 

int  two_x,  two_y,  or_color; 

int  col,  row,  rel_x,  rel_y;  /*  rel_x  and  rel_y  are  relative  */ 
double  square_aspect;  /*  to  the  center  */ 

long  temp,  beta,  alpha,  two_alpha,  four_alpha,  two_beta,  four_beta,  d; 

/*  d  is  the  decision  variable  */ 

/*  error  checking  follows  */ 

if  ((aspect  <  0.004)  ||  !inrange(0,  color,  15)  ||  !inrange(1,  r  sub  m, 
154))  (  " 

printf ("ELL IPSE (Xd,  Xd,  Xd,  Xd,  Xf)  -  BAD  ARG\n",  x,  y,  r_sub_m, 
color,  aspect); 
return  ERROR;  > 


square  aspect  =  aspect 

*  aspect; 

/* 

initialize  the  beginning  row 

*/ 

i f  (aspect  <  1.0)  { 

/* 

and  set  the  values  of 

*/ 

alpha  =  r  sub  m  * 

r_sub_m; 

/* 

constants 

*/ 

beta  =  alpha  *  square_aspect; 
row  =  y  +  r_sub_m  *  aspect;  > 
else  { 

beta  =  r_sub_m  *  r_sub_m; 
alpha  =  beta  /  square_aspect ; 
row  =  y  +  r_sub_m;  > 

if  (alpha  ==  OL)  alpha  =  1L;  /*  compensates  for  very  small  ellipses  */ 

if  (beta  ==  OL)  beta  =  1L; 

or_color  =  OxcOO  |  color;  /*  sets  the  high  byte  for  a  screen  interrupt  */ 

col  =  x;  /*  initializes  the  beginning  column  */ 

two_x  =  x  «  1; 

two_y  =  y  «  1; 

rel_y  =  row  -  y; 

two_alpha  =  alpha  «  1; 

four_alpha  =  alpha  «  2; 

four_beta  =  beta  «  2; 

two_beta  =  beta  «  1; 

/*  initialize  the  decision  variable  —  Eq.  9  or  10  */ 

d  =  two_alpha  *  ((rel_y  -  1)  *  rel_y)  +  alpha  +  two_beta  *  (1  -  alpha); 

/*  when  the  slope  <=  -1,  choose  the  new  set  of  points  */ 

/*  --  Eq.  17  */ 

while  (alpha  *  (rel_y  =  row  -  y)  >  beta  *  (rel_x  =  col  -  x))  < 
write_pix(col,  row,  two_x  -  col,  two_y  -  row,  or_color); 
if  (d  >=  0)  < 

d  +=  four_alpha  *  (1  -  rel_y);  /*  Eq.  6;  also  first  */ 

row — ;  y  /*  half  of  Eq.  8;  */ 

d  ♦=  two_beta  *  (3  +  (rel_x  «  1>);  /*  second  half  of  Eq.  8  */ 

col++;  > 

/*  initialize  the  decision  variable  for  the  rest  of  the  ellipse  */ 

/*  --  Eq.  11  */ 

d  =  two_beta  *  (rel_x  *  (rel_x  +  1)  +  two_alpha  *  (rel_y  *  (rel_y  -  2) 
+1)  +  (1  -  two_alpha)  *  beta; 

while  ((rel_y  =  row  -  y)  +1)  < 

wri te_pi x (col,  row,  two_x  -  col,  two_y  -  row,  or_color); 


if  (d  <=  0)  < 

/* 

col  -  X 

==  rel  x 

*/ 

d  +=  four  beta  *  (1  +  col  -  x); 

/* 

Eq.  15; 

also  first 

*/ 

col++;  5 

/* 

half  of 

Eq.  13; 

*/ 

row — ;  (Continued  on  page  48) 


Dr.  Dobb's  Journal,  May  1 985 


46 

365 


Decision  Variables  (Listing  Continued,  text  begins  on  page  40) 

Listing  One 


d  +=  two_alpha  *  (3  -  (rel_y  «  1));  >  /*  second  half  of  Eq.  13  */ 

return  SUCCESS;  > 

/*  write_pix  writes  four  points  to  the  screen,  taking  * 

*  advantage  of  the  ellipse's  four-way  symmetry  */ 


wri  te_pi  x  (col,  row,  neg_col,  neg_row,  or_color) 
int  col,  row,  neg_col,  neg_row,  or_color; 


_pixasm(col,  neg_row,  or_color); 

_pi xasm(neg_col,  neg_row,  or_color); 
_pi xasm(neg_col,  row,  or_color); 
_pixasm(col,  row,  or_color);  > 


/*  symmetry  —  y  axis  */ 
/*  symmetry  —  center  */ 
/*  symmetry  —  x  axis  */ 
/*  mapped  point  */ 


End  Listing  One 


Listing  Two 


8086  Assembly  function  that  clips  against  the  screen  bounds 


;  _pixasm(x,  y,  type_color) 

;  int  x,  y,  type_color 

;  type  -  get  or  read  pix  in  hi  byte 

;  color  -  if  set  pix,  in  lo  byte 

DGROUP  GROUP  DAT* 

DATA  SEGMENT  WORD  PUBLIC  ’DATA' 

ASSUME  DS: DGROUP 

EXTRN  CRT_BNDS:WORD 

PGROUP  GROUP  PROG 

PROG  SEGMENT  BYTE  PUBLIC  'PROG' 

ASSUME  CS: PGROUP 


PUBLIC  _pixas» 
_pixasm  proc  near 


push 

bp 

raov 

bp. 

sp 

mov 

cx. 

Cbp+4] 

emp 

cx. 

CRT_BNDS 

jg 

BYE 

emp 

cx. 

CRT_BNDS+4 

H 

BYE 

mov 

dx. 

tbp+63 

emp 

dx. 

CRT_BNDS+2 

jg 

BYE 

emp 

dx. 

CRT_BNDS+6 

jt 

BYE 

mov 

ax. 

Cbp+8D 

int 

lOh 

sub 

ah. 

ah 

pop 

ret 

bp 

_pixasm  endp 
PROG  ends 
DATA  ends 
end 


;  set  return  address 


set  x  (column) 
check  for  max  x  value 

check  for  min  x  value 

set  y  (row) 

check  for  max  y  value 

check  for  min  y  value 

set  color  (al)  /  fen  number  (ah) 
do  video  interrupt 

reset  frame  pointer 
bye ! 


End  Listings 


48 


Dr.  Dobb’s  Journal,  May  1985 


Solid  Shape  Drawing  on  the 
Commodore  64 


by  Richard  Rylander 


Computer  graphics  is  one  of  the 
fastest  growing  areas  of  com¬ 
puter  software  and  hardware 
development.  The  old  saying  that 
“one  picture  is  worth  a  thousand 
words”  couldn’t  be  truer  when  you 
consider  the  effectiveness  of  a  pie 
chart  or  bar  graph  versus  columns  of 
numbers.  Even  the  simplest  of  home 
computers  is  capable  of  at  least  this 
basic  form  of  computer  graphics.  But 
the  term  computer  graphics  usually 
brings  to  mind  more  exciting  images 
than  just  mundane  business 
applications. 

Special  visual  effects  for  motion 
pictures  and  some  television  adver¬ 
tisements  are  now  being  done  with 
computers  to  create  scenes  of  “en¬ 
hanced  reality.”  We  see  spacecraft 
making  impossible  manuevers  or 
shimmering  corporate  logos  “flying 


easy-to-use  graphics  capabilities  to 
the  masses.  The  “masses,”  however, 
may  still  find  the  Macintosh  pricey, 
especially  if  they  just  want  to  play 
around  a  bit  with  MacPaint.  Low- 
cost  computers,  such  as  the  Commo¬ 
dore  64,  while  lacking  the  slick 
graphic  interaction  of  the  Mac,  are 
selling  for  such  ridiculously  low 
prices  ($150  at  the  time  of  writing 
this  article)  that  they  are  hard  to  pass 
up.  They  have  the  potential  for  some 
fairly  sophisticated  graphics,  but  so 
far  most  of  the  commercially  avail¬ 
able  software  has  done  little  more 
than  add  the  usual  “point  plotting” 
and  “line  drawing”  commands. 

This  article  describes  a  software 
package  that  will  allow  you  to  create 
your  own  computer-generated 
scenes,  such  as  “Coffee  and  Donuts” 
in  Figure  1  (page  55)  and  “Goblets” 


Pumping  pixels  requires  tight ,  efficient  code.  Here 
are  halftone  shading ;  backlighting  and  other  visual 
effects  in  a  tight  little  package. 


by”  in  ways  that  would  be  difficult  to 
simulate  with  conventional  photo¬ 
graphic  techniques.  The  enormous 
amount  of  data  and  processing  neces¬ 
sary  to  produce  such  images  has 
made  the  use  of  “super  computers” 
essential.  Even  with  the  largest  com¬ 
puters  working  full  time  on  the  task, 
only  a  few  feet  of  film  are  produced 
per  day.  For  home  computer  owners 
who  want  to  create  their  own  com¬ 
puter-generated  masterpiece,  howev¬ 
er,  the  task  is  not  hopeless. 

The  Apple  Macintosh  represents  a 
major  step  in  bringing  powerful. 


Richard  Rylander,  179  N.  McKnight 
Rd.  Apt.  203,  St.  Paul,  MN  55119. 


in  Figure  2  (page  55).  You  can  con¬ 
struct  images  from  combinations  of 
elementary  shapes  (spheres,  cylin¬ 
ders,  and  toroids  in  various  orienta¬ 
tions)  or  by  the  “polygon  mesh”  tech¬ 
nique  typically  used  to  render  more 
complex  objects.  You  can  produce 
drawings  with  realistic  shading  ef¬ 
fects  and  in  a  variety  of  styles  to 
make  some  surprisingly  detailed  pic¬ 
tures  with  a  minimum  of  effort.  The 
package  includes  demonstration  pro¬ 
grams  to  illustrate  how  you  use  each 
graphic  function  and  style  option. 

In  keeping  with  the  “running  light 
without  overbyte”  theme  of  DDJ,  the 
entire  graphics  package  fits  in  3K  of 
RAM.  The  program  sits  in  an  area  of 


50 


Dr.  Dobb's  Journal,  May  1985 


QR7 


RAM  that  is  inaccessible  to  BASIC  (a 
4K  block  following  the  BASIC  ROM) 
so  that  you  don’t  lose  any  useful  pro¬ 
gram  space.  Commodore  supplies  a 
“DOS  Wedge”  program  that  occu¬ 
pies  the  last  1 K  of  this  block,  and  one 
of  my  objectives  in  the  design  of  the 
graphics  package  was  to  maintain 
compatibility  with  this  useful  utility. 

The  compactness  of  the  code  re¬ 
quired  me  to  leave  out  some  “bells 
and  whistles,”  but  I’ve  made  no  sacri¬ 
fices  to  execution  speed  or  user-inter- 
face  ease.  The  main  omission  is  error 
trapping — the  software  performs  no 
checking  to  ensure  that  you  use  only 
“legal”  point  coordinates  and  so  on. 
This  may  make  program  develop¬ 
ment  a  little  more  difficult,  but  once 
you  have  written  and  debugged  a  pro¬ 
gram  properly,  error  checking  be¬ 
comes  extraneous  and  only  slows  pro¬ 
gram  execution. 

We  must  address  several  general 
tasks  in  developing  a  shape-drawing 
program.  The  first  is,  of  course,  how 
to  calculate  the  apparent  brightness 
for  all  points  on  visible  surfaces  of  ob¬ 
jects.  Next,  how  do  we  display  these 
different  brightness  levels  on  a  high- 
resolution  display  that  is  basically  on 
or  off?  Finally,  a  “general”  task,  but 
detailed  in  nature,  is  creating  the  spe¬ 
cific  software  tools  that  will  let  us  do 
the  required  calculations  and  bit-map 
manipulations. 

The  program  itself  is  written  in 
machine  language,  because  even  with 
our  simplifying  restrictions,  the  plot¬ 
ting  of  each  pixel  still  involves  consid¬ 
erable  computation.  The  program 
uses  integer  arithmetic  throughout, 
and  we  utilize  all  the  symmetry  avail¬ 
able  to  eliminate  redundant  calcula¬ 
tions.  The  main  program  actually 
consists  of  five  separate  subprograms 
to  break  the  process  into  manageable 
pieces  and  to  provide  a  library  of  sep¬ 
arate  utilities  that  you  may  find  use¬ 
ful  in  other  programs.  Before  getting 
into  the  problem  of  shade  calculation, 
I’ll  present  the  first  subprogram, 
which  is  a  collection  of  integer  arith¬ 
metic  utilities  that  all  the  later  sub¬ 
programs  need. 

Integer  Arithmetic  Utilities 

Listing  One  (page  61)  provides  the 
source  code  for  a  set  of  four  integer 


arithmetic  routines.  Because  we  will 
draw  our  shaded  shapes  on  a  high- 
resolution  display  of  320  x  200 
points,  we  need  only  single-precision 
arguments  for  most  functions  and 
double-precision  results  for  some  in¬ 
termediate  values. 

The  first  two  routines,  multiplying 
two  single-precision  numbers  to  yield 
a  double-precision  product  and  divid¬ 
ing  a  double-precision  dividend  by  a 
double-precision  divisor  to  yield  a 
double-precision  quotient,  are  fairly 
standard  routines  that  should  need  no 
description  other  than  the  comments 


in  the  code.  The  multiplication  rou¬ 
tine  also  contains  a  special  case  to 
treat  a  single-precision  number  as  a 
signed  integer,  which  is  then  squared. 

The  integer-square-root  routine 
deserves  some  special  comment. 
Have  you  ever  tried  the  BASIC 
square-root  function?  If  you  have, 
you  know  it  takes  about  52  millisec¬ 
onds.  At  this  rate,  plotting  the  64,000 
pixels  of  a  320  x  200  screen  would 
take  about  3,328  seconds,  or  nearly 
an  hour,  if  we  need  a  square  root  per 
point!  Of  course,  we  don’t  need  a 
square  root  for  each  point,  but  it  is  an 


51 


Dr.  Dobb’s  Journal,  May  1985 


essential  part  of  many  shade  calcula¬ 
tions.  Obviously,  anything  we  can  do 
to  speed  up  this  particular  function 
will  have  a  great  effect  on  the  overall 
program  speed. 

The  BASIC  square-root  routine  is 
slow  because  BASIC  is  written  to  save 
space,  not  time.  Virtually  all  small 
BASICS  find  a  square  root  by  first 
taking  the  log  of  the  argument,  divid¬ 
ing  that  result  by  2,  and  then  expon¬ 
entiating.  Because  LOG  and  EXP 
functions  are  already  available,  why 
use  any  more  space  than  necessary  to 
derive  SQR? 

One  way  to  speed  up  this  function 
is  to  use  Newton’s  method,  an  itera¬ 
tive  procedure  that  can  give  full  float¬ 
ing-point  precision  in  less  time.  Be¬ 
cause  we  are  interested  only  in 
integer  results,  we  can  do  much  bet¬ 
ter  yet,  though. 

The  method  we  actually  use  is  es¬ 
sentially  as  fast  as  doing  a  single-di¬ 
vide  operation.  We  construct  the  root, 
bit  by  bit,  in  a  manner  similar  to  the 
way  we  would  take  a  decimal  square 
root  by  hand  using  pencil  and  paper 
(does  anyone  remember  how  to  do 
that  in  this  age  of  electronic  calcula¬ 
tors?).  The  process  is  even  easier  for  a 
binary  root  because  the  guesses  are,  of 
course,  only  1  or  0.  The  procedure  is 
almost  identical  to  that  for  division: 
we  guess  a  partial  root  bit  (as  we 
would  guess  the  next  quotient  bit) 
and,  in  effect,  compare  the  square  of 
the  partial  quotient  to  the  argument  to 
see  if  we  keep  the  1  guessed  or  change 
it  to  a  0  and  restore  the  argument.  For 
more  details  about  this  procedure,  I 
recommend  section  17.3,  “Binary 
Square  Roots — Restoring  Method,” 
of  the  book  The  Logic  of  Computer 
Arithmetic  by  Ivan  Flores  (Engle¬ 
wood  Cliffs,  NJ:  Prentice-Hall). 

The  last  integer  arithmetic  routine 
is  a  fast  procedure  for  generating 
pseudo-random  bytes.  An  old  and 
popular  method  for  generating  pseu¬ 
do-random  numbers  is  the  so  called 
congruential  method,  in  which  you 
derive  each  successive  random  num¬ 
ber  from  the  previous  random  num¬ 
ber  by  multiplying  by  a  suitably  cho¬ 
sen  constant  and  taking  the  product 
modulo  P,  where  P  is  a  large  prime 
(see,  for  example,  “A  Better  Random 
Number  Generator,”  by  H.  Cem 


Kaner  and  John  R.  Vokey  in  the  June 
1984  issue  of  MICRO).  Again, 
though,  because  we  are  interested 
only  in  generating  random  bytes,  we 
can  use  a  much  faster  routine. 

The  method  we  use  is  to  store  two 
previous  random  bytes,  exclusive  OR 
them  bit  by  bit,  and  rotate  the  result 
to  the  right  by  one  bit  to  generate  a 
new  random  byte.  Because  it  needs 
no  multiply  or  divide  operations,  pro¬ 
vides  a  fairly  uniform  distribution  of 
random  bytes,  and  has  a  long  period, 
this  process  is  fast.  Note  that  even 
though  we  are  only  generating  ran¬ 
dom  bytes  (0-255),  the  pattern  or  se¬ 
quence  of  successive  bytes  doesn’t  re¬ 
peat  until  after  35,805  calls  to  the 
random-number-generator  routine 
(for  the  initial  parameters  used  in  the 
program).  The  long  period  is  neces¬ 
sary  to  ensure  that  we  won’t  produce 
any  unwanted  secondary  patterns  in 
what  should  be  “randomly”  shaded 
images — the  human  eye  is  adept  at 
picking  out  such  correlations. 

Now  that  some  low-level  number 
crunching  is  out  of  the  way,  we  can 
concentrate  on  the  graphics  aspects 
of  the  main  program. 

Graphics  Utilities 

Listing  Two  (page  62)  provides  the 
source  code  for  the  elementary 
graphics  functions  of  displaying  and 
clearing  the  bit-map,  filling  the  color 
map  (determining  dot/background 
colors),  and  plotting  (and  unplotting) 
individual  points  both  directly  and 
with  the  plot  or  unplot  decision 
weighted  by  a  shade  value  and  a  par¬ 
ticular  shade  style  function.  The 
PLOT  (and  UNPLOT),  CLEAR,  COL¬ 
OR,  GRFON  (display  graphics 
screen),  and  GROFF  (return  to  text 
screen)  routines  are  specific  to  the 
Commodore  64.  To  adapt  this  pro¬ 
gram  for  other  6502-based  comput¬ 
ers,  you  need  to  rewrite  these  rou¬ 
tines,  but  the  rest  of  the  graphics 
package  (save  the  final  user  interface 
to  BASIC)  is  machine  independent. 

The  SHADE  routine  is  simple  and 
straightforward.  We  set  up  an  8x8 
threshold  matrix  as  a  linear  array  of 
values  0  to  63.  The  six  bits  we  need  to 
address  an  element  of  this  threshold 
array  are  determined  by  masking  off 
the  lower  three  bits  of  the  absolute  X 


and  Y  screen  coordinates  (we  take  X 
modulo  8  and  Y  modulo  8)  with  the 
Y  bits  shifted  to  become  the  upper 
three  bits.  This  effectively  repeats  the 
threshold  pattern  over  the  entire  bit¬ 
map  area.  For  each  point  within  an 
object  to  be  drawn,  we  calculate  a 
shade  value  normalized  to  the  range 
0-63  and  then  compare  it  to  the 
threshold  value  at  that  screen  point  to 
decide  whether  to  plot  or  unplot. 

If  we  have,  for  example,  a  large 
area  where  the  shade  value  is  con¬ 
stant  at  31  (a  50  percent  gray),  then 
within  each  8  X  8-pixel  character 
cell  block,  half  of  the  threshold  values 
will  be  greater  than  or  equal  to  the 
shade  value,  turning  on  half  the  pix¬ 
els.  The  order  in  which  the  values  0  to 
63  are  arranged  in  the  threshold  table 
determines  how  these  on  pixels  are 
distributed.  A  common  pattern  for 
these  so  called  ordered-dither  matri¬ 
ces  is  a  recursive  arrangement  such 
as  that  shown  in  Table  1  (page  53).  If 
that  matrix  is  subdivided  into  quar¬ 
ters,  sixteenths,  and  so  on,  each  sub¬ 
matrix  has  the  same  general  pattern, 
with  offsets  between  submatrices. 

The  matrix  in  Table  1  (classic  Bay¬ 
er  ordered  dither)  keeps  the  on  and 
off  pixels  spread  as  far  apart  as  possi¬ 
ble  for  any  shade.  This  keeps  the  spa¬ 
tial  frequencies  in  the  shade  “tex¬ 
ture”  as  high  as  possible  for  a 
particular  shade.  A  disadvantage, 
though,  is  that  texture  changes  then 
accompany  shade  changes,  making 
shade  quantization  more  apparent. 
Another  disadvantage  is  that  many 
color  monitors  have  a  hard  time  cop¬ 
ing  with  the  very  high  frequency  on- 
off-on  sequence  of  pixels,  distorting 
shade  values  when  isolated  pixels  get 
lost. 

As  an  aside,  a  convenient  algorithm 
to  generate  the  ordered-dither  matrix 
in  Table  1  (or  such  a  matrix  of  any 
size)  is  found  in  Figure  7  (page  59). 

The  threshold  matrix  we  actually 
use  in  the  program  is  shown  in  Table 
2  (page  53).  Here  we  follow  a  recur¬ 
sive  scheme  as  we  start  turning  on 
pixels  until  eight  nuclei  have  been  es¬ 
tablished.  As  shades  increase,  new 
pixels  are  added  around  the  edges  of 
these  nuclei,  simulating  the  dot- 
growth  behavior  seen  in  normal 
printing  halftones.  Except  in  the  ex- 


Dr.  Dobb’s  Journal,  May  1985 


52 

369 


treme  highlight  and  shadow  regions, 
the  shade  texture  remains  fairly  con¬ 
stant.  Also,  the  clustering  of  on  or  off 
pixels  is  much  less  demanding  on  the 
display  bandwidth.  But  take  your 
pick — try  filling  the  threshold  matrix 
with  your  own  patterns. 

The  RSHADE  routine  shades  by 
comparing  the  shade  value  to  a  pseu¬ 
do-random  byte  shifted  right  twice  to 
match  the  0-63  range.  This  scheme 
also  tends  to  average  out  the  tone  er¬ 
rors  generated  as  each  pixel  is  turned 
only  on  or  off  (though  we  want  an 
intermediate  shade  of  gray)  by  dith¬ 
ering  the  threshold  value  randomly  at 
each  pixel.  Both  shading  schemes 
produce  sharp  edges  because  each 
pixel  is  plotted  independently. 
Abrupt  shade  changes  are  then  fol¬ 
lowed  faithfully. 

The  SCALE  routine  provides  an 
opportunity  to  mention  a  general  rule 
you  should  follow  when  you  try  to 
write  fast  programs:  If  at  all  possible, 
avoid  division;  and  if  you  must  divide, 
try  to  make  it  by  a  power  of  2  (so  that 
you  can  use  right  shifts).  The  SCALE 
routine  helps  correct  some  of  the  geo¬ 
metric  distortion  that  otherwise  re¬ 
sults  from  plotting  objects  on  the 
Commodore  64’s  rectangular  bit 
map.  While  the  monitor  display  has 
an  aspect  ratio  of  approximately  4:3, 
the  bit-map  aspect  ratio  is  320:200  or 
8:5.  A  simple  way  to  keep  sphere  out¬ 
lines  circular  (instead  of  the  usual 
egglike  appearance)  is  to  work  in 
pseudoscreen  coordinates  of  320  X 
240,  giving  the  bitmap  the  same  4:3 
aspect  ratio  as  the  screen  display. 
The  SCALE  routine  then  converts  0- 
239  YPLOT  values  to  a  0-199  range. 

An  obvious  way  to  do  this  is  to  first 
multiply  by  5  and  then  divide  by  6.  Or 
you  might  save  a  multiplication  by 
first  dividing  a  copy  of  YPLOT  by  6 
and  subtracting  that  from  the  original 
YPLOT.  A  much  faster  way  is  to  mul¬ 
tiply  the  single-precision  YPLOT  by 
213  and  then  take  just  the  upper  byte 
of  the  double-precision  product  (ef¬ 
fectively  dividing  by  256)  as  the 
scaled  YPLOT  value.  This  gives  us  the 
proper  range  of  absolute  screen  Y  val¬ 
ues  and  “rounds”  the  scaling  as  well. 

This  method  of  scaling  means  that 
you  effectively  replot  every  sixth  Y 
line  of  pixels,  but  the  routine’s  sim¬ 


plicity  more  than  makes  up  for  plot¬ 
ting  20  percent  more  points.  Scaling  is 
left  as  an  option  because  some  printers 
(such  as  the  Commodore  1525)  have 
1:1  dot  densities.  By  not  scaling,  the 
screen  display  is  distorted  but  a  hard¬ 
copy  printout  will  have  the  proper  ge¬ 
ometry.  On  the  other  hand,  you  can 
set  up  printers,  such  as  the  Epson  RX- 
80,  with  the  appropriate  horizontal 
and  vertical  dot  densities  to  produce  a 
4:3  aspect  ratio  screen  dump.  Scaling 
here  corrects  both  the  screen  and  the 
hard-copy  output. 

The  routine  PLTSHD  is  a  higher  lev¬ 
el  routine  that  simply  checks  a  couple 
of  flags  to  see  what  kind  of  shading  we 
want  and  whether  to  scale  or  not.  It 
then  calls  the  appropriate  shading  rou¬ 
tine.  To  plot  a  shade-weighted  pixel 
from  BASIC,  POKE  a  shade  value  (0- 
63)  into  VALUE;  POKE  the  absolute  X 
and  (optionally  scaled)  Y  values  into 
XPLOT,  XPLOT+1,  and  YPLOT;  set 
up  the  flags  HTORRN  (HALFTONE 
or  RANDOM)  and  NOSCAL;  then 


0 

32 

8 

40 

48 

16 

56 

24 

12 

44 

4 

36 

60 

28 

52 

20 

3 

35 

11 

43 

51 

19 

59 

27 

15 

47 

7 

39 

63 

31 

55 

23 

SYS  to  PLTSHD.  This  seems  like  a  lot 
of  work  to  plot  a  single  point,  but  we 
really  won’t  be  plotting  shade-weight¬ 
ed  points  by  hand — the  later  shape¬ 
drawing  routines  take  care  of  this. 

Line  and  Facet  Drawing 

Listing  Three  (page  64)  completes 
the  elementary  graphics  functions  by 
adding  line-drawing  and  shaded-fac- 
et-drawing  routines.  We  draw  lines 
using  a  modified  form  of  Bresen- 
ham’s  algorithm,  a  DDA  ( Digital  Dif¬ 
ferential  Analyzer)  technique  that 
keeps  the  actual  plotted  points  within 
one  half-screen  unit  of  the  true  line. 
Regardless  of  the  order  in  which  we 
specify  the  line’s  endpoints,  the  pro¬ 
gram  sorts  them  so  that  lines  are  al¬ 
ways  drawn  from  left  to  right  (the  X 
position  is  incremented  or  unchanged 
at  each  step).  We  then  need  only  de¬ 
termine  which  is  greater,  the  change 
in  X  or  the  change  in  Y,  and  whether 
the  Y  coordinate  difference  is  posi¬ 
tive  or  negative.  The  program  checks 


2 

34 

10 

42 

50 

18 

58 

26 

14 

46 

6 

38 

62 

30 

54 

22 

1 

33 

9 

41 

49 

17 

57 

25 

13 

45 

5 

37 

61 

29 

53 

21 

Table  1 

Recursive  Arrangement  of  Ordered- Dither  Matrix 


0 

8 

53 

61 

2 

10‘ 

55 

63 

16 

24 

37 

45 

18 

26 

39 

47 

49 

57 

4 

12 

51 

59 

6 

14 

33 

41 

20 

28 

35 

43 

22 

30 

3 

11 

54 

62 

1 

9 

52 

60 

19 

27 

38 

46 

17 

25 

36 

44 

50 

58 

7 

15 

48 

56 

5 

13 

34 

42 

23 

31 

32 

40 

21 

29 

Table  2 

Threshold  Matrix 


53 

a70 


Dr.  Dobb's  Journal.  May  1985 


the  scale  flag  to  see  if  the  endpoints 
should  be  adjusted  (in  their  Y  coordi¬ 
nates)  before  plotting  commences. 
This  keeps  a  common  coordinate  sys¬ 
tem  for  drawing  shaded  shapes 
(spheres  and  so  on)  and  lines  in  the 
same  image.  A  flag  MODE  deter¬ 
mines  whether  the  line  is  drawn  by 
setting  or  clearing  pixels  (“black”  or 
“white”  lines). 

The  program’s  last  elementary 
function  is  to  draw  shaded  triangular 
facets.  A  powerful  and  flexible  tech¬ 
nique  for  rendering  objects  is  by 
means  of  a  polygon  mesh.  Natural 
polyhedral  objects  (for  example, 
cubes  and  pyramids)  are  obvious  can¬ 
didates  for  this  method,  but  you  can 
also  approximate  curved  surfaces  by 
a  connected  mesh  of  planar  polygonal 
sections.  As  in  piecewise-linear  ap¬ 
proximations  of  curves,  the  finer  the 
segmentation,  the  better  the  approxi¬ 
mation — although  at  the  price  of 
greater  computational  overhead.  The 
coordinates  of  the  polygon  vertices 
then  constitute  a  data  base  that  you 
can  manipulate  easily  for  rotational 
transformations,  perspective  trans¬ 
formations,  and  so  on.  Triangular 
sections  are  the  simplest  to  handle 
and  the  most  general  because  you  can 
break  any  higher-order  polygon  down 
into  triangular  sections.  / 

As  in  the  line-drawing  routine,  the 
program  sorts  the  endpoints  of  the  fac¬ 
ets  into  a  left-to-right  order  and  checks 
the  scale  flag  to  maintain  a  consistent 
coordinate  system.  At  each  X  position 
across  the  facet,  a  top  and  bottom  Y 
coordinate  pair  is  determined  by  a  sim¬ 
ple  proportionality  between  the  X  dif¬ 
ference  and  Y  difference  for  the  partic¬ 
ular  triangular  sides.  Although  this 
involves  both  multiplication  and  divi¬ 
sion  operations,  it  is  not  the  innermost 
loop  here  (drawing  the  shaded  line  seg¬ 
ment  between  Y  pairs  is).  It  also  does 
not  give  more  than  one  Y  value  per  X 
position,  as  would  a  DDA  method  (for 
lines  with  slope  greater  than  1  in 
magnitude). 

The  maximum  X  difference  is  re¬ 
stricted  to  less  than  256  (single  preci¬ 
sion),  though  the  absolute  X  coordi¬ 
nates  for  the  triangle  vertices  can  be 
anywhere  on  the  screen.  This  is  not 
too  severe  a  constraint  because  most 
polygon  meshes  are  made  up  of  small 


sections,  and,  if  a  larger  triangle  is 
essential,  you  can  break  it  into  small¬ 
er  triangles  that  meet  this  condition. 
The  occasional  inconvenience  this 
might  cause  is  worth  the  increase  in 
speed  and  shortened  program. 

We  leave  the  shade  value  used  in 
drawing  facets  as  a  parameter  that 
the  calling  program  specifies.  Al¬ 
though  adding  “surface  normal”  cal¬ 
culation  and  shade  value  computa¬ 
tion  based  on  some  illumination 
model  (and  requiring  the  Z  coordi¬ 
nates  then  to  be  specified  for  the  ver¬ 
tices)  is  not  difficult,  the  same  shade 
value  is  used  for  all  points  of  the  fac¬ 
et,  making  shade  determination  by  a 
BASIC  program  practical.  Also,  leav¬ 
ing  shade  calculations  up  to  BASIC 
adds  the  flexibility  that  we  can  use 
any  shading  model.  The  curved  sur¬ 
face-drawing  routines  I  describe  later 
do  include  shade  determination,  but 
here  each  point  can  potentially  have  a 
different  shade  value,  putting  it  in  the 
innermost  loop.  For  this  reason,  the 
curved  surfaces  must  have  a  fixed 
shading  model. 

An  interesting  use  for  the  BASIC- 
specified  shade  values  is  drawing 
“white”  facets  by  setting  the  shade  to 
64.  This  doesn’t  seem  particularly 
useful,  but  when  you  draw  “wire¬ 
frame”  polyhedra,  it  makes  a  simple, 
hidden-line  removal  scheme  possible. 
If  the  facets  of  an  object  are  sorted  so 
that  they  are  drawn  from  those  fur¬ 
thest  to  those  nearest  the  observer, 
with  “black”  lines  added  around  the 
edges  of  each  facet,  then  foreground 
facets  that  partially  obscure  back¬ 
ground  facets  erase  the  lines  in  their 
interior.  We  can  set  a  flag  EDGES  so 
that,  after  the  program  has  drawn  a 
shaded  facet,  it  can  add  lines  auto¬ 
matically  to  outline  and  emphasize 
the  edges.  The  MODE  flag  again  de¬ 
termines  how  the  lines  are  drawn  (set 
or  cleared). 

Now  that  we  have  a  toolbox  of 
arithmetic  and  primitive-graphics 
routines,  we  can  concentrate  on  the 
main  subprogram  for  drawing  shad¬ 
ed,  curved  surfaces  rapidly. 

Determining  Shade  Values  for 
Curved  Surfaces 

Many  recent  advances  in  computer 
image  synthesis  have  dealt  specifical¬ 


ly  with  how  different  surfaces  reflect, 
refract,  scatter,  or  absorb  light.  In¬ 
creasingly  complex  models  for  the  vi¬ 
sual  appearance  of  various  surface 
textures  have  led  to  ever  more  realis¬ 
tic  images,  but  increasing  computa¬ 
tional  overhead  accompanies  these 
models.  We’ll  employ  the  simplest 
shading  scheme  to  start  with  and 
then  suggest  how  you  might  modify  it 
to  add  “realism,”  while  still  keeping 
the  computations  manageable. 

If  we  restrict  ourselves  to  simple, 
symmetrical  objects,  we  can  greatly 
simplify  the  problem  of  calculating 
surface  brightness.  This  is  not  a  se¬ 
vere  restriction  in  itself,  as  you  can 
break  most  “complex”  objects  down 
into  combinations  of  simple  elemen¬ 
tal  shapes.  We  will  have  to  limit  our¬ 
selves  to  “normal”  (that  is,  head-on) 
views  of  the  objects,  because  once  we 
allow  objects  to  be  rotated,  they  lose 
most  of  their  symmetry.  Also,  calcu¬ 
lation  time  (particularly  that  for  hid¬ 
den  surface  removal)  increases 
enormously. 

We  use  the  Lambertian  (diffuse 
reflector)  model  to  determine  the 
surface  brightness.  We  assume  that 
light  comes  from  a  single,  specific  di¬ 
rection  and  then  find  the  cosine  of  the 
angle  between  this  reference  vector 
and  a  surface  normal  vector.  This 
gives  us  shade  values  from  1,  where 
the  surface  faces  the  light  source,  to  0 
at  the  terminator,  where  light  just 
grazes  the  surface;  and  negative  val¬ 
ues  where  the  surface  is  turned  away 
from  the  light.  Here  is  our  first  op¬ 
tion:  We  can  clip  the  brightness  val¬ 
ues  at  0  so  that  areas  facing  away 
from  the  light  have  zero  brightness, 
as  an  object  lit  by  a  single  source  (in 
deep  space)  would  appear.  Or,  we  can 
use  the  absolute  value  of  the  cosine  so 
that  the  object  appears  to  be  lit  by 
two  identical  sources  on  opposite 
sides.  Either  way  is  simple,  so  we  al¬ 
low  for  both  cases  by  using  a  flag  to 
determine  what  kind  of  illumination 
scheme  we  want  to  use  for  a  whole 
scene  or  any  individual  shape  making 
up  the  scene. 

Although  this  simple  model  is  valid 
for  diffusely  reflecting  surfaces,  it 
does  not  account  for  any  contribu¬ 
tions  from  specular  surface  reflec¬ 
tions,  secondary  light  sources  (other 


Dr.  Dobb's  Journal,  May  1985 


54 

371 


SPHERE  H-CVLHDR  U-CYLNDR  TOROID 


SAMPLE  SHAPES 


•rm!».y.v.v.v.v.v.v.v.vl 

than  the  “opposite  face”  light)  or  am¬ 
bient  (nondirectional)  light.  These 
additional  subtleties  are  not  difficult 
to  implement,  but  for  the  level  of  de¬ 
tail  and  dynamic  range  (number  of 
gray  levels)  in  our  halftone  display, 
they  really  aren’t  worth  the  effort. 

Object  Geometry 

Calculating  the  surface  normal  vec¬ 
tors  for  points  on  an  arbitrary  surface 
can  be  difficult,  but  this  is  why  we 
have  restricted  ourselves  to  objects 
that  can  be  made  up  of  combinations 
of  the  eight  simple  shapes  shown  in 
Figure  3  (page  55).  All  the  basic 
shapes  have  at  least  fourfold  symme¬ 
try;  that  is,  once  we  have  found  the 
surface  normal  for  a  point  (X,  Y)  in 
one  quadrant  of  the  object,  we  can 
easily  find  the  surface  normals  for  the 
other  three  quadrants  by  comple¬ 
menting  appropriate  components. 
Also,  by  restricting  ourselves  to 
shapes  with  relatively  simple  surfaces, 
we  can  determine  the  required  surface 
normal  vectors  without  resorting  to 
trigonometric  functions,  using  instead 
some  simple  geometric  consider  - 
ations. 

Another  time-saver  is  to  choose  an 
illumination  reference  vector  to  best 
take  advantage  of  symmetries  in  the 
objects.  Unfortunately,  the  “best” 
choice  for  computational  purposes — 
a  light  source  directly  behind  the 
viewer — gives  a  rather  flat-looking 
image.  Much  more  pleasing  shading 
results  from  illumination  coming 
from  “over  the  shoulder.”  The  slight 
assymmetry  that  results  gives  a 
stronger  sense  of  depth  to  the  objects. 

The  coordinate  system  describing 
objects  on  the  screen  has  its  X  axis 
increasing  horizontally  to  the  right, 
the  Y  axis  increasing  vertically  up¬ 
ward,  and  the  Z  axis  increasing  out  of 
the  screen  toward  the  viewer  (a  con¬ 
ventional  right-handed  coordinate 
system).  The  illumination  reference 
vector  used  here  has  X,  Y,  and  Z 
components  of  (1,1,2),  representing 
light  coming  from  over  the  right 
shoulder.  The  symmetry  in  X  and  Y 
makes  for  easier  shade  calculations 
and  gives  a  similar  looking  scene  if 
you  rotate  a  hard-copy  output  90  de¬ 
grees  to  fit  objects  that  are  taller  than 
they  are  wide  into  the  display.  Light¬ 


ing  in  the  “portrait”  (as  opposed  to 
“landscape” — unrotated)  mode  then 
[  comes  from  over  the  left  shoulder. 

Drawing  a  Shaded  Sphere 

Spheres  are  particularly  easy  objects 
to  deal  with  because  a  radial  vector 
from  the  center  to  a  point  on  the  sur¬ 
face  is  in  the  same  direction  as  a  sur¬ 
face  normal  from  that  point.  The 
only  real  problem  we  must  deal  with 
is  one  of  normalization.  We  will  al¬ 
ways  deal  with  coordinates  relative  to 
the  “local  center  of  curvature”  of  ob¬ 
jects.  For  spheres,  this  is  just  their 
geometric  center. 

Consider  a  point  on  the  surface 
that  extends  out  of  the  screen  from 
relative  coordinates  (X,Y).  We  find 
the  corresponding  Z  value  using  the 
equation  for  a  sphere: 

Z  =  SQR(R*R  -  X*X  -  Y*Y) 

This  calculated  Z  then  forms  the 
third  member  of  the  surface  normal 
vector.  To  calculate  the  cosine  of  the 
angle  between  this  vector  and  the  il¬ 
lumination  reference  vector,  we  must 
first  normalize  both  to  have  unit 
length.  The  normal  we  have  just 
found  has,  of  course,  a  length  of  R,  so 
we  need  only  divide  each  component 
by  the  radius  of  the  sphere.  The  illu¬ 
mination  vector  (1,1,2)  has  a  length 
of  SQR(6),  so  we  normalize  this  vec¬ 
tor  to  unit  length  by  dividing  each 
component  by  SQR(6). 

Now  that  we  have  two  vectors  of 
unit  length  but  different  directions, 
we  find  the  cosine  of  the  angle  be¬ 
tween  them  by  taking  their  inner 
product,  which  is  nothing  more  than 
summing  the  products  of  the  X,  Y, 
and  Z  coordinate  pairs — like  so: 

COSINE  =  (l*X  +  I *Y  -I-  2*Z) 

/  (R*SQR(6)) 

To  use  integer  arithmetic  through¬ 
out,  we  don’t  really  want  brightness 
expressed  as  a  fraction  between  0  and 
1 ,  but  rather  scaled  to  a  range  of  inte¬ 
gers  from  0  to  63.  We  choose  a  maxi¬ 
mum  shade  value  of  63  to  match  the 
64  gray  levels  that  can  be  approxi¬ 
mated  by  an  8X8  threshold  matrix 
(described  earlier  in  the  “Graphics 
Utilities”  section).  We  scale  the 


pseudo-random  bytes  (used  in  the 
RSHADE  routine)  to  the  same  range 
simply  by  shifting  them  right  twice. 
So,  for  a  surface  point  (X,  Y,  Z)  on  a 
sphere  of  radius  R,  the  appropriate 
SHADE  integer  (0-63)  is: 

SHADE  =  26*(X  +  Y  +  2*Z)/R 

The  factor  26  properly  accounts  for 
the  SQR  (6)  in  the  denominator  of  the 
previous  equation. 

As  stated  earlier,  all  the  primitive 
shapes  considered  here  have  at  least 
fourfold  symmetry.  We  need  com¬ 
pute  the  Z  component  only  once  for 
each  ±  X  and  ±  Y  quartet  of  points 
(relative  to  the  geometric  center  of 
the  object).  The  sphere  and  top-view 
toroid  actually  have  n-fold  symme¬ 
try,  which  the  Cartesian  grid  of 
screen  pixels  reduces  to  eightfold 
symmetry.  That  is,  in  addition  to 
changing  signs  of  X  and  Y,  we  can 
exchange  the  X  and  Y  coordinates  to 
find  another  surface  point  with  the 
same  Z  value. 

Shape-Drawing  Routines 

Listing  Four  (page  68)  is  a  set  of  rou¬ 
tines  for  drawing  the  eight  simple 
shapes  shown  in  Figure  5.  The  routines 
resemble  some  form  of  compiled  BA¬ 
SIC  in  that  they  use  no  special  bit  ma¬ 
nipulations  or  unusual  addressing 
methods,  but  just  have  appropriately 
ordered  calls  to  the  lower-level  routines 
set  up  earlier.  Comment  statements 
precede  each  shape-drawing  routine, 
giving  an  equivalent  BASIC  routine. 
This  seems  to  be  the  easiest  way  to  ex¬ 
plain  each  routine  because  most  read¬ 
ers  are  familiar  with  BASIC  program 
methods.  I  thus  give  special  comment 
to  only  a  few  of  the  routines  here. 

GETVAL  is  a  shade-normalization 
routine  that  also  checks  for  normal  or 
backlit  illumination  via  the  flag  BAK- 
LIT.  The  byte  pair  at  TONE  contains 
the  inner  (dot)  product  of  the  illumina¬ 
tion  vector  with  the  local  surface  nor¬ 
mal  vector.  GETVAL  then  effectively 
does  the  division  by  SQR(6)*R  and 
multiplication  by  63  to  put  VALUE 
into  the  proper  range,  clipping  at  zero 
or  taking  the  absolute  value,  as 
necessary. 

PTPLOT  does  all  the  dirty  work 
needed  to  plot  shaded  points  for  four- 


Dr.  Dobb's  Journal,  May  1985 


56 

373 


fold  symmetrical  objects.  Provision  is 
also  made  here  for  clipping  the  object 
at  independent  levels  up,  down,  left, 
and  right  of  the  object’s  center.  The 
clipping  feature  is  needed  to  blend 
various  primitive  objects  into  more 
complex  ones  with  smooth  transitions 
at  the  seams.  Clipping  also  allows  us 
to  create  some  intricate  “weaving” 
effects  by  redrawing  portions  of  over¬ 
lapping  shapes  as  in  Figure  4, 
“Linked  Toroids,”  (page  55). 

The  flag  NOROT  determines 
whether  the  X  and  Y  coordinates  will 
be  exchanged.  This  is  useful  both  for 
drawing  the  eightfold  symmetrical  ob¬ 
jects  (top-view  toroid  and  sphere)  and 
for  rotating  the  fourfold  symmetrical 
objects  90  degrees  so  that  a  single 
drawing  routine  can  produce  two  ori¬ 
entations  of  an  object.  The  calculations 
are  basically  those  for  the  sphere,  but 
we’ll  see  later  that  we  can  put  other 
objects,  such  as  toroids,  into  forms  that 
look  like  spheres  (at  least  locally). 

GETZ  calculates  the  effective  Z  co¬ 
ordinate  for  a  spherelike  surface,  giv¬ 
en  the  X  and  Y  coordinates  relative 
to  the  local  center  of  curvature.  Note 
that  this  routine  needs  the  SQRT 
function,  and  because  it  is  in  general 
called  for  each  quartet  of  points  plot¬ 
ted,  our  fast  SQRT  makes  a  big  dif¬ 
ference  in  execution  speed. 

SPHERE  follows  the  previously 
outlined  algorithm  and  takes  full  ad¬ 
vantage  of  the  available  symmetry. 
The  radius  is  POKEd  into  RADIUS, 
and  clipping  distances  relative  to  the 
sphere  center  are  POKEd  into  CLIPL 
(left),  CLIPR  (right),  CLIPU  (up), 
and  CLIPD  (down). 

CYLNDR  draws  a  cylinder  with 
sizes  POKEd  into  RADIUS  and 
HLEN  (half-length).  The  flag 
NOROT  determines  whether  the  cyl¬ 
inder  will  be  drawn  with  a  horizontal 
or  a  vertical  axis.  The  routine  is  sim¬ 
ple,  using  PTPLOT  for  the  main 
shade-calculation  tasks.  Actually,  we 
could  write  a  much  faster  routine  to 
take  advantage  of  the  fact  that  we 
can  use  the  same  shade  value  for  all 
points  along  lines  parallel  to  the  cyl¬ 
inder  axis.  This  would  require  consid¬ 
erably  more  space,  however. 

The  peculiar  manipulations  of  rel¬ 
ative  coordinates  for  plotting  toroids 
in  various  orientations  are  easier  to 


explain  with  the  help  of  the  diagram 
in  Figure  5  (appropriate  for  a  top- 
view  toroid)  (page  55). 

Consider  a  point  P  at  coordinates 
(XREL,  YREL)  relative  to  the  center 
of  the  object.  We  can  take  the  local 
center  of  curvature  of  the  toroid  to  be 
at  the  point  C,  which  lies  on  the  inter¬ 
section  of  the  line  passing  through 
the  center  of  the  toroid  and  a  point 
beneath  P  (where  Z  =  0)  with  a  circle 
of  radius  RC  (the  “average”  radius  of 
the  toroid  is  RC  =  (RO  +  RI)  /  2).  We 
determine  the  surface  normal  by  the 
relative  X,  Y,  and  Z  displacements 
from  the  point  C. 

The  byte  R0  is  used  for  temporary 
storage  of  the  radial  distance  from 
the  toroid  center  to  the  point  beneath 
P  (where  Z  =  0): 

R0  =  SQR(XREL*XREL 
+  YREL*YREL) 

We  can  determine  the  relative  X  and 
Y  displacements,  XSHD  and  YSHD, 
respectively,  of  P  from  C  easily  by  us¬ 
ing  similar  triangles: 

XSHD  =  XREL*(  1  -  RC/RO) 

YSHD  =  YREL*(  1  -  RC/RO) 

We  find  the  Z  coordinate  via  the  Py¬ 
thagorean  theorem,  using  the  radius 
of  the  “ring”  portion,  RT,  as  the  hy¬ 
potenuse.  RT  is  then  also  the  length 
of  the  normal  vector  and  is  the  value 
POKEd  into  RADIUS  for  normaliza¬ 
tion  in  the  shade  calculations. 

The  routine  TOROID  follows  this 
algorithm,  taking  advantage  of  the 
eightfold  symmetry  in  the  object. 
EDGTOR  and  SPOOL  use  similar 
center  of  curvature  coordinates  to 
draw  other  orientations  of  a  toroid.  I 
have  given  the  BASIC  equivalents  but 
leave  it  to  readers  to  draw  the  appro¬ 
priate  geometric  constructions  if  they 
desire  more  details. 

Using  the  Shape-Drawing  Rou¬ 
tines — Interface  to  BASIC 

The  last  module,  completing  our 
graphics  package,  is  a  convenient  in¬ 
terface  to  BASIC.  Listing  Five  (page 
76)  is  a  collection  of  routines  to  pass 
parameters  easily  to  the  graphics  rou¬ 
tines  from  the  BASIC  programs  that 
control  image  generation.  These  rou¬ 


tines  are  specific  to  the  Commodore 
64  in  that  they  use  some  of  the  rou¬ 
tines  in  the  BASIC  ROM  to  interpret 
statements  in  a  BASIC  program  or  en¬ 
tered  in  the  direct  command  mode. 

Graphics  commands  are  given  in 
the  form: 

SYS(FNCTN),PARAM  1.PARAM2, 
[OPTIONAL  PARAMETERS] 

where  FNCTN  is  the  address  of  the 
graphic  function  you  desire  (or  a 
variable  that  has  been  assigned  the 
address  value)  and  PARAM1  and 
PARAM2  are  parameters  (such  as  the 
center  coordinates  of  an  object)  that 
the  function  requires,  followed  by 
any  optional  parameters. 

The  parameters  can  be  either  liter¬ 
al  numerics  or  expressions  that  are 
evaluated  for  the  desired  values.  We 
do  not  have  to  put  the  function  ad¬ 
dress  in  parentheses,  but  it  helps  to 
make  the  program  more  readable. 
Separating  parameters  by  commas  is 
required.  If  you  desire  an  optional  pa¬ 
rameter  (such  as  specifying  a  new  ra¬ 
dius  for  sphere  drawing),  you  add  it 
by  following  the  last  required  param¬ 
eter  by  a  comma  and  then  the  option¬ 
al  value  or  expression. 

The  use  of  optional  parameters  is 
more  easily  explained  with  the  se¬ 
quence  of  BASIC  statements  found  in 
Figure  8  (page  59): 

For  all  the  shape-drawing  func¬ 
tions,  you  must  specify  the  X  and  Y 
center  coordinates,  but  shape  sizes 
are  optional  parameters.  The  last  val¬ 
ues  specified  become  the  new  default 
sizes,  allowing  you  to  draw  copies  of 
similar  objects  (such  as  a  bunch  of 
grapes)  just  by  setting  the  new  center 
coordinates.  All  shapes  but  the 
sphere  take  two  size  parameters,  and 
you  must  specify  both  when  you  de¬ 
sire  a  change  (even  when  only  one  pa¬ 
rameter  takes  on  a  new  value).  All 
size  parameters  (radii  and  cylinder 
half-lengths)  must  be  less  than  256, 
not  a  severe  limitation  considering 
the  screen  is  only  200  pixels  high 
(“240”  with  scaling). 

The  commands  to  clear  the  bit  map 
and  initialize  the  color  map  each  take 
a  single  optional  parameter,  but  the 
default  values  remain  unchanged.  Un¬ 
less  you  specify  alternate  bytes,  the  bit 


58 


Dr.  Dobb's  Journal,  May  1985 


map  is  filled  with  Os  (cleared)  and  the 
color  map  is  filled  with  Is  (for  black 
dots  on  a  white  background).  The 
most  useful  alternate  byte  with  which 
to  fill  the  bit  map  is  255,  setting  the 
entire  background.  Because  the  shad¬ 
ing  process  clears,  as  well  as  sets,  ap¬ 
propriate  points,  objects  still  appear 
normal  but  against  a  black  back¬ 
ground  (particularly  effective  when 
you  use  the  “back  light”  style). 

The  byte  used  to  fill  the  color  map 
is  made  up  of  background  and  fore¬ 
ground  nibbles.  To  initialize  the  color 


10 

20 

30 

40 

50 

60 

100 

110 

120 

130 

140 

150 


' 

REM  ORDERED  DITHER  ARRAY— RECURSIVE  FILL 
l  =  0:J  =  0:K=1:N  =  0:P  =  3 

REM  ‘P-  IS  ORDER  OF  ARRAY,  SIZE  IS  (2  '  P)  x  (2  ‘  P) 

P  =  2  '  P:DIM  A(P  -  1,P-  1) 

GOSUB 100 
END 

IF  K  =  P  THEN  A(I,J)  =  N:N  =  N  +  1  :K  =  K/2:RETURN 

K  =  2*K:GOSUB  100 

l  =  l  +  K:J=J  +  K:K  =  2'K:GOSUB  100 

1  =  1  -K:K  =  2XK:GOSUB  100 

l  =  l  +  K:J  =  J  -  K:K  =  2*K:GOSUB  100 

l  =  l-K:K  =  K/2:RETURN 

Figure  7 


map  for  a  different  color  combination 
than  black  on  white,  use: 

SYS(52001  ),16*DC+BC 

where  DC  is  the  dot-color  number 
and  BC  is  the  background-color  num¬ 
ber  (as  given  in  any  reference  to  pro¬ 
gramming  the  Commodore  64). 

The  addresses  for  all  the  graphic 
functions  and  the  parameters  they 
take,  along  with  locations  to  be 
POKEd  with  clipping  values  and  flags 
for  various  drawing  styles,  are  sum¬ 
marized  in  Table  3  (page  60).  The 
best  way  to  see  how  these  shape¬ 
drawing  routines  are  used  is  to  exam¬ 
ine  the  demonstration  programs  in 
Listing  Six  (page  78),  “SHAPES 
DEMO,"  and  Listing  Seven  (page 
80),  “STELLATION.”  All  the  differ- 


(SET  UP  GRAPHICS,  STYLES,  ETC) 

200  SP  =  52119:REM  ADDRESS  OF  SPHERE  FUNCTION 

210  SYS(SP),80,75,30:REM  DRAW  A  SPHERE  OF  RADIUS  30  AT  X  =  80  Y  =  75 

220  SYS(SP),300,50  :REM  DRAW  ANOTHER  SPHERE  AT  X  =  300,  y  =  50 

230  REM  SINCE  NO  RADIUS  IS,  SPECIFIED,  LAST  VALUE  IS  USED  AS  DEFAULT 
240  SYS(SP),200, 1 50,40:REM  NEW  SPHERE  RADIUS  (40)  BECOMES  DEFAULT 

Figure  8 


ent  style  options  are  exercized  there. 
It  is  useful  to  save  an  abbreviated  ver¬ 


sion  of  SHAPES  DEMO,  consisting  of 
just  the  lines  up  to  340  and  the  sub¬ 
routines  following  line  1620.  This 
skeleton  program  can  then  form  a 
base,  providing  all  the  POKE  and  SYS 
locations  you  need  and  to  which  you 
can  add  your  own  image-generation 
control  program. 


Auxiliary  Programs 

SHAPES  DEMO  and  STELLATION 
make  use  of  two  auxiliary  programs 
that,  while  not  strictly  needed  to  pro¬ 
duce  graphic  images,  provide  utilities 
to  both  enhance  the  image  itself  and 
speed  the  drawing  process.  Listing 
Eight  (page  80)  is  a  routine  for  sort¬ 
ing  an  integer  array  indirectly 
through  a  key  array  (to  determine 
drawing  order  for  facets  in  a  polygon 
mesh  quickly).  Listing  Nine  (page 
82)  allows  you  to  add  text  to  graphic 


59 


Dr.  Dobb's  Journal,  May  1985 


images  in  a  variety  of  styles.  the  graphics  package  in  one  piece.  imum  element  index  of  the  arrays 

The  code  for  these  routines  is  short  The  KEYSORT  routine  works  with  into  location  140,  the  address  of  the 
enough  to  be  tucked  in  behind  Com-  two  1 -dimensional  integer  arrays  of  first  (0th)  element  of  the  key  array 
modore’s  DOS  Wedge  program  so  the  same  size.  One  is  filled  with  some  into  locations  251  (low  byte)  and  252 
that,  again,  you  don’t  lose  any  BASIC  “priority”  parameter  (such  as  the  (high  byte),  and  the  base  address  of 
program  space  but  maintain  full  “average  Z”  coordinates  for  the  fac-  the  priority  array  into  253  and  254. 
compatibility  with  the  wedge.  If  you  ets  in  a  polygon  mesh),  and  the  other  You  can  find  these  array  base  ad- 
are  willing  to  give  up  the  convenience  becomes  a  “key”  array  whose  ele-  dresses  easily  because  the  Commo- 
of  the  wedge,  however,  you  can  easily  ments  index  those  in  the  “priority”  dore  64  places  the  address  of  the 
relocate  these  routines  to  the  end  of  array  in  increasing  order.  “current  BASIC  variable”  into  loca- 

the  “interface”  subprogram  to  keep  To  use  the  routine,  POKE  the  max-  tions  71  and  72.  Setting  the  0th  ele- 


BIT  MAP  SCREEN 

40960-48959  ($A000-$BF3F) 

COLOR  MAP 

33792-34791  ($8400-$87E7) 

CLIPPING  BOUNDS 

(relative  to  object  center): 

893  ($037D) 

894  <$037E) 

895  (S037F) 

896  ($0380) 

LEFT  BOUND 

RIGHT  BOUND 

UPPER  BOUND 

LOWER  BOUND 

53280  ($D020) 

BORDER  COLOR  [0-1 5],  POKE  with  1  to  match  white  screen 

STYLE  FLAGS 

838  ($0346) 

839  ($0347) 

868  ($0364) 

871  ($0367) 

898  ($0382) 

SHADE  STYLE  [0= random,  1  =  halftone] 

SCALE  FLAG  [0= normal  (1:1),  1  =  scaled  (3:4)J 

EDGES  FLAG  [0= normal,  1  =add  lines  to  facet  edges) 

EDGE/LINE  MODE  [0=draw,  1  =  erase] 

LIGHTING  STYLE  [0= normal  single  source,  1  =  backlit] 

FUNCTION  LOCATIONS— CALL  WITH  "SYS(FNCTN),  PARAMETERS."  OPTIONAL  PARAMETERS  IN  SQUARE  BRACK¬ 
ETS,  DEFAULT  VALUES  ARE  USED  IF  NOT  SPECIFIED 

49378  ($C0E2) 

49411  ($C103) 

SWITCH  TO  GRAPHICS  MODE 

RETURN  TO  TEXT  SCREEN 

51979  ($CB0B) 

52001  ($CB21) 

CLEAR], CLEAR  BYTE]  FILL  BIT  MAP  WITH  CLEAR  BYTE  (DEFAULT  =  0] 

COLOR], COLOR  BYTE]  FILL  COLOR  MAP  WITH  COLOR  BYTE 

52023  ($CB37) 

52026  ($CB3A) 

PLOT,X,Y  SET  POINT  AT  (X,Y) 

UNPLOT, X,Y  CLEAR  POINT  AT  (X,Y) 

52049  ($CB51) 

LINE, XI  ,Y  1  ,X2,Y2  DRAW  A  LINE  BETWEEN  (X 1  ,Y  1 )  AND  (X2,Y2) 

52052  ($CB54) 

FACET, XI  ,Y1,X2,Y2,X3,Y3,VA  DRAW  A  SHADED  TRIANGULAR  FACET  BETWEEN  COORDINATE 

PAIRS  (XI  ,Y1 ),  (X2,Y2)  AND  (X3,Y3)  USING  "VA"  SHADE  VALUE  (0:BLACK  TO  64:WHITE) 

CURVED  SURFACES 

52119  ($CB97) 

52141  ($CBAD) 

52150  ($CBB6) 

52153  ($CBB9) 

52186  ($CBDA) 

52189  ($CBDD) 

52203  ($CBEB) 

52206  ($CBEE) 

SPHERE, X,Y[,R]  DRAW  A  SPHERE  CENTERED  AT  (X,Y)  WITH  RADIUS  R 

TOROID, X,Y[,RI,RO]  DRAW  A  TOP-VIEW  TOROID  WITH  INNER  RADIUS  Rl  AND  OUTER  RADIUS  RO 
VCYL,X,Y[,R,HL]  DRAW  A  CYLINDER  WITH  AXIS  VERTICAL,  RADIUS  R  AND  HALF-LENGTH  HL 
HCYL,X,Y[,R,HL]  DRAW  A  CYLINDER  WITH  AXIS  HORIZONTAL 

VTOR,X,Y[,RI,RO]  DRAW  AN  EDGE-VIEW  TOROID  WITH  AXIS  VERTICAL 

HTOR,X,Y[,RI,RO]  DRAW  AN  EDGE-VEIW  TOROID  WITH  AXIS  HORIZONTAL 

VSPOOL,X,Y[,RI,RO]  DRAW  AN  INSIDE-VIEW  TOROID  ("SPOOL”)  WITH  AXIS  VERTICAL 
HSPOOL,X,Y[,RI,RO]  DRAW  AN  INSIDE-VIEW  TOROID  ("SPOOL")  WITH  AXIS  HORIZONTAL 

Table  3 

Graphics  Addresses 

_ 1 

60 


Dr.  Dobb's  Journal,  May  1985 


ment  of  an  array  equal  to  itself  mere¬ 
ly  establishes  it  as  the  current 
variable.  Then  you  can  transfer  the 
contents  of  71  and  72  to  the  proper 
pointer  at  251/252  or  253/254.  Be 
careful  to  use  literal  numerics  for 
“251”  and  so  on,  because  you  don’t 
want  to  change  the  current  variable. 
After  setting  up  the  pointers,  SYS  to 
the  start  of  the  KEYSORT  routine. 
Another  caution  to  observe  if  you  use 
KEYSORT  for  other  applications  is 
not  to  create  any  new  variables  be¬ 
tween  finding  the  array  base  address¬ 
es  and  invoking  KEYSORT,  because 
arrays  will  be  pushed  up  in  memory 
to  keep  them  at  the  end  of  BASIC’s 
variable  storage. 

KEYSORT  is  set  up  to  handle  arrays 
with  up  to  256  elements  each.  If  need¬ 
ed,  you  can  sort  larger  arrays  by  break¬ 
ing  them  into  smaller  arrays,  sorting 
with  KEYSORT,  and  then  collating  the 
results.  Merging  a  few  already  sorted 
arrays  is  a  fairly  simple  operation  that 
you  can  do  in  a  single  pass,  extending 
the  usefulness  of  this  routine. 

STELLATION  uses  KEYSORT 
(lines  840  880)  to  determine  the 
drawing  order  for  the  facets  of  a 
small  stellated  dodecahedron  (Figure 
6,  page  55).  This  is  a  polyhedron  that 
is  not  strictly  convex  (some  internal 
dihedral  angles  are  greater  than  180 
degrees),  causing  some  foreground 
facets  to  partially  obscure  those  in 
the  background.  The  Painter’s  algo¬ 
rithm  is  used  to  handle  hidden  sur¬ 
face  removal.  Each  newly  drawn  fac¬ 


et  completely  overwrites  the 
background  (clearing,  as  well  as  set¬ 
ting,  points)  so  that  drawing  facets 
from  back  to  front  gives  a  proper  ren¬ 
dering  of  the  object. 

The  stellated  dodecahedron  of  Fig¬ 
ure  6  usually  has  only  30  visible  or 
partially  visible  facets.  Sorting  with 
BASIC  would  be  satisfactory  here,  but 
more  complex  polygon  mesh  objects 
can  easily  contain  hundreds  of  visible 
and  partially  visible  facets.  You  can 
really  appreciate  KEYSORT  in  those 
cases.  Although  it  uses  the  simple  and 
inefficient  bubble-sort  algorithm,  be¬ 
ing  implemented  in  machine  code  lets 
KEYSORT  run  circles  around  any  BA¬ 
SIC  sorting  alternative. 

The  final  routine,  in  Listing  Nine, 
WRITE,  allows  you  to  add  text  to  the 
graphic  display.  The  primary  reason 
for  implementing  this  as  a  machine 
code  routine  is  that  the  bit  map  for 
the  graphic  image  is  located  in  the 
shadow  RAM  beneath  the  BASIC 
ROM  (again  saving  useful  BASIC 
program  memory  space).  We  can 
transfer  bit  images  from  the  charac¬ 
ter  ROM  to  the  bit  map  just  by  PO- 
KEing  values  to  the  bit  map,  but  we 
gain  more  flexibility  if  we  first  read 
the  bit-map  bytes  to  exclusive  OR 
them,  AND  them,  and  so  on  with 
character  image.  Reading  the  shad¬ 
ow  RAM  requires  that  the  BASIC 
ROM  be  switched  out  (through  the 
bank  switching  used  in  the  Commo¬ 
dore  64),  something  a  BASIC  pro¬ 
gram  cannot  do  (and  hope  to  contin¬ 


ue  execution). 

The  subroutine  following  line  1780 
in  SHAPES  DEMO  illustrates  how  to 
print  text  in  a  variety  of  styles  on  the 
bit-map  image.  Although  the  pro¬ 
gram  actually  uses  only  one  style,  the 
subroutine  allows  for  five  possibili¬ 
ties.  Normal  (“black”  on  “white”) 
characters  and  reverse  characters  can 
overwrite  the  background,  black 
characters  can  be  ORed  with  the 
background,  white  characters  AND- 
ed  with  the  background,  or  black 
characters  exclusive  ORed  with  the 
background.  The  last  possibility  gives 
characters  that  “change  phase” 
across  black/white  edges  in  the 
image. 

Conclusion 

The  graphics  package  described  here 
we  hope  presents  some  new  ap¬ 
proaches  to  computer-generated  im¬ 
ages  on  small  systems.  At  the  same 
time,  I’m  sure  I’ve  missed  some  obvi¬ 
ous  tricks  that  would  speed  up  the 
programs  or  reduce  their  size.  I  will 
be  interested  to  see  responses  to  this 
article  and  shortcuts  or  enhance¬ 
ments  that  might  be  added.  Although 
the  price/performance  ratio  of  small 
graphics  system  continues  to  improve 
(especially  with  the  development  of 
custom  LSI  graphics  chips),  there  are 
yet  many  unexplored  software  ap¬ 
proaches  to  the  problem.  DD| 


Drawing  on  the  C-64  (Text  begins  on  page  50) 

Listing  One 


00O01  0000 

00002  0000 
00003  0000 

00004  0000 

0000*5  0000 

00006  0000 
0000/  0000 
00008  0000 
0000-/  0000 
O0010  0000 

000 1 1  0000 
00012  0000 
00013  0000 

00014  0000 

000 1 5  0000 

000 1 6  0000 
00017  0000 

000  I  8  0000 

00019  0000 

00020  0000 
00021  0000 
00022  0000 
00023  0000 

00024  0000 

00025  0000 

00026  0000 
0002/  0000 
00028  C000  FF 

00028  C0OI  55 
00029  C002  00 

000. ’9  COO 3  00 

00030  C004 

00031  C004 


lNTCGCR  ARITHMETIC  ROUTINES 

RICHARD  L-  RYl  AND!  R  8/12/04 

REVISED  10/  27/04  TO  ADD  Fill  L  DOliBI  £ 
PRECISION  AKfil  JMENT  S  IN  DIVIDE  KOI  II  I  ME 


»  USE  PAGE  ZCRO  LOCATIONS  WHERE  ROSS  IDLE  FUR 
5  ITERATIVE  PROCEDURE  WORl  SPACE 
; 

MLPCND  “PAC  ;  MULTIPLICAND 

Ml  PIER  =  »AD  !  MULTIPLIER 

PROD  -IAE  ;  PRODUCT 


DVDND  nil 
DVGOR  ‘ITD 
RMNDR  «*B4 


DIVIDEND/QU0T1CNI 

DIVISOR 

REMAINDER 


RADCND  “I AC  *  RAD I C AND 

ROOT  =  103  315  ;  SQUARE  ROOT 


“JFB 


;  SET  UP  SEED  VALUES  FOR  PSEUDO  RANDOM  NUMBERS 
•  =  II  000 

RNDM  .BYTE  *rF,*S5 


RTCMP  .BYTE  100,100 


00032 
00033 
00034 
00033 
000  36 
000  37 
00038 
000  39 
00040 
0004  1 
0004  2 
0004  3 
0004  4 
00045 
00046 
00047 
0004Q 
00049 
00050 
00051 
00052 
00053 


C004 
C004 
C004 
C004 
C004 
COO  4 
C.004 
CO04 
C004 
E004 
C006 
C00O 
C009 
C00B 
C0OD 
C00F 

cen 

C013 
C0I5 
C0 1 7 
C019 
C01A 


A5  AC 

10  07 
38 

A9  00 
E5  AC 
85  AC 
85  AD 
A9  00 
A2  00 
46  AD 
90  03 
18 

65  AC 


;  MULTIPLY  SINGLE  PRECISION  Mill  T  I  FT.  I  LAND 
5  BY  SINGLE  PRECISION  MULTIPl IER  UtVING 
*  DOUBLE  rr<EClSION  PRODUCT  (LNIIK  A I  ■•MIILT") 
l 

j  SPECIAL  CASE*  ENTER  AI  "SQUARE"  TO  FIND 
I  SOUARE  OF  SIGNED  8 -BIT  NUMBER 


1 

SOUARE  L  DA  MLPCND 
BPL  POSI rv 
SEC 

LDA  Ml 00 
SBC  MLPCND 
SIH  MLPCND 
POSI TV  S I A  ML  T  LER 
MULT  LDA  Ml 00 
LDX  Ml  06 

ML OOP  LSR  Ml  PLER 
BCC  NOADD 
CLC 

ADC  MLPCND 


5  ENTRY  TO  SQUARE 
I  USC  ABSOLUTE  VALUE 
S  MENU  IE  IF  NEEDED 


5  ENIRY  TU  MULTIPLY 


00054  C01C  6A 
00055  C01D  66  AE 
00056  C01F  CA 
00057  C020  D0  F3 

00058  C022  85  AF 

00059  C024  60 

00060  C02S 
0006 1  C02S 


NOADD 


ROR  A 
ROR  PROD 
DEX 

BNE  ML OOP 
STA  PROD+1 
RTS 


(Continued  on  next  page ) 


Dr.  Dobb’s  Journal.  May  1985 


61 

377 


Drawing  on  the  C-64  (Listing  Continued,  text  begins  on  page  50) 
Listing  One 


00062  C023 

00063  C023 

00064  C025 

00063  C025 

00066  car 3 

00067  C023 

00068  C023 

00069  C023 

00070  C023 

00071  C023 

00072  C023  A9 

00073  C027  83 

00074  C029  83 

00073  C02B  A2 
00076  C02D  26 
00077  C02F  26 
00078  C031  26 

00079  C033  26 

00080  C033  38 

0008 1  C036  A3 

00082  C038  E5 

00083  C03A  A8 
00084  C03B  AS 
00083  C03D  ES 
00086  C03F  90 
00087  C04 1  84 

00088  C04  3  83 

00089  C043  CA 

00090  C046  DO 

00091  C048  26 

00092  C04A  26 
00093  C04C  06 
00094  C04E  26 
00093  C050  B0 

00096  C032  38 

00097  C033  A3 

00098  C03S  ES 
00099  C037  A3 

00100  C0S9  ES 
00101  C0SB  B0 
00102  C03D  E6 
00103  C03F  D0 
00104  C061  E6 

00103  C063  60 

00106  C064 

00107  C064 

00108  P0A4 
00109  C064 

00110  C064 

00111  C064 

00112  C064 

00113  C064  A2 

00114  C066  A9 

00113  C06B  80 
00116  C06B  8D 
00117  C06E  83 
00118  C070  BS 

00119  C072  0E 

00120  C075  2E 

00121  C07B  EE 
00122  C07B  D0 
00123  C07D  EE 
00124  C080  06 

00125  C0R2  26 
00126  C0D4  26 
00127  C086  26 

00128  C088  06 

00129  C08A  26 
00 1 30  C08C  26 
00131  C08E  26 
00132  C090  38 

00133  C09 1  AS 

00134  C093  ED 

00135  C096  AB 

00136  C097  A3 

00137  C099  ED 

00138  C09C  90 
00139  C09E  83 
00140  C0A0  84 
00141  C0A2  EE 
00142  C0A3  D0 
00143  C0A7  EE 
00144  C0AA  CA 
00145  C0AB  D0 
00146  C0AD  4C 
00147  C0B0  38 
00148  COB  1  AD 
00149  C0B4  E9 
00130  C0b6  BD 
00151  C0D9  B0 
00 1 32  COBB  CE 
00153  C0BE  CA 
00154  C0Br  D0 
00153  C0C1  6E 
00136  C0C4  6E 
00137  C0C7  60 
00158  C0C8 
00159  C0CB 
00160  COCO 
00161  C0C8 
00162  C0C8 
00163  C0C8 
00164  C0C8  AD 
00163  C0CB  8D 
00166  C0CE  4D 
00167  COD  1  2E 
00168  C0D4  6A 
00169  CODS  6E 
00170  C0D8  8D 
00171  C0DB  AD 
00172  CODE  8D 
00173  COE  1  60 

00174  C0E2 
ERRORS  -  00000 
ERRORS  -  00000 
SYMBOL  TABLE 


00 

B4 

B5 

10 

FD 

FE 

B4 

B5 

B4 

FB 

BS 

FC 

04 

B4 

B5 

ES 

FD 

FE 

B4 

B5 

0B 


FB 

B4 

FC 

BS 

06 

FD 

02 

FE 


3C  03 
3D  03 


00  C0 
02  C0 
01  C0 
03  CO 

03  C0 
00  C0 
02  CO 
01  C0 


» 

i  DIVIDE  DOUBLE  PRECISION  DIVIDEND 
,  BY  DOUBLE  PRECIGION  DIVISOR  GIVING 
;  DOUBLE  PRECISION  QUOTIENT 
t 

|  DIVIDEND  IS  REPLACED  BY  QUOTIENT 
{  IN  THE  PROCESS 
i 

;  QUOTIENT  IS  ROUNDED  TO  NEAREST  INTEGER 


DIVIDE  LDA 
STA 
STA 
LDX 

DLOOP  ROL 
ROL 
ROL 
ROL 
SEC 
LDA 
SBC 
TAY 
LDA 
SBC 
BCC 
STY 
STA 

DECCNT  DEX 
BNE 
ROL 
ROL 
ASL 
ROL 
BCS 
SEC 
LDA 
SBC 
LDA 
SBC 
BCS 

ROUND  INC 
BNE 
INC 

NOCHNG  RTS 


#*00 

RMNDR 
RMNDR ♦ 1 
4*10 
DVDND 
DVDND* t 
RMNDR 
RMNDR* 1 

RMNDR 

DVSOR 

RMNDR* 1 
DVSOR* 1 
DECCNT 
RMNDR 
RMNDR* 1 

DLOOP 
DVDND 
DVDND *1 
RMNDR 
RMNDR* 1 
ROUND 

DVSOR 
RMNDR 
DVSOR *1 
RMNDR  * l 
NOCHNG  ■ 
DVDND 
NOCI ING 
DVDND* 1 


I  CHECK  IF  REMAINDER 
j  IS  >-  1/2  OF  DIVIDEND 
I  FOR  ROUNDING 


1  TALE  INTEGER  SQUARE  ROOT  OF  A 
I  DOUBLE  PRECISION  RADICAND  GIVING 
S  SINGLE  PRECISION  ROOT  <  REAL  ROOT  ) 
I 

SORT  LDX  4108 
LDA  N *00 
STA  ROOT 
STA  ROOT ♦ 1 
SI  A  TEMP' 

STA  TEMP* 1 
SORT  1  ASL  ROOT 

ROL  ROO  T ♦ 1 
INC  ROOT 
BNE  NEXT1 
INC  ROOT  *  1 
NEXT1  ASL  RADCND 

KOI  RADCND  *  1 
ROL  TEMF 
ROL  TEMP  *  1 
ASL  RADCND 
ROL  RADCND* 1 
ROL  TEMP 
ROL  TEMF ♦ 1 
SEC 

LDA  TEMP 
SBC  ROOT 
TAY 

LDA  TEMP* 1 
SBC  ROOT ♦ 1 
BCC  RE ST OR 
STA  TEMP ♦ 1 
STY  TEMP 
INC  ROUT 
BNE  NEXT2 
INC  R0UT*1 
NEXT2  DEX 

BNE  SORT  1 
JMP  F INI 
RES TOR  SEC 

LDA  ROOT 
SbC  4101 
STA  ROOT 
BCS  NC X  T 3 
DEC  ROOT* l 
NEXT3  DEX 

BNE  SORT  1 
F INI  ROR  ROOT* I 
ROR  ROOT 
RTS 


1  ASSUME  CURRENT  LSB  OF 
;  ROOT  WILL  BE  1 


1  SHIFT  RADICAND  LEFT 
;  TWICE  IN I O  TLMP 


I  SUBTRACT  ROOT  ESTIMATE 
j  FROM  TEMP 


5  SUBTRACTION  OL 


1  IGNORE  SUBTRACTION 
l  AND  RESET  LSB  OF  ROOT 


;  FINA1  /2  TO  NORMALIZE 


I 

I  GENERATC  PSEUDO-RANDOM  BYTES 
j  EXIT  WITH  P-R  BYTE  IN  ACCOM. 

; 

RANDOM  LDA  RNDM 
STA  RTEMP 
EOR  RNDM* 1 

ROL  RTEMP* I  I  RTEMP *1  PRESERVES 

ROR  A  ;  CARRY  BIT  FOR  CYCLING 

ROR  RTEMP* 1  I  RANDOM  NUMBERS 

STA  RNDM 

LDA  RTEMP 

STA  RNDM  *  1 

RTS 


SYMBOL  VALUE 
DECCNT  C045 
DVSOR  00FB 
MLPLER  00AD 
NEXT3  C0BE 
PROD  0OAE 
RMNDR  0084 
RTEMP  C002 
TEMP  00KB 


DIVIDE  C025 
FINI  C0CI 
MULT  C011 
NOADD  C01C 
RADCND  O0AC 
RNDM  C000 
SORT  C064 


DLOOP  C02D 
ML OOP  C015 
NEXT  1  C0BO 
NOCI  IMG  C063 
RANDOM  C0C8 
R001  033C 
SORT  1  C072 


DVDND  00FD 
MLPCND  00AC 
NEXT 2  C0AA 
POS I  TV  C00F 
RESTOR  C0B0 
ROUND  C05D 
SQUARE  C004 


END  OF  ASSEMBLY 


End  Listing  One 


Listing  Two 


00001 

00002 

00003 

00004 

00005 


0000 


0000 


0000 


00008  0000 


00010  0000 
00011  0000 
00012  0000 
00013  0000 

00014  0000 

00015  0000 

00016  0000 


00019  0000 

00020  0000 
00021  033E 

00022  0 33F 

00023  0341 

00024  0342 

00025  0343 

00026  0344 

00027  0346 

00028  0347 

00029  0348 

00030  034A 

00031  034A 

00032  COE 2 
00033  C0E2 
00034  COE 2 
00035  COE 2 
00036  COE 2 
00037  COE 2 
00038  COE 2 
00039  C0E2 
00040  COES 
00041  COE 7 
00042  C0EA 
00043  COED 
00044  C0F0 
00045  C0F2 
00046  C0F4 
00047  C0F7 
00048  COFA 
00049  COFD 
00030  COFF 
00051  Cl 02 
00032  Cl  03 
00033  Cl  03 
00034  Cl  03 
00033  C103 


AD  II  DO 
09  20 
80  It  DO 
AD  00  DD 
8D  42  03 
29  FC 
09  01 
BD  00  DD 
AD  18  00 
BD  43  03 
A9  19 
8D  18  D0 
60 


00056 

00057 

C00S8 

00059 

00060 

00061 

00062 

00063 

00064 

00065 

00066 

00067 

00068 

00069 

00070 

00071 

00072 
00073 
0007  4 
00075 
00076 
00077 
00078 
000/9 


00082 

00083 

00084 

000H5 

00086 

00087 


00090 
0009  1 
00092 
00093 
00094 
00095 
00096 
00097 


00101 
00102 
00103 
00104 
00  l  OS 
00106 
00107 
00108 
00109 
001  10 


C103 

C 1 03  AD  tl  DO 
C 106  29  DF 

CIOS  BD  II  DO 
C l OB  AD  42  03 
ClOE  8D  00  DD 
Cl  1 1  AD  43  03 
Cl  14  80  10  DO 

Cl  17  60 

Cl  IB 
CUB 
Cl  18 

cue 

CUB 

cue  A9  01 

C1I A  A2  00 

CUC  90  00  84 

Cl  IF  9D  00  85 

Cl 22  9D  00  86 

Cl  25  90  00  07 

Cl 28  CA 

Cl 29  DO  FI 

C12B  60 

CITC 

C12C 

CI2C 

C12C 

CI2C 

C12C  A9  A0 
CITE  85  F C 
C 1 30  AO  00 
C I  32  84  FB 

Cl 34  A9  00 
Cl  36  A2  20 
C 1 38  91  FB 

C 1 3A  C8 
Cl  36  D0  FB 
Cl  3D  E6  FC 
C 1 3F  CA 
Cl  40  DO  F6 
C142  60 

C 143 
Cl  43 
C 1  43 
C  1 43 
C143 
C  1 43 
C143 

Cl  43  A9  00 
Cl  45  2C 
C 1  46  A9  80 
Cl  48  8D  3E  03 
C14B  A5  01 
C14D  29  FE 
CI4F  85  01 


;  GRAPHICS  UTILITIES 
t 

;  RICHARD  L.  RYLANDER  11/4/84 
S 

;  LOAD  ARITHMETIC  UTILITIES  FIRST 
t 

RAM»*033E 

ORIGIN-JC0E2 


I 

Ml  PCNDMAC 
Ml  PLER=*AD 
PROD-*AE 
MULT-*C0U 


j  MULTIPLICAND  (S> 

I  MULTIPLIER  (S) 

;  PRODUCT  ID) 

1  CALL  FOR  MULTIPLY 


RNDM=*C000  {  RANDOM  NUMBER 

RANDOM- »C0CB  5  CALL  FOR  RANDOM 

t  NOTE  -  A  CALL  TO  RANDOM  LEAVES  A  RANDOM  BYTE 
5  IN  THE  ACCUMULATOR 
I 

•  -RAM 


PLTFLG  1 
XPLT  *-«*2 
YPLT  »-«*l 
VIC1  ««**l 
VIC2  •-•♦ l 
VALUE  «-»*2 
HTORRN  «-»*l 
NOSCAL  •  -•♦  1 
TEMP  »-**2 


j  PLOT /UNPLOT  FLAG 
;  ABSOLUTE  PLOT  COORD 
;  ABS01U1E  PLOT  COORD 
5  REGISTER  STORAGE 
j  REGISTER  STORAGE 
1  FINAL  NORMALIZED  SHADE  VALUE 
5  SHADE  FLAG,  1 -HALFTONE 
j  SCALE  FI  AG,  1 -NO  SCALE 
I  TEMPORARY  STORAGE 


•“ORIGIN 


t 

1  TURN  ON  BIT  MAP  GRAPHICS  MODE, 
j  SAVING  REGISTER  VALUES  FUR 
;  RETURN  TO  TEXT  MODE  LATER. 

; 

GRFON  LDA  4D0U 
ORA  #*20 
STA  *D0 1 1 
LDA  *DDOO 
STA  VIC1 
AND  N*FC 
ORA  N«01 
STA  «DD00 
LDA  *D0 1 0 
STA  VIC2 
LDA  #*19 
STA  *0018 
RTS 


RETURN  TO  TEXT  SCREEN 


STA 

LDA 

STA 

LDA 

STA 

RTS 


•  DO  1  1 

•  •DF 

•  DO  11 
V1CI 

•  DD0O 
VIC2 
*0018 


FILL  COLOR  MAP  FOR  BLACK  DOTS  ON  WHITE 


LDA  #*01 
LDX  #0 
STA  *8-100 ,  X 
STA  *8500, X 
STA  *8600, X 
STA  *0700, X 
DEX 

BNE  COL  1 
RfS 


I  POKE  NEW  COLORS  HERE 


I 

;  CLEAR  HI -RES  GRAPHICS  SCREEN 
I 

CLEAR  LDA  #*A0 
STA  *FC 
LDY  #0 
STY  <F6 

LDA  #0  j  CLEAR  BYTE 

LDX  «*20 

CLRLP  STA  (*FB),Y 
I  NY 

BNE  CLRLP 
INC  #FC 
DEX 

BNE  CLRLP 
RTS 


I  PLOT  AND  UNPLOT  POINTS  ON  HI  RES  GRAPHICS 
|  SCREEN.  ABSOLUTE  X  AND  Y  SCREEN  COORDINATES 
;  ARE  POKED  INTO  XPLT,  XPLT*l,  AND  YPLT 
I 

PLOT  LDA  NO 

.BYTE  *2C 
UNPLOT  LDA  N*80 

STA  PLTFLG 

LDA  *01  I  BASIC  ROM  OUT 

AND  #«FE 
STA  *01 


00111  C151  30 

00112  CI52  A9  C7 

00113  C 154  ED  41  03 

00114  C 157  AA 
00115  C 1 58  4 A 

00116  C 1 59  4A 

00117  ClSA  4A 
00118  C 1 SB  AB 

001 19  CISC  B9  AB  Cl 

00120  C15F  BS  FB 
00121  C 1 6  I  B9  C4  Cl 
00122  C 1 64  BS  FC 


SEC 

LDA  #*C7 
SBC  YPLT 
TAX 
LSR  A 
LSR  A 
LSR  A 
TAY 

LDA  TABLE 1 , Y 
S 1  A  4FB 
l  DA  TABLE2, Y 
STA  *FC 


I  INVERT  V  COORDINATE  TO 
;  TUT  ORIGIN  IN  LOWER  LEFT 
I  CORNER  OF  SCREEN 
i  <199. -YPLT) 


(Continued  on  page  64) 


62 


Dr.  Dobb's  Journal,  May  1985 


Drawing  on  the  C-64  (Listing  Continued,  text  begins  on  page  50) 
Listing  Two 


00123  C 1 66 
00124  C 1 67 
00123  Cl  69 
00126  C16A 
00127  C16C 
00120  C  1  6E 
00129  C171 

00130  Cl  73 
00131  Cl  73 
00132  Cl  77 
00133  C17A 
00134  C17C 
00133  C17E 
00136  C 1 80 
00137  C 182 
00138  Cl  04 
00139  Cl  8  7 
00140  Cl  89 
00141  C 18b 
00142  CISC 
00143  C1UE 
00144  C 1  BE 
001-43  C 1 9  1 
00146  C 1 92 
00147  C 1 94 
00148  C 1 96 
00149  C 1 99 
00130  C 1 96 
00131  C19D 
00132  C19F 
00153  C1A0 
00134  C1A2 
00153  C1A4 
00156  C1A6 
00157  C1AB 
00138  C1AA 
00139  C1AB 
00160  C I AB 
00160  Cl AC 
00160  Cl AD 
00160  C1AE 
00161  C.  I AF 
00161  C1B0 
00161  C  1  B  1 
00161  C1B2 
00162  C1B3 
00162  C1B4 
00162  C1B3 
00162  C1B6 
00163  C1B7 
00163  C1B8 
00163  C1B9 
00163  Cl  BA 
00164  C1BB 
00164  C1BC 
00164  C1BD 
00164  Cl  BE 
00165  C 1 BF 
00163  C1C0 
00163  C1C1 
00163  C1C2 
00163  C1C3 
00166  C1C4 
00167  C1C4 
00167  C1C3 
00167  C1C6 
00167  C 1C  7 
00168  C1CB 
00168  CIC9 
00168  C1CA 
00168  C1CB 
00169  circ 
00169  CICD 
00169  C ICE 
00169  C1CF 
00170  C 1 D0 
00170  C1D1 
00170  C1D2 
00170  C1D3 
00171  C1D4 
00171  C1D3 
00171  CID6 
00171  C1D7 
00172  Cl  08 
00172  C1D9 
00172  Cl  DA 
00172  Cl  DO 
00172  Cl  DC 
00173  C1DD 
00174  Cl DO 
00173  Cl  DO 
00176  CIDD 
00177  CIDD 
00178  CIDD 
00179  C1E0 
00180  C 1 E  2 
00181  C1E3 
00182  C1E8 


8A 

29  07 
18 

65  FB 
83  FB 
AD  3F  03 
29  F8 
63  FB 
85  FB 
AD  40  03 
65  FC 
03  FC 
A9  AO 
65  FC 
B5  FC 
AO  3F  03 

29  07 
49  07 
AA 

A9  01 
CA 

30  03 
0A 

D0  FA 
A0  00 
2C  3E  03 

10  05 
49  FF 

31  FB 

2C 

11  FB 
91  FB 
AS  01 
09  01 
85  01 
60 

00 

40 

00 

CO 

00 

40 

80 

C0 

00 

40 

80 

C0 

00 

40 

80 

C0 

00 

40 

80 

C0 

00 

40 

80 

C0 

00 

00 
01 
02 
03 
05 
06 
07 
08 
0A 
0B 
0C 
OD 
0F 
10 
1  1 

12 
14 
13 
16 
17 
19 
1A 
IB 
1C 
IE 


AD  3F  03 
29  07 

8D  48  03 
AD  41  03 
29  07 


TXA 

AMD  9407 
CLC 

ADC  (FB 
STA  (FB 
L DA  JOLT 
AND  tt(FQ 
ADC  (FD 

sta  *ro 

LDA  XPLTM 
ADC  (FC 
STA  *FC 
LDA  «(AQ 
ADC  (FC 
STA  (FC 
LDA  XT’LT 
AND  9(07 
EQR  9(07 
TAX 

IDA  9101 
PLUTLP  DLX 

BUI  PLOT 2 
ASL  A 
DNE  PL01LP 
PL0T2  LDY  90 

BIT  PLTFLG 
DPL  NOPlOT 
EOF  9(FF 
AND  ( JFB) , Y 
.BYTE  *2C 

NOPLOT  ORA  (I KB)  ,  Y 
STA  <(FD>,Y 
LDA  (01 
OKA  9(01 
STA  (01 
MS 


BASIC  ROM  RESTORED 


TAB! El  .BYTE  (00 . (40 , (80 . (CO 


.BYTE  (00, *40, (00, ICO 


.,BYTE  100,  (40,  #00,  (C0 


.BYTE  (00, *40, #80, (CO 


.BYTE  *00, *40, (00, (CO 


.BYTE  *00 , (40 , (80 , (C0 , * 00 


TABLE2  .BYTE  <00, (01 ,*02, (03 

.BYTE  *05,(06,(07,(00 

.BYTE  (0A,*0B,*0C,*0D 

.BYTE  (0F,(10,( 11,(12 

.BYTE  (14,(13,(16,(17 

.BYTE  (19, (1A, (IB, (1C, (IE 


SHADING  BY  HYBRID  DITHER/DOT -GROWTH 


I 

SHADE  LDA  XPLT 
AND  9(07 
STA  TEMP 
LDA  YPLT 
AND  9(07 


S  USE  BITS - *•* 

I  OF  1 X ’  SCREEN  COORD 

I  AND  BITS - 

,  OF  ' Y ‘  SCREEN  COORD 


00183  C1EA  0A 

00184  C1EB  0A 

00183  Cl EC  0A 

00186  C 1  ED  0D  48  03 

00187  C1F0  AA 

00188  CIF1  BD  2F  C2 

00189  C1F4  CD  44  03 

00190  C1F7  10  03 

00191  C1F9  4C  46  Cl 

00192  C1FC  4C  43  Cl 

00193  C IFF 

00194  C1FF 

00195  C 1 FF 

00196  C1FF 

00197  C 1 FF 

00198  C IFF  20  C8  C0 

00199  C202  4A 

00200  C203  4A 

00201  C204  CD  44  03 

00202  C207  10  03 

00203  C209  4C  46  Cl 

00204  C20C  4C  43  Cl 

00205  C20F 

00206  C20F 

00207  C20F 

00208  C20F 

00209  C20F 

00210  C20F 

00211  C20F 

00212  C20F 

00213  C20F  AD  47  03 


GREATR 


ASL  A 
ASL  A 
ASL  A 
ORA  TEMP 
TAX 

LDA  THRESH, X 
CMP  VALUE 
BPL  GREATR 
JMP  UNF'l  OT 
JMP  PLOT 


I  SHIFTED  INTO  — - 

|  POSITION  TO  DETERMINE 
t  6- BIT  OFFSET  IN 
j  THRESHOLD  TABLE 

I  SCREEN- POSITION-WEIGHTED 
1  THRESHOl  D  VAl  UE 


1  SHADING  BY  RANDOM  HALFTONE 
I 

RSHADE  JSR  RANDOM 

LSR  A  I  REDUCE  RANDOM  BYTE 

LSR  A  ;  TO  6  BITS  FOR  SHADE 

CMP  VALUE  5  VALUE  COMPARISON 

BPL  MORE 
JMP  UNPLOT 
MORE  JMP  PLOT 


i 

}  PLOT  A  POINT  WEIGHTED  BY  SHADING  SI.  ML  ME 
5  AND  SHADE  VALUE 

;  CHECk  NOSCAL'  FLAG  FOR  SCALING  OF  Y  COORD 
;  CHECK  ' HTORRN '  FLAG  FOR  TYPE  OF  SHADING 
; 

PLTSHD  l  DA  NOSCAL 


00214  C21 2 

00215  C214 

00216  C21 4 

00217  C21 4 

00218  C21 4 

00219  C214 

00220  C214 

00221  C217 

00222  C21B 
00223  C21A 
00224  C21C 
00225  C21E 
00226  C221 

00227  C224 

00228  C227 

00229  C229 

00230  C22C 
00231  C22F 
00232  C22F 
00233  C22F 
00234  C22F 
00234  C230 

00234  C231 

00234  C232 

00233  C233 

00233  C234 

00233  C233 

00233  C236 

00236  C237 

00236  C238 

00236  C239 

00236  C23A 
00237  C23B 
00237  C23C 
00237  C23D 
00237  C23E 
00238  C23F 
00238  C240 

00238  C24 1 

00238  C242 

00239  C24  3 

00239  C244 

00239  C245 

00239  C246 

00240  C247 

00240  C248 

00240  C249 

00240  C24A 
00241  C24B 
00241  C24C 
00241  C24D 
00241  C24E 
00242  C24F 
00242  C250 

00242  C251 

00242  C252 

00243  C253 

00243  C254 

00243  C253 

00243  C256 

00244  C257 

00244  C238 

00244  C239 

00244  C23A 
00243  C23B 
00243  C23C 
00243  C25D 
00243  C25E 
00246  C25F 
00246  C260 

00246  C26 1 

00246  C262 

00247  C263 

00247  C264 

00247  C263 

00747  C266 

00248  C267 

00248  C268 

00248  C269 

00248  C26A 
00249  C26B 
00249  C26C 
00249  C26D 
00249  C26E 
00230  C26F 


F0  10 


AC  41  03 
C8 

84  AD 
A9  D5 
83  AC 
20  11  C0 
BD  41  03 
AD  46  03 
F0  03 
4C  DD  Cl 
4C  FF  Cl 


02 

0A 

37 

3F 

10 

18 

25 

2D 

12 

1A 

27 

2F 

31 

39 

04 

0C 

33 

3B 

06 


29 
14 
1C 

23 
2B 
16 
IE 
03 
0B 
36 
3E 
01 
09 
34 
3C 
13 
IB 
26 
2E 
1 1 

19 

24 
2C 
32 
3A 
07 
0F 

30 
38 
05 
0D 
22 
2A 
17 
IF 

20 
28 
13 
ID 


BEQ  NORM 

;  SCALE  Y  FROM  0-239  PSEUDO-COORDINATES 
|  TO  0-199  TRUE  SCREEN  COORDINATES  BY 
x  Y  »  (YU)  »213/256 


1 

SCALE  LDY  YPLT 
I  NY 

STY  MLPLER 
LDA  V(D5 
STA  MLPCND 
JSR  MULT 
STA  YPLT 

NORM  LDA  HTORRN 
BEO  RFT.T 
JMP  SHADE 
RPLT  JMP  RSHADE 


1 

THFrESH  .BYTE  (00,  *08, 


217. 

RETURN  WITH  HIGH  BYTE 
IN  ACCUMULATOR 


,  (3D 


.BYTE  (02 , (0A , (37 , (3F 


.BYTE  *10, *18, (23, (2D 


.BYTE  ( I  2 , *  1 A , (27 , (2F 

.BYTE  *31 ,(39, *04 ,*0C 

.BYTE  *33, (3B , *06 , (0E 


.BYTE  (21 ,*29, (14, (1C 


. DYTE  *23, (28,(16, (IE 


.BYTE  ( 03 , *0B , I  36 , *  3E 


•BYTE  *01 ,*09,(34, *3C 

.BYTE  (13,*1B,(26,*2E 

.BYTE  (11,(19,(24, (2C 

.BYTE  (32 , ( 3A , (07 , (0F 

.BYTE  (30,(38, (05 , *0D 

.BYTE  (22, (2A, *17, (IF 


.BYTE  (20, (28, (15, (ID 


.END 


SYMBOL  TABLE 


SYMBOL  VALUE 
CLEAR  C12C  CLRLP 

GREATR  CIFC  GRFOFF 

MLPCND  00AC  MLPLER 

NOPLOT  C1A0  NORM 

PLOT  Cl  43  PL0T2 

PLTSHD  C20F  PROD 

RNDM  C000  RPLT 

SHADE  CIDD  TABLE  1 

THRESH  C22F  UNPLOT 

VIC2  0343  XPLT 

END  OF  ASSEMBLY 


Cl  38  COL  1  CUC 
C 103  GRFON  C0E2 
00AD  MORE  C20C 
C224  NOSCAL  0347 
C 1 94  PLOTLP  Cl BE 
00AE  RAM  033E 
C22C  RSHADE  C1FF 
C1AB  TABLE 2  CIC4 
C.  1 46  VALUE  0344 
033F  YPLT  0341 


COLOR  CUO 

HTORRN  0346 

MU  T  C01  1 

ORIGIN  C0E2 

PLTFLG  033E 

RANDOM  C0C8 

SCALE  C214 

TEMP  0348 

VIC1  0342 

End  Listing  Two 


Listing  Three 


00001  0000 

00002  0000 
00003  0000 

00004  0000 


00010  0000 
000 1 1  0000 
000 1 2  0000 
000 1 3  0000 

00014  0000 

00013  0000 

00016  0000 
00017  0000 


I  FACET  -  DRAW  SHADED  TRIANGULAR  FACETS 
I  AND  STRAIGHT  LINES. 


X  RICHARD  L.  RYLANDER 
1 

I  LOAD  "ARITH. HEX"  AND 
X  BEFORE  USING 
I 

ORIGIN  -  (C26F 
RAM  -  ( 034A 
I 

XPLT  -  (033F 
YPLT  -  *0341 
NORM  -  (C224 
NOSCAL  -  *0347 
PLOT  -  (Cl 43 
UNPLOT  -  *C 1 46 


11/4/84 
•GRAPH. HEX" 


(Continued  on  page  66) 


Dr.  Dobb’s  Journal.  May  1985 


64 

379 


Drawing  on  the  C-64  (Listing  Continued,  text  begins  on  page  50) 
Listing  Three 


00018  0000 


00020  0000 
00021  0000 
00022  0000 
00023  0000 


00023  0000 

00026  0000 
00027  0000 

00028  0000 
00029  0000 

00030  034A 

00031  034A 

00032  034C 

00033  034D 

00034  034F 

00033  0330 

00036  0332 

00037  0333 

00038  0334 

00039  0333 

00040  0336 

00041  0338 

00042  0339 

00043  0I5A 
00044  0338 

00043  033C 

00046  035D 

00047  035E 

00048  035F 

00049  0360 

00030  0361 

00051  0362 

00032  0363 

00033  0364 

00034  0363 

00033  0367 

00036  0368 

00037  036 A 

00038  036A 

00039  036A 

00060  C26F 
0006 1  C26F 
00062  C26F 
00063  C26F 
00064  C26F 
00063  C26F 
00066  C26F 
00067  C26F 
00068  C27 1 

00069  C273 

00070  C273 

00071  C278 

00072  C27A 
00073  C27D 
00074  C280 

00073  C281 

00076  C282 

00077  C203 

00078  C283 

00079  C286 

00080  C286 

00081  C286 

00082  C286 

00083  C286 

00084  C286 

00083  C286 

00086  C288 

00087  C26B 
00088  C28C 
00089  C28F 
000V0  C292 

00091  C293 

00092  C296 

00093  C297 

00094  C299 

00093  C29A 
00096  C29A 
00097  C29A 
00098  C29A 
00099  C29A 
00100  C29A 
00101  C29C 
00102  C29F 
00103  C2A0 
00104  C2A3 
00103  C2A6 
00106  C2A7 
00107  C2AA 
00108  C2AB 
00109  C2AO 
00110  C2AE 
00111  C2AE 
00112  C2AE 
00113  C2AE 
00114  C2AE 
00113  C2AE 
00116  C2B0 
00117  C281 

00118  C2B4 
00119  C2b7 
00120  C2BA 
00121  C2BD 
00122  C2BF 
00123  C2C2 
00124  C2C3 
00 1  23  C2C3 
00126  C2C6 
00127  C2C9 
00128  C2CC 
00129  C2CF 
00130  C2D2 
00131  C2D4 
00132  C2D7 
00133  C2DA 
00134  C2DB 
00133  C2DB 
00136  C20B 
00137  C2DB 
00138  C2DB 
00139  C2DB 
00140  C20B 
00141  C2DB 
00142  C2DD 
00143  C2E0 
00144  C2E3 
00143  C2E6 
00146  C2E8 


A0  06 
A9  03 
83  AC 
B9  4C  03 
83  AD 
20  11  C0 
99  4C  03 


88 

10  F0 
60 


A0  02 
B9  4A  03 
48 

B9  40  03 
99  4A  03 
60 

99  4D  03 
88 

10  EF 
60 


A0  02 
B9  40  03 
48 

B9  30  03 
99  40  03 
68 

99  30  03 
88 

10  EF 


A2  02 
38 

AD  4D  03 
ED  4A  03 
AD  4E  03 
ED  4B  03 
B0  03 
20  86  C2 
CA 

F0  13 
38 

AO  30  03 
ED  4D  03 
AD  31  03 
ED  4E  03 
B0  DC 
20  9A  C2 
4C  B0  C2 
60 


A9  02 
8D  31  03 
20  AE  C2 
AD  47  03 
F0  03 
20  6F  C2 


MLPCND  -  *AC 
ML PLEA  -  f  AD 
PROD  -  «AE 
MULT  -  *C011 
t 

DVDND  -  *FD 
DVSOR  •  *FB 
OUOT  -  *FD 
DIVIDE  -  4C023 
I 

•  -RAM 
I 

XM1N  •-*  *2 
YMIN  •  — •♦ 1 
XMID  *-•♦2 
YMID  *-*♦  1 
XMAX  *-*  *2 
YMAX  *-*♦  1 
YTOP  •  -  »  ♦  1 
YbOT  •“•♦ 1 
YBASE  *-*♦ 1 
DLTAX1  •-•♦2 
DLTAX2  •  -*♦ 1 
DLTAX3  *-**1 
DELTAX  *-•*! 
DLTAY1  «-**l 
DLTAY2  1 

DLTAY3  • 

DELTAY 
XDIFF  *-«*l 
FLAG1  •  -•♦ 1 
FLAG2  *-«*l 
FLAGS  *-»*l 
FLAG  •-•♦l 
EDGES  •  1 

ERROR  •-*♦2 
MODE  *-**1 
COUNT  *-*♦2 


1  SCALE  ALL  Y  COORDINATES  FROM  0. .239 
I  PSUE DO-COORD  I NATE  RANGE  TO  0.  .  199 
I  TRUE  SCREEN  COORDINATE  RANGE 
I 

SCALE  LDY  66 

LDA  ««D3 
STA  MLPCND 
SCLP  LDA  YMIN, Y 
STA  MLPLER 
JSR  MULT 
STA  YMIN, Y 
DEY 
DEY 
DEY 

BPL  SCLP 
RTS 


EXCHANGE  MIN'  AND  MID'  COORDINATES 


SWAP  12  LDY  62 
LOOP  1  LDA  XMIN.Y 
PHA 

LDA  XMID , Y 
STA  XMIN , Y 
PLA 

STA  XMID, Y 
DEY 

BPL  LOOP  1 
RTS 


»  EXCHANGE  MID'  AND  MAX'  COORDINATES 
I 

SWAP 2 3  LDY  62 
LOOP2  LDA  XMID.Y 
PHA 

LDA  XMAX.Y- 
STA  XMID.Y 
PLA 

STA  XMAX , Y 
DEY 

BPL  LOOP2 
RTS 


SORT  COORDINATES  ACCORDING  TO  X  COMPONENTS 


SORT  X  LDX 
SORTLP  SEC 
LDA 
SBC 
LDA 
BBC 
BCS 
JSR 

NOSWP1  DEX 
BEO 
SEC 
LDA 
SBC 
LDA 
SBC 
BCS 
JSR 
JMP 

SORTED  RTS 
I 


62 

XMID 
XMIN 
XMID*1 
XMIN* 1 
NOSWF I 
SWAP 12 

SORTED 

XMAX 

XMID 

XMAX* 1 

XMID* I 

SORTLP 

SWAP23 

SORTLP 


I  DRAW  A  LINE  BETWEEN  XMIN, YMIN  AND  XMID, YMID 
1  USING  FAST  DDA  (DIGITAL  DIFFERENTIAL  ANALYZER) 
I  TECHNIQUE 


LDA  62 
STA  XMAX  *  1 
JSR  SOR  T X 
LDA  NOSCAL 
BEO  OUT LN 
JSR  SCALE 


|  ENSURE  XMAX  IS 
;  LARGEST  BEFORE 
I  ORDERING  MIN'  AND 


00147  C2EB  20  6F  C4 

00148  C2EE  AD  4A  03 

00149  C2F 1  BD  3F  03 

00130  C2F4  AD  4B  03 

00131  C2F7  8D  40  03 

00132  C2FA  AD  4C  03 

00133  C2FD  8D  41  03 

00134  C300  AD  57  03 

00153  C303  D0  7D 

00136  C30S  38 

00137  C306  AD  36  03 

00138  C309  ED  SB  03 

00139  C30C  B0  74 

00160  C30E  AD  SB  03 

00161  C31 1  8D  63  03 

00162  C314  8D  68  03 

00163  C31 7  4E  63  03 

00164  C31A  38 

00163  C31B  AD  36  03 


00166  C31E 
00167  C321 

00168  C324 

00169  C327 

00170  C329 

00171  C32C 
00172  C32F 
00173  C332 

00174  C334 

00173  C337 

00176  C33A 
00177  C33D 
00 1 70  C340 

00179  C342 

00180  C343 

00181  C347 

00182  C34A 
00183  C34D 
00184  C34F 
00183  C3S2 
00186  C334 

00167  C3S7 
00188  C338 

00189  C35B 
00190  C33E 
00191  C361 

00192  C364 

00193  C366 

00194  C369 

00193  C36A 
00196  C36D 
00197  C370 

00198  C373 

00199  C376 

00200  C379 

00201  C37C 
00202  C37F 
00203  C381 

00204  C382 

00203  C382 

00206  C383 

00207  C388 

00208  C38B 
00209  C38E 
00210  C39 1 

00211  C394 

00212  C397 

00213  C39A 
00214  C39B 
00213  C39E 
00216  C3A1 
00217  C3A4 
00218  C3A6 
00219  C3A9 
00220  C3AC 
00221  C3AF 
00222  C3B1 
00223  C3B4 
00224  C3B7 
00223  C3BA 
00226  C3BD 
00227  C3BF 
00228  C3C2 
00229  C3C3 
00230  C3C7 
00231  C3CA 
00232  C3CC 
00233  C3CF 
00234  C301 

00233  C3D4 
00236  C3D3 
00237  C3D8 
00238  C3DB 
00239  C3DE 
00240  C3E 1 
00241  C3E4 
00242  C3E7 
00243  C3E8 
00244  C3EB 
00243  C3EE 
00246  C3F 1 
00247  C3F4 
00248  C3F6 
00249  C3F9 
00230  C3FA 
00231  C3FD 
00232  C3FF 
00233  C402 

00234  C404 

00253  C407 

00236  C40A 
00237  C40C 
002S8  C40D 
00239  C40D 
00260  C40D 
00261  C40D 
00262  C40D 
00263  C40D 
00264  C40E 
00263  C411 

00266  C414 

00267  C416 

00268  C419 

00269  C41A 
00270  C41D 
00271  C42B 
00272  C421 


ED  63  03 
8D  63  03 
AD  37  03 
E9  00 
8D  66  03 
EE  68  03 
AD  67  03 
D0  06 
20  43  Cl 
4C  3D  C3 
20  46  Cl 
AD  60  03 
DO  03 
EE  41  03 
DO  03 
CE  41  03 
2C  66  03 
30  1A 
EE  3F  03 
D0  03 
EE  40  03 
38 

AD  63  03 
ED  SB  03 
8D  63  03 
AD  66  03 
E9  00 
8D  66  03 
18 

AD  63  03 
60  36  03 
8D  63  03 
AD  66  03 
6D  37  03 
80  66  03 
CE  68  03 
D0  AE 
60 

AD  36  03 
BD  63  03 
8D  68  03 
AD  57  03 
8D  66  03 
BD  69  03 
4E  66  03 
6E  63  03 
38 

AD  SB  03 
ED  63  03 
BD  65  03 
A9  00 
ED  66  03 
BD  66  03 
AD  67  03 
D0  06 
20  43  Cl 
4C  BA  C3 
20  46  Cl 
EE  3F  03 
DO  03 
EE  40  03 
2C  66  03 
30  20 
AD  60  03 
D0  05 
EE  41  03 
D0  03 
CE  41  03 
38 

AD  63  03 
ED  36  03 
OD  65  03 
AD  66  03 
ED  37  03 
8D  66  05 
18 

AD  65  03 
6D  SB  03 
BD  65  03 
AD  66  03 
69  00 
8D  66  03 
38 

AD  68  03 
E9  01 
SD  68  03 
B0  03 
CE  69  03 
2C  69  03 
10  A0 
60 


38 

AD  S3  03 
ED  54  03 
B0  0E 
AD  53  03 

48 

AD  34  03 
8D  53  03 
68 

8D  34  03 


OUTLN  JSR  FINDXY 
LDA  XMIN 
STA  XPLT 
LDA  XMIN* 1 
STA  XPLT* 1 
LDA  YMIN 
STA  YPLT 
LDA  DLTAX 1*1 
BNE  STEPX 
SEC 

LDA  DLTAX 1 
SBC  DLTAY1 
BCS  STEPX 
STEPY  LDA  DLTAY1 
STA  ERROR 
STA  COUNT 
LSR  ERROR 
SEC 

LDA  DLTAX 1 
SBC  ERROR 
STA  ERROR 
LDA  DLTAX 1*1 
SBC  #0 
STA  ERROR* l 
INC  COUNT 
LNLP1  LDA  MODE 

BNE  ERASE  1 
JSR  PLOT 
JMP  SKI 

ERASE 1  JSR  UNPLOT 
SKI  LDA  FLAG  1 
BNE  NSLOPE 
INC  YPLT 
BNE  SK2 

NSLOPE  DEC  YPLT 
SK2  BIT  ERROR* 1 
BMI  SK3 
INC  XPLT 
BNE  NO I NCI 
INC  XPLT* 1 
NO I NCI  SEC 

LDA  ERROR 
SBC  DLTAY1 
STA  ERROR 
LDA  ERROR* 1 
SBC  «0 
STA  ERROR* 1 
SK3  CLC 

LDA  ERROR 
ADC  DLTAX 1 
STA  ERROR 
LDA  ERROR* 1 
ADC  DLTAX 1*1 
STA  ERROR* 1 
DEC  COUNT 
BNE  LNLP1 
RTS 
I 

STEPX  LDA  DLTAX 1 
STA  ERROR 
STA  COUNT 
LDA  DLTAX 1*1 
STA  ERROR* 1 
STA  COUNT ♦ 1 
LSR  ERROR* 1 
ROR  ERROR 
SEC 

LDA  DLTAY1 
SBC  ERROR 
STA  ERROR 
LDA  «0 
BBC  ERROR* 1 
STA  ERROR* l 
LNLP2  LDA  MODE 

BNE  ERASE 2 
JSR  PLOT 
JMP  SKP1 

ERASE2  JSR  UNPLOT 
S»'P1  INC  XPLT 

BNE  N0INC2 
INC  XPLT*1 
NOINC2  BIT  ERROR* 1 
BMI  SKP3 
LDA  FLAG1 
BNE  NGSLP 
INC  YPLT 
BNE  BKP2 
NGSLP  DEC  YPLT 
Si:P2  SEC 

LDA  ERROR 
SBC  DLTAX 1 
STA  ERROR 
LDA  ERROR* 1 
SBC  DLTAX 1*1 
STA  ERROR* l 
SKP3  CLC 

LDA  ERROR 
ADC  Dl.TAYl 
STA  ERROR 
LDA  ERROR* 1 
ADC  #0 
STA  ERROR* 1 
SEC 

LDA  COUNT 
SbC  Ml 
STA  COUNT 
BCS  TEST 
DEC  COUNT *1 
TEST  BIT  COUNT* 1 
BPL  LNLP2 
RTS 


;  ENTRY  POINT  TO 
I  OUTLINE  FACETS 

I  CHECK  FOR  DX  >DY 


I  0  -  DRAW,  1  -  ERASE 

I  0  -  POSITIVE  SLOPE 
I  ALWAYS  BRANCH 


I  ALWAYS  BRANCH 


j  DRAW  A  SHADED  VERTICAL  LINE  AT 
5  XPLT  FROM  YTOP  TO  YBOT 
I 

VLINE  SEC  {  MAKE  SURE  YTOP >YBOT 

LDA  YTOP 
SBC  YBOT 
DCS  DRAW 
LDA  YTOP 
PHA 

LDA  YbOT 
STA  YTOP 
PLA 

STA  YDOT 


(Continued  on  page  68) 


66 


Dr.  Dobb’s  Journal,  May  1985 


Drawing  on  the  C-64  (Listing  Continued,  text  begins  on  page  50) 
Listing  Three 


80273  C424 

00274  C427 

00273  C42A 
00276  C42D 
00277  C430 

00270  C433 

00279  C433 

00280  C  4  ZB 
00281  C43B 
00282  C43C 
00283  C43C 
00284  C43C 
00283  C43C 
00286  C43C 
00287  C43C 
00288  C43C 
00289  C43F 
00290  C44 1 

00291  C444 

00292  C446 

00293  C449 

00294  C44B 
00293  C44D 
00296  C44F 
00297  C451 

00298  C453 

00299  C456 

00300  C458 

00301  C43B 
00302  C43E 
00303  C460 

00304  C46 1 

00305  C464 

00306  C466 

00307  C468 

00308  C469 

00309  C46C 
00310  C46E 
00311  C46F 
00312  C46F 
00313  C46F 
00314  C46F 
00313  C46F 
00316  C46F 
00317  C46F 
00318  C46F 
00319  C46F 
00320  C470 

00321  C473 

00322  C476 

00323  C479 

00324  C47C 
00323  C47F 
00326  C482 

00327  C483 

00328  C486 

00329  C489 

00330  C48C 
00331  C48D 
00332  C490 

00333  C493 

00334  C496 

00333  C496 

00336  C496 

00337  C496 

00338  C496 

00339  C498 

00340  C49B 
00341  C49E 
00342  C4A1 
00343  C4A2 
00344  C4AS 
00343  C4A8 
00346  C4AA 
00347  C4AD 
00348  C4B0 
00349  C4B3 
00330  C4B6 
00331  C4B7 
00352  C4BA 
00333  C4BD 
00334  C4BF 
00333  C4C2 
00336  C4C3 
00337  C4C8 
00338  C4CB 
00339  C4CC 
00360  C4CF 
00361  C402 

00362  C4D4 
00363  C4D7 
00364  C4DA 
00363  C4DD 
00366  C4E0 
00367  C4E1 
00368  C4E 1 
00369  C4E 1 
00370  C4E1 
00371  C4E1 
00372  C4E1 
00373  C4E4 
00374  C4E7 
00373  C4E9 
00376  C4EC 
00377  C4EF 
00378  C4F2 
00379  C4F3 
00380  C4F8 
00381  C4FB 
00382  C4FC 
00383  C4FF 
00384  C302 

00383  C503 

00386  C50B 
00387  C50A 
00388  C50D 
00389  C510 

00390  C313 

00391  C516 

00392  C519 

00393  C51C 
00394  C51F 
00393  C322 

00396  C323 

00397  C52B 
00398  C52A 
00399  C52D 
00400  C330 


AO  53  03 
BD  41  03 
20  24  C2 
AD  53  03 
CD  54  03 
F  0  06 
CE  53  03 
4C  24  C4 
60 


AD  5F  03 
85  AC 
AD  5E  03 
85  AD 
20  11  C0 
85  FE 
AS  AE 
83  FD 
A9  00 
85  FC 
AD  SA  03 
85  FB 
20  25  C0 
AD  63  03 
D0  08 
18 

AD  55  03 
65  FD 
90  06 
38 

AD  35  03 
E5  FD 
60 


38 

AD  4D  03 
ED  4 A  03 
8D  56  03 
AD  4E  03 
ED  4B  03 
8D  57  03 
38 

AD  30  03 
ED  4D  03 
8D  58  03 
38 

AO  50  03 
ED  4A  03 
8D  39  03 


A9  00 
80  60  03 
8D  61  03 
8D  62  03 
38 

AD  4F  03 
ED  4C  03 
B0  09 
EE  60  03 
AD  4C  03 
ED  4F  03 
8D  SB  03 
38 

AD  32  03 
ED  4F  03 
B0  09 
EE  61  03 
AD  4F  03 
ED  52  03 
8D  SC  03 
38 

AD  52  03 
ED  4C  03 
B0  09 
EE  62  03 
AD  4C  03 
ED  52  03 
8D  SD  03 
60 


20  AE  C2 
AD  47  03 
F0  03 
20  6F  C2 
20  6F  C4 
AD  4A  03 
8D  3F  03 
AD  4B  03 
BD  40  03 
38 

AD  3F  03 
ED  4 A  03 
8D  5F  03 
AD  56  03 
F0  53 
8D  3A  03 
AD  SB  03 
8D  5E  03 
AD  60  03 
8D  63  03 
AD  4C  03 
BD  53  03 
20  3C  C4 
BD  53  03 
AD  59  03 
F0  33 
BD  5A  03 
AD  3D  03 
8D  5E  03 


DRAW  LDA  YTOP 
STA  YPLT 

JSR  N0RI1  j  PLOT  A  SHADE -WE  IGHTED 


DONE 


LDA  YTOP 
CMP  YBQT 
BEO  DONE 
DEC.  YTOP 
JMP  DRAW 
RTS 


PIXEL  CHECKING  ONLY 
FOR  SHADE  STYLE 


FIND  ENDPOINTS  FOR  VERTICAL  LINES 
BETWEEN  FACET  EDGES 


ENDPTS  LDA 
STA 
LDA 
STA 
JSR 
STA 
LDA 
STA 
LDA 
STA 
LDA 
STA 
JSR 
LDA 
RNE 
CLC 
LDA 
ADC 
BCC 

NEGSLP  SEC 
LDA 
SBC 

SKIP2  RTS 


XDIFF 

MLPCND 

DEL T AY 

MLPLER 

MOLT 

DVDND* 1 

PROD 

DVDND 

«0 

DVSOR ► 1 

DEL  TAX 

DVSOR 

DIVIDE 

FLAG 

NEGSLP 

YBASE 

QUOT 

BKIP2 

YBASE 

OUOT 


i  FIND  COORDINATE  DIFFERENCES 
I 

j  ALL  "DELTA  X"  VALUES  POSITIVE, 
l  SINGLE  PRECISION  (JUST  LOWER  BYTE) 

» 

FINDXY  SEC 

LDA  XM1D 
SbC  XMIN 
STA  DLTAX1 
LDA  XM I D* 1 
SBC  XMIN* 1 
STA  DLTAX 1*1 
SEC 

LOA  XMAX 
SBC  XMID 
STA  DLTAX2 
SEC 

LDA  XMAX 
SBC  XMIN 
STA  DLTAX3 
I 

I  USE  ABS( DELTA  Y>  VALUES, 

I  FLAGS  INDICATE  SLOPE  OF  LIMIT  LINES 

LDA  N«00 
STA  FLAG1 
STA  FLAG2 
STA  FLAG3 
SEC 

LDA  YMID 
SBC  YMIN 
BCS  STORE  1 
INC  FLAG1 
LDA  YMIN 
SBC  YMID 

STORE  1  STA  DLTAYI 
SEC 

LDA  YMAX 
SBC  YMID 
BCS  ST ORE 2 
INC  FLAG2 
LDA  YMID 
SBC  YMAX 

ST0RE2  STA  DL T AY2 
SEC 

LDA  YMAX 
SBC  YMIN 
BCS  STORES 
INC  FLAG3 
LDA  YMIN 
SBC  YMAX 

STORES  STA  DLTAY3 
RTS 


DRAW  A  SHADED  TRIANGULAR  FACET 


FACET  JSR 
LDA 
BEO 
JSR 

YSOK  JSR 
LDA 
STA 
LDA 
STA 

FCETLP  SEC 
LDA 
SBC 
STA 
LDA 
BEQ 
STA 
LDA 
STA 
LDA 
STA 
LDA 
STA 
JSR 
STA 
LDA 
BEQ 
STA 
LDA 
STA 


SORTX 

NOSCAL 

YSOK 

SCALE 

FINDXY 

XMIN 

XPLT 

XMIN* 1 

XPL  T ♦ 1 

XPLT 

XMIN 

XDIFF 

DLTAX 1 

CONT 

DFLTAX 

DLTAYI 

DELTAY 

FLAG1 

FLAG 

YMIN 

YBASE 

ENDPTS 

YTOP 

DLTAX 3 

CONT 

DEL  TAX 

DLTAY3 

DELTAY 


00401  C533 

00402  C536 

00403  C539 

00404  C33C 
00403  C53F 
00406  C542 

00407  C345 

00408  C548 

00409  C54A 
00410  C34D 
00411  C350 

00412  C332 

00413  C533 

00414  CSS 7 
00415  C55A 
00416  C55D 
00417  C53E 
00418  C361 

00419  CS64 
00420  C567 

00421  CS6A 
00422  C56C 
00423  C36F 
00424  C572 

00423  C573 

00426  C578 

00427  C57B 
00428  C37E 
00429  C581 

00430  CS84 
00431  C587 

00432  C388 

00433  C38B 
00434  C58E 
00433  C59 1 

00436  C394 

00437  C396 

00438  C399 

00439  C59C 
00440  C39F 


AD  62  03 
BD  63  03 
20  3C  C4 
BD  54  03 
20  0D  C4 
AD  40  03 
CD  4E  03 
D0  08 
AD  3F  03 
CD  40  03 
F0  0B 
EE  3F  03 
D0  03 
EE  40  03 
4C  FB  C4 
38 

AD  3F  03 
ED  4A  03 
80  5F  03 
AD  59  03 
F0  63 
8D  5A  03 
AD  5D  03 
BD  5E  03 
AD  62  03 
bD  63  03 
AD  4C  03 
8D  55  03 
20  3C  C4 
8D  54  03 
38 

AD  3F  03 
ED  4D  03 
8D  5F  03 
AD  58  03 
F0  39 
8D  5A  03 
AD  SC  03 
8D  SE  03 
AD  61  03 


00441  C3A2 
00442  C3A3 
00443  C3A8 
00444  CSAB 
00443  C3AE 
00446  C3B 1 
00447  C3B4 
00448  C5B7 
00449  C5BA 
00450  C3BC 
00431  C3BF 
00432  C3C2 
00433  CSC 4 
00434  CSC 7 
00453  C5C9 
00456  C5CC 
00457  C5CF 
00458  C5D2 
00439  C3D4 
00460  C3D7 
00461  C3DA 
00462  CSDD 
00463  CSE0 
00464  CSE3 
00463  C5E6 
00466  C5E9 
00467  CSEA 


8D  63  03 
AD  4F  03 
8D  53  03 
20  3C  C4 
8D  33  03 
20  0D  C4 
AD  40  03 
CD  51  03 
D0  08 
AD  3F  03 
CD  50  03 
F0  0B 
EE  3F  03 
D0  03 
EE  40  03 
4C  5D  C5 
AD  64  03 
F  0  15 
20  EB  C2 
20  9A  C2 
20  EB  C2 
20  86  C2 
20  9A  C2 
20  86  C2 
20  EB  C2 
60 


LDA  FLAG3 
STA  FLAG 
JSR  ENDPTS 
STA  YBOT 
JSR  VLINE 
LDA  XPLT *1 
CMP  XMID* l 
BNE  NEXTX1 
LDA  XPLT 
CMP  XMID 
BEQ  CONT 
NEXTX1  INC  XPLT 
BNE  SKIP3 
INC  XPLT ♦ 1 
SKIP3  JMP  FCETLP 
CONT  SEC 

LDA  XPLT 
SBC  XMIN 
STA  XDIFF 
LDA  DLTAX3 
BEO  F INI 
STA  DELTAX 
LDA  DI.TAY3 
STA  DELTAY 
LDA  FLAG3 
STA  FLAG 
LDA  YMIN 
STA  YBASE 
JSR  ENDPTS 
STA  YBOT 
SEC 

LDA  XPLT 
SbC  XMID 
STA  XDIFF 
LDA  DLTAX2 
BEQ  F INI 
STA  DELTAX 
LDA  DLTAY2 
STA  DELTAY 
LDA  FLAG2 
STA  FLAG 
LDA  YMID 
STA  YBASE 
JSR  ENDPTS 
STA  YTOP 
JSR  VLINE 
LDA  XPLT ♦ 1 
CMP  XMAX ♦ 1 
BNE  NEXTX2 
LDA  XPLT 
CMP  XMAX 
BEQ  FINI 
NEXTX2  INC  XPLT 
BNE  SKIP4 
INC  XPLT  *  1 
6KIP4  JMP  CONT 
FINI  LDA  EDGES 
BEO  FINISH 
JSR  OUTLN 
JSR  SWAP23 
JSR  OUTLN 
JSR  SWAP  12 
JSR  SWAP23 
JSR  SWAP 12 
JSR  OUTLN 
FINISH  RTS 
.END 


SYMBOL  TABLE 


SYMBOL  VALUE 
CONT  CS3D 

DIVIDE  C023 

DLTAYI  033B 

DRAW  C424 

ENDPTS  C43C 

FACET  C4E 1 

FINISH  C5E9 

FLAGS  0362 

LOOP 1  C288 

MODE  0367 

NEXTX2  CSC 4 

NORM  C224 

ORIGIN  C26F 

QUOT  00FD 

SKI  C33D 

SKIPS  C55A 

SKP3  C3E7 

STEPX  C382 

STORES  C4DD 


COUNT  0368 
DLTAX 1  0336 
DLTAY2  035C 
DVDND  00FD 
ERASE  1  C33A 
FCETLP  C4FB 
FI  AG  0363 
LINE  C2DB 
L00P2  C29C 
MULT  C0U 
NGSLP  C3D1 
NOSCAL  0347 
OUTLN  C2EB 
RAM  034 A 
SK2  C34A 
SI  IP4  C5CC 
SORTED  C2DA 
STEPY  C30E 
SWAP 12  C286 


DELTAX  033A 
DLTAX 2  0358 
DLTAY3  035D 
DVSOR  00F B 
ERASE2  C3B7 
FINDXY  C46F 
FLAG1  0360 
LNLP l  C32F 
MLPCND  00 AC 
NEGSLP  C46B 
NO I NCI  C357 
N0SWP1  C2C2 
PLOT  Cl  43 
SCALE  C26F 
SK3  C369 
SKPl  C3BA 
SORTLP  C2B0 
STORE  1  C4B3 
SWAP23  C29A 


DELTAY  035E 
DLTAX 3  0359 
DONE  C438 
EDGES  0364 
ERROR  0365 
FINI  C5CF 
FLAG2  0361 
I.Nt  P2  C3AC 
MLPLER  00AD 
NEXT  XI  C552 
NQINC2  C3C2 
NSLOPE  C34  7 
PROD  00AE 
SCLP  C275 
SI  IP2  C46E 
SKP2  C3D4 
SORTX  C2AE 
ST0RE2  C4C8 
TEST  C407 


SYMBOL  TABLE 
SYMBOL  VALUE 
UNPLOT  Cl 46 

XMID  034D 

YBOT  0334 

YPLT  0341 


VLINE  C40D 
XMIN  034A 
YMAX  0352 
YSOK  C4EC 


XDIFF  035F 
XPLT  033F 
YMID  034F 
YTOP  0353 


XMAX  0330 
YBASE  0355 
YMIN  034C 


Listing  Four 


End  Listing  Three 


00001 

00002 

00003 

00004 

00005 

00006 

00007 

00008 

00009 

00010 

00011 

00012 

00013 

00014 

00013 

00016 


i  PRIMITIVE  SOLID  SHAPE  DRAWING 

» 

I  RICHARD  L.  RYLANDER  11/7/84 
I 

I  LOAD  ARITHMETIC  AND  GRAPHIC  UTILITIES  FIRST 

« 

RAM=*036A 
0RIGIN-#C5£A 

MLPCND-4AC 
MLPLER“fAD 
PROD«JAE 
MULT=4C01 1 

DVDND-4FD 

(Continued  on  page  70) 


|  MULTIPLICAND  <S> 

;  MULTIPLIER  (S) 
j  PRODUCT  CD) 
j  CALL  FOR  MULTIPLY 

{  DIVIDEND  <D> 


Dr  Dobb’s  Journal.  May  1985 


Drawing  on  the  C-64  (Listing  Continued,  text  begins  on  page  50) 
Listing  Four 


17  BOBU 
00010  0000 
00019  0000 

00020  0000 
00021  0000 
00022  0000 
00023  0000 

00024  0000 


00026  0000 
00027  0000 

00020  0000 
00029  0000 


00031  0000 

00032  0000 

00033  0000 

00034  0000 

00033  0000 

00036  0000 

00037  0000 

0003B  0000 

00039  0000 

00040  0000 

00041  0000 

00042  0000 

00043  036A 

00044  036C 

00043  0360 

00046  036F 

00047  0370 

00048  0371 

00049  0373 

00030  0373 

00031  0377 

00032  0377 

00033  0379 

00034  0378 

00033  0370 

00036  037D 

00037  037E 

00038  037F 

00039  0380 

00060  0301 

00061  0381 

00062  0382 

00063  0382 

00064  0383 

00063  0384 

00066  0386 

00067  0387 

00068  0308 

00069  0309 

00070  0309 

00071  030 A 

00072  038C 

00073  0180 

000  74  0  .'BI¬ 

BOO  75  03UF 
00076  0390 

00077  0392 

00078  0393 

00079  0393 

00080  0393 

00001  0393 

00082  C5EA 
00083  C5EA 
00084  C5EA 
00085  C5EA 
00086  C3FA 
00087  C3CA 
00088  C5EC 
00089  C5LE 
00090  C5F1 
00091  C5F 1 
00092  C5F1 
00093  C5F 1 
00094  C5F 1 
00095  C5F 1 
00096  C5F 1 
00097  C5F1 
00098  C3F4 
00099  C5F6 
00100  C5F9 
00101  CSFB 
00102  C5FE 
00103  C5FF 
00104  C600 

00103  C602 

00106  C605 

00107  C608 

00108  C60B 
00109  C60D 
00110  C60F 
00111  C61 1 

00112  C614 

00113  C616 

00114  C618 

00113  C61A 
00116  C61D 
00117  C61F 
00118  C622 

00119  C624 

00120  C627 

00121  C628 

00122  C628 

00123  C628 

00124  C628 

00123  C628 

00126  C62B 
00127  C628 

00128  C628 

00129  C62B 
00130  C628 

00131  C 628 
001  12  C62Q 
00133  C620 

00134  C628 

00133  C628 

00136  C628 

00137  C628 

00138  C628 

00139  C628 

00140  C62Q 
00141  C628 

00142  C620 

00143  C62B 
00144  C628 

00143  C628 


A  9  00 
85  FC 
4C  2S  CO 


2C  7A  03 

10  12 
AD  02  03 
DO  04 
BD  44  03 
60 
38 

A9  00 
ED  79  03 
8D  79  03 
AD  79  03 
85  AC 
A9  1A 
83  AD 
20  11  CO 
85  FE 
AS  AE 
83  FD 
AD  77  03 
85  FB 
20  EA  C5 
A5  FD 
8D  44  03 
60 


DVS0R-4FB 
OUOT-tFD 
DIVIDE- 4 C025 
( 

ARG-4AC 
SOR-IAE 
SQUARE-* C00 4 
> 

RADCND-JAC 

ROOT-4033C 

SQRT-4C064 


I  DIVISOR  (D> 

;  QUOTIENT  <D> 

I  CALL  FOR  DIVIDE 

{  ARGUMENT  (S) 

I  SQUARE  OF  ARQ  <D> 
|  CALL  FOR  SQUARE 

I  RADICAND  (D) 

I  SQUARE  ROOT  <S> 

I  CALL  FOR  SORT 


; 

RNDM-4C000  |  RANDOM  NUMBER 

RANDOM- IC0C8  »  CALL  FOR  RANDOM 

;  NOTE  -  A  CALL  TO  RANDOM  LEAVES  A  RANDOM  BYTE 
j  IN  THE  ACCUMULATOR 
I 

XPLT-4033F 
YPLT-40341 
NORM-4C224 
F-LTSHD-4C20F 
VALUE-40344 
HTORRN- 40346 
NOSCAL-40347 
I 

•  -RAM 


FINAL  NORMALIZED  SHADE  VALUE 
SHADE  FLAG,  1 -HALFTONE 
SCALE  FLAG,  1 -NO  SCALE 


XCENT  *-**2 
XREL  *-**  1 
XSHD  *-**2 
YCENT  •  -*  ♦  1 
YREL  •  -•  ♦  1 
YSMD  *-•■♦2 
ZREL  *-•♦2 
ZWX  *-• *2 


CENTER  COORD 
RELATIVE  (TO  CENTER) 
USED  IN  SHADE  CALC 
CENTER  COORD 
RELATIVE  (TO  CENTER) 
USED  IN  SHADE  CALC 
RELATIVE  (TO  CENTER) 
Z  WITH  X  (♦  OR  -) 


RADIUS  •-*♦2 
TONE  •-•♦2 
TNTMP  *-**2 


Cl.  I  PL  •-*♦1 
CL  I PR  *-**1 
CLIPIJ  •  -‘*♦1 
CL  I  PD  «  "1 


LOCAL  RADIUS  OF  SURFACE 
USED  IN  SHADE  CALC 
USED  IN  SHADE  CALC 

LI  FT  CLirr  iNb  BOUND 
RIGHT  Cl  IFF  I  NO  HOUND 
UP  LI  IPPINC  EX  AINU 
DOWN  Cl  If -PING  BOUND 


PLOTTING  HEMISPHERE 


da)  in  »  *  •  i 
HVFLAG  •  *  I 

1LMP  ■  •«2 
C.NTX  ■-‘•*1 
CN I  Y  •  *■••! 
MAX  »-^**  1 


BACH  IT  f  I  AO 

MUF>  I 70NTA1  'VERMCAI  FLAG 

TEMPORARY  STORAGE  , 

1  OOP  T.Ot  INI  ER 
IOOP  COUNTER 
I  OOP  l.  IMI  1 


HI  FN  »  -•♦! 

RS  *-• *2 

R1  •  *-»*! 

RC  •-•*! 

RU  •  -*♦  I 

R I  •-•41 

XSOR  •-*4’ 2 

XMAX  •-• ♦  1 


HAI  F  l  ENG  I H  OF  CYLINDERS 
SQUARE  m  IUR01D  RADIUS 
TOROID  'RING!  RADIUS 
Cl  Ml  PR  RAD IIIT>  III  1  DROID 
UOII'.R  RADIUS  ID  1UR0ID 
INNER  RADIUS  (IF  TOROID 


R0-UL  EN 
I 

•-ORIGIN 


j  DIVIDE  WITH  SINGLE  PRECISION  DIVISOR 
l  (USED  OFTEN  IN  SHAPE  ROUTINTS) 
t 

SDI V  LDA  40 

ST A  DVS0R41 
JMP  DIVIDE 


i  CALCULATE  SHADE  VALUE  <0  63 I  LY 
I  MULTIPLYING  TONE  BY  26  THEN 
S  DIVIDE  RESULT  DY  RADIUS  OP  SURFACE 

I 


GETVAL  BIT  TONE M 
BPL  CNTNU 
LDA  BAI  LIT 
DNE  NEGATE 
ST  A  VALUE 
RTS 

NEGATE  SEC 


j  IF  TONE  -0, 
j  MA)E  VALUE  O 
I  DEPENDING  ON 


THEN 

OR  ABS ( TONE  I 
BAH  IT  FI  AG 


LDA  4400 
SBC  TONE 
STA  TONE 
CNTIIU  LDA  TONE 

STA  Ml  F'CND 
LDA  44 1 A 
STA  MLPI.ER 
JSR  MULT 
STA  DVDNDM 
LDA  PROD 
STA  DVDND 
LDA  RADIUS 
STA  DVSOR 
JSR  SDI V 
LDA  QUOT 
STA  VALUE 
RTS 


POINT  PLOTTING  BY  QUADRANTS  USING 

THE  FOUR-FOLD  SYMMETRY  OF  SIMPLE  OBJECTS 

DEPENDING  ON  STATUS  OF  HVFLAG  ,  EXCHANGE 
X  AND  Y  COORDINATES  TO  ROTATE  OBJECTS  90  DEG 
SINGLE  SHAPE  ROUTINE  CAN  I  HI N  bl  USED  TO 
DRAW  HORIZONTAL'  OR  'VERTICAL  VERSIONS 
OF  AN  OBJECT 

THE  FOLOWING  IS  A  BASIC  SUBROUTINE 
LOU I  VALENT  TO  EXPLAIN  ITS  OPERATION 

NOTE  THAT  LABELS  ARE  USED  IN  PL  ALE  UF 
LINE  NUMBERS 

PTPI  OT  IF  HVFLAG <0  THEN  GOlfJ  NOROT  ' 

(STACF  )  “XREL  :  XRCl.-YIvCl.s  YREL-  (STACl  > 
(STACK ) -XSHDt  XSHD-YSHD; YSMD- < STACK > 
NOROT '  GOSUB  GETZ' 

REM  CALCULATE  2*Z  FROM  X,Y  AIID  RADIUS 
HEMI  -  I 

IF  XREL.CLIF'L  THEN  GOTO  RMCMI 


00146 

C628 

}  ZWX-*2»Z~  XSHD 

00147 

C628 

;  '  XF'll -XCENT- XREI  :  REM  LEFT  HEMISPHERE 

00148 

C628 

;  ' CHCl.UF' '  IF  YREL  -CLIPU  THEN  GOIO  DHFNI 

00149 

C628 

j  TONF-  =  ZWX  ♦YSMD 

00150 

C628 

;  GOSUB  GETVAL:  REM  NORMAI  1 7E  SHADE  VAI. 

00151 

C628 

1  YPL  T-YCCNT  •  YFtEL 

00152 

C620 

;  GOSUB  '  PLTSMD  '  :  REM  PLOT  OR  IINI  I  Ol 

00153 

C628 

;  REM  POINTS  WEIGHTED  BY  SHADE  VAI  LIE 

00154 

C628 

S  DHEMI'  IF  YREL  .'CL  I  PD  THEN  GOTO  RHEMI 

00155 

C628 

»  TONT-*ZWX  -  YSMD 

00136 

C628 

5  GOSUB  GETVAL 

00157 

C628 

1  YPL T-YCENT- YREL 

00138 

C628 

j  GOSUB  'PLTSMD' 

00159 

C628 

j  ’RHEMI'  IF  IIEMI“0  THEN  RETURN 

00160 

C628 

;  HCMI -0 

00161 

C628 

{  IF  XREL ^ CL I PR  THEN  RETURN 

00162 

C628 

j  ZWX =2*  2 ♦ XSHD 

00163 

C62B 

1  XF"L  T -XCEN  T ♦ XREL 

00164 

C628 

;  GOSUB  ' CMCLUP ' 

00165 

C62B 

;  RL  TURN 

00166 

C628 

1 

00167 

C628 

2C 

83 

03 

PTPLOT  BIT  HVFLAG 

00168 

C62B 

10 

2D 

BPL  NOROT 

00169 

C62D 

M 

6C 

03 

LDA  XREI. 

00170 

C630 

48 

F'HA 

00171 

C631 

4  U 

PHA 

00172 

C632 

AD 

70 

03 

LDA  YREL 

00173 

C633 

8D 

6C 

03 

STA  XREL 

00174 

C63B 

68 

PLA 

00173 

C639 

8D 

70 

03 

STA  YREL 

00176 

C63C 

AD 

60 

03 

LDA  XSHD 

00177 

C63F 

PHA 

00178 

C640 

48 

PHA 

00179 

C64  1 

AD 

71 

03 

LDA  YSHD 

00180 

C644 

80 

60 

03 

STA  XSHD 

00181 

C647 

68 

FLA 

00182 

C648 

BD 

71 

03 

STA  YSHD 

00183 

C648 

AO 

6E 

03 

LDA  XSHD* 1 

00184 

C64E 

4U 

PHA 

00183 

C64F 

4B 

PHA 

00186 

C650 

AO 

72 

03 

LDA  YSHD* 1 

00187 

C6S3 

80 

6E 

03 

STA  XSHD* 1 

00188 

C6S6 

68 

PI.  A 

00189 

C657 

8D 

72 

03 

SI A  YSHD* 1 

00190 

C65A 

20 

45 

C7 

NOROT  JSR  GETZ 

00191 

C65D 

A9 

01 

PTPLT2  LDA  4*01 

00192 

C65F 

BD 

81 

03 

STA  HEMI 

00193 

C662 

38 

SEC 

00194 

C663 

AD 

7D 

03 

LDA  CLIPL  »  CHECK  IEFT  HEMISPHERE 

00193 

C666 

CD 

6C 

03 

CMP  XREL 

00196 

C669 

90 

7D 

BCC  RHEMI 

00197 

C66B 

'  B 

SEC 

00198 

C66C 

AD 

3C 

03 

LDA  ROOT 

00199 

C66F 

ED 

6D 

03 

SBC  XSHD 

00200 

C672 

OD 

75 

03 

STA  ZWX 

00201 

C673 

AD 

3D 

03 

LDA  ROOT ♦ 1 

00202 

C678 

EO 

6E 

03 

SBC  XSHD* 1 

00203 

C67B 

80 

76 

03 

STA  ZWX*  1 

00204 

C67E 

38 

SEC 

00203 

C67F 

BD 

6A 

03 

LDA  XCENT 

00206 

C682 

t  D 

6C 

03 

SBC  XREL 

00207 

C685 

8D 

3F 

03 

STA  XPLT 

00208 

C688 

AD 

6B 

03 

LDA  XCENT* 1 

00209 

C68B 

E9 

00 

SBC  4400 

00210 

C6BD 

BD 

40 

03 

STA  XPLT ♦ 1 

0021  1 

C690 

1 

00212 

C690 

38 

CMCLUP  SEC 

00213 

C69 1 

M 

7F 

03 

IDA  CL  I PU  |  CHECK  FOR  UP  CLIPPING 

00214 

C694 

CD 

70 

03 

CMP  YREL 

00213 

C697 

90 

23 

BCC  DHEMI 

00216 

C699 

IB 

CL  C 

00217 

C69A 

80 

73 

03 

LDA  ZWX 

00218 

C69D 

6D 

71 

03 

ADC  YSHD 

00219 

C6A0 

8D 

79 

03 

STA  TONE 

00220 

C6A3 

AO 

76 

03 

LDA  2WX*1 

00221 

C6A6 

*0 

72 

03 

ADC  YSHD* 1 

00222 

C6A9 

BD 

7A 

03 

SI  A  T ONC  *  1 

00223 

C6AC 

20 

FI 

C“ 

JSR  GETVAL 

00224 

C6AF 

18 

CLC 

00223 

C6B0 

AD 

6F 

03 

LDA  YCENT 

00226 

C6B3 

6D 

70 

03 

ADC  YREL 

00227 

C6B6 

8D 

41 

03 

SI  A  YF'LT 

00228 

C6B9 

20 

0F 

C2 

JSR  PLTSMD 

00229 

C6BC 

1 

00230 

C6BC 

38 

DHEMI  SEC 

00231 

C6BD 

AD 

80 

03 

LDA  CLIF'D  l  CHECK  FOR  DOWN  CLIPPING 

00232 

C6C0 

CD 

70 

03 

CMP  YREL 

00233 

C6C3 

94 

23 

BCC  RHEMI 

00234 

C6C3 

38 

SEC 

00233 

C6C6 

AD 

75 

03 

LDA  ZWX 

00236 

C6C9 

ED 

71 

03 

SBC  YSHD 

00237 

C6CC 

8D 

79 

03 

STA  TONE 

00238 

C6CF 

AO 

76 

03 

1  DA  ZWX«t 

00239 

C6D2 

ED 

72 

03 

SBC  YSHD* 1 

00240 

C6D5 

BD 

7A 

03 

STA  T ONE* 1 

0024  1 

C6D8 

20 

FI 

CS 

JSR  GETVAL 

00242 

C6DB 

38 

SEC 

00243 

C6DC 

AD 

6F 

03 

LDA  YCENT 

0024  4 

C6DF 

ED 

70 

03 

SBC  YRCL 

00245 

C6E2 

BD 

41 

03 

STA  YPLT 

00246 

C6E5 

20 

OF 

C2 

JSR  PLTSMD 

00247 

C6E8 

1 

00248 

C6E8 

AD 

81 

03 

RHEMI  LDA  lirMI 

00249 

C6EB 

F0 

34 

BEO  PL DONE 

00230 

C6ED 

CE 

81 

03 

DEC  HEMI 

00231 

C6F0 

38 

SEC 

00232 

C6F1 

AD 

7E 

03 

LDA  Cl.  I  PR  1  CHECK  FOR  RIGHT  CLIPPING 

00253 

C6F4 

CD 

6C 

03 

CMP  XREL 

00234 

C6F  7 

90 

28 

BCC  PLDONE 

00233 

C6F9 

18 

CLC 

00236 

C6FA 

AD 

3C 

03 

LDA  ROOT 

00237 

C6FD 

6D 

6D 

03 

ADC  XSHD 

00258 

C700 

BD 

73 

03 

STA  ZWX 

00259 

C703 

AD 

3D 

03 

LDA  ROOT ♦ 1 

00260 

C706 

60 

6E 

03 

ADC  XSHD* 1 

00261 

C709 

BD 

76 

03 

STA  ZWX*1 

00262 

C70C 

ia 

CLC 

00263 

C70D 

AD 

6A 

03 

LDA  XCENT 

00264 

C710 

6B 

6C 

03 

ADC  XREL 

00265 

C7I3 

8D 

3F 

03 

STA  XPLT 

00266 

C716 

AD 

cb 

03 

LDA  XCENT* 1 

00267 

C719 

69 

00 

ADC  4*00 

00268 

C71B 

BD 

40 

03 

STA  XPLT ♦ 1 

00269 

C71E 

4C 

70 

C6 

JMP  CMCLUP 

00270 

C721 

2C 

83 

03 

PLDONE  BIT  HVFLAG 

00271 

C724 

10 

IE 

BPL  NORSTR 

(Continued  on  page  72) 


70 


006 


Dr.  Dobb’ s  Journal,  May  1985 


Drawing  on  the  C-64  (Listing  Continued,  text  begins  on  page  50) 
Listing  Four 


00272  C  726 
00273  C729 

00274  C72C 
00273  C72D 
00276  C730 

00277  C733 

00270  C736 

00279  C737 

00280  C73A 
0020 1  C730 

00202  C740 

00283  C74 1 

00204  C744 

00283  C743 

00286  C743 

00287  C743 

00288  C745 

00209  C743 

00290  C745 

00291  C743 

00292  C74B 
00293  C74A 
00294  C74D 
00295  C750 

00296  C  752 
00297  C733 

00290  C750 

00299  C75A 
00300  C75D 
00301  C75E 
00302  C761 

00303  C  763 
00304  C766 

00303  C769 

00306  C766 

00307  C76E 
00308  C77 1 

00309  C773 

00310  C776 

00311  C 777 
00312  C77A 
00313  C77C 
00314  C77E 
00313  C78 1 

00316  C783 

00317  C7Q5 
00310  C707 

00319  C70A 
00320  C780 

00321  C790 

00322  C79 1 

00323  C793 

00324  C796 

00323  C799 

00326  C79A 
00327  C79A 
00320  C79A 
00329  C79A 
00330  C79A 
00331  C79A 
00332  C79A 
00333  C79A 
00334  C79D 
00333  C79E 
00336  C7A 1 
00337  C7A2 
00330  C7A3 
00339  C7A0 
00340  C7A9 
00341  C7AC 
00342  C7AF 
00343  C762 

00344  C764 

00343  C787 

00346  C789 

00347  C7RC 
00340  C7BE 
00349  C7C1 
00330  C7C3 
00331  C7C6 
00332  C7C7 
00353  C7C7 
00334  C7C7 
00353  C7C7 
00336  C7C7 
00337  C7C7 
00330  C7C7 
00339  C7C7 
00360  C7C7 
00361  C7C7 
00362  C7C7 
00363  C7C7 
00364  C7C7 
00363  C7C7 
00366  C7C7 
00367  C7C7 
00368  C7C7 
00369  C7C7 
00370  C7C7 
00371  C7C7 
00372  C7C7 
00373  C7C7 
00374  C7C7 
00373  C7CA 
00376  C7CC 
00377  C7CF 
00378  C7D1 
00379  C703 

00380  C7D3 
00301  C7D7 
00382  C709 

00383  C7DB 
00304  C7DE 
00583  C7E 1 
00386  C7E4 
00387  C7E7 
00388  C7EA 
00389  C7EC 
00390  C7EF 
00391  C7F2 
00392  C7F5 
00393  C7F0 
00394  C7FA 
00395  C7FD 
00396  C800 

00397  C802 

00398  CB05 
00399  C808 

00400  C80B 


AO  6E  03 
80  72  03 
68 

0D  6E  03 
AD  6D  03 
80  71  03 
68 

8D  60  03 
AD  6C  03 
80  70  03 
68 

80  6C  03 
60 


AD  77  03 
85  AC 
20  04  C0 
8D  7C  03 
AS  AE 
80  7B  03 
AD  6D  03 
03  AC 
20  04  C0 
38 

AD  7B  03 
E5  AE 
80  7B  03 
AD  7C  03 
E5  AF 
00  7C  03 
AD  71  03 
85  AC 
20  04  C0 
38 

AO  7B  03 
E3  AE 
85  AC 
AO  7C  03 
E5  AF 
85  AD 
30  0A 
20  64  C0 
0E  3C  03 
2E  30  03 
60 

A9  00 
00  3C  03 
BD  30  03 
60 


AO  BE  03 
38 

ED  0F  03 

4A 

BD  BC  03 
80  77  03 
18 

60  8F  03 
8D  80  03 
AD  BC  03 
85  AC 
20  04  C0 
A5  AE 
BD  8A  03 
A3  AF 
8D  8B  03 
A9  00 
80  06  03 
60 


AD  77  03 
85  AC 
20  04  C0 
06  AE 
26  AF 
AS  AE 
83  AC 
A3  AF 
85  AD 
20  64  C0 
4E  3D  03 
6E  3C  03 
AD  3C  03 
80  92  03 
A9  00 
8D  86  03 
80  6C  03 
BD  72  03 
AD  77  03 
85  AC 
20  04  C0 
80  85  03 
A5  AE 
80  84  03 
AD  86  03 
80  87  03 
85  AC 


LDA 
STA 
PL  A 
STA 
LDA 
STA 
PLA 
STA 
LOA 
STA 
FLA 
STA 

NORSTR  RTS 


XSHD* 1 
YSHD* 1 

XSHD*1 

XSHO 

YSHD 

XSHO 

XREL 

YREL 

XREL 


RESTORE  COORDS 


CALCULATE  2  FROM  LOCAL  X,Y  BY 
PYTHAGOREAN  SUM 


GET2  LDA  RADIUS 
STA  ARG 
JSR  SQUARE 
STA  TNTMP* 1 
LDA  SCR 
STA  TNTMP 
LDA  XSHD 
STA  ARG 
JSR  SQUARE 
SEC 

LDA  TNTMP 
SBC  8QR 
STA  TNTMP 
LDA  TNTMP*- 1 
SBC  SDR* 1 
STA  TNTMP* 1 
LDA  YSHD 
STA  ARG 
JSR  SQUARE 
SEC 

LDA  TNTMP 
SBC  SOR 
STA  RAOCND 
LDA  TNTMP* 1 
SBC  SQR* 1 
STA  RAOCND* 1 
BMl  ZEROOT 
JSR  SORT 
ASL  ROOI 
ROL  ROOT* l 
RTS 

ZEROOT  LOA  «<00 
STA  ROOT 
STA  RUO T *  1 
RTS 


SET  UP  PARAMETERS  FOR  TOROIDS 


RT-(R0-RI>/2  RS-RT  »R  T  RC-*RT*RI 


TPARM 


LDA  RO 
SEC 
SBC  R I 
LSR  A 
STA  RT 
STA  RADIUS 
CLC 
ADC  R I 
STA  RC 
LDA  RT 
STA  ARG 
JSR  SQUARE 
LDA  SQR 
STA  RS 
LDA  SQR* 1 
STA  RS* 1 
LDA  *0 
STA  CNTX 
RTS 


;  DRAW  A  SHADED  SPHERE 

;  BASIC  SUBROUTINE-  EQUIVALENT 

5  SPHERE-  FOR  CNTX-0  TO  RAD IUS/SQR ( 2) 

,  XREl  =CNTX:  XSHD-CNTX 

;  FOR  CNTY-CNTX  TO  SQR <RAD»RAD-CNT X*CNTX ) 

j  YREL-CNTYi YSHD-CNTY 

,  HVFLAG-0 

I  GOSUB  " PTPLOT " 

l  REM  EXCHANGE  X  (.  Y  TO  USE  8-FOLD  SYM 

I  HVFLAG— 128 

I  GOSUB  PTPLOT- 

;  NEXT  CNTY 

;  NEXT  CNTX 

f  RETURN 


SPHERE  LDA  RADIUS 
STA  ARG 
JSR  SQUARE 
ASL  SOR 
ROL  SQR* 1 
LDA  SOR 
STA  RADCND 
LDA  SOR* 1 
STA  RADCND* 1 
JSR  SORT 
LSR  ROOT ♦ 1 
ROR  ROOT 
LDA  ROOT 
STA  XMAX 
LDA  4100 
STA  CNTX 
STA  XSHD* 1 
STA  YSHD* 1 
LDA  RADIUS 
STA  ARG 
JSR  SQUARE 
STA  TEMP* l 
LDA  SQR 
STA  TEMP 
LOOPX  LDA  CNTX 
STA  CNTY 
STA  ARG 


00401  C80D 
00402  C810 

00403  C813 

00404  C816 

00405  C817 

00406  C81A 
00407  C81C 
00408  C81E 
00409  CB2 1 
00410  C823 

00411  C025 

00412  C828 

00413  C82B 
00414  CB2E 
00415  C831 

00416  C034 

00417  C837 

00410  C839 

00419  C83C 
00420  C83F 
00421  C84 1 

00422  C844 

00423  C847 

00424  C84A 
00425  C84D 
00426  C04F 
00427  C852 

00428  CBS3 
00429  C85B 
00430  CBSB 
00431  C83D 
00432  C860 

00433  C863 

00434  CB64 
00433  CB64 
00436  CB64 
00437  C864 

00438  C864 

00439  C864 

00440  C864 

00441  C864 

00442  C864 

00443  C864 

00444  C864 

00443  C864 

00446  CB64 
00447  C864 

00440  C864 

00449  C864 

00450  C864 

00431  C866 

00432  C869 

00433  C86C 
00434  C86F 
00453  CB72 
00456  C873 

00457  C078 

00458  C87B 
00439  C87E 
00460  C801 

00461  C884 

00462  C887 

00463  C889 

00464  C88C 
00463  C88E 
00466  C88F 
00467  C88F 
00468  C88F 
00469  C8QF 
00470  C88F 
00471  C88F 
00472  C88F 
00473  C88F 
00474  C88F 
00473  C88F 
00476  C80F 
00477  C8BF 
00478  C88F 
00479  C88F 
00480  C88F 
00481  C88F 
00482  CBBF 
00483  CB0F 
00484  CBBF 
004B5  C88F 
00486  C092 

00487  C894 

00488  CB97 
00489  C89A 
00490  C89D 
00491  C8A0 
00492  C8A3 
00493  CBA3 
00494  C8A8 
00493  CBA9 
00496  CBAC 
00497  C8AE 
00498  CBB0 
00499  C8B3 
00500  C8B3 
00501  C8B7 
00502  C8BA 
00503  C8BD 
00304  CBC0 
00503  CSC  1 
00506  C8C4 
00507  CSC 7 
00508  C8C9 
00509  C8CC 
00510  C8CF 
00511  C8D2 
00312  CBD4 
00313  CBD7 
00314  C8D9 
00513  C8DC 
00516  C8DE 
00317  C8E0 
00518  C0E2 
00519  C8E5 
00520  C8E7 
00321  C8EA 
00522  C8EC 
00523  C8EF 
00524  C8F2 
00323  CBF3 
00526  C8F8 
00527  C8FA 


BD  6C  03 
80  6D  03 

20  04  C0 

38 

AD  84  03 
ES  AE 
85  AC 
AD  85  03 
E5  AF 
85  AD 
20  64  C0 
AD  3C  03 
BD  88  03 
AD  87  03 
0D  70  03 
8D  71  03 
A9  00 
8D  83  03 
20  28  C6 
A9  80 
8D  83  03 
20  28  C6 
AD  07  03 
CD  88  03 
F0  06 
EE  87  03 
4C  2E  C0 
AD  86  03 
CO  92  03 
F0  06 
EE  86  03 
4C  05  CB 
60 


A9  00 
8D  6D  03 
8D  6E  03 
80  72  03 
AD  77  03 
BD  70  03 
AD  89  03 
8D  6C  03 
AD  70  03 
BD  71  03 
20  28  C6 
CE  6C  03 
10  F8 
CE  70  03 
10  E7 
60 


20  9A  C7 
A9  00 
QD  6E  03 
80  72  03 
AD  86  03 
0D  6C  03 
8D  6D  03 
85  AC 
20  04  C0 
30 

AD  8A  03 
E5  AE 
85  AC 
AO  QB  03 
E3  AF 
85  AD 
20  64  CQ 
AD  3C  03 
BD  Q9  03 
18 

6D  QD  03 
BD  88  03 
A9  00 
BD  87  03 
AD  87  03 
8D  70  03 
85  AD 
AD  89  03 
85  AC 
20  11  C0 
85  FE 
A5  AE 
85  FD 
AD  88  03 
85  FB 
20  EA  C5 
A3  FD 
BD  71  03 
20  28  C6 
AD  87  03 
CD  88  03 
F 0  06 
EE  07  03 


LOOPY 


DONEY 


DONE 


STA  XREL 
STA  XSHD 
JSR  SQUARE 
SEC 

LDA  TEMP 
SBC  SOR 
STA  RADCND 
LDA  TEMP* 1 
SBC  SQR* 1 
STA  RADCND* 1 
JSR  SORT 
LDA  ROOT 
STA  MAX 
LDA  CNTY 
STA  YREL 
STA  YSHD 
LDA  H0 
STA  HVFLAG 
JSR  PTPLOT 
LDA  <1480 
STA  HVFLAG 
JSR  PTPLOT 
LDA  CNTY 
CMP  MAX 
BEQ  DONEY 
INC  CNTY 
JMP  LOOPY 
LDA  CNTX 
CMP  XMAX 
BEQ  DONE 
INC  CNTX 
JMP  LOOPX 
RTS 


DRAW  SHADED  CYLINDERS 
BASIC  SUBROUTINE’  EQUIVALENT 


XSHD-0 

FOR  YREL-RADIUS  TO  0 
YSHD-YREL 

FOR  XREL-HLEN  TO  0 
GOSUB  • PTPLOT ' 

NEXT  XREL 
NEXT  YREL 
RETURN 


CYLNDR  LDA 
STA 
STA 
STA 
LDA 
STA 

CYLOOP  LDA 
STA 
LDA 
STA 

CXLOOP  JSR 
DEC 
BPL 
DEC 
BPL 
RTS 


«0 

XSHD 

XSHD* 1 

YSHD* 1 

RADIUS 

YREL 

HLEN 

XREL 

YREL 

YSHD 

PTPLOT 

XREL 

CXLOOP 

YREL 

CYLOOP 


DRAW  EDGE-VIEW  TOROIDS 
BASIC  SUBROUTINE'  EQUIVALENT 


" EDGTOR "  GOSUB  ' TPARM ' i REM  SET  UP  RADII 
FOR  CNTX-0  TO  RT 
XREL-CNTX i XSHD-CNT  X 
R0-SQR (RT  «RT-CNTX«CNTX ) 

FOR  CNTY-0  TO  R0*RC 
YREL-CNTY 

YSHD-  (R0**CNTY >  /  <R0*RC> 

GOSUB  PTPLOT- 
NEXT  CNTY 
NEXT  CNTX 
RETURN 


EDGTOR  JSR  TPARM 
LDA  <1400 
STA  XSHD* 1 
STA  YSHD* 1 
LOOPX4  LDA  CNTX 
STA  XREL 
STA  XSHD 
STA  ARG 
JSR  SQUARE 
SEC 

LDA  RS 
SBC  SQR 
STA  RADCND 
LDA  RS* 1 
SBC  SOR* l 
STA  RADCND* 1 
JSR  SORT 
LDA  ROOT 
STA  R0 
CLC 
ADC  RC 
STA  MAX 
LDA  N400 
STA  CNTY 
L00PY4  LDA  CNTY 
STA  YREL 
STA  MLF'LER 
LDA  R0 
STA  MLPCND 
JSR  MULT 
STA  DVDND* 1 
LDA  PROD 
STA  DVDND 
LDA  MAX 
STA  DVSOR 
JSR  SDIV 
LDA  QUOT 
STA  YSHD 
JSR  PTPLOT 
LDA  CNTY 
CMP  MAX 
BEQ  DONE 4 
INC  CNTY 


(Continued  on  page  74) 


72 


Dr.  Dobb’s  Journal,  May  1985 


Drawing  on  the  C-64  (Listing  Continued,  text  begins  on  page  50) 
Listing  Four 


00328  CBFD 
00329  C900 

00330  C903 

0033 1  C906 

00332  C908 

00333  C90B 
00334  C90E 
00535  C90F 
00336  C90F 
00537  C90F 
00338  C90F 
00339  C90F 
00540  C90F 
0034 1  C90F 
00342  C90F 
00343  C90F 
00344  C90F 
00343  C90F 
00346  C90F 
00347  C90F 
00348  C90F 
00349  C90F 
00350  C90F 
00331  C90F 
00332  C90F 
00333  C90F 
00534  C90F 
00333  C90F 
00356  C90F 
00337  C90F 
00338  C90F 
00339  C90F 
00360  C90F 
00361  C90F 
00362  C90F 
00363  C90F 
00364  C912 

00363  C913 

00366  C917 

00367  C91A 
00368  C9tC 
00369  C91E 
00370  C920 

0057  V  C922 
00372  C924 

00573  C926 

00374  C929 

00373  C92C 
00376  C92F 
00577  C932 

00578  C935 

00379  C938 

00380  C938 

00501  C93D 
00382  C940 

00383  C943 

00304  C943 

00383  C948 

00306  C94B 
00587  C940 

00380  C950 

00309  C951 

00590  C933 

00391  C956 

00392  C938 

00593  C93A 
00394  C93D 
00393  C95F 
00596  C962 

00397  C963 

00398  C968 

00599  C969 

00600  C96C 
00601  C96F 
00602  C97 I 

00603  C974 

00604  C976 

00603  C979 

00606  C97A 
00607  C97C 
00608  C97F 
00609  C981 

00610  C983 

00611  C986 

00612  C988 

00613  C9BB 
00614  C90E 
00613  C99 1 

00616  C994 

00617  C997 

00618  C99A 
00619  C99D 
00620  C9A0 
00621  C9A2 
00622  C9A3 
00623  C9A6 
00624  C9AB 
00625  C9AB 
00626  C9AD 
00627  C9AF 
00628  C9B2 
00629  C9B4 
00630  C9B7 
00631  C9BA 
00632  C9BD 
00633  C9BF 
00634  C9C2 
00633  C9C4 
00636  C9C7 
00637  C9C9 
00638  C9CC 
00639  C9CE 
00640  C9O0 
00641  C9D2 
00642  C903 

00643  C9D6 
00644  C9D9 
00643  C9DB 
00646  C90E 
00647  C9E0 
00648  C9E2 
00649  C9E5 
00630  C9E8 
00631  C9EA 
00632  C9ED 
00653  C9EF 
00634  C9F2 
00633  C9F4 
00636  C9F6 


4C  CC  CB 
AD  86  03 
CD  8C  03 
F  0  06 
EE  86  03 
4C  9A  CB 
60 


20  9A  C7 
AD  8E  03 
83  AC 
20  04  C0 
06  AE 
26  AF 
A3  AE 
85  AC 
AS  AF 
85  AD 
20  64  C0 
4E  3D  03 
6E  3C  03 
AD  3C  03 
8D  92  03 
AD  06  03 
8D  6C  03 
83  AC 
20  04  C0 
BD  91  03 
A3  AC 
QD  90  03 
AD  8E  03 
85  AC 
20  04  C0 
38 

A5  AE 
ED  90  03 
85  AC 
A3  AF 
ED  91  03 
83  AD 
20  64  C0 
AD  3C  03 
BD  80  03 
38 

AD  BF  03 
ED  86  03 
90  23 
AO  BF  03 
83  AC 
20  04  C0 
38 

AS  AE 
ED  90  03 
85  AC 
A3  AF 
ED  91  03 
85  AD 
20  64  C0 
AD  3C  03 
BD  87  03 
4C  9A  C9 
AD  86  03 
8D  87  03 
AD  87  03 
8D  70  03 
85  AC 
20  04  C0 
18 

AS  AE 
6D  90  03 
85  AC 
A5  AF 
6D  91  03 
83  AD 
20  64  C0 
AD  3C  03 
BD  89  03 
85  FB 
AD  86  03 
85  AD 
AD  8D  03 
83  AC 
20  It  C0 
85  FE 
A5  AE 
85  FD 
20  EA  C3 
38 

AO  86  03 
E5  FD 
BD  6D  03 
A9  00 
ES  FE 
BD  6 E  03 
AD  87  03 
85  AD 
AD  8D  03 
85  AC 
20  It  C0 
85  FE 
AS  AE 
85  FD 


JMP  LOOPY 4 
DONE 4  LDA  CNTX 
CMP  RT 
BED  DONEHT 
INC  CNTX 
JMP  LOOPX4 
DONEHT  RTS 


I  DRAM  A  SHADED,  TOP-VIEW  TOROID 
;  BASIC  SUBROUTINE-  EQUIVALENT 

;  TOROID'  GOSIJB  "  TPARM " 
j  FOR  CNTX-0  TO  RO/SOR(2> 

t  REM  8-FOLD  SYMMETRY  USED 

{  XREL-CNTX 

t  MAX  *-SQR  <RO*RO-CNT X  »CNTX ) 

;  IF  CNTX  >RI  THEN  GOTO  ' GRTR " 

S  CNTY-SQR<RI*RI-CNTY«CNTY> 

1  GOTO  ‘ LLPY 1 

t  GRTR-  CNTY-CNTX 

|  "LLPY1 •  YREL-CNTY 

I  R0-SQR  <CNTY*CNTY*CNTX*CNTX  > 

I  XSHD-CNTX-<CNTX«RC>/R0 

I  YSHD-CNTY- <CNTY*RC ) /R0 

I  HVFLAG-01 GOSUB  PTPLOT' 

j  HVFLAG— 128 1  GOSUB  PTPLOT- 

j  IF  CNTY-MAX  THEN  GOTO  * DDNY 1 1 

I  CNTY-CNTY+1 

I  GOTO  "LLPY1 " 

1  ' DDNY l ’  NEXT  CNTX 

I  RETURN 

TOROID  JSR  TPARM 

LDA  RO 

STA  ARG 

JSR  SQUARE 

ASL  SQR 

ROL  SQR*’  1 

LDA  SOR 

STA  RADCND 

LDA  SOR* 1 

STA  RADCND* 1 

JSR  SORT 

LSR  ROOT ♦ 1 

ROR  ROOT 

LDA  ROOT 

STA  XMAX 

LLPX1  LDA  CNTX 

STA  XREL 

STA  ARG 

JSR  SQUARE 

STA  XSQR* 1 

LDA  SQR 

STA  XSQR 

LDA  RO 

STA  ARG 

JSR  SQUARE 

SEC 

LDA  SQR 
SBC  XSQR 
STA  RADCND 
LDA  SQR* 1 
SBC  XSQR ► 1 
STA  RADCND *1 
JSR  SORT 
LDA  ROOT 
STA  MAX 
SEC 
LDA  R  1 
SBC  CNTX 
BCC  GRTR 
LDA  R1 
STA  ARG 
JBR  SQUARE 
SEC 

LDA  SQR 
SBC  XSQR 
STA  RADCND 
LDA  SOR* 1 
SBC  XSQR* l 
STA  RADCND*! 

JSR  SORT 
LDA  ROOT 
STA  CNTY 
JMP  LLPY 1 
GRTR  LDA  CNTX 
STA  CNTY 
LLPY l  LDA  CNTY 
STA  YREL 
STA  ARG 
JSR  SQUARE 
CLC 

LDA  SQR 
ADC  XSQR 
STA  RADCND 
LDA  SQR* 1 
ADC  X  SQR ♦ 1 
STA  RADCND* 1 
JSR  SORT 
LDA  ROOT 
STA  R0 
STA  DVSOR 
LDA  CNTX 
STA  MLFLER 
LDA  RC 
STA  HLPCND 
JSR  MULT 
STA  DVDND* 1 
LDA  PROD 
STA  DVDND 
JSR  SDI V 
SEC 

LDA  CNTX 
SBC  QUOT 
STA  X5H0 
LDA  4400 
SBC  OUOT  *  1 
STA  XSHD* 1 
LDA  CNTY 
STA  MLFLER 
LDA  RC 
STA  MLPCND 
JSR  MULT 
STA  DVDND* I 
LDA  PROD 
STA  DVDND 


00637  C9F8  AD 
00638  C9FB  83 
00659  C9FD  20 
00660  CA00  38 
00661  CA0 1  AD 
00662  CA04  E3 
00663  CA06  8D 
00664  CA09  A9 
00663  CA0B  8D 
00666  CA0E  ES 
00667  CA10  BD 
00668  CA1 3  20 

00669  CA1 6  A9 
00670  CA18  BD 
00671  CA1B  20 
00672  CA1E  AD 
00673  CA2 1  CD 
00674  CA24  F0 
00675  CA26  EE 
00676  CA29  4C 
00677  CA2C  AD 
00678  CA2F  CD 
00679  CA32  F0 
00680  CA34  EE 
00681  CA37  4C 
00682  CA3A  60 
00683  CA3B 
00604  CA3B 
00683  CA3B 
00686  CA3B 
00687  CA3B 
00688  CA3B 
00689  CA3B 
00690  CA3B 
00691  CA3B 
00692  CA3B 
00693  CA3B 
00694  CA3B 
00695  CA3B 
00696  CA3B 
00697  CA3B 
00698  CA3B 
00699  CA3B 
00700  CA3B 
00701  CA3B 
00702  CA3B  20 
00703  CA3E  AD 
00704  CA4 1  8D 
00703  CA44  83 
00706  CA46  38 
00707  CA47  A9 
00708  CA49  ED 
00709  CA4C  BD 
00710  CA4F  A9 
00711  CA51  E9 
00712  CA33  BD 
00713  CA36  20 
00714  CA59  38 
80713  CASA  AD 
00716  CA3D  ES 
00717  CA5F  85 
00718  CA6 1  AO 
00719  CA64  ES 
00720  CA66  85 
00721  CA68  20 
00722  CA6B  38 
00723  CA6C  AD 
00724  CA6F  ED 
00723  CA72  8D 
00726  CA75  A9 
00727  CA77  BD 
00728  CA7A  AD 
00729  CA7D  8D 
00730  CA80  05 
00731  CA82  AO 
00732  CABS  83 
00733  CA87  20 
00734  CABA  85 
00735  CA8C  A3 
00736  CA8E  85 
00737  CA90  AD 
00738  CA93  83 
00739  CA93  20 
00740  CA98  A3 
00741  CA9A  38 
00742  CA9B  ED 
00743  CA9E  8D 
00744  CAA1  A3 
00743  CAA3  E9 
00746  CAA3  80 
00747  CAA8  20 
00748  CAAB  AD 
00749  CAAE  CD 
00750  CAB  1  F  0 
00731  CAB3  EE 
00752  CAB6  4C 
00753  CAB9  AD 
00754  CABC  CD 
00753  CABF  F0 
00756  CACt  EE 
00757  CAC4  4C 
00738  CAC7  60 
00759  CAC8 

ERRORS  -  00000 
SYMBOL  TABLE 

SYMBOL  VALUE 
ARG  00AC 

CLIPL  037D 

SYMBOL  TABLE 

SYMBOL  VALUE 
CNTX  0386 

CYLOOP  C873 

DIVIDE  C023 

DONEY  CSSS 

DVSOR  00FB 

GRTR  C994 

HVFLAG  0383 

LLPY2  CA7A 

LOOPY4  C8CC 

MULT  ceu 


89  03 
FB 

EA  CS 

87  03 
FD 

71  03 

00 

83  03 
FE 

72  03 
28  C6 
80 

83  03 
28  C6 

87  03 

88  03 
06 

87  03 
9A  C9 
86  03 
92  03 
06 

86  03 
33  C9 


LDA  R0 
STA  DVSOR 
JSR  SDI V 
SEC 

LDA  CNTY 
SBC  QUOT 
STA  YSHD 
LDA  4400 
STA  HVFLAG 
SBC  OUOT ♦ 1 
STA  YSHD* 1 
JSR  PTPLOT 
LDA  4480 
STA  HVFLAG 
JSR  PTPLOT 
LDA  CNTY 
CMP  MAX 
BEQ  DDNY1 
INC  CNTY 
JMP  LLPY I 
DDNY l  LDA  CNTX 
CMP  XMAX 
BEQ  DUNTOR 
INC  CNTX 
JMP  LLPX1 


j  DRAW  "INSIDE  VIEW"  TOROIDS 
I 

J  'BASIC  SUBROUTINE'  EQUIVALENT 
I 

1  SPOOL-  GOSUB  TPARM 
|  FOR  CNTX-0  TO  RT 

j  XREL-CNTX «  XSHD-CNTX 

1  MAX-RC-SOR (RS-CNTX*CNTX) 

;  FOR  CNTY-0  TO  MAX 

j  YREL-CNTY 

t  YSHD- (RC«CNTY/MAX> -CNTY 

;  GOSUB  PTPLOT- 

I  NEXT  CNTY 

I  NEXT  CNTX 

1  RE  TURN 

I 

9A  C7  SPOOL  JSR  TPARM 

86  03  LLPX2  LDA  CNTX 

6C  03  STA  XREL 

AC  STA  ARG 

SEC 

00  LDA  4100 

86  03  SBC  CNTX 

6D  03  STA  XSHD 

00  LDA  4400 

00  SBC  4400 

6E  03  STA  XSHD* l 

04  C0  JSR  SQUARE 

SEC 

BA  03  LDA  RS 


AE 

8R  03 

AF 

AD 

64  C0 

8D  03 
3C  03 

88  03 
00 

87  03 

87  03  LLPY2 

70  03 
AD 

8D  03 
AC 

1  1  CM 
FE 
AE 
FD 

88  03 
FB 

EA  CS 
FD 

87  03 

71  03 
FE 
00 

72  03 
28  C6 

87  03 

88  03 
06 

87  03 
7A  CA 
86  03 
8C  03 
06 

86  03 
3E  CA 


SBC  SQR 
STA  RADCND 
LDA  RS* 1 
SBC  SQR* 1 
STA  RADCND*! 
JSR  SORT 
SEC 

LDA  RC 
SBC  ROOT 
STA  MAX 
LDA  4400 
STA  CNTY 
LDA  CNTY 
STA  YREL 
STA  MLFLER 
LDA  RC 
STA  MLPCND 
JSR  MULT 
STA  DVDND* 1 
LDA  PROD 
STA  DVDND 
LDA  MAX 
STA  DVSOR 
JSR  SO IV 
LDA  OUOT 
SEC 

SBC  CNTY 
STA  YSHD 
LDA  QUOT  *  1 
SBC  4400 
STA  YSHD* l 
JSR  Pit  LOT 
LDA  CNTY 
CMP  MAX 
BEQ  DDNY2 
INC  CNTY 
JMP  LLPY2 
LDA  CNTX 
CMP  RT 
BEQ  DUNHSP 
INC  CNTX 
LLPX2 


DDNY2 


JMP 

DUNHSP  RTS 
.END 


BAtLIT  0382 
CUPR  037E 


CHCLUP  C690 
CL1PU  037F 


CL  I PD  0380 
CNTNU  C60U 


CNTY  0387 
DDNY 1  CA2C 
DONE  C863 
DUNHSP  CAC7 
EDGTOR  C88F 
HEM I  0381 
LLPX1  C935 
LOOPX  C803 
MAX  0388 
NEGATE  C3FF 


CXLOOP  CB81  CYLNDR  CB64 
DDNY2  CAB9  DHEMI  C6BC 
DONE 4  C900  DONEHT  C90E 
DUNTOR  CA3A  DVDND  00FO 
GETVAL  C3F1  GETZ  C743 
HLLN  0389  HTORRN  0346 
LLPX2  CA3E  LLPY 1  C99A 
LOOPX 4  CB9A  LOOPY  C82E 
MLPCND  00AC  MLPLER  00AD 
NORM  C224  NOROT  C63A 

End  Listing  Four 


(Listing  Five  begins  on  page  76) 


74 


Dr.  Dobb’s  Journal,  May  1 985 


Drawing  on  the  C-64  (Listing  Continued,  text  begins  on  page  50) 
Listing  Four 


NORSTR  C744  NOSCAL  0347 
PLTSHD  C20F  PROD  00AE 
QUOT  00FD  R0  0389 
RAM  036A  RANDOM  C0C8 
R I  038F  RNDM  C000 
RS  038A  RT  038C 
SPOOL  CA  SB  SQR  00AE 
TEMP  0384  TNTMP  037B 
TPARM  C79A  VALUE  0344 
XPLT  033F  XREL  036C 
YCENT  036F  VPLT  0341 
ZEROOT  C791  ZREL  0373 

END  OF  ASSEMBLY 


ORIGIN  CSEA  PL DONE  C721 
PTPLOT  C628  PTPLT2  C63D 
RADCND  00AC  RADIUS  0377 
RC  038D  RHEMI  C6E0 
RO  03 BE  ROOT  033C 
SDI V  CSEA  SPHERE  C7C7 
SORT  C064  SQUARE  C004 
TONE  0379  TOROID  C90F 
XCENT  036A  XMAX  0391 
XSHD  036D  XSOR  0390 
YREL  0370  YSHD  0371 
ZWX  0373 


Listing  Five 

00001  0000 

00002  0000 
00003  0000 

00004  0000 

00003  0000 

00006  0000 
00007  0000 

00008  0000 
00009  0000 

00010  0000 
00011  0000 
00012  0000 
00013  0000 

00014  0000 

00013  0000 

00016  0000 
00017  0000 

00018  0000 
00019  0000 

00020  0000 
00021  0000 
00022  0000 
00023  0000 

00024  0000 

00023  0000 

00026  0000 
00027  0000 

00028  0000 
00029  0000 

00030  0000 

00031  0000 

00032  0000 

00033  0000 

00034  0000 

00033  0000 

00036  0000 

00037  0000 

00038  0000 

00039  0000 

00040  0000 

00041  0000 

00042  0000 

00043  0000 

00044  0000 

00043  0000 

00046  0000 

00047  0000 

00048  0000 


S  INTERFACE  -  EASY  PARAMETER  SETTING  FOR  SHAPt 
j  DRAWING  ROUTINES  FROM  BASIC. 

I 

I  RICHARD  L.  RYLANDER  11/23/04 


, ••••■•••••••«■••••■•••••■••••••••« •*••«< 

OR  I G I N-4CAC8 
RAM  -10393 
1 

j  PARAMETER  LOCATIONS  FOR  VARIOUS  SHAPES 
1 

XCENT  -*036A 
YCENT  -*036F 
XPLOT  -I033F 
YPl.OT  -*034  1 
XMIN  -*034A 
YMIN  - T034C 
XMID  -4034D 
YMID  -*034F 
XMAX  -10330 
YMAX  -*0352 
RADIUS  -10377 
HLEN  -40389 
R I  -403QF 

RU  -4038E 

HVFLAG  -40383 
VALUE  -40344 
PLTFLG  -4033E 
I 

DEFLAG  -4FB 


;  FUNCTION  LOCATIONS 
I 

GRFON  -4C0E2  |  SWITCH  TO  GRAPHICS  MODE 

GRFOFF  -4C103  ;  RETURN  TO  TEXT  DISPLAY 

S 

CLEARR-4C12C  |  CLEAR  BITMAP 

CLRBYT-4C135  I  CLEAR  (FILL)  BYTE 

C0L0RR-4C 1 18  j  LOAD  COLOR  MAP 

C0LBYT-4C1 19  I  COLOR  BYTE 

I 

PLOTR  -4C14B  1  POINT  PLOT  ROUTINE 

LINER  -4C2DB  j  DRAW  A  LINE 

FACETR  -4C4E1  S  DRAW  A  SHADED  FACET 


00050  0000 

00051  0000 

00052  0000 

00053  0000 

00054  0000 

00055  0000 

00036  0000 

00037  0000 

00058  0000 

00059  0000 

00060  0000 

00061  0000 

00062  0000 

00063  0000 

00064  0000 

00063  0000 

00066  0000 

00067  0000 

00068  0393 

00069  0394 

00070  0394 

00071  CAC8 

00072  CAC8 

00073  CAC8 

00074  CAC8 

00073  CAC8 

00076  CAC8 

00077  CAC8 

00078  CACB 

00079  CAC8 

00080  CAC8 

00081  CAC8  20  FD  AE 

00082  CACB  20  9E  AD 

00003  CACE  20  AA  B1 

00084  CADI 

00085  CADI  60 

00086  CAD2 

00087  CAD2 

00008  CAD2 

00089  CAD2  A9  2C 

00090  CAD4  AO  00 

00091  CAD6  84  FB 

00092  CAD8  01  7A 

00093  CADA  DO  03 

00094  CADC  4C  73  00 

00095  CADF  A0  80 

00096  CAE  1  84  FB 

00097  CAE3  60 

00098  CAE 4 

00099  CAE 4 

00100  CAE 4 

00101  CAE 4  20  D2  CA 

00102  CAE 7  24  FB 

00103  CAE9  30  0F 

00104  CAEB  20  9E  AD 

00105  CAEE  20  AA  B1 

00106  CAF 1  8C  8F  03 

00107  CAF 4  20  C8  CA 

00108  CAF 7  BC  BE  03 

00109  CAF A  60 

00110  CAFB 


I  SHADED  SHAPE  DRAWING  ROUTINES 

» 

SPHERR-4C7C7  j  SPHERE 

CYLNDR-4C864  I  CYLINDER 

TORUSR-4C90F  I  TOP-VIEW  TOROID 

EDGT0R-4C8BF  j  EDGE-VIEW  TOROID 

SP00LR-4CA3B  j  INSIDE-VIEW  TOROID 


{  BASIC  ROM  ROUTINES 
I 

CHKC0M-4AEFD  »  CHECK  FOR  COMMA 

EVAEXP-4AD9E  I  EVALUATE  EXPRESSION 

FLTFIX-4B1AA  I  CONVERT  TO  FIXED 

I 

•  -RAM 

L INF AC  •  -•♦!  I  LINE  OR  FACET  FLAG 

I 

•-ORIGIN 


( 

,  GET  PARAMETERS  FROM  BASIC  CALLING  STATEMENT 
I  OF  THE  FORM j 

j  SYS (FNCTN) , PARAM 1 ,PARAM2,PARAM3COPT3 
I  WHERE  THE  THIRD  PARAMETER  (FOR  EXAMPLE) 

I  MAY  BE  OPTIONAL  (A  DEFAULT  VALUE  IS  USED 
;  IF  THE  PARAMETER  IS  NOT  SPECIFIED) 
i 

GETNUM  JSR  CHKCOM  I  LOOK  FOR  COMMA 

JSR  EVAEXP  j  EVALUATE  EXPRESSION 

JSR  FLTFIX  I  CHANGE  TO  INTEGER  WITH 

f  HIGH  BYTE  IN  “A"  AND  LOW  BYTE  IN  "»“ 

RTS 

s 

I  CHECK  FOR  ADDITIONAL  (OPTIONAL)  PARAMETERS 

PCHECK  LDA  4*2C  1  COMMA 

LDY  »0 
STY  DEFLAG 
CMP  ( 4  7A> , Y 

BNE  NOMORE  |  NO  COMMA  -  USE  DEFAULT 

JMP  *0073 
NOMORE  LDY  tt*80 

STY  DEFLAG 
RTS 
I 

I  GET  TWO  ADDITIONAL  PARAMETERS  FOR  TOROIDS 
I 

GETTWO  JSR  PCHECK 
BIT  DEFLAG 
BMI  DFAULT 
JSR  EVAEXP 
JSR  FLTFIX 
STY  R I 
JSR  GETNUM 
STY  RO 
DFAULT  RTS 
I 


00111  CAFB 

00112  CAFB 
00113  CAFB 
00114  CAFB 

00113  CAFB  20  C8  CA 

00116  CAFE  BC  6A  03 

00117  CB01  BD  6B  03 

00118  CB04  20  C8  CA 

00119  CB07  BC  6F  03 

00120  CB0A  60 

00121  CB0B 

00122  CB0B 
00123  CB0B 

00124  CB0B 

00123  CB0B 
00126  CB0B 
00127  CB0B  20  D2  CA 

00128  CB0E  24  FB 

00129  CB 10  30  07 

00 1 30  CB12  20  9E  AD 

00131  CB13  20  AA  B 1 

00132  CB18  2C 

00133  CB19  A0  00 

00134  CB1B  8C  35  Cl 

00135  CBIE  4C  2C  Cl 

00136  CB2 1 
00137  CB21 
00138  CB21 
00139  CB21 
00140  CB21 

00141  CB21 

00142  CB2 1 

00143  CB21  20  D2  CA 

00144  CB24  24  FB 

00145  CB26  30  07 

00146  CD2B  20  9E  AD 

00147  CB2B  20  AA  B1 

00148  CB2E  2C 

00149  CB2F  A0  01 

00130  CB31  8C  19  Cl 

00151  CB34  4C  18  Cl 

00152  CB37 

00133  CB37 

00134  CB37 

00155  CB37 

00156  CB37 

00137  CB37  A9  00 

00138  CB39  2C 

00159  CB3A  A9  80 

00160  CB3C  BD  3E  03 

00161  CB3F  20  C8  CA 

00162  CB42  BC  3F  03 

00163  CB43  BD  40  03 

00164  CB48  20  C8  CA 

00165  CB4B  BC  41  03 

00166  CB4E  4C  4B  Cl 

00167  CB31 

00168  CB5 1 

00169  CB51 

00170  CB51 

00171  CB31 

00172  CB51 

00173  CBS 1 

00174  CB51  A9  00 

00173  CBS3  2C 

00176  CB54  A9  80 

00177  CB36  8D  93  03 

00178  CB39  20  C8  CA 

00179  CB5C  BC  4A  03 

00180  CBSF  8D  4B  03 

00181  CB62  20  C8  CA 

00182  CB63  8C  4C  03 

00183  CB68  20  CB  CA 

00184  CB6B  8C  4D  03 

00183  CB6E  80  4E  03 

00186  CB7 1  20  C8  CA 

00187  CB74  BC  4F  03 

00188  CB77  2C  93  03 

00189  CB7A  10  18 

00190  CB7C  20  C8  CA 

00191  CB7F  BC  50  03 

00192  CB82  OD  51  03 

00193  CB83  20  C8  CA 

00194  CB88  BC  52  03 

00195  CBBB  20  C0  CA 

00196  CB8E  BC  44  03 

00197  CB91  4C  El  C4 

00198  CB94  4C  DB  C2 

00199  CB97 

00200  CB97 

00201  CB97 

00202  CB97 

00203  CB97 

00204  CB97 

00205  CB97  20  FB  CA 

00206  CB9A  20  02  CA 

00207  CB9D  24  FB 

00208  CB9F  30  09 

00209  CBA 1  20  9E  AD 

00210  CBA4  20  AA  B1 

00211  CBA7  8C  77  03 

00212  CBAA  4C  C7  C7 

00213  CBAD 

00214  CBAD 

00213  CBAD 

00216  CBAD 

00217  CBAD 

00218  CBAD 

00219  CBAD  20  FB  CA 

00220  CBB0  20  E4  CA 

00221  CBB3  4C  OF  C9 

00222  CBB6 

00223  CBB6 

00224  CBB6 

00223  CBB6 

00226  CBB6 

00227  CBB6 

00228  CBB6 

00229  CBB6  A9  80 

00230  CBBB  2C 

00231  CBB9  A9  00 

00232  CBBB  8D  83  03 

00233  CBBE  20  FB  CA 

00234  CBC 1  20  D2  CA 

00235  CBC4  24  FB 

00236  CBC6  30  0F 

00237  CBC8  20  9E  AD 


I 

|  SET  CENTER  COORDINATES 
i 

CENTER  JSR  GETNUM 
STY  XCENT 
STA  XCENT* 1 
JSR  GETNUM 
STY  YCENT 
RTS 


I  CLEAR  THE  BITMAP,  FILLING  WITH  (OPTIONAL) 

|  FILL  VALUE  SPECIFIED  OR  WITH  (DEFAULT)  "0" 
I 

CLEAR2  JSR  PCHECK 
BIT  DEFLAG 
BMI  DEFCLR 
JSR  EVAEXP 
JSR  FLTFIX 
.BYTE  42C 
DEFCLR  LDY  40 

STY  CLRDYT 
JMP  CLEARR 


I  FILL  COLOR  MAP  WITH  (OPTIONAL)  COLOR  BYTE 
I  SPECIFIED  OR  WITH  (DEFAULT)  "*01" 

{  (BLACK  DOTS  ON  WHITE  BACKGROUND) 

I 

C0L0R2  JSR  PCHECK 
BIT  DEFLAG 
BMI  DCFCOL 
JSR  EVAEXP 
JSR  FLTFIX 
.BYTE  *2C 
DEFCOL  LDY  4*01 

STY  COLbYT 
JMP  COLORR 


;  PLOT  OR  UNPLOT  POINTS 
t 

PL0T2  LDA  40 

.BYTE  42C 
UNPLT2  LDA  4*80 

STA  PL1FLG 
JSR  GETNUM 
STY  XPLOT 
STA  XPLOT* 1 
JSR  GETNUM 
STY  YPLOT 
JMP  PLOTR 


;  DRAW  LINES  BETWEEN  (X1,Y1>  AND  (X2.Y2) 
j  OR  SHADED  FACETS  BETWEEN  THREE  POINTS 
1  (XI, Yl),  (X2.Y2)  AND  (X3,Y3) 

I 

LINE 2  LDA  40 

.BYTE  42C 
FACET2  LDA  4*80 

STA  L INF AC 
JSR  GETNUM 
STY  XMIN 
STA  XMIN* 1 
JSR  GETNUM 
STY  YMIN 
JSR  GETNUM 
STY  XMID 
STA  XM I D* 1 
JSR  GETNUM 
STY  YMID 
BIT  LINFAC 
BPL  LDRAW 
JSR  GETNUM 
STY  XMAX 
STA  XMAX ♦ 1 
JSR  GCTNUM 
STY  YMAX 
JSR  GETNUM 
STY  VALUE 
JMP  FACETR 
LDRAW  JMP  LINER 


{  DRAW  A  SPHERE  CENTERED  AT  ( XCENT , YCENT > 
1  DEFAULT  RADIUS  IS  LAST  VALUE  USED 
; 

SPHER2  JSR  CENTER 
JSR  PCHECK 
BIT  DEFLAG 
BMI  SKIP1 
JSR  EVAEXP 
JSR  FLTFIX 
STY  RADIUS 
SKIP1  JMP  SPHERR 


;  DRAW  A  TOP-VIEW  TOROID  AT  ( XCENT , YCENT ) 

{  DEFAULT  INNER  AND  OUTER  RADII  ARE  LAST  USED 
I 

T0RUS2  JSR  CENTER 
JSR  GETTWO 
JMP  TORUSR 


I  DRAW  CYLINDERS  WITH  AXES  HORIZONTAL  OR 
I  VERTICAL.  DEFAULT  RADIUS  AND  "HALF-LENGTH" 
I  ARE  LAST  VALUES  USED. 

I 

VCYL2  LDA  4*80 
.BYTE  *2C 
HCYL2  LDA  40 

STA  HVFLAG 
JSR  CENTER 
JSR  PCHECK 
BIT  DEFLAG 
BMI  SKIP2 
JSR  EVAEXP 


(Continued  on  page  78) 


Dr.  Dobb’s  Journal,  May  1985 


76 


Drawing  on  the  C-64  (Listing  Continued,  text  begins  on  page  50) 
Listing  Five 


00238  CBCB 
00239  CBCE 
00240  CBD1 
00241  CBD4 
00242  CBD7 
00243  CBDA 
00244  CBDA 
00243  CBDA 
00246  CBDA 
00247  CBDA 
00240  CBDA 
00249  CBDA 
00230  CBDA 
00231  CBDC 
002S2  CBDD 
00233  CBDF 
00234  CBE2 
00255  CBE5 
00256  CBE0 
00257  CBEB 
00238  CBEB 
00259  CBEB 
00260  CBEB 
00261  CBEB 
00262  CBEB 
00263  CBEB 
00264  CBEB 
00265  CBED 
00266  CBEE 
00267  CBEB 
00268  CBF3 
00269  CBF6 
00270  CBF9 
00271  CBFC 


20  AA  B 1 
0C  77  03 
20  CB  CA 
8C  89  03 
4C  64  C8 


A9  80 

2C 

A9  00 
8D  83  03 
20  FB  CA 
20  E4  CA 
4C  8F  CB 


A9  80 

2C 

A9  00 
8D  83  03 
20  FB  CA 
20  E4  CA 
4C  3B  CA 


JSR  FLTFIX 
STY  RADIUS 
JSR  GETNUM 
STY  HLEN 

SKIP2  JMP  CYLNDR 


j  DRAW  EDGE-VIEW  TOROIDS  WITH  AXES  HORIZONTAL 
}  OR  VERTICAL 

I  INNNER  AND  OUTER  RADII  ARE  OPTIONAL 
I 

VT0R2  LDA  ##B0 
.BYTE  42C 
HT0R2  LDA  NO 

ST A  HVFLAG 
JSR  CENTER 
JSR  GET TWO 
JMP  EDGTOR 


I  DRAW  INSIDE-VIEW  TOROIDS,  "SPOOLS", 
j  WITH  AXES  HORIZONTAL  OR  VERTICAL 
S  INNER  AND  OUTER  RADII  ARE  OPTIONAL 

VSPL2  LDA  N480 
.bYTE  S2C 
HSPL2  LDA  *0 

ST A  HVFLAG 
JSR  CENTER 
JSR  GET TWO 
JMP  SPOOL R 

.END 


ERRORS  -  00000 

SYMBOL  TABLE 

SYMBOL  VALUE 
CENTER  CAF8 
CLRBYT 
CYLNDR 
DFAULT 
FACETR 
GRFOFF 
HSPL2 
LINE2 
ORIGIN 
PLTFLG 


RO 

SPHERR 

UNPLT2 

VT0R2 

XMIN 

YMID 


Cl  33 
CB64 
CAFA 
C4E  1 
C  1 03 
CBEE 
CB31 
CACB 
033E 
03OE 
C7C7 
CB3A 
CBDA 
034A 
034F 


CHI  COM 

COL BYT 

DEFCLR 

EDGTOR 

FLTFIX 

GRFON 

HT0R2 

LINER 

F'CHECK 

RADIUS 

SKIP  l 

SF'OOLR 

VALUE 

XCENT 

XF'LOT 

YMIN 


AEFD 
Cl  19 
CB  1 9 
CBQF 
B  1  AA 
C0E2 
CBDD 
C2DB 
CAD2 
0377 
CBAA 
CA3B 
0344 
036A 
033F 
034C 


CLEAR2 

C0L0R2 

DEFCOL 

EVAEXP 

GETNUM 

HCYL2 

HVFLAG 

L INF AC 

PL0T2 

RAM 

SK  IF'2 

T0RUS2 

VCYL2 

XMAX 

YCENT 

YPLOT 


CB0B 
CB2 1 
CB2F 
AD9E 
CAC8 
CBB9 
0383 
0393 
CB37 
0393 
CBD7 
CBAD 
CBB6 
0350 
036F 
034  1 


CLEARR 

COLORR 

DEFLAG 

FACET 2 

GET TWO 

HLEN 

LDRAW 

NOMORE 

PLOTR 

RI 

SPHER2 

TORUSR 

VSFL2 

XMID 

YMAX 


C12C 
Cl  18 
0OFB 
CB54 
CAE  4 
0389 
CB94 
CADF 
C14B 
038F 
CB97 
C90F 
CBEB 
034D 
0332 


END  OF  ASSEMBLY 


End  Listing  Five 


Listing  Six 


10  REM 

20  i 

30  REM 


SHAPES  DEMO 


RICHARD  L.  RYLANDER 


11/23/84  (REVISED  1/20/85  TO  ADD  LABELING) 


sREM  LEFT  bOUND 
:REM  RIGHT  BOUND 
i REM  UP  BOUND 
: REM  DOWN  bOUND 


SPHERE 

TOP- VIEW  TOROID 
CYLINDER  (AXIS  VERTICAL) 

CYLINDER  (AXIS  HORIZONTAL) 

EDGE-VIEW  TOROID  (AXIS  VERTICAL) 

EDGE-VIEW  TOROID  (AXIS  HORIZONTAL) 

INSIDE-VIEW  TOROID  CSPOOL"!  (AXIS  VERTICAL) 
INSIDE-VIEW  TOROID  C"SPOOL"J  (AXIS  HORIZONTAL) 


50  GR-4937B 
60  TX-49411 
70  » 

80  LB-893 
90  RB-B94 
100  UB-893 
110  DB-896 

120  i 

130  REM  FLAGS  FOR  VARIOUS  DRAWING  MODES 

140  i 

150  SH=83B  :  REM  SHADE  STYLE  -  0-RANDOM,  1 -HALF TONE 

160  SC-839  :  REM  SCALING  -  0-NORMAL  (1:1),  1 -SCALED  (3:4)  FOR  SCREEN  DISPLAY 

170  LT -898  iREM  LIGHTING  -  0-NORMAL  S INGLE- SOURCE ,  1-BACKLIT  ILLUMINATION 

180  : 

190  BO-53280  i  REM  BORDER  COLOR 

200  i 

210  REM  FUNCTION  LOCATIONS 
220  l 

230  CL -5 1979 
240  CO-32001 
230  i 

260  SP-521 19  i REM 

270  TR-321 4 1  j REM 

280  VC-52150  :  REM 

290  HC-52153  s REM 

300  VT-52186  i REM 

310  HT-S2189  :  REM 

320  VS-52203  : REM 

330  HS-52206  :  REM 

340  i 

330  REM  DRAW  SAMPLES  OF  PRIMITIVE  SHAPES  WITH  LABEL  BENEATH  EACH 
360  i 

370  POKE  SH, 1  t REM  HALFTONE  SHADING 

380  POKE  SC,1  iREM  USE  SCREEN  SCALING  (FOR  UNDISTORTED  SPHERES,  ETC.) 

390  POKE  LT ,  0  :  REM  USE  NORMAL  (NO  BACKLIGHT)  ILLUMINATION 

400  SYS (CL)  :  REM  CLEAR  SCREEN  (DEFAULT  -  FILL  BITMAP  WITH  0'S) 

410  SYS(CO) , 16*1 1 »1  i REM  COl.OR  COMBINATION  -  DARK  GRAY  (11)  DOTS  ON  WHITE  (1) 
412  REM  LOOKS  BETTER  THAN  DEFAULT  COLOR  COMBINATION  OF  BLACK  DOTS  ON  WHITE 
414  REM  ON  MOST  COLOR  MONITORS 

420  POKE  BO, I  >  REM  WHITE  BORDER  TO  MATCH  BACKGROUND 
430  SYS(GR)  iREM  ENTER  BITMAPPED  GRAPHICS  MODE 

432  RW- 12i CM- 1 4 : MD- 1  :  AT -"5AMPI E  SHAPES GOSUB  1900:REM  PUT  TITLE  ON  IMAGE 
434  Xl-1101 Vl-120:  X2-2 1 0 :  Y2- 120: BC- 1 : DC=7 : GOSUB  1700:REM  COLOR  TITLE  TEXT 
440  POKE  LB , 38: POI  E  RB,3Q:P0KE  U6,3B:POKE  DB,38  : REM  INITIALIZE  CLIPPING 

430  SYS(SP) ,40, 199,38 

453  RW-9 i CM- 2: MD-1 :  A4- "SPHERE " : GOSUB 1 900 

460  SYS(HC) , 120, 199,38,38 

463  CM- 1 1 : A4-"H-CYLNDR" : GOSUB1900 

470  SYS (VC) ,200, 199  :  REM  NO  SIZE  PARAMETERS  GIVEN,  DEFAULT  (PREVIOUS)  ARE  USED 

473  CM-21 i A4-"V-CYLNDR" : GOSUB) 900 

480  SYS(TR) ,280, 199, 15,38 

485  CM-32 : A*- " TORO ID": GOSUB 1 900 

490  SYS  (VD  ,  40,64 

493  RW-23 i CM- l:At-"V-T ORO IDM:GOSUD1900 
300  SYS(HT) ,120,64 

503  CM— 1 1 i A»-"H-T0R01D" i GOSUB 1900 

310  SYS(HS) ,200,64,3, 100 

3 1 3  CM-22 1  A4-  "H-SPOOl. "  :  GOSUB  1 900 

320  SYS (VS) ,280,64 

323  CM-32: A*-"V-SPOOL" : GOSUB 1900 


330 

340 

350 

360 

570 

580 

382 

584 

386 

390 

600 

610 

620 

630 

640 

650 

660 

670 

680 

690 

700 

710 

720 


760 
770 
780 
790 
792 
794 
796 
798 
800 
810 
820 
830 
840 
830 
860 
870 
880 
890 
900 
910 
920 
930 
940 
950 
960 
970 
980 
990 
1000 
1010 
1020 
1030 
1040 
1030 
1060 
1070 
1080 
1090 
1  100 
1  102 
1  104 
1  110 
1120 
1130 
1140 
1  150 
1  160 
1170 
1  190 


1220 
1230 
1240 
1230 
1260 
1270 
1280 
1290 
1300 
1310 
1320 
1330 
1340 
1350 
1360 
1370 
1380 
1390 
1410 
1420 
14  30 
1440 
1430 
1460 
1470 
1480 
1490 
1500 
1510 
1520 
1530 
1540 
1330 
1360 
1370 
1580 
1390 
1600 
1610 
1620 
1630 
1640 
1650 
1660 
1670 
1680 
1690 
1700 
1710 
1720 
1730 
1740 
1730 


POKE  198,0: WAIT  198,1:P0KE  198,0 
REM  WAIT  FOR  A  KEY  TO  BE  PRESSED 
l 

REM  DRAW  TWO  "GOBLETS"  ONE  WITH  HALFTONE,  THE  OTHER  RANDOM  SHADING. 

I 

SYS  (CL)  (SYS  (CO)  ,  16M  1  ♦  l 

RW- 14: CM- IS: A4- "COMPAR I SON" : GOSUB  1 900: RW- 1 3: CM- 1 9: A4- "OF " : GOSUB  1900 
RW- 1 6 : CM- 1 4 : A* - ” SHADE  STYLES" : GOSUB  1 900: RW- 1 8: CM- 1 4 : A*-"' —  HALFTONE" 
GOSUB  1900: RW-20: CM- 15: A4-“RAND0M  — >":GOSUB  1900 

POKE  LB , 255: POKE  R6,2S3:P0KE  U6,49:P0KE  DB.253:  REM  CLIP  AT  SCREEN  TOP 
SYS(SP) ,80, 190, e0 

POKE  UB,51 : POKE  DB.51:  REM  CLIP  AT  JUNCTION  WITH  SPHERE  FOR  SMOOTH  SEAM 
SYS (VS) ,80,69,10, 130 
POKE  DB ,9: POKE  UB,8 
SYS(VT) ,80,9,25,45 

POKE  SH , 0  : REM  SWITCH  TO  RANDOM  SHADING 

POKE  LB, 235: POKE  RB, 233: POKE  UB, 49: POKE  DB,253 

SYS(SP) ,240, 190,80 

POKE  UB  ,31:  F‘01  E  DB.51 

SYS (VS) ,240,69, 10,130 

POKE  DB ,9: POK E  UB,B 

SYS(VT) ,240,9,25,45 

POKE  1 98 , 0: WA I T  198,1:P0KE  198,0 


REM  DRAW  "WINE"  SCENE 

POKE  LT , 1  : REM  BACKLIT  ILLUMINATION 

POKE  SH , 1  : REM  HALFTONE  SHADING  FOR  MOST  (RANDOM  "LABEL"  ON  BOTTLE) 

SYS(CO) : SYS(CL) ,253:  REM  FILL  BITMAP  WITH  I  S  ("SET"  BACI  GROUND) 

POKE  BO.0  :  REM  BLACK  BORDER  TO  MATCH  BACKGROUND 

RW-0: CM-0: MD-2: Al -"DRAWING  WITH" : GOSUB  1 900: RW- 1 : CM-2: A*-" BACKL IGHT" 

GOSUB  1 900: RW-2: CM-0: A4- “AGA I NST  A  SET":GOSUB  1900 
RW-3: CM-2: A4-" BACKGROUND " : GOSUB  1 900 
RW- 1 : CM- 26 : A*- "COL  ORS  ADDED" : GOSUB  1900 
RW-2: CM- 25: A4-" TO  SELECT  AREAS" : GOSUB  1900 
REM  DRAW  DOTTLE 

POKE  UB ,0: POKE  DB, 235: POKE  LB, 255: POKE  RB.253 

SYS(VT) , 130, 10,30,50 

POKE  UB, 235: SYS (VC) , 150,70,50,60 

POKE  DB,0:SYS(VT) , 150, 130,6,50 

POKE  DB , S3: POKE  UB , 0: SYS ( VS) , 1 50 , 204 , 15, 1 8 1 

POKE  UB, 253: SYS (VC) , 130,221 , 16, 17 

I 

REM  DRAW  WINE  GLASS 

POKE  UB,20:SYS(SP) ,80, 120,60 

POKE  UB , 33: POKE  DB , 34 : SYS ( VS) , 80 , 34 , 10 , 1 1 0 

i 

REM  DRAW  SOME  GRAPES 

SYS(SF) ,8,8,8:  REM  OTHER  "GRAPES"  WILL  BE  SAME  RADIUS  -  JUST  GIVE  POSITIONS 
SYS(SP) ,20,8: SYS (SP) , 40,8: SYS (SP) , 1 2 , 20: SYS (SP) , 30, 20: SYS <SP) ,23, 16 

REM  DRAW  AN  APPLE  BY  A  PAIR  OF  EDGE-VIEW  TOROIDS  AND  A  SPHERE  SECTION 

POKE  UB, 235: POKE  DB, 255: POKE  LB, 255: POKE  RB,59 

SYS(VT) , 260 , 29 , 0 , 50: SYS ( VT) ,260,79 

POKE  UB  ,43:  F'OKE  DB  ,  43:  SYS  (SP)  ,  260 , 54 , 60 

REM  PUT  STEM  ON  APPLE 

POKE  RB , 0: POKE  DB , 0: SYS ( TR> , 272 , 1 04 , 1 0 , 1 3 

REM  ADD  A  LEAF  BY  A  SPHERE  SECTION 

POKE  DB , 235: PO) E  RB , 0: SYS (SP ) , 256 , 1 19 , 15 

REM  ADD  A  RANDOM  SHADED  "LABEL"  TO  THE  BOTTLE 

POKE  UB, 233: POKE  RB, 255: POKE  LB, 6 

POKE ,SH , 0: SYS (VC) , 150,72,50,40 


REM  ADD  COLORS  TO  "WINE"  SCENE 

SYS (CO) ,12: REM  REPLACE  WHITE  "HOLES"  (BACKGROUND)  WITH  MEDIUM  GRAY 
Xl-0: Yl-200: X2-100: Y2-239: DC-0: BC-0: GOSUB  1700:REM  HIDE  TEXT  IN  CORNERS 
X 1-180: Y 1-200: X2-319: Y2-239: DC-0:  BC-0:  GOSUB  I  700 
X 1-200: Y 1-1 : X2-315: Y2-100: DC-0: BC-2: GOSUB  1700 
X 1 -240: Y1 - 1 10: X2-255: Y2- 1 50: BC-5: GOSUB  1700 
X 1-260: Y 1 - 1 10: X2-270: Y2- 1 35: BC-9: GOSUB  1700 
X 1-1 : Yl-li X2-48: Y2-70: BC-4: GOSUB  1 700 
X 1 - l 40: Y 1-205: X2- 1 80: Y2-235: BC-7 : GOSUB  1700 
X 1 - 1 45: Y 1-25: X2- 195: Y2-1 15:  BC-6: GOSUB  1 700 
POKE  198,0: WAIT  198,1:P0KE  198,0 
: 

REM  "COFFEE  AND  DONUTS" 

POKE  SH , 0  : REM  RANDOM  SHADING  ON  DONUTS 

SYS (CO) , 16* 1 1 ♦ 1 1  SYS (CL' t  POKE  B0,1:REM  WHITE  BORDER  TO  MATCH  BACI  GROUND 
POKE  LB, 255 : POI  E  R6,255:P0KE  UB,2S5:P0KE  DB.255  : REM  NO  INITIAL  CLIPPING 
SYS(VT) ,60,20,20,60 

PO)  E  RB  ,  29:  SYS  (VT  )  ,99,60:POI.E  RB,255 
SYS(TR) , 188, 100 

REM  ADD  COFFEE  CUP  (HALFTONE  SHADING) 

POKE  SH , 1 : POKE  UB , 0: SYS (VT ) , 1 88 , 20: POKE  UB.255 
POKE  DB,0:SYS(TR> ,278, 1 10,20,40 
POKE  DB, 255: POKE  UB,0:POKE  LB,0 

SYS(TR) ,248,90,50, 70: POKE  LB, 255: SYS (SP) ,248, 1 10, 10:POIE  IJB,253 

SYS (VC) ,308, 100,10,10 

SYS (VC ) , 188,77,60,57 

POKE  DB ,0: SYS ( VT >  , 188, 134,40,60 

REM  ADD  COLOR  -  FIRST  MAKE  SCREEN  BROWN  DOTS  ON  WHITE  THEN  MAKE  CUP  GREEN 
SYS (CO) , 1 ♦ 16-9  : REM  1 -WHITE  BACKGROUND,  9-BROWN  DOTS 
X 1 -1 30: Y1 -1 : X2-319:  Y2-1 36:  6C-1:  DC-5: GOSUB  1700 
X 1 -250: Yl -144: X2-319: Y2-1 44: GOSUB  1700 
POKE  198,0: WAIT  198,1:P0KE  198,0 
: 

REM  DRAW  "LINKED"  TOROIDS  BY  REDRAWING  OVERl  AP  AREAS  USING  CLIPPING 

POI  E  LT , 0  : REM  BLUE  DOTS  ON  WHITE,  NO  BACKLIGHT 

SYS (CL) : SYS (CO) , 1+16*6:  REM  1-WHITE  BACKGROUND,  6-bLUE  DOTS 

POKE  UB , 255: POKE  DB,255:F0IE  LB, 235: POKE  Rb,233:  REM  NO  INITIAL  CLIPPING 

POKE  SH , 0  : REM  RANDOM  SHADING 

SYS(TR) ,244,84,48,70 

SYS(TR) , 1 60 , 84 : SYS ( TR) ,76,84 

SYS(TR) ,118, 156: SYS (TR) ,202,156 

REM  ADD  PROPER  OVERLAP  SECTIONS 

POKE  RB,0;POIE  DB ,  0:  SYS  (  TR  >  ,  1  60 , 04 :  FOKE  RB,255:F0IE  Lb,0 

SYS(TR) ,76,B4:POt E  DB,255:P0IE  UB , 0: SYS ( TR) , 1 1 8 , 1 36 

POKE  LB, 253: POKE  RB,0: SYS (TR)  ,202, 156:POI  E  LB, 27 

POKE  DB ,0: POI  E  UB , 255: SYS ( TR)  , 1 60 , 84 

POKE  DB ,0: FOl  E  UB , 255: SYS ( TR> , 1 60 , 84 

POKE  LB , 253: POKE  UB , 27 : SYS ( TR) , 244 , 84 

POKE  LB,0:FOKE  RB,27:POIE  UB , 255: SYS ( TR) , 24 4 , B4 

POKE  198,0 

GET  A*: IF  A*-  "■  THEN  1590 

SYS (TX) (POKE  BO ,14: RCM  RETURN  TO  TEXT  MODE 
END 


REM  SUBROUTINE  FOR  ADDING  COLOR  TO  DIFFERENT  SCREEN  AREAS 

REM  REMEMBER  THAT  COLOR  BOUNDARIES  MUST  CORRESPOND  TO  CHARACTER  BOUNDARIES 
REM  DEFINE  A  RECTANGULAR  AREA  BY  LOWER-LEFT  AND  UPPER-RIGHT  COORDINATES 
REM  (XI ,Y1 ) -LOWER-LEFT  POINT,  ( X2, Y2) -UPPER-RIGHT  POINT 
REM  THE  CORNER  POINTS  CAN  BE  ARBITRARY  BUT  COLOR  CHANGE  WILL  BE  IN  THE 
REM  SMALLEST  CHARACTER-CELL  BOUNDED  RECTANGLE  THAT  INCLUDES  THOSE  POINTS 
REM  Y-COORDINATES  MUST  BE  "UNSCALED"  IF  SCALE  FLAG  IS  SET: 

IF  PEEK (SC) THEN  Y 1 -( Y 1 ♦ 1 > »21 3/256« Y2- ( Y2* 1 ) *21 3/256 

REM  COLOR  TO  BE  POKED  IN  IS  GIVEN  BY  VARIABLE  CC- "COMPOS I TE  COLOR" 

REM  WHERE  CC-16-DC  ♦  BC  CDC-DOT  COLOR,  BC-BACKGROUND  COLOR  NUMBERS 1 


CC-16*DC-BC 

FOR  I  X- INT (XI /D)  TO  INTIX2/8) 
FOR  I Y- INT ( Y 1 /8)  TO  INT(Y2/B> 


(Continued  on  page  80) 


78 


Dr.  Dobb's  Journal.  May  1 985 


Drawing  on  the  C-64  (Listing  Continued,  text  begins  on  page  50) 
Listing  Six 


1760  POKE  34752*IX-40*IY,CC 
1770  NEXTtNEXTtRETURN 
1780  i 

1790  REM  SUBROUTINE  TO  ADD  TEXT  TO  GRAPHIC  SCREEN. 

1800  REM  "RW"  AND  "CM"  ARE  THE  ROW  (0-24)  AND  COLUMN  <0-7-9)  COORDINATES  FOR  THE 
1810  REM  FIRST  LETTER  OF  THE  TEXT  STRING  TO  BE  PRINTED. 

1820  REM  THE  TEXT  STRING  ITSELF  IS  ASSIGNED  TO  "At". 

1830  REM  "MD"  IS  THE  MODE  FOR  THE  PRINTING.  FIVE  MODES  ARE  ALLOWED* 

1840  REM  1  -  NORMAL  < “ BLACK"  LETTERS  ON  "WHITE"  BACKGROUND) 

1850  REM  2  -  REVERSED  ("WHITE"  LETTERS  ON  "BLACK"  BACKGROUND) 

1860  REM  3  -  SET  ("BLACK")  LETTERS  "ORED"  WITH  BACKGROUND 
1870  REM  4  -  UNSET  ("WHITE")  LETTERS  "AND  ED"  WITH  BACKGROUND 
1880  REM  5  -  SET  LETTERS  "XORED"  WITH  BACKGROUND 
1890  : 

1900  SB-40952:  TB-=54272:  IF  (MD  AND  1  >  THEN  TB=5324B:REM  SCREEN  AND  TEXT  BASE  ADDR  ' S 

1910  OS-320«RW*8*CM:  REM  OFFSET  FROM  CHARACTER  SCREEN  BASE 

1920  POKE  56334, PEE)  <56334>AND  254:REM  DISABLE  IRC!  TIMER 

1930  POKE  1 .PEEK < 1 ) AND  251: REM  SWITCH  CHARACTER  ROM  IN 

1940  L-LEN  ( At )  :  FOR  N=  1  TO  I. :  N8^N»U*0S*SB 

1950  X-ASC (MIDt (At.N, 1 > ) : IF  X  63  THEN  X-X-64 

1960  TC-TB*8*X 

1970  ON  MD  GOTO  1980,1900,1990,2000,2010 

1980  POKE  53231 ,36: GOTO  2020 

1990  POKE  53231 , 17:G0T0  2020 

2000  POKE  53231 ,49:C.0T0  2020 

2010  POKE  53231,81 

2020  P0KE252 ,N8/256: POI  E251 , N8-256* I NT (N8/256) 

2030  P0KE254 , TC/ 256: POKE 257 , TC-256* I NT (TC/256) 

2040  SYS  <  5322 1 ) : NEXT 

2050  POKE  1, PEEK <1)0R  4 :POIE  56334 .PEEK (56334) OR  1 : REM  RESTORE  TO  NORMAL 
2060  RETURN 

REM’Y-  End  Listing  Six 

Listing  Seven 

10  REM  "STELLAT ION"  DRAW  A  SMALL  STELLATED  DODECAHEDRON  IN  VARIOUS  ORIENTATIONS 
20  REM  AND  STYLES  RICHARD  L.  RYLANDER  12/5/84 

30  : 

40  GR-49378  : REM  GRAPHICS  MODE 

50  TX-49411  : REM  TEXT  MODE 

60  60-53200  : REM  BORDER  COLOR 

70  : 

80  REM  INITIALIZE  A  FEW  STYLE  PARAMETERS 

90  POKE  839,1  :  REM  SCALE  (3:4)  FOR  UNDISTORTED  SCREEN  DISPLAY 

100  POKE  871,0  : REM  FACET  EDGE/LINE  MODE  (0-DRAW,  1 -ERASE ) 

110  SH-87B  : REM  SHADE  STYLE  (0-RANDUM,  1 -HALFTONE ) 

120  EG-86B  : REM  EDGES  FLAG  (0-NORMAL ,  1 =ADD  LINES  TO  FACET  EDGES) 

130  : 

140  REM  FUNCTION  LOCATIONS 

150  CL-51979  : REM  CLEAR  BITMAP 

160  CO-52001  : REM  FILL  COLOR  MAP 

170  FC-52052  : REM  DRAW  A  SHADED  FACET 

180  KS-530B 1  *  REM  DO  ) EYED  SORT 

190  i 

200  XC-160: YC-120  : REM  (SCALED)  SCREEN  CENTER  COORDINATES 

210  : 

220  PRINT"  CSC!  . . . . 

230  PRINT"  *  SMALL  STELLATED  DODECAHEDRON  «" 

240  PRINT"  »#•••#*•***•«•••• . »**" 

250  PRINT" CCD> CCD)  SELECT  SHADING  STYLE:" 

260  PRINT"  R-RANDOM,  H-HALFTONE" 

270  INPUT" CCD)  YOUR  CHOICE  H tCL ) <CL )  (CL) ” ) A  * 

280  POKE  SH,0: IF  At»"H“  THEN  POt E  SH , I 

290  PRINT" (CD) ICD)  SELECT  STYLE  FOR  DRAWING:" 

300  PRINT" CCD)  N  -  NORMAL " : PR INT " CCD>  E  -  EDGES  EMPHASIZED" 

310  PRINT" (CD)  W  -  WIRE  FRAME  (HIDDEN  LINES  REMOVED)" 

320  INPUT" (CD)  YOUR  CHOICE  N(CL) (CL) (CL) At 
330  POKE  EG,0: WI -0: IF  AI-"N"  THEN  360 
-340  POKE  EG ,  1 :  IF  At-"W"  THEN  WI--1 
350  : 

360  PRINT" (CD)  READING  VERTEX  DATA" 

370  VN-32:  DIM  P7.  (VN- 1  ,2)  :  REM  VN  -  NUMBER  OF  VERTICES 
380  FOR  N-0  TO  VN-  1 :  READ  P7.  <N,0>  ,PX  <N,  1  >  ,P7.  (N, 2) : NEXT 
390  : 

400  PRINT" (CD)  ENTER  X,  Y,  AND  Z  ANGLES  FOR  ROTATION" 

410  PRINT"  (ANGLES  IN  DEGREES)" 

420  INPUT  X , Y , Z 

430  J-3. 14159265/180: X=X»J: Y=Y*J: Z-Z»J 

4  40  X0-COS(Y)*COS(Z>  -SIN(X)*SIN(Y)«S!N(Z)  : X  1-COS (Y> «SIN(Z) *SIN(X>  *SIN(Y)  »COS(Z) 
450  X2--C0S (X) *S I N ( Y) * Y0— COS ( X > #SIN ( Z > : Y1 -COS < X > *COS ( A)  :  Y2-S IN ( X ) 

460  Z0=SIN(Y)« COS (Z) *S 1 N ( X ) *COS (Y)»SIN(Z) 

470  Z1-SIN<Y)*SIN(Z)-SIN(X) *COS ( Y ) «COS  <  Z ) : Z2-C0S ( X ) »COS ( Y  > 

480  PRINT" (CD)  PERFORMING  ROTATION" 

490  FOR  N=0  TO  VN- 1 

500  X-P7.(N,0>  :  Y-P7.(N,  1 )  :  Z-P7.(N,2> 

510  P7.(N,0)  «X0*X*X1*Y*X2#Z:PX(N,  1)  =Y0»X  ♦  Y 1  •  Y*  Y2«  Z :  P-/.  (N ,  2)  =Z0»  X*  Z  1  *  Y*Z2*Z  :  NEXT 
520  : 

530  FA-60:  REM  TOTAL  NUMBER  OF  FACETS 

540  DIM  F 7.  (FA/2, 2)  , SH  (FA/ 2)  ,  Z7.  (FA/2)  ,KX  (FA/2) 

550  PRINT"  READING  CONNECTION  DATA 
560  VF— It  REM  VF  =  4  VISIBLE  FACETS 
570  FOR  N— 1  TO  FA 
580  VF-VF+1 

590  FOR  1-0  TO  2  :  READ  F7.  <  VF  ,  I  >  :  NEXT 

600  REM  CALCULATE  COMPONENTS  OF  NORMAL  VECTOR  TO  DETERMINE  VISIBILITY 
610  Z-  IPX  <F’/.  (VF  ,2)  ,0)  -P7.(F7.(VF,  1)  ,0)  )  *  (P7.  <F7.  ( VF  , 0)  ,  I  ) -P7.  (F*/.  (VF ,  1  )  ,  1)  > 

620  Z-Z-(PX<F7.(VF,0)  ,0>-Py.(Fy.(VF,  1) ,0) > •  (Py.(F-/.  (VF,2)  ,  1>-PX<F7.<VF,1>  ,  1)  ) 

630  IF  Z-<0  THEN  720:REM  FACET  NOT  VISIBLE,  GO  ON 

640  x-  <py. (Fy. <vf,2>  ,  i )  -py. (F7. (vf,  i ) ,  i ) )  *  <py. <f'/.(vf  ,0)  ,2>  -PX  <F7.  (VF ,  1  )  ,2)  ) 

650  X-X-<Py.<F-/.<VF,0>  ,  1)-PX(FV.(VF,  1)  ,  1 )  )  *  (PX  <FX  ( VF  ,2)  ,2)  -PX  (FX  <VF  ,  1  >  ,2)  > 

660  Y-(Py.(F*/.  <VF,2)  ,2>-PX(FX(VF,  1  >  ,  2)  )  *  (PX  CF'/.  <  VF ,  0  >  ,  0)  -PX  (FX  (VF  ,  1  >  ,0)  ) 

670  Y-Y-<PX<FX(VF,0)  ,2)  — PX  <F7.  (VF  ,  1  >  , 2) >  * (PX (FX < VF , 2 >  ,  0)  -P7.  (FX  (VF  ,  1  >  ,0)  > 

680  NC-SQR  <X*X+Y*Y*Z«Z)  :  REM  LENGTH  OF  NORMAL  VECTOR 
690  SH(VF) -26» (2«Z+X*Y) /NC 

700  SH (VF) - (SH(VF>*64) » (SH (VF) +64) /256l REM  RAISED  COSINE  SHADING 

710  GOTO  730 

720  VF-VF-1 

730  NEXT 

740  t 

750  PRINT"  SCALING  TO  DISPLAY  SIZE" 

760  Y-0:  FOR  N-0  TO  VN-ltIF  ABS (PX (N , 1 > > >Y  THEN  Y-ABS  (P7.  (N ,  1  >  ) 

770  NEXT : S- 1 1 9/Y 

780  FOR  N-0  TO  VN-  1 :  P7.  (N ,  1 )  «S*PX  (N ,  1 )  +YC:  P7.  (N  ,  0 )  -S»PX  (N ,  0)  *XC :  NEXT 
790  l 

800  REM  FIND  AVERAGE  Z  FOR  EACH  FACET 
810  FOR  N-0  TO  VF 

820  Zy.(N)-(PX(FX(N,0>  ,2>*PX(FX<N,  1)  ,2)  *PX  (FX  (N,2>  ,2)  )/3:NEXT 
830  : 

840  PRINT"  SORTING  FACETS  ACCORDING  TO  AVG  "Z-  " 

850  POKE  1 40 , VF 

860  KX  ( 0  >  -K7.  ( 0  >  :  POKE  251  ,  PEEK  <7  I )  i  POKE  252,  PEEK  (72) 

870  Z'/.  <  0 )  “  Z  */.  <  0  >  :  POKE  253 ,  PEEK  (7 1 )«  POKE  254,PEEK<72> 

880  SYS(KS) 

890  : 


900  REM  DRAW  FACETS  (ACCORDING  TO  Z  DEPTH  SINCE  NOT  CONVEX) 

910  SYS (GR) : SYS (CO) : SYS (CL) : POKE  B0,1 
920  FOR  N-0  TO  VF:FA=K7.(N) 

930  IF  WI  THEN  SH(FA)-64 

940  X0-PX  (FX  (FA ,  0 )  ,0)  :  Y0-PX  (F7.  (FA ,  0 )  ,  1  )  :  X  1  -PX  <F7.  (FA ,  t  >  ,0)  : Yl-PX (FX (FA, 1  >  ,  1) 

950  X2«Py.(F7.(FA,2>  ,0)  :  Y2-P7.  (F7.  (FA,  2)  ,  1  > 

960  SYS(FC) , X0 , Y0 , X 1 , Y 1 ,X2,Y2, SH (FA > 

970  NEXT 
980  POKE  198,0 

990  GET  At: IF  At=M"  THEN  990 
1000  SYS < TX >  *  POKE  BO, 14: END 
1010  : 

1020  REM  VERTEX  DATA 

1030  DATA  1000,618,0,  1000,-618,0,  -1000,618,0,  -1000,-618,0 

1040  DATA  0,1000,618,  0,1000,-618,  0,-1000,618,  0,-1000,-618 

1050  DATA  618,0,1000,  -618,0,1000,  618,0,  1000,  -618,0,-1000 

1060  DATA  618,0,236,  618,0,-236,  -618,0,236,  -618,0,-236 

1070  DATA  236,618,0,  -236,618,0,  236,-618,0,  -236,-618,0 

1080  DATA  0,236,618,  0,-236,618,  0,236,-618,  0,-236,-618 

1090  DATA  382,382,382,  382,382,-382,  382,-302,382,  382,-302,-382 

1100  DATA  -382,382,382,  -382,382,-382,  -382,-382,382,  -382,-382,-382 

11  10  i 

1120  REM  CONNECTION  DATA 

1130  DATA  0,12,13,  0,13,25,  0,25,16,  0,16,24,  0,24,12 

1140  DATA  1,12,26,  1,26,10,  1,18,27,  1,27,13,  1,13,12 

1150  DATA  2,15,14,  2,14,28,  2,28,17,  2,17,29,  2,29,15 

1160  DATA  3,14,15,  3,15,31,  3,31,19,  3,19,30,  3,30,14 

1170  DATA  4,16,17,  4,17,28,  4,28,20,  4,20,24,  4,24,16 

1180  DATA  5,17,16,  5,16,25,  5,25,22,  5,22,29,  5,29,17 

1190  DATA  6,19,18,  6,18,26,  6,26,21,  6,21,30,  6,30,19 

1200  DATA  7,18,19,  7,19,31,  7,31,23,  7,23,27,  7,27,18 

1210  DATA  8,20,21,  8,21,26,  8,26,12,  8,12,24,  8,24,20 

1220  DATA  9,21,20,  9,20,28,  9,28,14,  9,14,30,  9,30,21 

1230  DATA  10,23,22,  10,22,25,  10,25,13,  10,13,27,  10,27,23 
1240  DATA  11,22,23,  11,23,31,  11,31,15,  11,15,29,  11,29,22 

READy'  End  Listing  Seven 

Listing  Eight 

00001  0000  ;  KEYSORT  -  RELOCATABLE  BUBBLE  SORT  USING  KEY  ARRAY 

00002  0000  ;  POINTING  TO  INTEGER  ARRAY 

00003  0000  ; 

00004  0000  ;  RICHARD  L.  RYLANDER  1/12/85 


00006  0000 
00007  0000 

00008  0000 
00009  0000 

00010  0000 
00011  0000 
00012  0000 
00013  0000 

00014  0000 

00015  0000 

00016  0000 
00017  0000 

00018  0000 
00019  CF59 
00020  CF59  A0  FF 
00021  CF5B  C8 
00022  CF5C  98 
00023  CF5D  91  FB 
00024  CF5F  CS  8C 
00025  CF61  D0  FB 
00026  CF63 
00027  CF63  85  AD 
00028  CF65  A5  AD 
00029  CF67  85  AC 
00030  CF69  A2  00 
00031  CF6B  86  61 
00032  CF6D  86  AE 
00033  CF6F  86  64 
00034  CF7 1 
00035  CF7 1 
00036  CF71 
00037  CF7 1 
00038  CF7 ’ 

00039  CF71  8A 
00040  CF  72  AS 
00041  CF73  B 1  FB 
00042  CF75  0A 
00043  CF76  90  04 
00044  CF78  C6  61 
00045  CF7A  E6  FE 
00046  CF7C  A8 
00047  CF7D  B 1  FD 
00048  CF7F  48 
00049  CFB0  CB 
00050  CF8 1  B 1  FD 
00051  CF83  24  61 
00052  CF85  10  04 
00053  CF87  E6  61 
00054  CF89  C6  FE 
00055  CF8B  E4  64 
00056  CF8D  D0  08 
00057  CF8F  85  62 
00058  CF91  68 
00059  CF92  85  63 
00060  CF94  E8 
00061  CF9S  D0  DA 
00062  CF97 
00063  CF97 
00064  CF97 
00065  CF97 
00066  CF97 
00067  CF97  C5  62 
00068  CF99  68 
00069  CF9A  E5  63 
00070  CF9C  50  02 
00071  CF9E  49  80 
00072  CFA0  10  13 
00073  CFA2  BA 
00074  CFA3  A8 
00075  CFA4  86  AD 
00076  CFA6  B 1  FB 
00077  CFA8  48 
00078  CFA9  88 
00079  CFAA  B 1  FB 
00080  CFAC  CS 
00081  CFAD  91  FB 
00082  CFAF  68 
00083  CFB0  88 
00084  CFB1  91  FB 


OR IG I N-TCF59  j  53081.  (FOLLOWING  DOS  5.1) 

J 

KB  -  tFB  j  251.  POINTER  TO  KEY  ARRAY 

ZB  -  tFD  I  253.  POINTER  TO  DATA  ARRAY 

MAX  -  tBC  ;  140.  POKE  WITH  MAX  ARRAY  INDEX 

TOP  -  tAC 

TOPDIS  -  SAD 

FLAG  -  tAE 

NXTFLG  -  161 

CRRNT  -  t62 

REPEAT  -  164 

I 

•■ORIGIN 

; 

INI  T  LDY  tttFF  j  INITIALIZE  KEY  ARRAY 

INLOOP  I NY 
TYA 

STA  (KB) , Y 
CMP  MAX 
BNE  INLOOP 

1 

SORT  STA  TOPDIS  |  A’  HOLDS  ’MAX' 

L00P1  LDA  TOPDIS 
STA  TOP 
LDX  t»0 
STX  NXTFLG 
STX  FLAG 

L00P2  STX  REPEAT 

;  GET  BOTH  BYTES  OF  INTEGER  POINTED  TO  BY 
I  KEY'  ELEMENT.  RETURN  WITH  MSB  ON  STACK 
;  AND  LSB  IN  THE  ACCUMULATOR 
i 

GETINT  TXA 
TAY 

LDA  (KB) ,Y 
ASL  A 
BCC  LOAD 
DEC  NXTFLG 
INC  ZB* 1 
LOAD  TAY 

LDA  (ZB),Y 

PHA 

INY 

LDA  (ZB) , Y 
BIT  NXTFLG 
BPL  NODEC 
INC  NXTFLG 
DEC  ZB* 1 

NODEC  CPX  REPEAT 
BNE  ORDER 
STA  CRRNT 
PL  A 

STA  CRRNT*- 1 
I  NX 

BNE  GETINT 
I 

j  COMPARE  INTEGERS  OBTAINED  THROUGH  KEY  ARRAY 
;  IF  -CURRENT-  >-  ‘NEXT’  THEN  SWAP  KEY 
;  ELEMENTS,  ELSE  CONTINUE 

ORDER  CMP  CRRNT 
PLA 

SBC  CRRNT* 1 
BVC  TEST 
EOR  MS80 

TEST  BPL  NOSWAP 
SWAP  TXA 
TAY 

STX  TOPDIS 
LDA  (KB) , Y 
PHA 
DEY 

LDA  (KB) , Y 
INY 

STA  (KB) , Y 

PLA 

DEY 

STA  (KB) , Y 


(Continued  on  page  82) 


80 


Dr.  Dobb’s  Journal,  May  1985 


■?n7 


Drawing  on  the  C-64  (Listing  Continued,  text  begins  on  page  50) 
Listing  Eight 


00003  cfb3 

00066  CFB5 
00087  CFB7 
00088  CFB9 
00089  CFBB 
00090  CFBO 
0009 l  CFBD 
00092  CFBD 
00093  CFBD 
00094  CFBD 
00093  CFBF 
00096  CFC0 
00097  CFC1 
00098  CFC2 
00099  CFC3 
00100  CFC3 
00101  CFC6 
00102  CFC7 
00103  CFC8 
00104  CFCA 
00103  CFCC 
00106  CFCE 
00107  CFD0 
00108  CFD1 
00109  CFD2 
00110  CFD4 
00111  CFD6 
00112  CFD7 
00113  CFD9 
00114  CFDB 
00113  CFDD 
00116  CFDF 
00117  CFE 1 
00118  CFE2 
00119  CFE4 
00120  CFE3 


E6  AE 
E4  AC 
D0  B6 
A3  AE 
D0  A8 


A6  BC 

E8 

CA 

8A 

A8 

h  1  FB 
48 
8A 
0A 

09  01 

90  04 
E6  61 
E6  FC 
A8 

68 

91  FB 
A9  00 

88 

91  FB 
AS  61 
F0  04 
C6  61 
C6  FC 
8A 

D0  DC 
60 


INC  FLAG 
NOSWAP  CPX  TOP 

BNE  L00P2 
LDA  FLAG 
BNE  LOOP 1 


I 

|  UNPACK  THE  BYTE  ELEMENTS  OF  THE  KEY’  ARRAY 
{  INTO  BASIC'S  NORMAL  2- BYTE  INTEGER  FORMAT 

I 

UNPACK  LDX  MAX 
INX 

PKLOOP  DEX 
TXA 
TAY 

LDA  (KB) , Y 

PHA 

TXA 


ASL  A  |  MOVE  TO  2*IM 

ORA  It  1 
BCd  STORE 
INC  NXTFLG 
INC  KB+1 
STORE  TAY 
PLA 

STA  (KB) , Y 
LDA  N0 
DEY 

STA  (KB) , Y 
LDA  NXTFLG 
BEQ  OK 
DEC  NXTFLG 
DEC  KB+1 
OK  TXA 

BNE  PKLOOP 
DONE  RTS 
.END 


ERRORS  -  00000 


SYMBOL  TABLE 

SYMBOL  VALUE 
CRRNT  0062  DONE 

INI  T  CF39  INI. OOP 

LOOP  1  CF65  L00P2 

NOSWAP  CFB3  NXTFLG 

ORIGIN  CF39  PKLOOP 

STORE  CFD0  SWAP 

TOPDIS  00AD  UNPACK 

END  OF  ASSEMBLY 


CFE4  FLAG  00AE 
CF5B  KB  00FB 
CF6F  MAX  008C 
006 1  OK  CFE 1 
CFC0  REPEAT  0064 
CFA2  TEST  CFA0 
CFBD  ZB  00FD 


GETINT  CF7 1 
LOAD  CF7C 
NODEC  CFSB 
ORDER  CF97 
SORT  CF63 
TOP  00AC 

End  Listing  Eight 


Listing  Nine 


00001 

00002 

00003 

00004 

00003 

00006 

00007 

00008 

00009 

00010 

00011 

00012 

00013 

00014 

00013 

00016 

00017 

00018 

00019 

00020 

00021 

00022 

00023 

00024 

00023 

20026 

00027 


0000 
0000 
0000 
0000 
0000 
0000 
0000 
0000 
CFE3 
CFE  7 
CFE9 
CFEB 
CFED 
CFEF 
CFF1 
CFF3 
CFF3 
CFF3 
CFF3 
CFF3 
CFF3 
CFF4 
CFF6 
CFF8 
CFFA 
CFFC 
CFFD 


A3  01 
29  FE 
83  01 
A0  07 
B 1  FD 
31  FB 
91  FB 


88 

10  F7 
AS  01 

09  01 

83  01 
60 


"WRITE"  RICHARD  L.  RYLANDER 
12/30/84 

REVISED  1/19/83  -  ORIGIN  MOVED  TO  #CFE3  (33221.) 


PUT  TEXT  CHARACTERS  ON  GRAPHIC  SCREEN 
(UNDER  BASIC  ROM)  IN  VARIOUS  STYLES 


»-#CFE3 
WRITE  LDA  #01 
AND  ##FE 
STA  #01 
LDY  *7 

LOOP  LDA  ( #FD) , Y 
AND  (#FB> , Y 
STA  (#FB),Y 


I  PUT  CODE  AFTER  DOS  3. 
S  SWITCH  OUT  BASIC  ROM 


|  READ  CHARACTER  BYTE 
j  MODIFY  W/SCREEN  BYTE 
I  STORE  IN  SCREEN 


» 

{  POKE  NEW  LOGICAL  OPERATOR  TO  REPLACE 
I  AND'  (33231.)  FOR  DIFFERENT  STYLES 
I  ORA" 17.  BIT  (NOP) -36.  AND-49.  EOR-81. 

I 

DEY 

BPL  LOOP 

LDA  #01  I  RESTORE  BASIC  ROM 

ORA  Ml 
STA  #01 
RTS 


.END 


ERRORS  -  00000 


SYMBOL  TABLE 
BYMBOL  VALUE 

LOOP  CFED  WRITE  CFE3 

END  OF  ASSEMBLY 


End  Listings 


82 


Dr.  Dobb’s  Journal,  May  1985 


A  Compiler  Written  in  Prolog 


by  G.  A.  Edgar 


Why  would  anyone  write  a 
compiler  in  a  language 
like  Prolog?  Actually,  the 
compiler  seems  to  have  come  out 
rather  well.  I  wrote  it  primarily  as 
a  learning  exercise  for  Micro- 
PROLOG. 

Micro-PROLOG 

I  first  heard  of  the  Prolog  program¬ 
ming  language  in  connection  with  the 
Japanese  effort  to  develop  the  “fifth 
generation”  in  computing.  At  the 
time,  I  read  a  few  things  about  Pro¬ 
log,  such  as  the  book  by  Clocksin  and 
Mellish.1  My  impression  was  that 
Prolog  is  an  interesting  language  (I 
was  right),  but  that  it  probably  is  not 
practical  to  implement  PROLOG  on  a 
microcomputer  (was  I  ever  wrong!). 


substantial  effort  for  a  programmer 
experienced  only  in  Pascal,  C,  or  PL/ 
I.  The  tutorial2  is  published  separate¬ 
ly  as  a  book;  if  you  are  curious,  it 
should  be  available  in  most  large  uni¬ 
versity  libraries. 

After  I  began  to  get  a  feel  for  the 
language,  I  used  it  to  solve  a  few  logic 
puzzles.  For  example,  Lewis  Car¬ 
roll’s  logic  book3  includes  some  puz¬ 
zles  at  the  end  as  a  teaser  for  a  later 
book  that  was  never  published.  They 
are  much  too  complicated  to  solve 
simply  by  common  sense  reasoning, 
but  I  can  now  say  definitively  that 
“All  of  the  Monitors  are  awake.” 

If  Prolog  were  good  only  for  logic 
problems,  it  would  not  be  of  wide  in¬ 
terest.  So  I  looked  around  for  some 
other  relatively  easy  task  to  use  as  a 


The  early  returns  on  our  March  special  issue  on 
Prolog  are  encouraging — so  much  so  that  we're 
offering  this  excursion  into  programming  in  logic. 


D.  E.  Cortesi,  in  “Dr.  Dobb’s  Clin¬ 
ic”  for  May  1984,  gave  a  half-page 
description  of  Micro-PROLOG.  I 
wrote  for  information  on  it  and  by 
July  was  running  it  on  my  CP/M  Z80 
computer.  Micro-PROLOG  is  also 
available  for  MSDOS,  PCDOS,  CP/ 
M-86,  and  Unix. 

Micro-PROLOG  comes  with  an 
IBM  PC-size  loose-leaf  manual  with 
more  than  200  pages.  This  is  a  com¬ 
plete  reference  to  the  language  but 
not  a  good  tool  for  learning.  Micro- 
PROLOG,  however,  also  comes  with  a 
400-page  soft-cover  tutorial,  which  is 
a  good  place  for  a  beginner  to  start. 
Learning  the  language  will  take  a 


G.  A.  Edgar,  107  W.  Dod ridge  St., 
Columbus,  OH  43202 


programming  exercise.  A  compiler 
for  VALGOL  fit  the  need  nicely.  It 
improved  my  understanding  of  Mi¬ 
cro-PROLOG  and  my  understanding 
of  what  a  compiler  does. 

This  compiler  is  described  below. 
It  translates  VALGOL  I,  also  de¬ 
scribed  below,  into  8080  assembly 
language.  Of  course,  you  would  learn 
more  by  undertaking  such  a  pro¬ 
gramming  project  yourself  than  you 
will  by  examining  mine,  but  it  serves 
as  proof  that  you  can  use  Micro- 
PROLOG  for  something  unexpected. 

Using  Micro-PROLOG 

Before  I  describe  the  compiler,  let  me 
give  a  brief  description  of  how  Micro- 
PROLOG  differs  from  other  versions 
of  Prolog. 


Dr.  Dobb’s  Journal,  May  1985 


84 

389 


Upper-  and  lower-case  letters  are 
considered  different,  and  the  hyphen 
is  treated  like  a  letter.  Variables  are 
X,  Y,  Z,  x,  y,  z,  or  one  of  these  six 
letters  followed  by  a  positive  integer, 
such  as  XI 32  or  z22.  Other  strings  of 
letters  and  numbers  are  identifiers 
for  constants. 

Micro-PROLOG  consists  primarily 
of  a  pattern  matcher,  plus  a  few  other 
built-in  routines,  such  as  a  routine  for 
I/O.  The  way  Micro-PROLOG  looks 
depends  on  which  of  the  front  ends 
you  are  using.  Each  front-end  pro¬ 
gram  is  written  in  Prolog.  If  you  are 
using  the  SIMPLE  front  end,  one- 
place  predicates  can  be  given  either 
in  prefix  form 

alphanumeric(X) 
or  in  postfix  form 

X  alphanumeric 

Two-place  predicates  can  be  given  ei¬ 
ther  in  prefix  form 

LESS(@  X) 
or  in  infix  form 

@  LESS  X 

Predicates  with  three  or  more  argu¬ 
ments  must  be  in  prefix  form.  Thus,  a 
Prolog  program  entered  via  SIMPLE 
tends  to  look  like  this: 

labelused  (0) 

X  alphanumeric  if  X  alphabetic 

X  alphanumeric  if  X  digit 

X  alphabetic  if  @  LESS  X  and  X 
LESS  [ 

X  alphabetic  if  ‘  LESS  X  and  X 
LESS  { 

One  useful  object  in  Micro-PRO¬ 
LOG  is  the  list.  The  notation  for  a  list 
is  to  enclose  the  items  between  paren¬ 
theses:  (A  B  C  1  2  3).  Some  of  the 
items  might  be  lists  themselves:  (A 
(B  C)  ( 1  23)). 

The  16-bit  versions  of  Micro-PRO¬ 
LOG  include  a  front-end  supervisor 
that  imitates  the  DEC- 10  Prolog: 

labelused(O). 

alphanumeric(X) :-  alphabetic  (X). 

85 


alphanumeric(X) :-  digit(X). 

The  form  in  which  an  operator  is  used 
is  declared  using  the  types  fx,  xf,  xfy, 
and  so  on  (see  Clocksin  and  Mellish, 
page  91 ).  DEC- 10  lists  are  built  using 
square  brackets:  [a,b,c]. 

The  standard  Micro-PROLOG  syn¬ 
tax  simply  uses  lists.  In  this  regard,  it 
is  somewhat  like  LISP.  The  list 
(labelused  0),  which  indicates  the 
predicate  name  “labelused”  applied 
to  the  argument  “0,”  is  an  example  of 
an  atom.  A  clause  is  a  list  of  atoms. 
You  add  things  to  the  data  base  in 
this  way: 

((labelused  0)) 

((alphanumeric  X)  (alphabetic  X)) 

((alphanumeric  X)  (digit  X)) 

This  may  seem  confusing  at  first,  but 
after  a  while  it  becomes  more  or  less 
routine. 

This  minimal  introduction  to  the 
Micro-PROLOG  interpreter  should 
be  enough  for  you  to  gain  some  un¬ 
derstanding  of  the  VALGOL  compil¬ 
er,  but  you  will  need  a  much  more 
complete  knowledge  of  Micro-PRO¬ 
LOG  to  understand  all  of  the  details. 

VALGOL I 

The  language  VALGOL  I  (very  small 
ALGOL?)  is  a  derivative  of  ALGOL- 
60.  D.  V.  Schorre  described  it  in  1964 
as  a  sample  language  for  the  compil¬ 
er-writing  language  META  II.  His 
paper4  was  reprinted  in  Dr.  Dobb's 
Journal  (April  1980),  which  is  where 
I  found  it.  I  had  worked  with  VAL¬ 
GOL  I  in  connection  with  the  compil¬ 
er-writing  language  Meta4  (in  a 
SIG/M  disk),  so  it  seemed  natural  to 
use  it  for  the  same  thing  in  Prolog. 

VALGOL  I  has  a  few  peculiarities. 
The  keywords  that  usually  are  type¬ 
set  in  boldface  here  are  preceded  by  a 
period  (  .begin,  .end,  .if,  .then,  .else, 
.until,  .do,  .integer).  This  also  applies 
to  the  equals  sign  for  comparing  two 
expressions  (  .=  ).  VALGOL  I  has 
only  one  data  type,  namely  .integer,  a 
16-bit  two’s-complement  number. 
The  assignment  statement  is  the  re¬ 
verse  from  the  normal  order  (  5  =:  x 
assigns  the  value  5  to  x  ).  Arithmetic 
allows  addition,  subtraction,  and 
multiplication,  but  there  is  no  unary 


minus  sign,  so  if  you  want  -2  you 
must  write  either  0  -  2  or  65534. 

A  more  complete  description  of  the 
syntax  appears  below  in  the  descrip¬ 
tion  of  the  compiler. 

Using  the  VALGOL  I  Compiler 

The  VALGOL  I  compiler  is  in  a  file 
called  VALGOL.LOG.  A  typical  use, 
to  compile  the  short  VALGOL  pro¬ 
gram  in  the  file  P.VAL,  is  shown  in 
Listing  One  (page  89).  The  com¬ 
mand  shown  is  in  the  standard  Mi¬ 
cro-PROLOG  syntax.  If  the  SIMPLE 
front  end  is  also  loaded,  you  could  use 
the  command: 

is(“P.VAL”  compile  “P.ASM”) 

The  compiler  prints  out  the  input  file 
P.VAL  on  the  console  as  it  works.  The 
result  of  the  compilation  in  the  sam¬ 
ple  is  P.ASM,  included  here  as  Listing 
Four  (page  96). 

If  you  are  familiar  with  the  Intel 
mnemonics  for  the  8080,  you  may 
find  it  instructive  to  compare  the 
VALGOL  input  with  the  assembly 
language  output  to  see  how  the  com¬ 
piler  works. 

The  VALGOL  Definition 

Take  a  look  at  the  compiler  in  Listing 
Two  (page  90).  I  have  repeated  part 
of  the  compiler  using  the  SIMPLE 
syntax  in  Listing  Three  (page  96). 
SIMPLE  is  unsuitable  for  the  com¬ 
plete  listing  for  several  reasons.  In  a 
few  places  in  the  compiler,  I  have 
used  “meta-variables,”  which  are  not 
supported  by  SIMPLE.  A  SIMPLE 
listing  also  would  not  keep  the  com¬ 
ments  and  the  formatting,  making  it 
confusing  to  read.  So  try  to  under¬ 
stand  instead  the  list  notation  used 
for  the  standard  syntax  of  Micro- 
PROLOG  in  Listing  Two. 

The  lists  preceded  by  a  ?  are  to  be 
executed  immediately  when  they  are 
loaded;  the  others  are  to  be  stored  in 
the  data  base.  Because  the  predicate 
symbol  (  /  *  )  is  a  special  one  that  is 
always  true,  a  list  in  the  file  of  the 
form 

?((  /  *  anything  goes  here  )) 
will  serve  as  a  comment.  (This  is  a 
clumsy  feature  of  Micro-PROLOG.) 

The  compiler  listing  is  in  two  parts. 
The  first  part  is  independent  of  the 
particular  syntax  of  VALGOL  I  and 
could  be  used  equally  well  with  many 


Dr.  Dobb’s  Journal,  May  1985 


other  programming  languages.  The 
second,  below  a  horizontal  line,  is  a 
VALGOL-specific  part.  In  one  sense, 
the  part  above  the  line  implements  an 
interpreter  that  runs  the  compiler  de¬ 
scribed  below  the  line.  Let’s  examine 
a  portion  of  the  VALGOL-specific 
part  of  the  code. 

The  message  line,  like  all  these 
lines,  becomes  a  statement  in  the  Pro¬ 
log  data  base.  The  predicate  symbol 
of  the  statement  is  “message,”  and 
the  single  argument  is  the  string  en¬ 
closed  in  quotation  marks.  The  com¬ 
piler  will  look  for  a  message  fact  in 
the  data  base  and  print  it  out  when  a 
compilation  starts. 

The  next  line  is  the  syntax  line. 
This  tells  the  compiler  that  the  pri¬ 
mary  unit  of  syntax  is  the  program. 

The  next  statement  is  a  description 
of  a  program.  First  comes  the  keyword 
.begin.  When  this  is  matched,  seven 
opening  lines  are  sent  to  the  output  file. 
(I  have  put  the  VALGOL  syntax  de¬ 
scription  on  the  left-hand  side  of  the 
page  and  the  ASM  output  description 
on  the  right-hand  side.  A  VALGOL  1 
compiler  for  another  processor  would 
keep  the  same  left-hand  side  and 
change  the  right-hand  side.)  Then  an 
opt-declaration  (optional  declaration, 
see  below)  is  followed  by  one  or  more 
statements  separated  by  semicolons. 
The  multiple  predicate  allows  match¬ 
ing  of  its  arguments  zero  or  more  times. 
Finally  comes  the  keyword  .end.  When 
this  is  matched,  the  final  JMP  is  insert¬ 
ed  in  the  output  file,  followed  by  the 
four  subroutines  and  the  stack  space. 

The  opt-declaration  could  be  a  dec¬ 
laration  followed  by  a  semicolon,  or  (if 
that  cannot  be  found)  it  could  be  emp¬ 
ty,  which  matches  the  input  file  auto¬ 
matically.  Inclusion  of  the  “empty”  al¬ 
ternative  is  a  way  to  allow  for  no 
declaration.  A  declaration  is  the 
keyword  .integer,  followed  by  one  or 
more  identifiers  separated  by  commas. 
For  each  identifier,  the  output  file  re¬ 
serves  a  two-byte  storage  location. 

A  statement  is  one  of  the  following: 

( 1 )  An  I /O  statement,  either 

edit(  expression  ,  ‘string’  V 

which  will  send  (expression)  spaces 
and  then  the  (string)  to  the  console, 
or 


print 

which  will  send  an  end-of-line  to  the 
console. 

(2)  An  assignment  statement: 
expression  =:  variable 

(3)  A  loop: 

.until  expression 
.do  statement 

A  value  of  zero  for  the  expression  is 
considered  false,  and  a  nonzero  value 
is  true. 

(4)  A  conditional  statement: 

.if  expression 
.then  statement 
.else  statement 

The  .else  is  not  optional. 

(5)  A  block,  which  consists  of  the 
keyword  .begin,  followed  by  an  op¬ 
tional  declaration,  followed  by  zero 
or  more  statements  separated  by 
semicolons,  followed  by  the  keyword 

.end.  The  null  statement 

.begin  .end 

is  allowed. 

The  following  test  for  equality  is 
allowed: 

expression  .=  expression 

This  has  value  1  if  true  and  0  if  false. 
Expressions  can  be  built  up  from 
variables,  numbers,  the  operations 
+  and  parentheses.  Notice  how 
the  syntax  near  the  end  of  Listing 
Two  is  arranged  to  take  into  account 
the  precedence  of  these  operations. 

The  Language-Independent  Part 

Now  let’s  look  at  the  first  part  of 
Listing  Two  (part  of  this  is  repeated 
in  SIMPLE  syntax  as  Listing  Three). 
When  Micro-PROLOG  attempts  to 
verify  a  compile  atom,  it  looks  first  in 
the  data  base  for  a  message  fact;  if  it 
finds  one,  it  verifies  the  assertion  (P 
x),  which  is  always  true  but  which 
has  the  side  effect  of  printing  x  on  the 
console.  The  assertion  PP  is  also  al¬ 
ways  true  and  starts  a  new  line  at  the 


console.  The  compile  atom  continues 
as  it  prints  X  ->  Y,  where  X  is  the 
first  argument  (presumably  the  file¬ 
name  of  the  VALGOL  input  file)  and 
Y  is  the  second  argument  (the  ASM 
output  file). 

Next  the  compiler  deletes  all  facts 
about  “labelused”  (which  may  be  left 
from  a  previous  compile)  and  starts 
over  with  (labelused  0).  It  does  some¬ 
thing  similar  with  “last-shown,” 
which  keeps  track  of  how  much  of  the 
file  has  been  shown  on  the  console. 

Then  the  compiler  opens  the  input 
file  X  and  adds  a  fact  to  the  data  base 
so  that  later  it  can  find  the  name  of 
the  input  file.  Similarly,  it  opens  the 
output  file  Y.  It  skips  white-space 
characters  in  the  input  file,  starting 
at  the  beginning  and  ending  at  loca¬ 
tion  Z1  (x  in  Listing  Three).  Then  it 
finds  the  syntax  fact  to  tell  it  what  to 
start  compiling  with  and  feeds  that  to 
the  Q  routine,  which  carries  out  the 
actual  compile. 

Finally,  the  compiler  closes  the 
files  and  prints  out  a  concluding  mes¬ 
sage.  If  compilation  fails  for  some 
reason,  control  goes  to  the  second 
compile  clause,  and  it  prints  out  the 
error  message. 

Now  look  at  Q.  This  short  routine 
implements  the  recursive-descent 
compile,  according  to  the  code  in  the 
last  half  of  the  compiler.  The  first 
two  arguments  are  the  before  and  af¬ 
ter  file  positions  for  both  the  input 
and  output  files.  Whenever  Q  has  to 
backtrack,  the  files  automatically  are 
rewound  to  the  appropriate  point  for 
another  try. 

Notice  the  use  of  the  variable  Z 
(the  third  argument  of  Q)  as  the 
predicate  symbol  in  a  search  of  the 
data  base.  This  is  called  a  “meta¬ 
variable”  in  Micro-PROLOG:  the 
name  of  the  predicate  to  be  verified  is 
supplied  as  a  variable,  not  as  a  con¬ 
stant.  This  powerful  feature  of  Mi¬ 
cro-PROLOG  may  not  be  available  in 
other  Prologs.  The  XI  after  the  verti¬ 
cal  line,  which  indicates  the  rest  of 
the  list,  is  another  meta-variable.  The 
remainder  of  this  half  of  the  code 
comprises  various  subroutines  for  use 
in  the  compile. 

This  is  a  very  brief  description  of 
only  a  small  part  of  this  compiler.  A 
complete  understanding  requires  a 


Dr.  Dobb’s  Journal.  May  1985 


86 

391 


greater  familiarity  with  the  many 
features  and  quirks  of  the  Micro- 
PROLOG  package. 


Compiler  in  Prolog  (Text  begins  on  page  84) 

Listing  One 


Conclusion 

The  compiler  shown  in  Listing  Two  is 
written  in  the  standard  syntax  of  Mi- 
cro-PROLOG.  As  such,  it  should  run 
under  any  version  of  Micro-PRO- 
LOG.  I  would  be  interested  in  hearing 
from  readers  who  get  it  to  work  under 
other  versions.  (Of  course,  the  output 
would  still  be  in  8080  assembly 
language.) 

This  compiler  runs  rather  slowly. 
The  common  belief  is  that  interpreters 
are  slow,  and  this  is  an  interpreter 
within  an  interpreter.  The  10-line 
sample  compilation  in  Listing  One 
takes  about  30  sec  on  my  4  MHz  Z80. 
Because  I  wrote  the  compiler  primari¬ 
ly  as  a  programming  exercise,  I  have 
not  been  overly  concerned  with  a  rem¬ 
edy  for  the  speed  problem.  Probably 
the  bottleneck  occurs  in  the  input  rou¬ 
tines  that  process  one  character  at  a 
time.  For  a  faster  compiler,  routines 
such  as  match,  id,  or  number  would  be 
recoded  in  assembly  language.  The 
Micro-PROLOG  manual  has  instruc¬ 
tions  on  how  to  link  such  new  built-in 
functions  in  assembly  code  with  Mi¬ 
cro-PROLOG. 


Use  of  the  compiler 

B>PROLOG  LOAD  VALGOL 

Micro-PROLOG  3.1  for  RML  380/AB0Z  29/03/BA 
(c)  198^  Logic  Programming  Associates  Ltd. 
38517  Bytes  Free 

*&.?< (compile  "P.VAL"  "P . ASM" ) ) 

VALGOL  I  compiler  -  translates  VALGOL  to  ASM 
P.VAL  ->  P . ASM 

•  begin 
•integer  xj 
0  =!  xj 

•until  x  . =  15  .do 
.begin 

edit  ( l+14*x-x*x,  '  +  ')? 
print i 
x+1  =:  x 
.  end 

•  end 

**  Compilation  of  "P.VAL"  complete  **. 

*.GT. 


B>ASM  P.BBZ 

CP/M  ASSEMBLER  -  VER  2,0 
0  1F7 

001H  USE  FACTOR 
END  OF  ASSEMBLY 


B>LOAD  P 


FIRST  ADDRESS  0100 
LAST  ADDRESS  01F6 
BYTES  READ  00B9 
RECORDS  WRITTEN  02 

B>P 


References 

1.  W.  F.  Clocksin  and  C.  S.  Mellish, 
Programming  in  Prolog,  Springer- 
Verlag,  1981. 

2.  K.  L.  Clark  and  F.  G.  McCabe, 
Micro-PROLOG:  Programming  in 
Logic,  Prentice-Hall,  1984. 

3.  Lewis  Carroll,  Symbolic  Logic, 
4th  edition,  1896  (Reprinted  by  Do¬ 
ver  Publications,  1958). 

4.  D.  V.  Schorre,  “META  II,  a  syn¬ 
tax-oriented  compiler  writing  lan¬ 
guage,”  Proc.  1 9th  Nat.  Conf.  ACM, 
1964  (Reprinted  in  Dr.  Dobb’s  Jour¬ 
nal,  April  1980,  pp.  17-21;  listings 
pp.  22-25). 


DDJ 


♦  begin 

.integer  x  J 
o  =:  x  ; 

♦until  x  .=9  .do 
. begin 

edit  (  x*x  +  1  f  )  J 

print  J 
x  ♦  1  =i  x 
.  end 


End  Listing  One 


88 


Dr.  Dobb’s  Journal,  May  1985 


Compiler  in  Prolog  (Listing  Continued,  text  begins  on  page  84) 
Listing  Two 


VALGOL.LOG 


?(  (  /* 

VALGOL.LOG 

Conpiler  for  VALGOL.  I  --  ver  .  1.2 
Written  in  Micr o-F'ROLOG . 

(Tested  with  hicro-F'ROLOG  version  3.1  for  CP/h-80) 

Translates  VAL.GOL  I  to  ASM-coMpatible  8080  assenbly  language. 
Uses  file  SEEKs  for  backtracking* 


)  ) 


written  by?  G.  A.  EDGAR 

status  ? 

publ ic 

0.3 

SepteMber  9,  198*1 

M80 ,  Z80 

1.0 

SepteMber  1A,  198*1 

output  for  ASM 

1.1 

NoveMber  1,  198*1 

display 

input 

1 .2 

NoveMber  10,  198^ 

version 

for  DDJ 

Usage 

-  in  SIMPLE  syntax? 

*.  isCFOO.VAL"  conpile  "F00.AShM) 

-  in  standard  syntax* 

& . ? ( <  conp i le  "FOO.VAL"  "FOO . ASM" ) ) 


dona  i  n 


?<(  /* - Language  independent  part - )) 

?(<  /*  conpile*  input  X,  output  Y)) 

( (comp i le  X  Y ) 

(Message  *>  (P  x)  (PP)  (P  X  Y)  (PP)  (PP) 

(KILL  labelused)  (ADDCL  ((labelused  0))  ) 

(KILL  last-shown)  (ADDCL  ((last-shown  <-l|-l>>>  ) 

(OPEN  X)  (KILL  infile)  (ADDCL  ((infile  X))  ) 

(erase  Y)  (CREATE  Y)  (KILL  outfile)  (ADDCL  ((outfile  Y))  ) 
(skip  (  (0  |  0)  |  (0  |  0) )  Z1  )  (syntax  z)  (0  7.1  Z  z) 

(CLOSE  X)  (CLOSE  Y) 

(PP)  (PP  **  Comp ilation  of  X  coMplete  **)) 

( ( comp i le  X  Y ) 

(PP)  (PP  **  Syntax  error  xx) 

(CLOSE  X)  (CLOSE  Y>) 

((C  X  Y) 

(comp i le  X  Y  )  ) 

?((  /x  o:  find  it  in  the  language-specific  database)) 

((Q  X  Y  Z) 

(Z  |  XI)  (sequential  X  Y  |  XI)) 

( ( sequential  X  X ) ) 

((sequential  X  Y  (z|Z)|y) 

(z  X  XI | Z)  /  (sequential  XI  Y|y>> 

?((  /x  out*  send  a  line  to  outfile)) 

((out  (yllzl)  ( y 1 | z2 )  I  X) 

(outfile  Z)  (SEEK  Z  zl)  (outx  Z  I  X) 

( outx  Z  "*h*J" )  (SEEK  Z  z2 ) ) 

( ( outx  Z  )  ) 

((outx  Z  X  |  x) 

(W  Z  (X))  /  (outx  Z  |  x)) 

?((  /x  Match,  the  input  Matches  string  Z)) 

((Match  X  Y  Z) 

(STRINGOF  Zl  Z)  /  (natchx  X  X2  Zl)  (skip  X2  Y)) 

((Match*  X  Y  ( x | Z ) ) 

(inchar  X  XI  xl)  (EQ  x  xl>  /  (natch*  XI  Y  Z)) 

( (natchx  X  X  (  )  )  ) 

?( (  /*  eMptyS  Matches  autoMat iCal ly ) ) 

( (eMpty  X  X) ) 

?((  /x  Multiple?  Match  the  following  zero  or  More  tiMes)) 
((Multiple  X  Y | Z ) 

(once  X  X1|Z)  /  (Multiple  XI  Y|Z)) 

( (Multiple  X  XIZ) ) 

( (once  XX)) 

((once  X  Y  (z|Z) |y) 

(z  X  XI |Z)  /  (once  XI  Y|y) ) 

?((  /*  label?  generate  a  new  label)) 

((label  X  X  Y) 

(labelused  Yl)  (SUM  Y1  1  Y)  (KILL  labelused) 

(ADDCL  ((labelused  Y)>  )) 


Dr.  Dobb’s  Journal,  May  1985 


89 

393 


m  rruiug  (Listing  Continued,  text  begins  on  page  84) 

Listing  Two 


?((  /*  string!  Match  a  string  quoted  within  characters  «)) 
((string  X  Y  x  Z) 

/  (inchar  X  X2  xl )  (EG  x  xl)  / 

( stringx  XZ  X3  x  Zl)  (skip  X3  Y)  (STRINGOF  Z1  Z)) 

(  (stringx  X  Y  x  ( ) ) 

( inchar  X  Y  xl)  (EG  x  xl)  /  ) 

((stringx  X  Y  x  (y  I  Z)) 

(inchar  X  Xl  y)  /  (stringx  Xl  Y  x  Z)) 

?((  /*  id!  Match  an  identifier)) 

((id  X  Y  Z> 

( idx  X  X2  Zl)  (skip  X2  Y)  (STRINGOF  Zl  Z)> 

( ( idx  X  Y  ( x | Z ) ) 

(inchar  X  Xl  x)  (alphabetic  x)  /  (alphanuM  Xl  Y  Z)) 
((alphanuM  X  Y  (x|Z)) 

(inchar  X  Xl  x)  ( a 1 phanuner ic  x)  /  (alphanun  Xl  Y  Z)) 
((alphanuM  X  X  ())) 

?((  /*  nuMber !  Match  a  nuMber )  ) 

((nuMber  X  Y  Z) 

/  ( nuMber x  X  X2  Zl) 

(NOT  EG  Zl  ())  (skip  X2  Y)  (STRINGOF  Zl  Z)> 

(( nuMber x  X  Y  (x|Z)) 

(inchar  X  Xl  x)  (digit  x)  /  (nuMberx  Xl  Y  Z)) 

(( nuMber x  X  X  ())) 

( ( alphanuner ic  x) 

(alphabetic  x ) ) 

( ( alphanuner ic  x) 

(digit  x ) ) 

( ( alphabetic  x ) 

(LESS  "0"  x > (LESS  x  "C")) 

( (alphabetic  x) 

(LESS  x) (LESS  x  "C")) 

((digit  x ) 

(LESS  "/"  x) (LESS  x  " ! " ) ) 

?((  /*  skip!  skip  over  characters  listed  as  "skipchar " ) ) 

((skip  X  Y) 

(inchar  X  Xl  x)  (skipchar  x)  /  (skip  Xl  Y)) 

((skip  X  X)) 

( ( skipchar  "  "  ) ) 

(  (  skipchar  "*I"  )  ) 

( ( skipchar  "* J" ) ) 

( ( skipchar  "*M" ) ) 

?((  /*  inchar!  input  one  character!  show  on  console  first  tine)) 
((inchar  (yl|zl)  (y2|zl)  x) 

(infile  Y)  (SEEK  Y  yl)  ( RDCH  Y  x)  (SEEK  Y  y2)  (last-shown  y3) 
(IF  (before  y3  y2) 

((F‘  x)  (KILL  last-shown)  (ADDCL  ((last-shown  y2))  )) 

( )  )  ) 

?((  /*  before!  conpare  two  file  positions  )) 

((before  (yl|zl)  (yl|z2)> 

/  (LESS  zl  z2 ) ) 

((before  (yl|zl)  (y2|z2)) 

(LESS  yl  y2 ) ) 

?((  /*  erase!  erase  a  file!  no  failure)) 

( ( er  ase  Y ) 

(ERA  Y)  /) 

((erase  Y)) 

?((  /* - VALGOL  specifics -  )) 

((Message  "VALGOL  1  conpiler  -  translates  VALGOL  to  ASM")) 


((syntax  PROGRAM)) 

( (PROGRAM 

(natch  "•begin") 

( out 

"VCPM 

EQU 

0"  ) 

( out 

"VPDOS 

EQU 

5"  ) 

( out 

"VTPA 

EGU 

256"  ) 

( out 

"VCR 

EQU 

13"  ) 

<  out 

"VLF 

EGU 

10"  ) 

( out 

ORG 

VTPA") 

( out 

LXI 

SP ! VST  ACK 

(G  OPT- DECLARATION) 

(G  STATEMENT) 


92 


QQ  / 


Dr.  Dobb’s  Journal,  May  1985 


(Multiple 

(natch  " !") 

(Q  STATEMENT)) 


\  U  j  i  n  i  I-Iiun  1  /  / 

(natch  ".end")  (out 

jmp 

OCF'M" ) 

( out 

"VMULTJ 

•  > 

( out 

" 

MOV 

B,H"> 

( out 

MOV 

C,L"> 

( out 

XRA 

A"  ) 

( out 

" 

MOV 

H,  A"  ) 

( out 

MOV 

L .  A"  ) 

( out 

MVI 

A, 16") 

( out 

"VMULT1 : 

PUSH 

F’SW") 

( out 

" 

DAD 

H"> 

( out 

" 

XRA 

A") 

( out 

MOV 

A,C"  ) 

( out 

RAL"  ) 

( out 

" 

MOV 

C,A"> 

( out 

" 

MOV 

A ,  B"  > 

( out 

RAL") 

( out 

MOV 

B,A"> 

( out 

" 

JNC 

0MULT2" ) 

( out 

DAD 

D") 

( out 

"VMUL.T2  * 

POP 

PSM"  > 

( out 

H 

DCR 

A") 

( out 

ORA 

A"  ) 

( out 

" 

JNZ 

0MULT1" > 

( out 

RET") 

( out 

"vedit: 

') 

( out 

•• 

MOV 

A ,  H"  ) 

( out 

" 

ORA 

L") 

( out 

•• 

JZ 

0EDIT1" ) 

( out 

•• 

MVI 

A  .  '  ‘  "  ) 

( out 

•• 

CALL 

OCF'MOUT"  > 

( out 

•• 

DCX 

H") 

( out 

" 

JMP 

OEDIT") 

( out 

"VEDITl : 

POP 

H") 

( out 

"VEDIT2 ♦ 

MOV 

A ,  M"  ) 

( out 

" 

CPI 

0"  ) 

( out 

" 

I  NX 

H") 

( out 

" 

JZ 

0EDIT3" ) 

( out 

" 

CALL 

OCF’MOUT"  ) 

( out 

" 

JMP 

VEDIT2" > 

( out 

"VEDIT3 ♦ 

PUSH 

H") 

( out 

" 

RET") 

( out 

"SPRINT 

( out 

" 

MVI 

A, OCR") 

( out 

" 

CALL 

OCF'MOUT") 

( out 

" 

MVI 

A , OLF" ) 

( out 

" 

CALL 

OCF’MOUT") 

( out 

" 

RET") 

( out 

"vcpmout: " > 

( out 

PUSH 

H"  ) 

( out 

MOO 

E ,  A"  ) 

( out 

MOI 

C,2") 

( out 

CALL 

OBDOS") 

( out 

POP 

H") 

( out 

RET") 

( out 

DS 

60") 

( out 

"vstack: 

DM 

0"  ) 

( out 

•• 

END"  ) 

)  ) 

(  ( OPT-DECLARATION 
(Q  DECLARATION) 
( Match  "»")>) 

(  (OF’T-DECLARATION 
(enpty ) ) ) 


(  (DECLARATION 

(Match  ".integer") 

<Q  ID-SEQUENCE) 


(label  XI) 

(out  "  JMF'  0"  XI) 

(out  "0"  XI  "!")  )) 


( (ID-SEQUENCE 

(Q  IDENTIFIER) 

(Multiple 

(Match  " . " ) 

(Q  IDENTIFIER)  >>> 

( (IDENTIFIER 

<id  Z>  (out  Z  "0!  DS  Z")  )) 

( (STATEMENT 

(Q  10-STATEMENT >  > ) 

( (STATEMENT 

(Q  ASSIGNMENT-STATEMENT))) 

( (STATEMENT 

( Q  UNTIL-STATEMENT ) ) ) 

( (STATEMENT 

(Q  CONDITIONAL-STATEMENT))) 

( (STATEMENT 

( Q  BLOCK  > ) ) 


93 


Dr.  Dobb’s  Journal.  May  1985 


IOC 


(natch  ".begin") 

<0  DECL-OR-ST) 
(Multiple 

(Match  "I") 

(Q  STATEMENT)  ) 
(Match  ".end")  )) 
((BLOCK 

(Match  ".begin") 
(Match  ".end")  )) 

((DECL-OR-ST 

(0  DECLARATION))) 

( (DECL-OR-ST 

(0  STATEMENT))) 

( ( IO-ST ATEMENT 

(Match  "edit") 
(Match  "(“> 

(0  EXPRESSION) 
(Match  " . " ) 


(siring  " ' "  Z) 

( out 

"  CALL 

DEDIT") 

( Match  " ) " )  ) ) 

(  (10-STATEMENT 

( out 

"  DB 

Z  "',0") 

(watch  "print") 

( out 

"  CALL 

DPRINT")  )) 

( (CONDITIONAL-STATEMENT 

(Match  ".if") 

(0  EXPRESSION) 

(label 

XI)  (label  X2 ) 

(Match  ".then") 

( out 

"  MOV 

A  #  H"  > 

( out 

"  ORA 

L") 

(0  STATEMENT) 

( out 

"  JZ 

V"  XI) 

(Match  ".else") 

( out 
( out 

"  JMP 

"V"  XI  "J") 

V"  X2 ) 

(0  STATEMENT) 

( out 

"V"  X2  "!")  )) 

( (UNTIL-STATEMENT 

(Match  ".until") 

(0  EXPRESSION) 

( label 
( out 

XI)  (label  X2) 
"V"  XI  "?") 

(Match  ".do") 

( out 

"  MOD 

A ,  H"  ) 

( out 

"  ORA 

L") 

( out 

"  JNZ 

D"  X2 ) 

<0  STATEMENT) 

( (ASSIGNMENT-STATEMENT 
(0  EXPRESSION) 

(Match  "=*"> 

( out 
( out 

"  JMP 

"V"  X2  "!")  )> 

D"  XI) 

(id  Z) 

( (EXPRESSION 

(Q  EXPRESSI0N1 ) 

(Q  OPT-RIGHT-SIDE ) )  ) 

( out 

"  SHLD 

"  Z  "D")  ) 

( (OPT-RIGHT-SIDE 

(Match  "♦=") 

(label 

XI)  (label  X2> 

( out 

"  PUSH 

H") 

( Q  EXPRESSI0N1 ) 

( out 

"  POP 

D"  ) 

( out 

"  MOD 

A ,  L"  ) 

( out 

"  SUB 

E") 

( out 

"  JNZ 

V"  X2 ) 

( out 

"  MOD 

A ,  H"  ) 

( out 

"  SBB 

D") 

( out 

"  JNZ 

V"  X2 ) 

( out 

"  LXI 

H  » 1 "  > 

( out 
( out 

"  JMP 

"D"  X2  "!"> 

V"  XI) 

( (OPT-RIGHT-SIDE 
( ewp ty ) ) ) 

( (EXPRESSION1 
<  Q  TERM) 

(Multiple 

(Q  SIGNED-TERM) )  )  ) 

( out 
(out 

"  LXI 

"D"  XI  "!")  )) 

H,0") 

( (SIGNED-TERM 

(watch  "+"> 

( out 

"  PUSH 

H") 

(G  TERM) 

( out 

"  POP 

D") 

( out 

"  DAD 

D"  )  )  ) 

94 


Dr.  Dobb’s  Journal,  May  1985 


( (SIGNED-TERM 

(natch  "-,l> 
(G  TERM ) 


(  (TERM 

(G  PRIMARY) 

( Multiple 

(natch  "*"> 
(Q  PRIMARY) 


( (PRIMARY 
(id  Z) 

( (PRIMARY 

(nunber  Z) 


( out  " 

PUSH 

H"  > 

( out 

POP 

D") 

( out 

MOU 

A,E") 

( out  " 

SUB 

L") 

(out  " 

MOD 

L ,  A"  ) 

( out  " 

MOD 

A  ,  D"  ) 

(out 

SBB 

H"  ) 

(out  " 

MOD 

H , A" >  )> 

( out  " 

PUSH 

H"  > 

(out  " 

POP 

D"  ) 

(out  " 

CALL 

UMULT" )  >>> 

(out  " 

L.HLD 

"  Z  "U"))) 

( out  " 

LXI 

H,"  Z))) 

( (PRIMARY 

( natch  " ( " ) 

(G  EXPRESSION) 
( natch  ")")  ) ) 


End  Listing  Two 


Compiler  in  Prolog 

Listing  Three 

(Listing  Continued,  text  begins  on  page  84) 

Part  of  VALGOL.LOG  in  SIMPLE  syntax 

X  coMpile  Y  if 

Message  (Z)  3nd 
P  (Z>  and 
PP  and 
PP  and 

KILL  (label used)  and 

add  ((0  label used))  and 

KILL  (last-shown)  and 

add  <<(— 1|— 1)  last-shown))  and 

OPEN  (X)  and 

KILL  (infile)  and 

add  ((X  infile))  and 

erase  (Y)  3nd 

CREATE  (Y)  and 

KILL  (out file)  and 

add  ((Y  outfile))  and 

skip  (  (  (  0  |  0  )  0|0)  x)  and 

syntax  (y)  3nd 

GO  (G  x  z  y)  3nd 

CLOSE  (X)  and 

CLOSE  (Y)  and 

PP  and 

PP  (***  CoMpilation  of  X  conplete  **) 

X  cowpile  Y  if 
PP  and 

PP  (**  Syntax  error  and 

CLOSE  (X)  and 
CLOSE  (Y) 

X  alphanuneric  if 
X  alphabetic 
X  alphanuMeric  if 
X  digit 

X  alphabetic  if 

0  LESS  X  and 
X  LESS  C 
X  alphabetic  if 

'  LESS  X  and 
X  LESS  C 
X  digit  if 

/  LESS  X  and 

x  LESS  i  End  Listing  Three 


Dr.  Dobb's  Journal.  May  1985 


95 

397 


Listing  Four 


P.ASM 


VCPM 

EQU 

0 

VBDOS 

EQU 

S 

VTPA 

EQU 

256 

VCR 

EQU 

13 

VLF 

EQU 

10 

ORG 

VTF'A 

LXI 

SP.VSTACK 

JMP 

VI 

«v: 

DS 

2 

vi : 

LXI 

H  t  0 

SHLD 

mV 

V2! 

LHLD 

mV 

PUSH 

H 

LXI 

H  *  15 

POF 

D 

MOV 

A,L 

SUB 

E 

JNZ 

V5 

MOV 

A,  H 

SBB 

D 

JNZ 

V5 

LXI 

H,  1 

JMP 

V4 

vs: 

LXI 

H » 0 

v't: 

MOV 

A ,  H 

ORA 

L 

JNZ 

V3 

LXI 

H,  14 

PUSH 

H 

LHLD 

mV 

POP 

D 

CALL 

VMULT 

POP 

D 

DAD 

D 

PUSH 

H 

LHLD 

mV 

PUSH 

H 

LHLD 

mV 

POP 

D 

CALL 

VMULT 

POP 

D 

MOV 

A  t  E 

SUB 

L 

MOV 

L ,  A 

MOV 

A,D 

SBB 

H 

MOV 

H ,  A 

CALL 

VEDIT 

DB 

'«• '  >o 

CALL 

VPRINT 

LHLD 

mV 

PUSH 

H 

LXI 

H  >  1 

POP 

D 

DAD 

D 

SHLD 

mV 

JMP 

V2 

V3S 

JMP 

VCPM 

VMULT ! 

MOV 

B  ;  H 

MOV 

C,L 

XPA 

A 

MOV 

H » A 

MOV 

L ,  A 

MUI 

A, 16 

VMULT 1 

!  PUSH 

PSW 

DAD 

H 

XRA 

A 

MOV 

RAL 

A  >  C 

MOV 

C,A 

MOV 

PAL 

A ,  B 

MOV 

B  >  A 

JNC 

VMULT2 

DAD 

B 

VMULT2S 

POP 

PSW 

DCR 

A 

ORA 

A 

JNZ 

RET 

VMULT 1 

VEDIT! 

MOV 

A,H 

ORA 

L 

JZ 

VEDIT 1 

MV  I 

A,  '  7 

CALL 

VCPMOUT 

OCX 

H 

JMP 

VEDIT 

VEDIT 1  : 

POP 

H 

VEDIT2 ! 

MOV 

A ,  M 

CPI 

0 

INX 

H 

JZ 

VEDIT3 

CALL 

VCPMOUT 

JMP 

VEBIT2 

VEDIT3 ! 

PUSH 

RET 

H 

vprint: 

MVT 

A  ,  VCR 

CALL 

VCPMOUT 

MVI 

A,VLF 

CALL 

RET 

VCPMOUT 

vcpmgut 

PUSH 

H 

MOV 

E;  A 

MVI 

C ,  2 

CALL 

VBDOS 

POP 

RET 

H 

DS 

60 

vstack: 

DW 

END 

0 

End  Listings 


96 


Dr.  Dobb's  Journal.  May  1985 


Toolworks  C/80  and  Tool¬ 
works  C/80  Mathpack, 
Version  3.1 

Company:  Software  Toolworks, 
15233  Ventura  Blvd.,  Suite 
1118,  Sherman  Oaks,  CA 
91403 

Computer:  Heath;  Kaypro  II,  4, 
and  10;  Osborne  1  and  the 
Executive;  DEC  VT - 1 80  and 
Rainbow;  Xerox  820 
Price:  Compiler,  $49.95; 

Mathpack,  $29.95 
Circle  Reader  Service  No.  101 
Reviewed  by  D.  C.  Shoemaker 

Few  readers  of  Dr.  Dobb's  Journal 
are  unaware  of  the  contribution 
made  to  small  computer  system  pro¬ 
grammers  by  Ron  Cain  and  his  mini¬ 
mal  C  subset  compiler.  Over  the 
years  since  its  introduction,  there 
have  been  many  extensions  and  im¬ 
provements,  all  building  on  his  basic 
idea.  One  of  the  best  and  most  com¬ 
plete  versions  is  from  Walt  Bilofsky’s 
Software  Toolworks.  First  released  in 
1981,  Version  3.1  is  the  most  current 
and  comes  the  closest  to  being  a  com¬ 
plete  C  compiler. 

This  software  is  a  bargain  at  the 
price.  The  package  comes  in  two 
parts,  sold  separately:  the  C  compiler 
itself  is  $49.95,  and  the  C/80  Math- 
pack  is  $29.95.  There’s  a  reason  for 
the  way  the  software  is  packaged  and 
priced.  Those  just  learning  C  proba¬ 
bly  have  no  need  for  the  FLOAT  and 
LONG  data  types  that  the  Mathpack 
provides.  Adding  these  features 
somewhat  increases  the  size  of  the  li¬ 
braries  and  the  assembled  code.  If 
you  need  them,  Mathpack  is  a  bar¬ 
gain-level  entry  into  other  more  pow¬ 
erful  capabilities  given  by  the  addi¬ 
tional  data  types.  Therefore,  you 
don’t  have  to  pay  for  something  you 
don’t  need,  but  you  aren’t  limited  to 


an  entry-level  compiler  that  can’t  do 
serious  work. 

C/80  is  a  complete  implementa¬ 
tion  of  the  C  language  described  by 
Brian  Kernighan  and  Dennis  Ritchie 
in  The  C  Programming  Language 
(Prentice-Hall,  1978)  with  the  excep¬ 
tion  of  the  following  features: 

•  FLOAT  and  LONG  data  types 
(available  in  the  C/80  Mathpack) 

•  DOUBLE  data  type 

•  typedef 

•  Arguments  to  #define  macros 

•  #line 

•  Declarations  within  nested  blocks 

•  Bit  fields 

Whether  or  not  these  omissions  are 
significant  depends  a  great  deal  on 
what  you  want  to  do  with  the  soft¬ 
ware.  If  you  want  to  learn  the  lan¬ 
guage  and  get  a  whiff  of  program¬ 
ming  in  C,  the  C/80  package  is 
sufficient.  If  you  want  to  write  some 
fast-running  utility  software  and 
don’t  want  to  invest  the  time  in  as¬ 
sembly  language,  these  omissions  will 
cause  no  problems. 

C/80  supports  structures,  statics, 
initialization,  casts,  compile  time 
evaluation  of  constant  expressions, 
and  all  the  other  C  language  features. 
It’s  worth  mentioning  some  of  the 
features  that  C/80  does  provide, 
however,  because  they’re  often  lack¬ 
ing  in  other  subsets: 

•  Unix-style  I/O  redirection 

•  Conventional  C  I/O  and  string 
library 

•  Formatted  and  random  access  file 
I/O 

•  Dynamic  storage  allocation 

•  Runtime  execution  profile 
capability 

•  In-line  assembly  language 
capability 


In  addition,  C/80  can  generate 
source  code  compatible  with  Macro- 
80  and  RMAC.  Should  you  have  nei¬ 
ther,  a  serviceable  assembler  comes 
with  the  C/80  package.  It  works  well 
and  does  the  job  with  no  fuss. 

If  you  have  a  version  of  C/80  earli¬ 
er  than  3.0,  here  are  the  main 
changes  in  version  3.1: 

•  An  expanded  runtime  library 

•  ROMable  code 

•  RMAC  compatibility 

•  A  menu-configurable  compiler 

•  \  at  the  end  of  a  line  for 
continuation 

•  #ifneed  for  selective  compilation 

•  True  alloc/free 

•  Command  line  wildcard  expansion 

•  CP/M  files  now  written  in  128-byte 
records 

•  File  0  always  the  terminal 

The  changes  from  version  3.0  to  3.1 
are  to  correct  minor  bugs  and  speed 
code  generation.  Some  new  compiler 
switches  have  been  added,  and  you 
are  now  prohibited  from  reading 
from  and  writing  to  the  same  open 
file.  If  you  have  one  of  the  earlier  ver¬ 
sions  of  C/80,  return  the  original  disk 
to  Software  Toolworks  with  $10.00, 
and  they  will  give  you  the  most  recent 
version.  I  bought  my  first  copy  of  C/ 
80  in  1981  and  have  updated  it  twice. 

Software  Toolworks  makes  the  C/ 
80  package  available  in  a  variety  of 
formats.  Walt  Bilofsky  was  one  of  the 
first  to  support  Heath’s  in-house  disk 
operating  system  HDOS,  and  he  still 
does.  However,  the  standard  remains 
CP/M,  and  Software  Toolworks  pro¬ 
vides  disks  for  Heath,  both  hard  and 
soft  sector;  Kaypro  II,  4,  and  10;  Os¬ 
borne  I  and  the  Osborne  Executive; 
DEC’s  VT-180  and  the  Rainbow;  the 
Xerox  820;  and  the  standard  8-inch 
CP/M  single-density  format.  There’s 


Dr.  Dobb's  Journal.  May  1985 


98 

399 


even  a  version  for  the  Epson  QX-10. 
Currently  no  version  is  offered  for  the 
IBM  PC,  PCjr,  or  any  MSDOS, 
PCDOS,  or  ZDOS  computer  because 
the  programs  are  written  in  8080 
code,  but  I  expect  that  will  change. 
Incidentally,  I’m  using  the  CP/M  ver¬ 
sion  on  a  Heath  HI 20  running  CP/ 
M-85  with  no  trouble  at  all.  Just  or¬ 
der  the  soft-sector  Heath  version. 

Using  C/80  is  simple.  The  hard 
part  (as  usual)  is  writing  the  original 
C  language  program.  Then  you  call 
C/80  and  let  it  compile  the  source 
into  8080  assembly  language  code. 
Let’s  use  one  of  the  test  files  on  the 
distribution  disk,  HELLO. C.  We  do 
this  by  typing  “C80  HELLO.”  The 
output  file  from  C/80  can  be  modi¬ 
fied  if  you  wish.  I’m  not  the  greatest 
assembly  language  programmer,  so  I 
don’t  change  the  code,  but  someone 
more  experienced  might  be  able  to 
tighten  it  up  in  some  areas. 

Next,  call  the  assembler  of  your 
choice.  I  use  the  one  that  comes  with 
the  package.  It’s  invoked  as  you 
might  expect,  by  typing  “AS  HEL¬ 
LO.”  When  the  assembly  is  finished, 
you  have  an  executable  file  called 
HELLO.COM.  It  will  be  rather  large, 
due  to  the  inclusion  of  pieces  from  the 
I/O  and  other  libraries,  but  that’s 
part  of  the  tradeoff.  The  completed 
code  executes  relatively  quickly.  I 
don’t  have  the  capability  to  do  much 
comparative  benchmarking,  but 
comparisons  I’ve  seen  suggest  that  in 
some  cases  C/80  is  a  bit  faster  than 
BDS  C  and  in  others  a  bit  slower.  On 
balance  they  seem  about  equal,  ex¬ 
cept  that  BDS  C  has  some  nonstan¬ 
dard  features  that  get  in  the  way  of 
portability,  and  it  costs  twice  what 
C/80  costs.  BDS  C  doesn’t  provide  as 
many  formats,  either. 

Software  Toolworks  doesn’t 
charge  a  license  fee  for  the  COM  files 
created  by  C/80.  This  means  that 
you’re  free  to  market  whatever  you 
write  and  compile  as  you  see  fit.  Un¬ 
like  some  of  the'so-called  “big  boys” 
of  the  software  industry,  Bilofsky  en¬ 
courages  programmers  to  market 
software  using  his  tools. 

A  word  about  documentation:  If 
you’re  a  first-time  C  user,  the  manual 
that  accompanies  C/80  won’t  be  too 
helpful.  There  are  some  examples  in 


the  documentation  that  you  can  play 
with,  and  several  source  code  files  on 
the  distribution  disks,  but  that’s  it  for 
introductory  material.  Several  good 
books  on  the  market  can  help  you  get 
a  better  grasp  of  the  language.  The 
documentation  lists  eight  of  the  most 
common  of  these,  and  new  books  ap¬ 
pear  almost  monthly. 

The  C/80  manual  is  50  pages  long 
with  a  three-page  index.  It  has  16 
chapters,  with  most  of  the  attention 
paid  to  a  language  summary,  a  de¬ 
scription  of  the  I/O  and  runtime  li¬ 
braries,  and  a  discussion  on  running 
the  compiler.  The  manual  includes  a 
brief  discussion  of  the  internal  fea¬ 
tures  of  the  compiler  and  useful 
tricks  to  bend  the  compiler  to  your 
will.  There  is  also  a  detailed  list  of 
compiler  error  messages,  what  they 
mean,  and  what  to  do  about  them. 

The  C/80  Mathpack  requires  C/80 
version  3.0  or  greater.  If  you’ve  been 
working  with  an  earlier  verson  of  C/ 
80,  the  Mathpack  is  a  good  reason  to 
take  advantage  of  the  Software  Tool¬ 
works’  update  policy.  Mathpack 
comes  on  one  disk  with  a  short  man¬ 
ual.  The  Mathpack  adds  true  32-bit 
LONG  and  FLOAT  data  types  to  the 
C/80  compiler  and  runtime  library.  It 
includes  a  program  to  patch  the  C/80 
compiler  to  recognize  LONG  and 
FLOAT,  a  runtime  library  to  perform 
32-bit  arithmetic,  routines  to  convert 
between  ASCII  and  floating-point 
representation,  an  augmented  printf, 
and  a  transcendental  library  written 
in  C. 

In  practical  terms,  this  gives  the 
C/80  a  32-bit  signed  number  capa¬ 
bility  in  the  range  -2,147,483,648  to 
2,147,  483,  647.  Floating-point  num¬ 
bers  have  24-bit  precision,  which 
equates  to  about  seven  decimal  digits, 
and  an  exponent  range  of  10  to  the 
plus  or  minus  38th  power. 

Installing  the  Mathpack  requires 
only  that  you  run  the  modifying  pro¬ 
gram  CCONFIGF.COM.  A  menu  will 
appear  with  a  range  of  options. 
Choose  option  N  and  press  RETURN. 
This  changes  FLOAT  and  LONG 
from  not  available  to  available.  Now 
type  a  Y  in  response  to  the  question 
about  making  changes  permanent, 
and  you’re  done.  Everything  else 
about  C/80  remains  as  before,  except  ! 


that  the  size  of  the  compiled  and  as¬ 
sembled  code  increases  a  bit. 

The  Mathpack  manual  is  only  10 
pages  long  with  six  chapters;  it 
spends  most  of  its  time  on  installation 
and  additions  to  the  function  library. 
You’ll  have  to  know  how  to  make  use 
of  FLOAT  and  LONG  before  the 
Mathpack  will  do  you  much  good. 

In  summary,  if  you  want  to  learn  C 
or  if  you  already  know  a  bit  about  the 
language  and  want  an  inexpensive 
but  powerful  implementation  for 
your  CP/M  computer,  my  first  choice 
would  be  the  Software  Toolworks  C/ 
80  and  an  introductory  book.  If  you 
want  an  enhanced  32-bit  capable  C, 
add  the  Mathpack.  For  under  $80.00 
you  can’t  miss. 

Disk  Maker  I 

Company:  New  Generation  Sys¬ 
tems,  1800  Michael  Faraday 
Drive,  Suite  206,  Reston,  VA 
22090 

(703)  471-5598, 

(800)  368-3359 
Operating  System:  CP/M  2.2 
Price:  $1695.00 
Circle  Reader  Service  No.  103 
Reviewed  by  Jim  Kronman 

Disk  Maker  I  from  New  Generation 
Systems  offers  a  practical  solution  to 
the  virtual  Tower  of  Babel  that  exists 
in  the  world  of  CP/M  and  MSDOS 
disk  formats.  Acting  as  a  peripheral 
device  in  an  S-100  system,  Disk  Mak¬ 
er  I  can  format,  read,  write,  and  du¬ 
plicate  over  170  disk  formats  (at  the 
time  of  this  review — the  total  grows 
every  month  or  two),  allowing  you  to 
copy  virtually  any  soft-sectored  for¬ 
mat  and  transfer  disk  files  between 
various  CP/M  and  MSDOS  disk  for¬ 
mats.  An  optional  file  transfer  utility 
program  transfers  files  from  Dec- 
Mate  II  and  Wang  OIS  word  process¬ 
ing  disk  formats. 

The  standard  Disk  Maker  I  pack¬ 
age  consists  of  an  S-100  disk  control¬ 
ler  board  capable  of  supporting  up  to 
four  disk  drives,  a  48  track-per-inch 
(tpi),  double-sided  5'/4-inch  disk  drive 
with  power  supply  in  a  fan-cooled 
cabinet,  and  the  Disk  Maker  soft¬ 
ware.  Other  disk  drives  for  Disk 
Maker  are  a  96  tpi  5 '4-inch  drive,  a 


99 


Dr.  Dobb's  Journal,  May  1985 


135  tpi  3 '/2-inch  drive,  or  a  96  tpi  1.2 
Mb  IBM  PC/AT  drive  (as  used  in  the 
IBM  PC/AT)  installed  in  the  cabinet 
with  the  48  tpi  drive.  A  double-sided 
double-density  (DSDD)  8-inch  drive 
with  its  power  supply  in  a  separate 
cabinet  is  also  available.  Software  op¬ 
tions  are  the  word  processing  transfer 
utilities  and  disk  drive  test  software. 

If  you  don’t  have  an  S-100  system, 
do  not  stop  reading:  New  Generation 
Systems  also  offers  a  stand-alone 
Disk  Maker  II  system.  It  is  a  6  MHz, 
Z80,  64K  CP/M  2.2  system  that  in¬ 
cludes  one  DSDD  8-inch  drive  and 
one  48  tpi,  DSDD  5'/4-inch  drive  plus 
the  same  software  provided  with  Disk 
Maker  I.  You  can  add  the  same  op¬ 
tions  described  for  Disk  Maker  I,  up 
to  a  total  of  four  drives;  a  10  Mb  hard 
disk  option  is  also  available.  Having 
reviewed  the  manual  for  the  Disk 
Maker  II  (but  not  having  used  the 
equipment),  I  am  reasonably  confi¬ 
dent  that  my  remarks  about  Disk 
Maker  I  will  be  applicable  to  the  op¬ 
eration  of  Disk  Maker  II  as  well. 

The  Disk  Maker  I  controller  uses 
the  Western  Digital  1795  controller 
chip,  allowing  the  software  to  read  or 
write  disks  in  all  170-plus  formats 
shown  in  the  Table  on  page  101  (if 
you  have  the  appropriate  drives,  of 
course).  Disk  Maker  I  cannot  accom¬ 
modate  some  disk  formats  such  as  Ap¬ 
ple,  NorthStar,  Micropolis,  Commo¬ 
dore,  and  Victor;  these  formats  either 
are  hard-sectored  or  depend  on  special 
tricks  in  the  disk  controller  hardware. 

When  you  have  the  optional  96  tpi 
5 'A-inch  drive  installed,  the  software 
allows  you  to  set  the  96  tpi  drive  as  a 
read-only  drive  for  a  48  tpi  format; 
this  allows  you  to  transfer  files  from 
one  5'/4-inch  disk  to  another  without 
having  to  do  an  intermediate  copy. 
With  the  two  5'A-inch  drives  set  to 
the  same  format,  rapid  disk  duplica¬ 
tion  is  possible  using  the  DUPE  utility 
program.  New  formats  are  added 
regularly,  and  New  Generation  Sys¬ 
tems  offers  each  upgrade  to  current 
users  for  only  $50. 

Programs 

The  Disk  Maker  I  software  includes 
these  programs: 

DMINSTAL.COM — sets  user-deter¬ 


mined  and  system-dependent  options 
DMSET.COM— loads  Disk  Maker 
BIOS  into  the  system 
DMFORM.COM — formats  disks 
WHATDISK.COM — determines  ex¬ 
act  format  of  IBM,  Z-100,  or  Morrow 
disk 

MC.COM — Mass  Copy,  a  file  copy 
program 

DUPE.COM — rapid  track-for-track 
copy  program  with  optional  verify 
DMCOMP.COM — rapid  track-for- 
track  compare  program 
TOMS.COM— CP/M  <  =  >  PC  DOS 
file  exchange  utility 

Various  public  domain  utilities  such 
as  SWEEP  and  D  normally  are  pro¬ 
vided  on  the  distribution  disk  as  space 
permits.  Two  or  more  versions  of 
DMSET  and  DMFORM  are  provided 
to  accommodate  various  hardware 
peculiarities. 

The  optional  software  is: 

DDD.COM — disk  alignment  program 
used  with  special  disks 
DEC2CPM — utility  to  transfer  files 
from  DecMate  II  word  processor  to 
CP/M 

WANG2CPM — utility  to  transfer 
files  from  Wang  OIS  word  processor 
to  CP/M 

Requirements 

Disk  Maker  I  is  designed  to  operate 
under  standard  CP/M  2.2  with  an 
8080,  8085,  or  Z80  CPU.  New  Gener¬ 
ation  Systems  recommends  using  it 
with  a  standard  CP/M  system  only, 
but  I  run  it  with  CP/M  2.2  and  ZCPR, 
version  1,  without  any  problems.  The 
controller  board  and  Disk  Maker  soft¬ 
ware  normally  use  ports  98  to  9F 
(hex),  but  New  Generation  Systems 
provides  software  and  hardware  mod¬ 
ification  instructions  to  relocate  the 
ports  to  other  addresses  if  you  have  an 
unresolvable  conflict  in  your  system. 

Although  a  64K  system  is  recom¬ 
mended  for  Disk  Maker  I,  it  will  run 
in  as  little  as  48  K.  The  more  disks  you 
attach  to  the  Disk  Maker,  the  more 
memory  the  software  requires.  When 
you  use  the  TOMS  utility,  a  smaller 
TPA  means  that  you  may  not  be  able 
to  handle  a  full  directory  on  an 
MSDOS  disk;  my  57K  system  will 
handle  the  maximum.  Double-density 


8-inch  formats  and  the  IBM  PCAT 
drive  require  at  least  a  4  MHz  system 
clock;  otherwise,  a  2  MHz  clock 
should  be  sufficient. 

Installation  of  the  Disk  Maker  I 
hardware  is  as  easy  as  plugging  the 
disk  controller  board  into  an  empty 
slot  in  your  S-100  bus  motherboard 
and  connecting  the  disk  cable  be¬ 
tween  the  disk  cabinet  and  the  con¬ 
troller  board.  A  menu-driven  instal¬ 
lation  program  allows  you  to 
customize  some  features  of  the  sys¬ 
tem  to  your  hardware  and  personal 
taste;  the  installation  process  takes 
only  a  few  minutes. 

How  It  Works 

Disk  Maker  I  is  simple  to  use.  To  for¬ 
mat  a  disk,  type  DMFORM  to  invoke 
the  format  program,  then  choose  a 
format  by  number  from  the  on-screen 
menu.  You  can  also  invoke 
DMFORM  by  making  a  format 
choice  on  the  command  line;  the  pro¬ 
gram  proceeds  without  showing  you 
the  menu.  You  can  return  to  the 
menu  if  you  want  to  change  your  se¬ 
lection.  DMFORM  will  verify  a  disk 
after  formatting  if  you  set  a  perma¬ 
nent  option  with  DMINSTALL  or 
specify  verification  on  the  command 
line  when  you  invoke  DMFORM. 

The  DMSET  program  gives  you  ac¬ 
cess  to  the  Disk  Maker  disk  drives. 
You  use  DMSET  the  same  way  you 
use  DMFORM:  you  can  choose  from 
a  menu  or  go  directly  to  work  by 
specifying  a  format  on  the  command 
line.  You  can  set  each  Disk  Maker 
drive  to  a  different  format.  Every 
time  you  warm  boot,  the  Disk  Maker 
software  displays  the  disk  formats 
you  have  selected. 

DMSET  installs  a  special  BIOS  un¬ 
der  your  normal  CP/M  system;  you 
can  run  any  CP/M  program  that  will 
fit  in  the  somewhat  reduced  memory 
left  for  applications.  Even  though 
there  is  less  memory,  I  have  found  it 
adequate  for  running  any  of  my  utili¬ 
ties,  dBASE  II  or  my  text  editor 
(which  does  bidirectional  scrolling 
through  a  disk  file).  You  probably 
will  notice  the  memory  reduction  the 
most  when  you  run  a  spreadsheet 
program  or  any  other  application 
that  uses  all  available  memory  but 
does  not  scroll  data  to  and  from  disk, 


Dr  Dobb's  Journal,  May  1985 


100 

401 


Sanyo  1200/50  96(DSDD)  620K 
Sanyo  4050  96(DSDD)  620K 
Vector  4-S  96(DSDD)  712K 
ALTOS  8"(SSDD)  446K 
CCS  (1024b)  8"( SSDD)  596K 
Colonial  Data  SB80  8"(DSDD)1208K 
CompuPro  (1024b)  8"(SSDD)  596K 
CP/M  SSSD  Standard  8"(SSSD)  241K 
Harris  1685-50MFT  8" (SSDD)  596K 
Harris  1685-50MFT  8"(DSDD)1196K 
IBM  5520  WP  8“(DSDD)1132K 
IBM  256b  no  skew  8" (SSDD)  482K 
Megaflex  Apple  8" (SSDD)  558K 
NEC  APC  CP/M-86  8H(DSDD)  980K 
S.  D.  Sales  (256b)  8"(SSDD)  476K 
TRS-80  11,12/6  P&T  8" (SSDD)  596K 
TRS-80  Lifeboat  8" (SSDD)  482K 
Vector  2800  8"(DSDD)  984K 
Zenith  Z-100  8" (DSDD)  980K 
HP-150  MSDOS  3" (SSDD)  258K 
IBM  PC  PCD0S1 . 1 ( SSDD)  156K 
IBM  PC  PCD0S2 .0( SSDD )  175K 
IBM  PC-AT  PCD0S3.0(DSDD)1186K 
Wang  MSDOS  (DSDD)  354K 
BurroughsB25  MSDOS  96( DSDD )  610K 
Eagle  1600  MSDOS  96( DSDD)  785K 
NCR  D-M  "M"  MSDOS  96 (DSDD)  790K 
Sanyo  MBC  MSDOS  96 (DSDD)  792K 
Lomas  Data  MSDOS  8" (SSSD)  239K 
Standard  MSDOS  8" (SSSD)  246K 


Sanyo  2000  96 (SSDD)  30?K 
TeleVideo  1603  96 (DSDD)  706k 
Vector  VSX  96(DSDD)  710K 
CCS  (256b)  8" (SSDD)  482K 
Colonial  Data  SB80  8" (SSDD)  596K 
Columbia  1800  8" (SSDD)  592K 
CompuPro  (1024b)  8" ( DSDD ) 1 192K 
Delta  Products  125  8"(DSDD)1212K 
Harris  1685-50MFT  8" (DSDD)  968K 
IBM  3740  TO  Direct  8“ (SSSD)  248K 
IBM  128b  no  skew  8"(SSSD)  241K 
Insight  IQ-120  8" (SSDD)  484K 
Molecular  8" (SSDD)  496K 
S.  D.  Sales  (128b)  8"(SSDD)  464K 
Tarbell  (128b)  8"(SSDD)  472K 
TRS-80  11,12/6  P&T  8"(DSDD)1210K 
TRS-80  Lifeboat  8“ (SSDD)  596K 
Zenith  Z-100  8" (SSDD)  482K 
ACT  Apricot  MSDOS  3" (SSDD)  314K 
Intertec  H-S  MSDOS  3" (SSDD)  395K 
IBM  PC  PCD0S1 . 1 ( DSDD )  315K 
IBM  PC  PCD0S2.0(DSDD)  354K 
Kaypro  10  MSDOS  (DSDD)  354K 
Zenith  ZDOS  l.l(DSDD)  315K 
DEC  Rainbow  MSDOS  96 (SSDD)  384K 
Monroe  MSDOS  96(DSDD)  712K 
Otrona  MSDOS  96(DSDD)  712K 
Tandy  2000  MSDOS  96 (DSDD)  714K 
NEC  APC  MSDOS  8"(DSDD)1221K 
Zenith  VI. 1  ZDOS  8" (SSSD)  245K 


NOTE:  all  formats  5  1/4"  48  tpi ,  CP/M-80/86  unless  noted  as: 
3"  -  3  1/2  inch  format 

8"  -  8  inch  format 

96  -  96  tpi  51/4  inch  format 

SS  =  Single  Sided;  DS  =  Double  Sided 
SD  =  Single  Density;  DD  =  Double  Density 

Table 

Disk  Maker  I  Formats 


the  way  many  text  editors  do. 

Because  of  the  radically  different 
structure  of  MSDOS  files,  you  need  a 
special  program  called  TOMS  (TO 
MSDOS)  to  access  individual  disk  files 
on  a  MSDOS  disk.  You  cannot  run  an¬ 
other  program  while  using  TOMS;  if 
you  want  a  CP/M  application  to  ac¬ 
cess  a  MSDOS  data  file,  you  first  must 
transfer  the  file  to  a  CP/M  format. 
TOMS  is  like  a  small  part  of  the 
MSDOS  operating  system,  providing 
the  commands  DIR,  COPY,  TYPE, 
and  ERASE.  TOMS  cannot  deal  with 
subdirectories,  so  all  files  must  be  in 
the  root  directory  on  a  MSDOS  disk 
coming  into  the  system;  all  files  writ¬ 
ten  by  TOMS  are  in  the  root  directory. 


Performance 

I  have  been  using  Disk  Maker  I  for 
well  over  a  year.  The  capabilities  of 
the  system  are  impressive,  but  even 
more  impressive  is  the  support  New 
Generation  Systems  provides.  I  was 
an  early  user  and,  as  you  might  ex¬ 
pect,  everything  did  not  start  out 
working  perfectly.  One  annoying 
problem  was  caused  by  the  routing  of 
the  data  cable  in  the  disk  drive  cabi¬ 
net.  The  cable  was  picking  up  noise 
from  the  power  supply,  which  gave 
me  errors  when  reading  the  inner 
tracks  of  the  48  tpi  drive.  I  solved  the 
problem  by  changing  the  routing  of 
the  cable  at  the  suggestion  of  New 
Generation  Systems,  but  in  the 


meantime,  I  received  a  replacement 
48  tpi  drive  that  had  been  specially 
aligned  and  certified.  I  have  always 
received  courteous  and  competent  as¬ 
sistance  anytime  I  have  had  a  reason 
to  call  New  Generation  Systems  with 
a  problem  or  a  question.  You  can  ex¬ 
pect  the  same  treatment.  (I  was  deal¬ 
ing  with  New  Generation  Systems  as 
just  another  user,  before  I  had 
thought  of  doing  this  review.) 

The  Disk  Maker  I  hardware  con¬ 
sists  of  off-the-shelf  components  inte¬ 
grated  by  New  Generation  Sys¬ 
tems — nothing  “razzle-dazzle”  but 
obviously  chosen  carefully  for  quality 
and  value.  What  makes  the  Disk 
Maker  I  package  outstanding  is  the 


101 

402 


Dr.  Dobb's  Journal.  May  1985 


Apricot  CP/M-86 

3"  (SSDD) 

306K 

Intertec  Headstart 

3" (SSDD) 

1 

372K 

Access 

(SSDD) 

169K 

Actrix  Matrix 

(DSDD) 

348K 

AMPRO 

(SSDD) 

188K 

AMPRO 

(DSDD) 

386K 

Bal cones  BRAVO 

(DSDD) 

346K 

Bondwel 1  Model  14/16  (DSDD) 

346K 

BYAD 

(SSDD) 

154K 

Casio  FP  1000/1100 

(DSDD) 

300K 

Compuview  CP/M-86 

(SSDD) 

193K 

Cromemco 

(SSSD) 

81K 

Cromemco 

(SSDD) 

188K 

Cromemco  C-10 

(DSDD) 

386K 

Davidge  1024 

(DSDD) 

368K 

DEC  VT180 

(SSDD) 

169K 

Digilog 

(DSDD) 

332K 

Eagle  I 

(SSSD) 

70K 

Epson  QX-10  CP/M 

(DSDD) 

378K 

Epson  QX-10  MF 

(DSDD) 

278K 

Fujitsu  Micro  16s 

(DSDD) 

314K 

Heath  H-37 

(SSDD) 

148K 

Heath  H-37 

(DSDD) 

304K 

Heath  w/CDR  Systems 

(DSDD) 

380K 

Heath  w/Magnolia 

(SSDD) 

162K 

HP  86/87/125 

(DSDD) 

248K 

IBM  PC  (CP/M-86) 

(SSDD) 

154K 

IBM  PC  (CP/M-86) 

(DSDD) 

314K 

ICL  PCI  10,30,31,32 

(DSDD) 

254K 

Insight  Dev.  IQ-120  (SSDD) 

146K 

KayPro  II 

(SSDD) 

191K 

KayPro  4/10 

(DSDD) 

394K 

Lobo  Max-80 

(SSDD) 

164K 

Lobo  Max-80  35tk 

(SSDD) 

142K 

Lobo  Max-80  35tk 

(DSDD) 

296K 

Microbee 

(DSSD) 

1 7 1 K 

Microbee 

(DSDD) 

386K 

Molecular  9X 

(DSDD) 

338K 

Morrow  Micro  Dec  I/II(SSDD) 

186K 

Morrow  Micro  Dec  III  (DSDD) 

384K 

Morrow  Micro  Dec  11 

(DSDD) 

384K 

MultiTech  MIC-501 

(SSDD) 

168K 

NCR  Decision  Mate  V 

(DSDD) 

304K 

NEC  PC-8000  Series 

(SSDD) 

169K 

NEC  PC-8000  Series 

(DSDD) 

300K 

Osborne 

(SSSD) 

90K 

Osborne  DD/Executive  (SSDD) 

183K 

Otrona  Attache 

(DSDD) 

360K 

PMC  101  (CP/M  3.0) 

(DSDD) 

386K 

Sanyo  1000/1100/1150  (DSDD) 

310K 

SD  Sales 

(SSSD) 

7  IK 

SD  Sales 

(SSDD) 

112K 

Sharp  3540 

(DSDD) 

292K 

SuperBrai n 

(SSDD) 

162K 

SuperBrai n 

(DSDD) 

340K 

Systel  III 

(DSDD) 

346K 

TeleVideo  802/3/6 

(DSDD) 

340K 

TeleVid.  806  TurboDos(DSDD) 

396K 

TI  Professional 

(SSDD) 

154K 

Toshiba  T100 

(DSDD) 

254K 

TRS-80  Mod  I  Omikron  (SSSD) 

7  OK 

TRS-80  Mod  I  Omikron  (SSDD) 

1 3  IK 

TRS-80  Mod  III  MM 

( SSDD ) 

186K 

TRS-80  Mod  IV 

(SSDD) 

154K 

TRS-80  IV  Montezuma 

(SSDD) 

166K 

Versa  Floppy  II 

(DSDD) 

278K 

Xerox  820 

(SSSD) 

82K 

Xerox  820 

(SSDD) 

155K 

Zenith  Z-100 

(SSDD) 

148K 

Zenith  Z-100 

(DSDD) 

304K 

Zenith  Z-100  (orig) 

(DSDD) 

304K 

Zorba 

(DSDD) 

388K 

ALTOS  586 

96 (DSDD) 

700K 

AMPRO 

96 (SSDD) 

386K 

AMPRO 

96 (DSDD) 

782K 

Analyzer 

96 (DSDD) 

620K 

Archives  I 

96( SSDD) 

386K 

Archives  II 

96 ( DSDD ) 

786K 

Associate  + 

96( DSDD) 

786K 

CompuPro  10 

96(DSDD) 

772K 

CompuStar  40 

96(DSDD) 

786K 

DEC  Rainbow 

96 (SSDD) 

386K 

Digilog  DBS  16 

96( DSDD) 

780K 

Direct  0A1000 

96 (DSDD) 

622K 

Di scovery 

96( DSDD ) 

624K 

Eagle  I I/I V 

96 ( SSDD ) 

386K 

Eagle  III 

96 (DSDD) 

762K 

Epic 

96(DSDD) 

782K 

Facit  DTC 

96( SSDD ) 

308K 

Heath  H-37 

96 (SSDD) 

308K 

Heath  H-37 

96 (DSDD) 

624K 

Honeywel 1 

96 (DSDD) 

632K 

ICL  Model  25 

96 (DSDD) 

628K 

ICL  Model  35 

96(DSDD) 

778K 

IMS  (TurboDos) 

96 (DSDD) 

772K 

Intel  iPDS 

96(DSDD) 

608K 

Ithaca  525 

96 (SSDD) 

346K 

Ithaca  525 

96(DSDD) 

628K 

Lobo  Max-80 

96 (DSDD) 

698K 

MACSYM  150/350 

96 (SSDD) 

308K 

Monroe  0C8820 

96( SSDD ) 

306K 

Monroe  2000 

96  (DSDD) 

628K 

Multi  Tech  MIC-504 

96 (DSDD) 

698K 

NCR  Work  Saver 

96 (DSDD) 

544K 

Olympia  People 

96 (DSDD) 

618K 

OSM  Zeus  4 

96( DSDD ) 

620K 

Otrona 

96 (DSDD 

770K 

Philips  P2000C 

96  DSDD 

628K 

Philips  3000 

96 (SSDD) 

294K 

Pied  Piper 

96(DSDD ) 

776K 

Table 

Disk  Maker  1  Formats 

( Continued  on  next  page) 

Dr.  Dobb’s  Journal,  May  1985 


1Q3 

403 


software  and  support.  The  programs 
perform  well  and  are  easy  to  use.  The 
no-nonsense  operator  interface  was 
designed  for  users  who  need  to  get 
things  done  quickly. 

Although  the  software  is  not 
friendly  in  the  current  sense  of  having 
elaborate  menus  and  on-line  help  fa¬ 
cilities,  each  user  prompt  and  pro¬ 
gram  response  is  clear  in  its  meaning. 
With  one  exception,  which  I  will  dis¬ 
cuss  later,  all  errors  are  trapped 
cleanly  and  appropriate  error  mes¬ 
sages  appear  on  the  screen.  The  user 
interface  is  appropriate  for  the  in¬ 
tended  users  of  this  package:  soft¬ 
ware  professionals,  experienced  com¬ 
puter  users,  and  serious  hobbyists 
with  a  need  to  transfer  files  between 
diverse  disk  formats.  Operation  is 
simple  enough  for  a  novice  to  use  the 
system,  too. 


The  Disk  Maker  I  (and  Disk  Mak¬ 
er  II)  documentation  is  well  orga¬ 
nized  and  appropriate  for  the  intend¬ 
ed  users.  The  User’s  Manual  for  Disk 
Maker  I  contains  over  50  pages  with 
a  good  table  of  contents  in  a  three- 
ring  binder.  The  table  of  contents  is 
adequate  for  using  the  manual:  no  in¬ 
dex  is  provided  and  none  is  needed. 
The  manual,  prepared  with  a  word 
processor,  is  much  cleaner  in  appear¬ 
ance  than  most  manuals  prepared 
this  way.  It  is  relatively  easy  to  flip 
through  the  pages  and  spot  whatever 
you  are  looking  for. 

New  Generation  Systems  has  as¬ 
sumed  that  you  already  know  how  to 
use  CP/M  and  your  computer.  The 
manual  will  say  “copy  this  file  to  an¬ 
other  disk”  rather  than  give  a  blow- 
by-blow  description  of  how  to  use  the 
file  copy  utility.  There  is  a  section  ti¬ 
tled  “How  To  Run  Disk  Maker  With¬ 
out  Reading  The  Manual”  for  the  im¬ 
patient  and  for  the  technical-minded  a 
section  on  each  program  called  “How 
xxxx  Works”  that  explains  the  inner 
workings  of  the  program. 

I  have  validated  Disk  Maker’s  abili¬ 
ty  to  format,  read,  or  write  disks  in 
about  20  of  the  170  available  formats. 
I  have  used  Disk  Maker  primarily  to 
move  files  between  an  S-100  system 
and  an  Osborne  I  (double  density)  or 
between  the  S-100  system  and  a 
PCDOS  system.  Other  than  the  read 
errors  I  encountered  (now  fixed),  the 
Disk  Maker  hardware  and  software 
have  performed  flawlessly  for  me. 

I  have  encountered  a  few  instances 
where  it  has  been  necessary  to  bulk 
erase  a  disk  and  format  it  with  Disk 
Maker  I  because,  if  I  wrote  on  a  disk 
already  formatted  by  the  receiving 
system,  that  system  could  not  read 
the  disk. 

Although  Disk  Maker  I  is  easy  to 
run,  one  enhancement  would  be  wel¬ 
come.  The  Disk  Maker  I  software  as¬ 
sumes  you  know  what  disk  format 
you  are  dealing  with.  If  you  tell  it  you 
are  feeding  it  a  disk  of  format  X  and 
then  give  it  a  disk  of  format  Y,  you 
may  or  may  not  get  an  error  message; 
the  program  you  run  could  go  merrily 
along  without  complaint,  producing 
either  garbage  files  or  a  disk  that  the 
target  system  cannot  read.  The  Disk 
Maker  software  cannot  identify  posi¬ 


tively  that  you  have  inserted  the  cor¬ 
rect  disk  format  in  the  drive  being 
written  to  because  no  standard  means 
of  identification  exists  for  a  blank 
formatted  disk;  even  if  you  determine 
the  sector  size,  sectors  per  track,  and 
so  forth,  you  still  cannot  deduce  the 
directory  size  and  other  characteris¬ 
tics  needed  for  positive  identification. 

WHATDISK  enables  you  to  deter¬ 
mine  which  of  several  similar  formats 
a  disk  might  be,  if  you  already  know 
that  the  disk  is  for  IBM  PCDOS, 
Heath/Zenith  Z-100,  or  Morrow 
Micro  Decision.  A  generalized 
WHATDISK  program  to  identify  any 
disk  format  would  be  very  useful. 
Such  a  program  is  impossible  to 
write,  however,  for  the  above  reasons. 

What  You  Pay 

The  basic  Disk  Maker  I,  which  in¬ 
cludes  the  controller  board,  one  48 
tpi,  DSDD  5'A-inch  drive,  and  the 
Disk  Maker  software  is  $1695.  The 
96  tpi,  DSDD  drive  is  an  additional 
$395.  The  8-inch  drive  and  cabinet 
for  Disk  Maker  I  is  $849.  The  Sc¬ 
inch  drive  costs  $295,  and  the  PCAT 
drive  is  $495.  The  word  processing 
transfer  utilities  package  is  $295,  and 
the  disk  testing  software  is  $150  plus 
$40  each  for  the  48  and  96  tpi  Dysan 
test  disks.  You  also  can  purchase  the 
Disk  Maker  I  controller  and  software 
with  no  drives. 

The  basic  Disk  Maker  II  system  is 
$3395  for  the  stand-alone  6  MHz 
Z80B  system  with  one  DSDD  8-inch 
drive,  one  48  tpi,  DSDD  5'/4-inch 
drive,  and  CP/M  2.2;  you  only  need  to 
add  a  terminal.  The  option  prices  are 
the  same  as  for  Disk  Maker  I,  and  a 
second  DSDD  8-inch  drive  costs  $600. 

Are  these  prices  reasonable?  It  de¬ 
pends  on  what  you  need.  Programs 
for  the  Kaypro,  IBM  PC,  Morrow, 
and  other  computers  that  allow  you 
to  read,  write,  and  format  a  number 
of  5'A-inch  formats  cost  less  than 
$100,  but  don’t  ask  them  to  recognize 
an  8-inch  disk  drive,  a  PCAT  drive,  or 
a  3 '/2-inch  drive.  At  the  other  end  of 
the  spectrum  are  systems  that  claim 
less  capability  than  Disk  Maker  II 
and  cost  over  $5000.  Disk  Maker  I 
and  Disk  Maker  II  offer  outstanding 
versatility  and  performance  for  the 
money. 


104 

404 


Dr.  Dobb's  Journal,  May  1985 


Conclusion 

Disk  Maker  I  is  a  solid  product 
backed  by  a  competent  and  coopera¬ 
tive  company  that  operates  as  if  it 
wants  to  remain  in  business  for  a  long 
time.  If  you  are  a  software  developer, 
software  distributor,  user  group, 
large  office,  or  serious  hobbyist  with 
the  need  to  exchange  files  between 
computers  with  various  disk  formats, 
you  should  seriously  consider  Disk 
Maker  I  or  Disk  Maker  II  as  an  an¬ 
swer  to  your  needs. 

AMPRO  Little  Board  and 
Bookshelf  Computers 

Company:  AMPRO  Computers, 
Inc.,  67  East  Evelyn  Ave., 
Mountain  View,  CA  94041 
(415)  962-0230. 

Price:  $349 

Circle  Reader  Service  No.  105 
Reviewed  by  Richard  Conn 

The  heart  of  the  AMPRO  Bookshelf 
Computer®  is  the  AMPRO  Little 
Board®,  a  full-featured  single-board 
computer  that  measures  only  Sc¬ 
inch  by  7%-inch  and  is  designed  for 
mounting  on  the  back  of  a  standard 
5 '/4-inch  minifloppy  disk  drive.  Al¬ 
though  it  is  small  in  size,  the  AMPRO 
Little  Board  is  big  in  features:  the 
Little  Board  contains  all  of  the  hard¬ 
ware  necessary  to  support  a  conven¬ 
tional  CP/M  environment. 

The  Little  Board  is  based  around  a 
4  MHz  Z80A  microprocessor  and  the 
associated  Zilog  Z80  family  of  sup¬ 
port  chips.  On  board  this  small  com¬ 
puter  is: 

•  A  4  MHz  Z80A  microprocessor 

•  64K  of  dynamic  RAM 

•  One  4K  2732-type  EPROM 

•  A  Z80A  CTC  (Counter/Timer 

Circuit) 

•  A  Z80  DART  (Dual  Asynchronous 

Receiver/Transmitter) 

•  A  parallel  output  port  (based  on  D- 

type  latches) 

•  A  Western  Digital  1770  Floppy 

Disk  Controller  Chip 

The  AMPRO  Bookshelf  Computer 
is  an  AMPRO  Little  Board  computer 
housed  in  an  attractive  case  with  one 
or  two  5!4-inch  floppies,  built-in 


power  supply,  two  25-pin  E1A  RS- 
23  2C  connectors,  one  Centronics  con¬ 
nector,  and  a  connector  for  tagging  on 
up  to  two  more  5'A-inch  floppies.  An 
ON/OFF  switch  and  a  RESET  switch 
are  available  on  the  front  panel. 

The  AMPRO  Little  Board 
Computer 

Hardware 

The  Little  Board  has  two  possible 
memory  configurations,  which  depend 
on  the  setting  of  an  EPROM-enable 
bit  in  the  Board  Control  Register.  En¬ 
abling  the  EPROM  establishes  the  fol¬ 
lowing  memory  configuration: 

Address  (Hex)  Memory  Element 
0000  -  OFFF  2732  EPROM 
1000-  7FFF  2732  EPROM 

duplicated  on  each 
4K  group 

8000  -  FFFF  RAM 

If  the  EPROM  is  disabled,  all  memo¬ 
ry  from  0  to  0FFFFH  is  enabled  as 
RAM. 

A  Z80  DART  and  RS-232C  line 
drivers  provide  the  serial  input/out¬ 
put  ports.  The  DART  is  an  asynchro¬ 
nous  device,  providing  for  baud  rates 
up  to  38,400  on  its  A  channel  and 
9600  on  its  B  channel  in  the  Little 
Board  configuration.  The  Little 
Board  does  not  implement  all  of  the 
RS-232C  signals  on  its  two  serial 
channels.  Only  the  following  signals 
are  provided: 

•  Serial  Data  Output  (RS-232C  pin 

3) 

•  Serial  Data  Input  (RS-232C  pin  2) 

•  Request  to  Send  Handshaking 

Output  (RS-232C  pin  5) 

•  Clear  to  Send  Handshaking  Input 

(RS-232C  pin  20) 

Both  channels  of  the  DART  are 
wired  as  DCE  (Data  Communica¬ 
tions  Equipment),  which  is  the  com¬ 
plement  of  DTE  (Data  Terminal 
Equipment) — the  wiring  of  the  RS- 
232C  connector  on  computer  termi¬ 
nals.  Notably  missing  from  this  list  is 
the  Data  Carrier  Detect  signal.  You 
may  use  the  Clear  to  Send  signal  in 
place  of  Data  Carrier  Detect. 

The  parallel  port  is  an  output-only 


port,  wired  in  a  Centronics-standard 
configuration.  The  Z80  CTC  on  the 
Little  Board  provides  four  counter/ 
timers  for  use  in  conjunction  with  the 
hardware  and  software  of  the 
computer. 

The  Western  Digital  1770  Floppy 
Disk  Controller  chip  used  in  the  Lit¬ 
tle  Board  design  can  work  with  just 
under  400K  per  disk  on  double-sided, 
48  tracks-per-inch  (TPI)  drives  and 
just  under  800K  per  disk  on  double¬ 
sided,  96  TPI  drives. 

Software 

The  AMPRO  Little  Board  is  supplied 
with  one  copy  of  CP/M  2.2,  which 
uses  the  ZCPR3  Command  Processor 
in  place  of  the  CP/M  CCP.  Although 
only  the  ZCPR3  Command  Proces¬ 
sor,  in  a  less  than  maximum  configu¬ 
ration,  is  provided  with  the  Little 
Board,  you  can  obtain  a  more  com¬ 
plete  ZCPR3  implementation  from 
Echelon  (see  below). 

The  ZCPR3  configuration  distrib¬ 
uted  with  the  Little  Board  provides 
CP/M  2.2  compatibility  while  simul¬ 
taneously  presenting  the  following 
additional  features: 

•  Multiple  commands  per  line,  sepa¬ 

rated  by  semicolons,  enabling 
command  lines  like  DIR;ERA 
*.BAK;DIR 

•  Automatic  command  search  path 

that  searches  through  directo¬ 
ries  in  the  following  sequence 
when  looking  for  COM  files:  ( 1 ) 
current  disk,  current  user;  (2) 
current  disk,  user  0;  (3)  disk  A, 
current  user;  (4)  disk  A,  user  0; 
(5)  disk  A,  user  1 5;  and  (6)  cur¬ 
rent  disk,  user  1 5 

•  Four-element  shell  stack,  so  shells 

other  than  the  ZCPR3  Com¬ 
mand  Processor  can  act  as  the 
user  interface  to  the  system 

•  Several  built-in  commands,  includ¬ 

ing:  GET — load  file  anywhere 
in  memory;  JUMP — call  sub¬ 
routine  anywhere  in  memory; 
and  LIST — print  file  on  printer 

•  Extended  directory  references  over 

normal  CP/M:  D: — reference 
disk  (e.g.,  TYPE  B:M  YFILE- 
.TXT);  U: — reference  user  area 
(e.g.,  DIR  5:);  and  DU: — refer¬ 
ence  disk  and  user  area  (e.g., 


Dr.  Dobb’s  Journal,  May  1985 


106 

405 


ERA  B7:*.*) 

See  articles  in  Dr.  Dobb’s,  Com¬ 
puter  Language ,  Byte ,  and  other  such 
magazines  for  more  information  on 
ZCPR3.  The  authorized  agent  for 
ZCPR3  is  Echelon,  Inc.,  101  First  St., 
Los  Altos,  CA  94022  (415)  948- 
3820.  The  ZCPR3  RCP/M  and  BBS 
are  also  available  to  you  at 
(415)489-9005. 

The  nine  standard  CP/M  utility  pro¬ 
grams  (ED,  DDT,  PIP,  etc.)  and  thir¬ 
teen  additional  utility  programs  are 
supplied  with  the  Little  Board.  Includ¬ 
ed  in  this  list  is  MULTI  DSK,  which  al¬ 
lows  the  user  to  dynamically  select  one 
drive  to  support  any  one  of  over  40  dif¬ 
ferent  5 ‘/4-inch  floppy  disk  formats, 
and  48TPI,  which  allows  the  user  to 
read  48  TPI  disks  on  a  96  TPI  drive. 

One  nice  option  that  AMPRO  gives 
the  customer  is  that  of  purchasing  the 
source  code  to  several  key  programs 
at  a  reasonable  price.  The  customer 
may  purchase  the  Technical  Support 
Software  package,  which  includes 
the  source  files  for  MULTIDSK,  the 
BIOS,  and  the  BOOT. 

Pricing 

The  AMPRO  Little  Board  computer 
costs  $349.  This  price  includes  the  sin¬ 
gle-board  computer  and  all  of  the  soft¬ 
ware  listed  above.  An  extra  $50  buys 
the  Technical  Support  Software 
package. 

The  AMPRO  Bookshelf  Computer 

Hardware 

As  mentioned  above,  the  AMPRO 
Bookshelf  Computer  consists  of  an 
AMPRO  Little  Board,  one  or  two  514- 
inch  minifloppy  disk  drives,  a  built-in 
power  supply,  two  25-pin  EIA  RS- 
232C  connectors,  one  Centronics  con¬ 
nector,  one  connector  for  attaching  up 
to  two  external  minifloppy  disk  drives, 
and  ON/OFF  and  RESET  switches  on 
the  front  panel.  The  Bookshelf  is 
available  in  the  following  models: 

Model  Configuration _ _ 

121  One  double-sided, 

48  TPI  drive  (400K) 

122  Two  double-sided, 

48  TPI  drives  (800K) 

141  One  double-sided, 


96  TPI  drive  (800K) 

142  Two  double-sided, 

96  TPI  drives  (1600K) 

Software 

All  of  the  software  provided  with  the 
AMPRO  Little  Board  is  also  provided 
with  the  AMPRO  Bookshelf  Comput¬ 
er.  In  addition,  a  ZCPR3  shell  de¬ 
signed  specifically  for  use  on  the  AM¬ 
PRO  computer  is  provided  (in  object 
code  only).  FRIENDLY  is  a  screen- 
oriented  tool  that  provides  a  directory 
of  files  to  the  user  of  the  current  disk 
and  allows  him  or  her  to  perform  a 
variety  of  operations  on  the  files  dis¬ 
played.  By  simply  moving  a  pointer 
to  a  desired  file  and  pressing  a  few 
keys,  a  FRIENDLY  user  can  copy  a 
file,  delete  a  file,  view  a  file  on  the 
console,  print  a  file  on  the  printer, 
run  a  program,  mark  a  file  for  later 
delete  or  copy  with  a  group,  perform 
a  sequence  of  commands  on  the  file 
based  upon  a  preprogrammed  menu, 
and  perform  many  more  operations. 

FRIENDLY  is  easy  to  use,  usually 
requiring  only  a  few  minutes  to  learn, 
and  it  removes  casual  computer  users 
from  the  CP/M  command  environ¬ 
ment,  relieving  them  of  the  necessity 
of  learning  how  to  use  CP/M  in  order 
to  use  the  computer. 

A  second  software  package  provid¬ 
ed  with  the  AMPRO  Bookshelf  is  T/ 
MAKER,  a  product  of  T/MAKER 
Company.  T/MAKER  .provides  a 
screen-oriented,  memory-based  edi¬ 
tor,  text  formatter,  electronic  spread¬ 
sheet,  bar  graph  generator,  and  file 
management  system  in  one  integrat¬ 
ed  package.  T/MAKER  will  run  un¬ 
der  conventional  CP/M,  CP/M  en¬ 
hanced  by  the  ZCPR3  Command 
Processor,  or  FRIENDLY.  Several  ar¬ 
ticles  have  already  been  published  on 
T/MAKER,  and  you  can  obtain  fur¬ 
ther  information  by  writing  to  T/ 
MAKER  Company,  P.O.  Box  6430, 
Falls  Church,  VA  22046. 

Documentation 

The  documentation  provided  with  the 
AMPRO  Little  Board  is  the  Little 
Board  User’s  Manual,  a  mainly 
hardware  manual  that  describes  the 
Little  Board  in  great  detail.  It  is  well 
written  and  clear  if  you  have  a  back¬ 
ground  in  digital  electronics. 


No  documentation  on  CP/M  or  the 
standard  CP/M  programs  is  provided. 
The  first  pages  of  the  manual,  howev¬ 
er,  give  pointers  to  where  to  obtain 
this  documentation.  Also,  no  docu¬ 
mentation  other  than  a  brief  overview 
of  ZCPR3  is  provided.  Again,  the 
manual  gives  pointers  to  Echelon. 

If  you  buy  the  AMPRO  Bookshelf 
Computer,  you  get  the  Little  Board 
User's  Manual  as  well  as  documenta¬ 
tion  on  FRIENDLY  and  T/MAKER. 
The  documentation  on  FRIENDLY 
and  T/MAKER  is  oriented  to  comput¬ 
er  users  having  some  limited  experi¬ 
ence  with  CP/M. 

General  Impressions  and 
Comments 

I  have  used  the  AMPRO  Bookshelf 
for  several  months  now.  I  started 
with  the  Model  122,  which  had  two 
400K,  48  TPI  drives,  and  am  now  us¬ 
ing  the  Model  142,  which  has  two 
800K,  96  TPI  drives.  I  find  its  clean 
design,  speed,  and  reliability  to  be  ex¬ 
cellent.  Given  my  background  in  CP/ 
M  and  digital  electronics,  I  found  all 
of  the  documentation  on  the  Little 
Board/ Bookshelf  to  be  readable  and 
understandable. 

The  Bookshelf  is  a  nice  computer 
for  any  user,  especially  with  the 
FRIENDLY  shell  as  a  front  end.  Al¬ 
though  it  is  a  good  choice  for  the  first¬ 
time  computer  user,  as  with  any  com¬ 
puter,  you  must  overcome  a  learning 
curve  before  you  can  expect  effective 
use  of  the  computer. 

The  AMPRO  BIOS  performs  well. 
The  speed  of  the  disks  with  this  BIOS 
is  nice:  the  disks  are  faster  than  most 
5!4-inch  disk  systems  I  have  observed 
and  many  8-inch  disk  systems  I  have 
used.  At  800K  per  disk  with  the  Mod¬ 
el  142  (96  TPI  drives),  I  find  the  disk 
capacity  also  to  be  quite  reasonable. 

Overall,  the  AMPRO  Bookshelf 
and  AMPRO  Little  Board  are  good 
computers,  but  there  are  a  few  draw¬ 
backs  for  some  applications: 

(1)  Lack  of  documentation  on  CP/M 
and  ZCPR3.  However,  AMPRO  sup¬ 
plies  pointers  to  where  you  can  obtain 
such  documentation,  at  additional 
cost. 

(2)  Limitation  of  serial  I/O  ports  to 
only  two  serial  RS-232C  ports  and 
one  parallel  port.  Nevertheless,  if 


107 


Dr.  Dobb's  Journal,  May  1985 


your  needs  call  for  no  more  than  a 
terminal,  modem,  and  printer  (as 
many  popular  computers  are  config¬ 
ured  today),  the  AMPRO  computers 
are  adequate. 

(3)  Lack  of  hard  disk  support  and  ex¬ 
pandability.  The  Little  Board  does 
not  support  hard  disks  or  other  such 
devices  in  its  current  form.  You  may 
buy,  however,  the  Little  Board  SCSI/ 
PLUS  Adapter  for  $99.  This  adapter 
“piggy-backs”  on  the  Little  Board 
and  allows  you  to  attach  a  Winches¬ 
ter  disk  drive,  slave  processor  and  de¬ 
vice  boards  (perhaps  for  I/O),  and 
other  facilities.  This  board,  with 
some  effort,  may  eliminate  draw¬ 
backs  2  and  3. 

I  am  excited  about  the  Little  Board 
and  the  Bookshelf  for  their  applica¬ 
tions  in  several  environments.  As  a 
stand-alone  personal  computer  with 
limited  I/O  resources  and  disk  space, 
these  computers  are  good  choices. 
The  SCSI/PLUS  Adapter  promises  to 
expand  their  capabilities. 

For  embedded  computer  applica¬ 
tions  in  which  4K  of  code  is  enough  to 
run  the  application  program,  the  Lit¬ 
tle  Board  provides  a  nice,  inexpensive 
self-contained  computer.  The  4K 
2732  EPROM  can  be  programmed  for 
the  application,  and  32K  of  RAM  is 
available  for  data.  The  two  RS-232C 
ports  are  there,  as  well  as  the  parallel 
output-only  port.  No  disks  would  be 
needed  for  this  type  of  application. 
For  embedded  computer  applications 
with  disks,  the  AMPRO  Little  Board 
would  “piggy-back”  on  a  5'A-inch  96 
TPI  minifloppy  and  feed  off  of  the 
floppy’s  power  supply.  This  would 
provide  a  very  small  embedded  com¬ 
puter  with  800K.  of  disk  and  64K  of 
RAM. 

As  a  slave  computer  to  another 
computer,  the  AMPRO  Bookshelf 
with  its  two  disks  could  act  as  a  back¬ 
ground  batch  processor,  communica¬ 
tions  controller  (for  a  BBS),  printer 
spooler,  or  other  such  intelligent  and 
flexible  slave  device. 

Overall,  I  like  the  AMPRO  Little 
Board  and  AMPRO  Bookshelf  and  in¬ 
tend  to  continue  using  my  Bookshelf 
for  a  variety  of  slave  computer  and 
stand-alone  applications  (such  as 
computer  assistance  for  talks  and 
presentations).  I  recommend  it  with 

Dr  Dobb's  Journal,  May  1985 


some  reservation  to  first-time  com¬ 
puter  users  and  with  no  reservation  to 
embedded  applications  designers  and 
more  experienced  computer  users. 


BetterBASIC,  Version  1.1 

Company:  Summit  Software, 
P.O.  Box  99,  Babson  Park, 
Wellesley,  M A  02157 
(617)235-0729 

Computer:  IBM  PC  or  close  com¬ 
patible  with  minimum  192K 
and  one  disk  drive 
Price:  BetterBASIC  $199;  8087 
Math  Module  $99;  Runtime 
System  $250 

Circle  Reader  Service  No.  107 
Reviewed  by  Matthew  Trask 

Among  the  reasons  that  Summit  Soft¬ 
ware  gives  for  purchasing  BetterBA¬ 
SIC  are  full  640K  memory  support, 
separately  compiled  program  mod¬ 
ules,  language  extensibility,  window 
support,  8087  support,  and  incremen¬ 
tal  compilation.  Although  these  rea¬ 
sons  would  be  persuasive  in  the  case  of 
another  programming  language,  I 
question  whether  they  will  induce 
many  programmers  to  shell  out  $199, 
the  cost  of  BetterBASIC,  when  some 
form  of  GW  BASIC  (aka  BASICA)  is 
provided  at  no  charge  with  most  I BM 
type  computers  these  days  and  an  ex¬ 
cellent  Pascal  compiler  can  be  pur¬ 
chased  for  less  than  fifty  dollars.  In 
order  to  help  programmers  make  a  de¬ 
cision  on  this  purchase,  I  will  discuss 
the  unique  features  of  this  program¬ 
ming  system,  benchmark  its  perfor¬ 
mance,  and  examine  the  difficulties  of 
converting  programs  from  GW  BASIC 
to  BetterBASIC. 

Installation 

Although  the  package  can  be  run 
right  out  of  the  box  by  using  its  de¬ 
fault  configuration,  you  will  probably 
want  to  configure  it  to  suit  your  hard¬ 
ware  environment.  B.COM  is  the  exe¬ 
cutable  portion  of  BetterBASIC  and  it 
uses  the  file  B.CNF  to  determine 
which  of  the  other  modules  to  load  for 
operation.  Table  1  (page  1 10)  is  a  list¬ 
ing  of  all  the  modules  that  are  provid¬ 
ed  on  the  BetterBASIC  disk  and  Table 
2  (page  1 10)  shows  the  default  config¬ 
uration.  An  ASCII  editor  can  be  used 


to  add  or  delete  modules  and  create 
new  configurations.  For  example,  us¬ 
ers  that  don’t  have  a  graphics  card 
may  wish  to  delete  GRAPHICS.IBM 
in  order  to  free  up  memory  for  pro¬ 
gram  use.  I  replaced  FILE. DOS  with 
FILE2.DOS  in  order  to  take  advantage 
of  DOS  2.x  subdirectories  and  added 
SYSCALL.IBM  in  order  to  use  the 
SHELL  command  and  BIOS/DOS 
calls.  You  may  specify  the  use  of  al¬ 
ternative  configurations  when  starting 
the  program  by  typing  B-MYCON- 
FIG/c  where  MYCONFIG  is  the  name 
of  the  optional  configuration  file. 

A  Tandy  2000  version  of  the  pro¬ 
gram  is  also  available.  The  modules 
with  the  extension  .IBM  are  all  re¬ 
placed  with  equivalent  modules  that 
end  with  .TDY. 

Startup  and  Operation 

After  a  sign-on  message  and  copy¬ 
right  notice  you  will  see  the  first  major 
difference  between  BetterBASIC  and 
GW  BASIC:  instead  of  the  usual 
“60891  Bytes  free”  message,  a  512K 
machine  will  display  “Left:260304 
bytes.”  All  available  system  memory 
is  used  as  BetterBASIC’s  workspace. 
This  allows  much  larger  programs  to 
be  written.  BetterBASIC  supports 
most  of  GW  BASIC’s  commands,  in¬ 
cluding  the  on-screen  editing  keys 
that  are  used  on  the  IBM  PC.  Table  3 
(page  111)  contains  a  complete  listing 
of  GW  BASIC  commands  that  are  dif¬ 
ferent  or  not  supported  and  a  list  of 
new  commands  that  are  found  in  Bet¬ 
terBASIC. 

Every  statement  that  you  enter  is 
checked  for  syntax  and  then  com¬ 
piled.  If  you  make  a  mistake  when 
entering  a  program  line,  an  error 
message  will  be  given  immediately. 
For  example: 

40  PRINT  (1  +2*3 


“  )  ”  Expected 

If  the  statement  is  entered  correctly,  it 
is  compiled  to  virtual-machine  code 
rather  than  being  interpreted  at  run¬ 
time,  thus  giving  a  large  speed  im¬ 
provement  over  the  GW  BASIC 
interpreter. 

As  in  Pascal  and  C,  all  variables 
are  declared  at  the  beginning  of  the 

108 

407 


program.  The  exception  to  this  is  the 
AUTODEF  command,  which  allows 
automatic  declaration  of  a  variable 
by  the  first  assignment  of  a  value. 
AUTODEF  defaults  to  ON,  but  if  you 
turn  it  off,  the  compiler  will  catch 
misspelled  variable  names.  The  data 
types  that  are  supported  in  BetterBA- 
SIC  are  byte,  integer,  real,  string  (up 
to  32,767  characters),  array,  pointer, 
and  structure.  Structures  are  identi¬ 
cal  to  Pascal’s  record  data  type  and 
may  contain  any  other  data  type.  Ar¬ 


rays  can  contain  byte,  integer,  real, 
and  string  data  as  well  as  structures 
and  other  arrays,  thus  allowing  ar¬ 
rays  of  arrays,  arrays  of  structures, 
and  structures  of  arrays. 

A  BetterBASIC  programming  ses¬ 
sion  superficially  resembles  one  in 
GW  BASIC  in  that  lines  are  num¬ 
bered  and  most  of  the  command 
keywords  are  identical.  When  saving 
to  disk,  however,  you  must  specify 
the  file’s  extension  because  BAS  is 
not  assumed  by  BetterBASIC.  At  the 


end  of  a  session  you  can  exit  to  DOS 
with  the  SYSTEM  command  or  by 
typing  BYE. 

Unique  Features  of  BetterBASIC 

The  best  feature  of  BetterBASIC  is 
the  use  of  true  procedures  with  vari¬ 
ables  of  local  scope  that  exist  only  in¬ 
side  the  procedure.  Global  variables 
can  be  used  by  declaring  them  as  EX¬ 
TERNAL  in  the  procedure  and  pro¬ 
viding  the  actual  declaration  at  a  lev¬ 
el  outside  the  procedure.  A  procedure 
can  be  declared  as  RECURSIVE  if  its 
name  is  declared  as  EXTERNAL  in¬ 
side  the  procedure.  GOSUB  and  RE¬ 
TURN  are  both  supported,  but  as  you 
gain  experience  with  BetterBASIC, 
they  will  probably  fall  by  the  wayside 
in  favor  of  these  Pascal-like,  para¬ 
meterized  procedures.  In  addition  to 
passing  arguments  by  value  or  refer¬ 
ence,  you  may  specify  optional  and/ 
or  default  arguments. 

One  particularly  distinctive  aspect 
of  BetterBASIC  is  the  ANY  ARG  dec¬ 
laration.  This  allows  a  procedure  or 
function  to  determine  what  type  of 
data  was  passed  to  it  at  runtime  with 
the  following  functions: 

TYPE(  )  extracts  the  type  of  da¬ 
tum 

DIM(  )  extracts  the  dimensions  of 
a  datum 

SIZE(  )  extracts  the  size  of  a 
datum 

For  example,  A  =  TYPE(FOO)  will  as¬ 
sign  the  following  values  to  A: 

0  if  FOO  is  a  byte 

1  if  FOO  is  an  integer 

2  if  FOO  is  a  real 

3  if  FOO  is  a  string 

4  if  FOO  is  a  structure 

10  +N  if  FOO  is  an  array  of 

above  type  N 

If  TYPE(A)  returns  3  for  string  you 
can  determine  its  allocated  size  (not 
length)  with  SIZE(A).  If  FOO  is  a 
structure,  DIM(A)  will  return  the 
number  of  fields  and  TYPE(FOO(N) ) 
will  return  the  type  of  field  N.  This 
flexibility  of  optional/ambiguous  pa¬ 
rameter  passing  can  be  a  tremendous 
improvement  over  Pascal’s  rigid 


B 

COM 

35312 

1-03-85 

1:00a 

B 

DEF 

27020 

1-03-85 

1:00a 

B 

CNF 

142 

1-03-85 

1:00a 

MATH 

BCD 

8112 

1-03-85 

1:00a 

CONSOLE 

IBM 

21392 

1-03-85 

1:00a 

MAIN 

3840 

1-03-85 

1:00a 

FILE 

DOS 

19056 

1-03-85 

1:00a 

GRAPHICS 

IBM 

10512 

1-03-85 

1:00a 

PLAY 

IBM 

3136 

1-03-85 

1:00a 

EVENT 

IBM 

7744 

1-03-85 

1:00a 

FILE2 

DOS 

21072 

1-03-85 

1:00a 

CHAIN 

MOD 

2704 

1-03-85 

1:00a 

SYSCALL 

IBM 

3520 

1-03-85 

1:00a 

COMM 

BAS 

8484 

1-03-85 

1:00a 

PLIST 

BAS 

2542 

1-03-85 

1:00a 

SECTOR 

BAS 

6702 

1-03-85 

1:00a 

STRUC 

BAS 

2980 

1-03-85 

1:00a 

SHELL 

CNF 

175 

1-03-85 

1:00a 

CHAIN 

SIZ 

11 

1-03-85 

1:00a 

READ 

ME 

126734 

1-03-85 

1:00a 

20  File(s) 

1024  bytes  free 

Table  1 

Directory  listing  of  the  BetterBASIC  diskette  showing  the  many 
modules  that  make  up  the  Better  BASIC  system. 


MODULES  =  MATH. BCD 
MODULES  =  CONSOLE. IBM 
MODULES  =  MAIN 
MODULES  =  FILE.  DOS 
MODULES  =  GRAPHICS. IBM 
MODULES  =  EVENT.  IBM 
MODULES  =  PLAY.IBM 
STATUS  =  ON 

Table  2 

The  default  B.CNF  configuration  file. 


110  Dr.  Dobb's  Journal.  May  1985 

408 


requirements.  10  INPUT  Value  data  types.  The  difference  is  that 

BetterBASIC  has  a  concept  called  20  PRINT  “Your  integer  is  ”,  they  are  used  in  expressions  to  return 

Procedure  Families  in  which  more  Value  a  value  that  is  used  in  the  expression, 

than  one  procedure  or  function  can  The  last  statement  of  a  function  must 

have  the  same  name  with  the  only  PROCEDURE:lntegerInput.a  be  RESULT=  and  the  value  that  is  to 

distinction  being  the  parameter  list.  STRING  ARG  SomeString  be  returned. 

This  allows  BetterBASIC  to  select  10  PRINT  SomeString,  “is  not  a  Both  procedures  and  functions  can 

which  one  to  use  based  on  the  type  of  valid  integer,  please  try  again”  use  KEYWORD  ARGuments — a  con- 

argument  that  is  used.  As  an  exam-  cept  that  is  similar  to  Pascal’s  user- 

pie,  the  two  procedures  that  follow  In  this  example,  Integerlnput.a  will  enumerated  data  types.  If  an  argu- 
can  be  used  to  give  informative  error  be  used  any  time  a  valid  integer  is  not  ment  is  declared  as 
messages  on  integer  input,  rather  entered  when  Integerlnput  is  called. 

than  GW  BASIC’s  cryptic  “Redo  This  technique  can  also  be  used  to  as-  KEYWORD  ARG:Color 

from  start.”  sign  additional  functionality  to  Bet-  \RED\GREEN\BLUE 

terBASIC’s  reserved  words. 

PROCEDURE'.Integerlnput  Functions  are  similar  to  proce-  then  the  procedure  will  accept  either 

INT  ARG  Value  dures  in  that  they  share  all  the  same  red,  GREEN,  or  BLUE  as  valid  data 


GW  BASIC  Statements  Not  Supported  in  BetterBASIC 

BLOAD 

BSAVE  CDBL 

CSNG 

CVD 

CVI  CVS 

CONT  DEFDBL 

DEFINT 

DEFSTR 

DEF  FN  DEF  USR 

EQV 

ERASE 

ERL  FIELD 

FRE  GET 

IMP 

MERGE 

MKD  MKI 

MKS 

MOTOR 

ON  PEN 

ON  STRIG  PEN 

PMAP 

PUT 

RESUME 

STICK  STRIG 

TROFF 

TRON 

USR  VARPTR 

VIEW  WEND 

WINDOW 

New  BetterBASIC  Statements 

ANY  ARG 

ASH 

AUTODEF 

BYE 

BYTE 

BYTE  ARG 

BYTE  ARRAY 

BYTE  ARRAY  STRUC 

BYTE  PTR 

CHECK 

CODE 

CODE? 

COLOR  BORDER  COMPRESS 

CONSTANT 

DEFINE  WINDOW 

DELS 

DO .  . END  DO 

DO  IF 

DO  UNTIL 

DYNAMIC 

EDIT  proc 

ERROR 

EXIT 

EXIT  n  LEVELS 

EXTERNAL  or  EXT 

FRAME  WINDOW 

HEADER 

INPUT  FROM 

INSS 

INTEGER  or  INT 

INT  ARG 

INT  ARRAY 

INT  ARRAY  STRUC 

INT  FUNCTION 

INT  PTR 

INTERRUPT 

INTERRUPT  PROC 

KEYWORD  ARG 

LINES 

LIST  ALL 

LIST  ARGS 

LIST  PROCS 

LOWERS 

MAIN 

MAKE  MODULE. 

MAKE  PROGRAM 

OFFSET 

ON  INTERRUPT 

PRECISION 

PROCEDURE 

PUBLIC 

or  PROC 

READCHR 

READCHR  FROM 

READLINE 

READLINE  FROM 

REAL 

REAL  ARRAY 

REAL  ARRAY 

REAL  ARG 

STRUC 

REAL  FUNCTION  REAL  PTR 

REPEAT 

REPEAT  IF 

RESTORE  SCREEN  RESULT  = 

ROT 

SAVE  MODULE 

SAVE  SCREEN 

SEG 

SET 

SH 

SIZE 

SPAN 

STRING  or  STR 

STR  ARG 

STR  ARRAY 

STR  ARRAY  STRUC 

STR  FUNCTION 

STR  PTR 

STRUCTURE 

UPPERS 

WHILE  .  .  DO 

XREF 

Table  3 

A  comparison  of  BetterBASIC  and  GW  BASIC  Keywords 

Dr.  Dobb’s  Journal,  May  1985 


111 

409 


for  Color. 

BetterBASIC  has  a  full  comple¬ 
ment  of  flow-controlling  block  struc¬ 
tures.  In  addition  to  the  usual  FOR- 
..NEXT,  IF.. THEN,  and  WHILE. - 
REPEAT,  BetterBASIC  has  DO 
UNTIL. REPEAT,  DO. REPEAT,  DO 
n  TIMES  . REPEAT,  DO  . END  DO, 
DO.  REPEAT,  DO  IF.  REPEAT  IF, 
and  more.  GOTOs  are  implemented 
in  a  way  that  could  placate  even  Dijk- 
stra — they  cannot  be  used  to  enter  or 
exit  a  control  block.  The  only  way  out 
of  a  control  structure  is  via  the  EXIT 
statement,  and  then  only  to  the  state¬ 
ment  that  immediately  follows  the 
block. 

Variables  can  be  declared  as  abso¬ 
lute  by  adding  a  segment  and  offset 
after  the  declaration.  BYTE  ARRAY - 
(4000):VideoBuffer  [&hB000:0]  is 
an  absolute  reference  to  the  IBM’s 
2000  character  monochrome  video 
refresh  buffer.  This  technique  can  be 
used  to  access  the  BIOS  communica¬ 
tion  area  in  low  memory  for  equip¬ 
ment  determination. 

The  BetterBASIC  language  can  be 
extended  by  adding  your  own  proce¬ 
dures  and  functions  to  the  language 
as  keywords.  The  MAKE  MODULE 
command  will  convert  all  procedures 
and  functions  currently  in  memory  to 
a  module  that  can  be  added  to  the 
B.CNF  configuration  file.  Like  C  lan¬ 
guage  function  libraries,  useful  pro¬ 
cedures  can  be  distributed  to  other 
BetterBASIC  users  as  modules  with  a 
good  degree  of  source  code  protection 
because  the  source  code  cannot  be  de¬ 
rived  from  a  module.  Summit  Soft¬ 
ware  is  currently  selling  an  8087 
module  and  expects  to  offer  an  inter¬ 
face  module  for  the  SoftCraft  Btrieve 
system  sometime  this  spring. 

Programs  can  be  prepared  for 
standalone  execution  with  the  MAKE 
PROGRAM  command  and  the  option¬ 
al  Runtime  System.  The  Runtime 
System  consists  of  RUN.COM, 
RUN .$$$,  and  MKEXE.COM.  RUN- 
.COM  will  execute  any  program  that 
has  been  prepared  with  MAKE  PRO¬ 
GRAM  and  MKEXE.COM  appends 
the  RUN.SSS  runtime  library  to  a  pro¬ 
gram  file  for  stand-alone  execution. 

Assembly  language  support  is 
pretty  straightforward  with  a  very 
good  example  given  in  Appendix  G  of 


the  manual.  The  keyword  CODE  is 
used  instead  of  BLOAD  in  order  to 
load  an  assembler  module  into  mem¬ 
ory  at  runtime.  CODE?  can  be  used  to 
report  the  absolute  load  address  if  de¬ 
bugging  is  necessary.  The  assembly 
language  procedure  is  then  invoked 
by  CALL  procedurename(arglist) 
just  as  in  GW  BASIC.  An  assembly 
language  module  can  contain  one  or 
more  procedures  with  a  Symbol  Ta¬ 
ble  at  the  beginning  that  defines  the 
entry  point  for  each  procedure  in  the 
module.  The  excerpt  from  the  Better¬ 
BASIC  manual  in  the  Listing  (page 
116)  will  give  the  flavor  of  assembly 
language  interfacing. 

Also  worthy  of  note  are  the  XREF 
command  and  the  debug  window. 
XREF  generates  a  symbol  cross-ref¬ 
erence  of  all  user-declared  identifiers 
and  the  line  numbers  on  which  they 
appear.  Advanced  programmers  may 
wish  to  use  the  DOS  debug  program 
to  test  programs  that  are  developed  in 
BetterBASIC.  If  they  start  with  DE¬ 
BUG  B  and  then  execute  BetterBA¬ 
SIC  inside  DEBUG,  the  [Ctrl-PgUp] 
keys  will  exit  to  the  debug  prompt. 

Current  Version 

The  current  version  of  BetterBASIC 
is  1.1.1  received  the  upgrade  from  1 .0 
to  1.1  during  the  review.  New  fea¬ 
tures  supported  include  CHAINing 
and  CALLing  modules  as  overlays, 
communication  between  overlays  via 
disk,  SYStem  calls  for  access  to  BIOS 
and  DOS  functions,  and  a  SHELL 
command  that  can  be  used  to  execute 
programs  (such  as  COMMAND- 
.COM)  from  within  BetterBASIC. 
The  new  XMEM  keyword  allows  data 
structures  such  as  arrays  up  to  the 
limits  of  system  memory,  not  just 
64K  as  in  vl.O.  Also,  the  release  notes 
describe  bug  fixes  that  were  applied 
to  this  new  version. 

Summit  Software  informs  me  that 
all  registered  users  will  be  provided 
with  free  updates  to  vl.l,  with  a  copy 
of  the  new  documentation  on  the  disk 
and  an  option  to  purchase  printed 
documentation  for  $15.  I  also  re¬ 
ceived  test  versions  of  the  MATH1- 
.BNY  and  MATH2.BNY  (single  and 
double  precision  binary  math)  that 
are  significantly  faster  than 
MATH. BCD,  but  less  precise.  By  the 


time  you  read  this,  both  modules 
should  be  included  in  the  standard 
BetterBASIC  system  in  addition  to 
the  BCD  math  module. 

Benchmarks 

I  compared  the  performance  of  Bet¬ 
terBASIC  and  GW  BASIC  by  running 
Rich  Malloy’s  four  BASIC  bench¬ 
mark  programs  that  I  downloaded 
from  the  Byte  bulletin  board.  These 
tests  separate  I/O  from  computation 
to  give  fairly  accurate  representa¬ 
tions  of  speed.  With  one  exception 
(see  Table  4,  page  1 14),  BetterBASIC 
was  the  better  performer  by  far.  The 
exception  was  in  the  floating  point 
calculations,  where  the  BCD  math 
module  was  about  30%  slower  than 
GW  BASIC.  The  new  single  precision 
binary  math  module  ran  about  twice 
as  fast. 

Round-off  error  can  be  of  concern 
in  business  and  financial  program¬ 
ming.  Although  MATH1.BNY 
showed  no  error  in  this  benchmark 
(compared  to  GW  BASIC’s  —1.788 
E  — 07  error),  it  is  probably  well  worth 
the  slower  speed  of  MATH. BCD  to 
have  guaranteed  zero  error  in  finan¬ 
cial  calculations. 

The  Runtime  System,  unlike  the 
BASCOM  compiler,  will  not  show  an 
improvement  in  execution  speed.  This 
is  because  BASCOM  generates  native 
8086  code  while  MKEXE  and  RUN- 
COM  only  provide  a  stand-alone  Bet¬ 
terBASIC  environment  to  run  the  vir¬ 
tual-machine  code  that  is  produced  by 
the  MAKE  PROGRAM  command. 

These  benchmarks  were  run  on  an 
IBM  PC  with  512K  of  RAM,  10Mb 
hard  disk,  and  a  monochrome  display. 
I  have  also  successfully  run  BetterBA¬ 
SIC  on  a  Leading  Edge  PC/XT,  a 
PCjr,  and  a  Hyperion  PC  with  no  diffi¬ 
culty. 

GW  BASIC  Program  Conversion 

To  put  it  bluntly,  there  is  no  easy  con¬ 
version  from  GW  BASIC  to  BetterBA¬ 
SIC.  There  is  a  simple  procedure  out¬ 
lined  at  the  end  of  the  manual  that 
involves  editing  an  ASCII  version  of 
the  original.  The  keywords  SOURCE 
and  ENDFILE  are  added  at  the  start 
and  end  of  the  file  for  compatibility 
with  BetterBASIC’s  LIST  ALL  ASCII 
storage  format.  In  addition,  variable 


113 

410 


Dr.  Dobb's  Journal,  May  1985 


declarations  must  be  made  at  the 
start  of  the  file.  If  you  don’t  declare 
the  variables,  they  will  be  defined  on 
use,  but  all  integers  will  be  defined  as 
reals  with  the  resultant  loss  of  speed 
and  precision  that  comes  from  the  use 
of  the  real  data  type. 

I  was  able  to  convert  with  no  diffi¬ 
culty  trivial  programs  such  as  the 
Byte  benchmarks,  and  one  fairly 
lengthy  musical  program  came  over 
without  much  modification.  Howev¬ 
er,  large  programs  such  as  PC-TALK 
or  RBBS-PC  would  be  easier  to  re¬ 
write  from  scratch  with  BetterBASIC 
than  they  would  be  to  convert.  This  is 
because  of  major  philosophical  dif¬ 
ferences  in  the  use  of  procedures  and 
the  use  of  structures  for  file  I/O  rath¬ 
er  than  FIELDS  in  an  I/O  buffer. 
Conceptually,  it  would  probably  be 
easier  to  translate  a  Pascal  or  C  pro¬ 
gram  than  an  existing  GW  BASIC 
program. 

Refer  to  Table  3  for  a  complete 
summary  of  the  differences  between 
the  two  BASICS. 

The  Documentation 

If  there  are  two  words  that  describe 
the  BetterBASIC  documentation, 
they  are  massive  and  thorough.  The 
manual  is  an  IBM-style  5V4  by  8-inch 
slip-cased  binder  with  over  550 
pages!  It  is  broken  up  into  seven  sec¬ 
tions:  General  Information,  Intro¬ 
duction  to  BetterBASIC,  Quick  Ref¬ 
erence,  Syntax  Visuals,  Appendices, 
and  two  new  additions  for  vl.l  — 
Chain/Overlays  and  Syscalls. 

The  syntax  visuals  are  comparable 
to  the  IBM  BASIC  manual  with  one 


or  more  pages  in  alphabetical  order 
to  describe  each  command  and  show 
examples  of  proper  usage.  The  intro¬ 
ductory  chapter  is  very  well  done 
with  an  excellent  tutorial.  It  is  geared 
to  beginners,  but  will  provide  a  re¬ 
fresher  for  experienced  BASIC  pro¬ 
grammers.  It  includes  in  the  lesson  on 
recursion  a  discussion  of  static  versus 
dynamic  storage  of  variables  that  will 
be  useful  to  many  microcomputer 
programmers.  The  presentation  of 
variable  scope  and  side  effects  in  the 
procedure  section  will  be  helpful  to 
those  programmers  whose  only  previ¬ 
ous  experience  is  with  other  BASICS. 
Appendices  include  an  ASCII  code 
table,  keyboard  scan  codes,  inter¬ 
rupts,  character  sets,  module  usage, 
module  defined  statements,  use  of 
Assembler,  error  codes,  and  GW  BA¬ 
SIC  conversion  hints. 

User  Support 

Because  Summit  Software  is  still  a 
small  company,  any  phone  calls  in 
search  of  support  will  be  handled  by 
one  of  the  program’s  developers.  This 
kind  of  direct  access  can  be  wonder¬ 
ful,  especially  when  problems  involve 
the  more  complex  aspects  of  the  sys¬ 
tem.  There  is  no  charge  for  phone  sup¬ 
port  for  registered  users  and  Summit 
can  be  reached  during  business  hours. 

Complaints 

As  in  all  new  products,  there  is  still 
room  for  improvement.  My  main 
gripe  has  to  do  with  errors  in  the  man¬ 
ual.  Until  I  received  my  vl.l  upgrade, 
I  could  find  no  reference  to  the  PUB¬ 
LIC  keyword  that  is  necessary  for  pre¬ 


paring  to  MAKE  MODULES.  There 
also  seems  to  be  some  confusion  when 
conceptual  words  from  other  lan¬ 
guages  are  used  interchangeably  with 
BetterBASIC  keywords;  structure  and 
record  are  used  in  this  fashion,  al¬ 
though  record  is  never  defined  to 
mean  the  same  as  structure. 

Some  of  the  error  codes  that  the 
compiler  can  generate  are  similar  to 
the  warnings  of  other  compilers.  I’d 
like  to  see  the  compiler  add  some  of 
the  missing  parentheses  that  it  can 
detect,  rather  than  just  notifying  the 
programmer.  Also,  you  cannot  speci¬ 
fy  a  program  on  the  command  line  to 
be  executed  by  BetterBASIC.  This 
precludes  the  use  of  B  SETCLOCK  to 
read  your  clock/calendar  in  an  AU¬ 
TOEXEC.BAT  file. 

There  is  no  provision  made  for  trac¬ 
ing  program  execution,  as  TRON/ 
TROFF  are  not  supported.  I  hope  the 
people  at  Summit  are  listening  and 
provide  a  trace  that,  unlike  GW  BA¬ 
SIC’s  primitive  version,  can  also  show 
variable  contents,  not  just  line  num¬ 
bers. 

My  final  complaint  has  to  do  with 
price — there  is  a  hidden  cost  that  a 
potential  developer  must  keep  in 
mind.  If  you  plan  to  market  the  soft¬ 
ware  you  develop  with  BetterBASIC, 
you  will  need  to  purchase  the  Run¬ 
time  System  for  an  additional  $250 
so  your  customers  will  be  able  to  run 
your  program  without  the  necessity 
of  purchasing  their  own  copy  of 
BetterBASIC. 

Conclusions 

So  who  will  find  this  package  useful? 


BetterBASIC  GW 

BASIC 

BENCH  1.  BAS- 

—write  64K  bytes  out  to  a  file 

14.6 

43.3 

BENCH2.BAS- 

—read  64K  bytes  in  from  a  file 

10.4 

29.1 

BENCH3.BAS- 

—5000  floating  point  operations 

92.9(BCD) 

69.4 

37.2(BNY) 

BENCH4.BAS- 

—calculate  all  primes  from  1  to  7000 

35.2 

208.1 

Table  4 

Performance  Benchmarks  (All  times  in  seconds) 

Dr.  Dobb’s  Journal.  May  1985 


114 

411 


Probably  not  the  C  wizards  and 
Forth  gurus  of  the  world.  They  will 
feel  restrained  by  the  line  numbering 
and  the  lack  of  mysterious,  syntactic 
constructions.  Pascal  programmers 
will  feel  right  at  home  with  proce¬ 
dures  that  have  local  variables  and 
statements  like  READLINE.  I  think 
this  package  will  find  its  place  mostly 
among  programmers  who  are  cur¬ 
rently  developing  vertical  market 
(i.e.,  business)  software  in  BASIC. 

A  large  percentage  of  software 
that  is  created  for  vertical  markets, 
such  as  insurance  agencies,  medical 
practices,  video  rental  clubs,  and  ac¬ 
counting  firms,  is  written  in  some 
form  of  the  BASIC  language.  One 
reason  that  is  often  given  for  this  dis¬ 
proportionate  popularity  is  that  BA¬ 
SIC  is  available  in  some  form  on  most 
personal  computers,  thus  providing  a 
greater  market  when  the  software  is 
ported  to  a  new  machine.  However, 
because  of  the  widespread  availabil¬ 
ity  of  Pascal  and  C  compilers  for  mi¬ 
cros  in  recent  years,  I  think  this  rea¬ 
son  is  less  important  than  the  fact 
that  much  vertical  market  software  is 
not  developed  by  trained  professional 
programmers.  Often  an  accountant/ 
car  salesman/real  estate  broker/ 
farmer  who  has  learned  a  little  BA¬ 
SIC  on  a  personal  computer  realizes  a 
need  for  some  industry-specific  piece 
of  software.  This  combination  of  in¬ 
dustry  expertise  and  programming 
experience  can  result  in  a  very  suc¬ 
cessful  program. 

In  the  words  of  Ivar  Wold,  Sum¬ 
mit’s  president,  these  people  are 
faced  with  the  “Pascal  Dilemma” — 
they  intend  to  learn  Pascal  or  C  but 
haven’t  found  the  time  for  it  yet. 
With  this  package,  a  BASIC  pro¬ 
grammer  will  be  able  to  write  better, 
more  maintainable  code  without  the 
learning  curve  that  is  associated  with 
learning  a  new  language  from 
scratch.  After  using  BetterBASIC,  it 
will  be  easy  to  pick  up  Pascal  with 
very  little  effort,  as  all  of  Pascal’s 
concepts  can  be  found  in  BetterBA¬ 
SIC.  However,  BetterBASIC  may 
provide  just  the  excuse  never  to  have 
to  learn  a  new  language  again. 

DDJ 


Better  Basic  Listing  (Text  begins  on  page  108) 

Sample  assembly  lanouao e  interface 
to  a  BetterBASIC  program 

org  0 

. - define  the  Symbol  Tabl 


syml 

db 

sym2-$ 

•link  to  next  symbol  table  *nt 

db 

'EXCHANGE' 

;name  of  the  Procedure 

dw 

0 

•  fill  word  (  MUST  be  here  M) 

dw 

header  1 

•pointer  to  Procedure  header 

sym2 

db 

sym3-$ 

•link  to  next  symbol  table  entr 

db 

FILL" 

! name  of  the  Procedure 

dw 

0 

•fill  word  (  MUST  be  here  !!) 

dw 

header2 

ipointer  to  Procedure  header 

sym3 

db 

0 

; Symbol  Table  Terminator 

e  v  e  n 

i headers  must  be  on 

;  WORD  boundary 

header  1 

dw 

1 54h 

iMUST  be  154h 

dw 

exchange 

•Pointer  to  actual  asm  procedur 

dw 

2 

inumber  of  Arguments 

dw 

1 

;type  of  argument  0  (Integer) 

dw 

1 

;type  of  argument  1  (Integer) 

header2 

dw 

1 54h 

•MUST  be  1 54h 

dw 

fill 

•Pointer  to  actual  asm  procedur 

dw 

2 

inumber  of  Arguments 

dw 

3 

;type  of  argument  0  (String) 

dw 

1 

itype  of  argument  1  (Integer) 

;  These 

are  the 

procedures 

proper 

exchange 

proc  fa 

;MUST  be  a  far  proc 

• 

;  the  EXCHANGE  code 

exchange 

endp 

fill 

proc  f 

• 

;  the  FILL  code 

fill 

endp 

End  Listing 


116 


Dr.  Dobb’s  Journal,  May  1985 


16-BIT  SOFTWARE  TOOLBOX 


by  Ray  Duncan 


68000  Square  Root 
Routine 

Jim  Cathey  of  Spokane,  Washington, 
writes:  “To  help  [eliminate]  the 
dearth  of  68000  programming  good¬ 
ies  you  were  mentioning,  here  is  an 
integer  square  root  program.  It  takes 
only  about  10-1 1  times  as  long  to  ex¬ 
ecute  as  a  divide  instruction.  I  found 
this  algorithm  a  few  years  back  in 
EDN  ....  I  thought  it  was  a  nifty 
trick,  and  saved  a  clipping  for  when  I 
needed  it.  Two  muls  instructions,  one 
add.l,  and  this  subroutine  implement 
a  true  16-bit  vector  length  subrou¬ 
tine,  in  less  than  2000  cycles  (250  mi¬ 
crosec.  at  8  MHz)! 

“The  original  program  (and  my 
first  attempt)  had  a  bug,  in  that  the 
intermediate  variables  were  half  the 
size  of  the  input  data.  This  works  until 
the  last  trip  through  the  loop,  where 
you  may  have  overflow  errors  that 
could  ruin  the  accuracy.  For  speed, 
the  main  loop  could  be  unrolled  using 
word  intermediates  for  the  first  15 
passes,  and  long  on  the  last  loop- 
equivalent.  This  should  save  some 
time  if  it’s  a  problem.  Of  course,  if 
speed  is  really  a  problem,  one  of  the 
approximations  for  vector  length  you 
presented  a  while  back  would  be  many 
times  faster.”  See  Listing  One  (page 
122)  for  the  source  code  for  Jim’s 
68000  square  root  routine. 

MacFeedback 

Jim  Howell  of  San  Jose,  California,  a 
regular  correspondent  to  DDJ,  writes: 
“I  am  writing  regarding  your  recent 
rather  disparaging  comments  (DDJ, 
December  1984,  #98)  on  the  Macin¬ 
tosh.  I  believe  it  is  much  too  early  for 
you  to  be  forecasting  the  demise  of 
the  Mac.  So  ‘only’  200,000  Mac’s 
were  delivered  in  its  first  year.  How 


many  IBM  PC’s  were  delivered  dur¬ 
ing  its  first  year?  And  have  you  for¬ 
gotten  that  even  a  year  after  the  IBM 
PC  came  out,  people  were  still  com¬ 
plaining  about  the  lack  of  software 
for  it?  As  for  comparing  the  Mac’s 
‘lackluster  sales’  with  the  Lisa,  I 
would  guess  that  there  are  already 
more  Macs  than  Lisas. 

“One  point  I  will  agree  on  is  the  dif¬ 
ficulty  of  developing  software  for  the 
Mac.  If  Apple  really  wants  lots  of 
software  for  it,  they  need  to  make  the 
required  technical  documentation 
available  (and  at  a  reasonable  price). 
Development  tools  (compilers  and  as¬ 
semblers)  need  to  be  able  to  run  using 
a  single  Mac.  There  was  an  ad  in 
Infoworld  recently  for  a  Mac  assem¬ 
bler  ‘that  requires  no  other  hardware 
or  software.’  I  don’t  know  much  about 
the  availability  of  other  language  pro¬ 
cessors  for  the  Mac,  though  I  suspect 
(and  hope)  that  most  of  them  will  be 
usable  with  only  a  single  Mac.  As  for 
‘weak,  bug-ridden’  high-level  lan¬ 
guages,  again  consider  the  IBM  PC. 
That’s  probably  a  fair  description  of 
many  of  the  language  processors  that 
were  available  for  the  IBM  PC  during 
its  first  year.  IBM’s  Pascal  compiler, 
for  example,  is  atrocious. 

“I  also  agree  that  a  68000  assem¬ 
bler  should  be  able  to  run  with  128K 
and  one  disk  drive,  but  comparing 
that  to  the  DRI  8080  assembler  that 
ran  in  32K  is  very  unfair.  8080  as¬ 
sembly  language  is  much  simpler 
than  that  of  the  68000.  The  68000 
has  more  addressing  modes,  more  in¬ 
struction  formats,  and  its  assembly 
language  has  a  more  complex  syntax. 
Also,  the  DRI  8080  assembler  must 
have  been  a  bare-bones,  absolute  as¬ 
sembler.  A  bare-bones  68000  assem¬ 
bler  could  be  written  to  run  on  a  64K 
CP/M  system  (but  probably  not  on  a 
32K  system).  A  similar  assembler 


should  run,  therefore,  on  a  128K 
Mac.  However,  I  would  assume  that 
Apple  does  not  want  to  put  out  just  a 
bare-bones  assembler.  If,  for  exam¬ 
ple,  the  Apple  assembler  generates 
object  code  that  can  be  linked  with 
separately  compiled  or  assembled 
modules,  this  would  add  considerable 
complexity  to  the  assembler. 

“While  a  Mac  assembler  should  be 
able  to  run  in  128K,  I  don’t  think  it’s 
a  big  problem  if  it  does  not.  Anyone 
doing  serious  software  development 
on  the  Mac  will  have  to  have  512K 
(the  next  step  after  128K)  and  two 
disk  drives,  even  if  some  of  the  tools 
run  in  128K.  After  all,  how  many  de¬ 
velopers  of  IBM  PC  software  have 
only  128K  in  their  machines?  And 
even  fewer  have  only  one  disk  drive. 

“I  will  be  like  the  other  readers  who 
requested  68000  material  and  not 
submit  any  68000  code.  Like  most  of 
these  readers  (probably),  I  am  inter¬ 
ested  in  the  68000,  but  do  not  actually 
have  a  68000  system.  Therefore,  I  also 
do  not  have  any  68000  code  to  submit. 
With  the  attitudes  expressed  in  your 
column  about  the  68000  (and  about 
Intel’s  80286),  one  wonders  whether 
you  would  want  to  include  any  68000 
code  or  material  anyway. 

“Finally  [in  reference  to  another 
column  on  identifying  IBM  PC  envi¬ 
ronments],  location  F000:FFFEH  on 
my  Corona  contains  0FEH,  just  like 
the  PC/XT.  Apparently,  they  want 
software  to  think  it’s  running  on  an 
XT,  just  in  case  a  hard  disk  happened 
to  be  attached  (yes,  I  do  own  an  IBM 
compatible,  though  I  like  my  6809 
system  better).  The  AT&T  computer 
that  we  use  at  work  has  00  (zero)  at 
that  location  (the  date  in  the  ROM  is 
05/03/84).” 

This  letter  from  Jim  neatly  illus¬ 
trates  the  polarization  that  the  Mac 
has  induced  within  the  programming 


Dr.  Dobb’s  Journal,  May  1985 


118 

413 


community.  There  is  a  certain  class  of 
people  who  are  dazzled  by  Apple’s 
“insanely  great”  publicity  and  will 
forgive  the  Macintosh  anything. 
There  is  another  group,  among  whom 
I  guess  I  must  number  myself,  who 
find  Apple’s  pretentious  advertising 
and  grandiose  claims  a  little  ridicu¬ 
lous,  considering  that  almost  every¬ 
thing  that’s  good  about  the  Mac’s  user 
interface  was  “borrowed”  directly 
from  the  Xerox  Star  that  has  been 
around  for  ten  years.  Speaking  of  pre¬ 
tentious,  see  the  recent  Playboy  inter¬ 
view  with  Steve  Jobs  for  some  amus¬ 
ing  pronouncements,  especially  about 
us  programmers  over  30  years  old. 

The  fact  is  that  everybody  would 
find  the  sales  of  200,000  Macs  in  the 
first  year  to  be  very  remarkable,  if 
Apple  hadn’t  spouted  off  all  those 
projections  about  how  it  was  going  to 
build  a  Mac  every  15  seconds  in  their 
new  super  MacFactory.  To  argue 
that  the  Mac  has  already  sold  more 
machines  than  the  Lisa  is  not  what 
you’d  call  a  devastating  rejoinder, 
since  Apple  has  already  thrown  in  the 
towel  on  the  Lisa  and  reincarnated  it 
as  the  Macintosh  XL. 

As  for  the  Macintosh  assembler,  I 
feel  that  I’m  on  very  solid  ground 
with  my  previous  comments,  having 
written  several  assemblers  myself. 
The  fact  that  the  instruction  set  of 
the  68000  is  powerful  and  extensive 
cuts  both  ways:  of  course  it  means 
that  you  have  to  worry  about  assem¬ 
bling  more  complex  syntax,  but  it 
also  means  that  you  have  a  much 
more  powerful  language  in  which  to 
write  the  assembler.  Unless,  of 
course,  you  are  so  bull-headed  as  to 
insist  on  writing  your  assembler  in  a 
high-level  language  like  Pascal, 
which  is  completely  unsuited  to  the 
job.  Then  you  do  end  up  with  mon¬ 
strosities  like  the  Apple  Macintosh 
Assembler  or  the  Microsoft  8086 
Macro  Assembler.  The  proof  of  my 
contention  has  been  conveniently 
provided  by  the  product  “MacASM” 
from  Mainstay  of  Agoura  Hills,  Cali¬ 
fornia.  This  is  an  integrated  full 
screen  editor,  macro  assembler,  and 
resource  compiler  for  the  Macintosh 
that  runs  nicely  in  128K  and  costs 
only  $  1 25.00.  “MacASM”  is  not  copy 
protected  and  supports  multiple 


drives  including  hard  disks.  Mainstay 
can  be  reached  at  (818)  991-6540. 

MSDOS  Device  Drivers 

Stan  Mitchell  of  San  Jose,  Califor¬ 
nia,  writes:  “I  thought  I  would  pass 
along  a  tool  that  comes  in  handy 
when  working  with  MSDOS  device 
drivers.  It  depends  upon  a  useful 
property  of  the  NUL  device  driver. 
Unlike  other  devices,  it  is  embedded 
in  the  DOS  module  (IBMDOS.COM 
for  the  PC)  and  its  device  header  is  at 
the  head  of  the  device  chain.  By  using 
the  next  device  pointer  in  successive 
headers,  the  complete  chain  can  be 
revealed.  It  is  interesting  that  if  any 
installed  devices  are  present,  the 
NUL  header  points  to  the  first  of 
these.  When  installed  devices  are  ex¬ 
hausted,  the  link  continues  with  the 
resident  devices  (contained  in  1BM- 
BIO.COM  for  the  PC). 

“The  only  ‘trick’  in  the  program  is 
using  the  OPEN  function  to  return 
the  device  header  pointer.  This  is  an 
undocumented  feature  of  MSDOS.” 

Readers,  please  note  that  the  for¬ 
mat  of  the  reserved  area  in  the  file 
control  block  is  different  under 
MSDOS  2.0  than  it  is  under  version 
3.0.  Stan’s  C  program  and  the  assem¬ 
bly  language  source  for  the  _peek 
function  are  provided  as  Listing  Two 
(page  122)  and  Listing  Three  (page 
125). 

Microsoft  Assembler  Bug 
of  the  Month 

Gregor  Owen  of  Port  Jefferson  Sta¬ 
tion,  New  York,  writes:  “I  don’t 
know  if  anyone’s  still  counting,  but 
here’s  my  contribution  for  Microsoft 
Assembler  bug  of  the  month.  In  the 
assembly  below,  the  code  esdodsb 
produces  an  error;  however,  the  code 
below  it,  mov  ax,es:[si  +  1  ],  produces 
an  error  too.  If  you  comment  out  the 
esdodsb,  both  errors  disappear — so 
superior  to  the  old-fashioned  kind  of 
assembler,  where  frequently  the  re¬ 
moval  of  defective  code  only  removes 
errors  in  the  vicinity  of  the  defect. 

“At  this  point,  I  don’t  really  know  if 
esdodsb  represents  a  legal  operation.  I 
don’t  happen  to  have  an  Intel  book  at 
this  location,  and  since  every  8086/88 


instruction  has  a  unique  set  of  ad¬ 
dressing  modes,  it’s  difficult  to  get  any 
kind  of  feel  for  what’s  allowed.  Need¬ 
less  to  say,  the  IBM/Microsoft  assem¬ 
bler  manual  is  not  helpful.  It’s  true 
that  if  one  comments  out  esdodsb  and 
assembles  lods  byte  ptr  es:[si  +  0]  both 
errors  disappear,  but  I  don’t  think 
anybody  who’s  used  the  Microsoft  as¬ 
sembler  for  any  short  while  would 
take  any  comfort  from  that. 

“And  while  we’re  on  the  subject, 
notice  the  expressive  elegance  and 
beauty  of  the  phrase 

lods  byte  ptr  es:[si  +  0] 

typical  of  the  language  Intel  has  pro¬ 
vided  for  us.  The  si  +  0,  I  concede,  is 
something  I’ve  been  doing  ever  since 
I  managed  to  assemble  some  kind  of 
complicated  expression  that  had  [si] 
in  it — producing  code  that  referenced 
[si  +  OFFFFH]  and  didn’t  generate  an 
assembler  error.  Of  course  I  probably 
don’t  have  to  use  si  +  O  in  every  ex¬ 
pression,  but  who  wants  to  spend  the 
time  to  find  out?  That’s  the  beauty  of 
the  language:  every  programmer,  in 
nrHpr  to  nroduce  stuff  that  works  at 
all,  will  develop  a  private  dialect  con¬ 
taining  much  myth  and  legend,  thus 
making  the  source  even  more  incom¬ 
prehensible  than  it  is  anyway. 

“My  personal  theory  about  8086/ 
88  assembly  language  is  that  what  we 
have  here  is  a  marketing  coup  on  the 
part  of  Intel.  They  had  a  ridiculous 
processor,  with  the  traditional  inept 
Intel  approach  to  registers  and  ad¬ 
dressing.  Somehow,  they  sensed  it 
would  be  awfully  embarrassing  to  ex¬ 
pose  this  thing  in  all  the  stark  sim¬ 
plicity  of  their  ‘lxi  h,7’-type  assem¬ 
bler  [for  the  8080  among  other 
processors].  So  they  commissioned 
the  finest,  most  convoluted,  Pascal- 
ized  minds  in  the  software  industry  to 
come  up  with  an  assembler  so  grand 
and  structured  and  incredibly  intri¬ 
cate  that  no  one  would  ever  notice 
how  awful  the  thing  was  for  which  it 
was  assembling.  Judging  by  the  re¬ 
sults,  they  seem  to  have  succeeded. 

“And  those  of  us  who  labor  in  the 
vineyards  of  the  information  revolu¬ 
tion  can’t  be  all  too  high-horsey 
about  this:  after  all,  the  Intel/Micro¬ 
soft  language  makes  it  almost  impos- 


119 

414 


Dr.  Dobb’s  Journal,  May  1985 


sible  for  those  pesky  amateurs  to  fig¬ 
ure  anything  out,  thus  preserving  the 
world  of  8086/88  assembler  for  us, 
the  elect:  people  paid  to  spend  the 
necessary  endless  hours  figuring  out 
how  to  use  the  language  and  avoid 
the  Microsoft  bugs.  A  sort  of  ‘pro¬ 
grammer’s  employment  project’  for 
the  new  age.” 

Gregor’s  program  is  provided  as 
Listing  Four  (page  126).  My  unedu¬ 
cated  guess,  in  this  case,  is  that  the 
Microsoft  Assembler  interprets  the  es: 
portion  of  the  statement  esdodsb  as 
the  definition  of  a  label  named  es.  It 
then  gives  you  the  error  message 
“symbol  already  different  type,”  since 
the  name  es  is  already  hardwired  into 
the  symbol  table  as  an  assembler  di¬ 
rective.  All  the  other  errors  follow 
from  the  ensuing  type  conflicts. 

Fun  with  your  PC/AT 

Assemble  the  following  instruction 
sequence  into  a  short  assembly  lan¬ 
guage  program  and  execute  it  on  the 
IBM  PC/AT  of  your  choice: 

mov  bx,0ffffh 

mov  ax,[bx] 

The  system  dies  immediately.  The 
same  sequence  of  instructions  will  run 
just  fine  on  an  8086  or  8088-based 
machine.  What  is  happening  here? 

It  turns  out  that  on  the  80286,  ac¬ 
cessing  a  word  operand  at  offset 
OFFFFH.  i.e..  a  segment  wrap,  causes 
a  hardware  interrupt  13H.  Alas  (as 
Jerry  Pournelle  would  say),  this  in¬ 
terrupt  is  already  used  by  the  IBM  PC 
ROM  BIOS  for  the  disk  driver.  So 
when  your  program  accidentally  gen¬ 
erates  a  segment  wrap  fault,  it  traps 
to  the  disk  driver  with  nonsense  pa¬ 
rameters  in  the  registers  and  proba¬ 
bly  writes  garbage  all  over  your  nice 
hard  disk.  This  kind  of  phenomenon, 
when  we  move  a  previously  happy 
program  from  an  IBM  PC  to  a  PC/AT 
and  watch  it  go  to  heaven,  helps  us 
while  away  those  rainy  afternoons. 

DDJ 


16-Bit  Toolbox  (Text  begins  on  page  1 18) 
Listing  One 


Integer  Square  Root  (32  to  16  Bit) 
(Exact  method,  not  approximate) 


Call  with: 

DO.L 


Unsigned  number 


Returns : 

DO, 

Dl. 


=  SQRT  (DO.L) 

<>  0  if  not  exact 


root 


Uses: 


D1-D4  as  temporaries  - 

Dl  =  Error  term 
D2  =  Running  estimate 
D3  -  High  bracket 
D4  =  Loop  counter 


* 

*  Notes: 

* 

Result 

first  in  : 

* 

Takes 

from  1480 

* 

It 

(Word  version  is 

lsqrt 

move . w 

#15, d4 

moveq 

#0,dl 

moveq 

#0,d2 

sqrtl 

asl.l 

#l,dO 

roxl . 1 

#l,dl 

asl .  1 

#l,d0 

roxl . 1 

#l,dl 

asl.l 

#l,d2 

move . 1 

d2,d3 

asl.l 

#l,d3 

cmp.  1 

d3,dl 

bis 

sqrt2 

addq. 1 

#1 ,  d2 

addq . 1 

#l,d3 

sub.  1 

d3 ,  dl 

sqrt2 

dbra 

d4 , sqrtl 

movel 

rts 

d2 ,  do 

Loop  count  (bits-1  of  result) 
Result  in  Dl 

Get  2  leading  bits  at  a  time  and 
into  Error  term  for  extrapolation. 
(Classical  method,  easy  in  binary) 

Running  estimate  *  2 


New  error  term  >2*  running  est.? 
Yes,  we  want  1  bit  then. 

Fix  up  new  error  term. 

Do  all  16  bit-pairs. 

Returns  answer  in  DO.W 


Listing  Two 


End  Listing  One 


"C"  source  code  for  Stan  Mitchell's  program 
to  dump  the  device  driver  chain. 

|  /IHUlHXHtHtHHIHHHMHmmHHHHmiHHXmiHHmilHIH* 

2  *  dd_duip.c  prograt  for  displaying  device  driver  chain  1 


3 

« 

for  DOS  2.88,2.18,3.88  * 

4 

« 

« 

5 

*  by  stan  (itched 

nove(ber  21,1984  * 

6 

« 

Lattice  C  v.  1.84  ♦ 

7 

8 

9 

•define  void  int 

le 

•define  byte  char 

li 

•include  'stdio.h* 

12 

13 

/*  DOS  function  calls  */ 

14 

•define  OPENFCB 

8x8f 

15 

•define  CLOSEFCB 

8x18 

IS 

•define  VERSION 

8x38 

17 

18 

extern  int  peek!); 

19 

28 

struct  DEVHDR 

21 

( 

22 

unsigned 

nxthdr  off1, 

/♦  doubleuord  ptr  to  next  device  hdr  in  chain  */ 

23 

unsigned 

nxthdr  sen; 

24 

unsigned 

attr; 

/*  type  of  device  driver  •/ 

25 

unsigned 

strat; 

/*  device  strategy  entry  point  */ 

26 

unsigned 

intrpt; 

/*  interrupt  entry  point  */ 

27 

char 

dnaeee  C87 ; 

!*  device  na»e  */ 

28 

); 

Dr.  Dobb’s  Journal,  May  1 985 


ah; 


16-Bit  Toolbox 

Listing  Two 


(Listing  Continued,  text  begins  on  page  118) 


29 


39 

struct 

FCB 

31 

( 

32 

bgte 

drive; 

/»  drive  designator  */ 

33 

char 

fnameilll; 

/*  file  or  device  name  *1 

34 

unsigned 

curblk; 

/*  current  blk  (set  to  8  by  OPENFCB)  */ 

35 

unsigned 

recsiz; 

/*  logical  record  size  (set  to  8x89  by  OPtNFCB)  ♦/ 

36 

long 

fsize; 

/*  file  size  in  bytes  */ 

37 

unsigned 

date; 

/*  creation  or  last  update  date  */ 

38 

byte 

sys  rsvtlBl; 

/*  fields  reserved  for  DOS  */ 

39 

byte 

bsetCSI ; 

/*  relative  record  numbers  */ 

48 

>i 

41 

42 

struct 

RSV2  X 

43 

( 

44 

unsigned 

time; 

/*  creation  or  last  update  time  */ 

45 

byte 

attribute; 

/*  device  or  file  attribute  */ 

46 

unsigned 

dhdroff; 

/*  offset  address  of  device  header  »/ 

47 

unsigned 

dhdrseg; 

/*  segment  address  of  device  header  */ 

48 

byte 

unk(31; 

/*  unknomn  usage  */ 

49 

); 

59 

51 

struct 

RSV3  X 

52 

( 

53 

unsigned 

time; 

/*  creation  or  last  update  time  */ 

54 

unsigned 

attribute; 

/*  device  or  file  attribute  */ 

55 

unsigned 

dhdr_of f ; 

/*  offset  address  of  device  header  */ 

56 

unsigned 

dhdr  seg; 

/*  segment  address  of  device  header  */ 

57 

byte 

unkC23; 

/*  unknown  usage  */ 

58 

); 

59 

69 

struct 

DEWDR 

device; 

/*  device  header  */ 

61 

struct 

FCB 

devicejcb; 

/*  standard  FCB  for  device  V 

62 

struct 

RSV2  X 

*dos2x; 

/»  reserved  field  definitions  DOS  2.x  */ 

63 

struct 

RSV3  X 

*dos3x; 

/*  reserved  field  definitions  DOS  3.x  */ 

64 

65 

mainO 

66 

( 

67 

int 

dev_of f , dev_seg, i,  j; 

68 

69 

initfcb(8,"NUl 

");  /•  set 

up  standard  FCB  for  NUL  device  */ 

7*  if  (bdos(OPENFCB,4device  fcb)  4  9xff)  /*  open  the  device  */ 

71  ( 

72  printf ( "Unable  to  open  device\n")j 

73  exit(l); 

74  ) 

75  if  ((bdos(VERSI(W,8)  4  8xff)  =  3) 

7 A  t  /*  the  reserved  fields  are  allocated  different  1 y  in  ICS  3.x  */ 

77  dos3x=(struct  RSV3_X  *)  &device_fcb. ss)s_rsvC01; 

78  dev_of f =dos3x->dhdr_of f ; 

79  dev  seg=dos3x->dhdr_seg; 

89  1 

81  else  if  < (bdos (VERSION, »)  4  8xff)  =  2) 

82  (/»...  then  they  were  in  DOS  2.x  */ 

83  dos2x=(struct  RSV2_X  *)  &device_fcb. sys_rsvt@1; 

84  dev_of f =dos2x->dhdr_of f ; 

85  dev_seg=dos2x->dhdr_seg; 

86  ) 

87  else 

88  {  /*  forget  it  for  DOS  1.18  or  earlier  V 

89  printf  (*  DOS  2.89  or  ne»er  required\n" ) ; 

99  exit(l); 

91  ) 

92 

93  printf < *  Device  driver  chain  ....  \n"l; 

94  printf ( *\n" >; 

95 

96  /*  display  the  current  DOS  chain  of  device  drivers  */ 

97  printf!"  ptr  type  na»e  strategg  ptr  interrupt  ptr  \n* ) 5 

98 

99  for  (;; > 

199  ( 

181  printf ("7.84x:7.94x  ’,dev  seq,dev  off);  /*  device  header  ptr  */ 

192  if  (devoff  ~  8xffff)  break;  "  /«  if  last  in  chain,  offset=9xffff  */ 

183  /*  move  the  device  header  into  the  data  segment  structure  "device"  */ 


124 


Dr.  Dobb's  Journal,  May  1985 


1*4 
1*5 
1*6 
1*7 
1*8 
1*9 
11* 
111 
1 12 

113 

114 

115 

116 

117 

118 
119 
12* 
121 
122 

123 

124 

125 

126 

127 

128 
129 
13* 


_p«ek(dev_seg, dev_of f ,  Jtdevice, sizeof ( device) ) ; 

printfC  7.*4x  ",  device,  attr);  /*  display  the  device  attribute  word  */ 

if  ((device. attr  4  *x8***l  ~  *)  /»  if  a  block  device  ...  */ 

for  (j=*;j<=3;j‘+)  printf C7.*2x", device. dnaaeljb;  /*  there  is  no  device  naae  */ 

else 

for  (j=*| j<=7; j++)  printf  (“7.c“, device. dnaaetjl);  /*  otherwise  display  device  nane  */ 

printf ("  7.*4x:7.«4x  “,dev_seg, device. strati;  /*  strategy  entry  point  */ 

printf ("7.*4x:7.*4x  \n\dev_seg, device. intrpt);  /«  “interrupt*  entry  point  */ 
dev_seg=device.nxthdr_seg;  /*  set  up  ptr  to  next  device  header  */ 
dev  off=device.nxthdr  off;  /*  and  loop  back  to  display  it  */ 

) 


) 

/*  initialize  standard  FCB  */ 
void  initfcb(drv,na«ie) 
byte  dr v; 

char  naaeU; 

( 

int  i; 

device_fcb.drive=drv;  /*  drive  designation:  default  dri ve=*,  A=1 ,  etc.  */ 
for  (i=*;i<=l*;i‘+)  device_fcb. f naneC i 3=na»et i 1;  /*  device  or  file  name  */ 
for  (i=*;i<=4;i++>  device_fcb.bsettil=*;  /*  fields  not  zeroed  by  open  call  */ 

End  Listing  Two 


Listing  Three 

Assembly  language  source  for  "  peek"  library  function, 
used  in  Stan  Mitchell's  device  driver  dump  program. 


=  ***4 

oujjia, 

Wvv 


PAGE 

PAGE  62,132 
NtflE  peek 

HSmHHIHHIlHtmtmHHHHimHIHHHHHHHHHHHIHHmm 

void  _peek(segaent,offset,buffer,nbytes) 


unsigned  segaent; 
unsigned  offset; 
byte  ‘buffer; 
unsigned  nbytes; 


/*  segaent  portion  of  aeaory  addr  */ 

/*  offset  portion  of  aeaory  addr  */ 

/*  local  aeaory  buffer  (in  data  segaent)  *1 
/*  nuaber  of  bytes  to  transfer  */ 


Lattice  C  asseably  language  interface  convention  followed 
(ES=DS  on  entry) 


EXTRA  EQU  4 


PGROUP  GROUP  PROG 
PROG  SEGMENT  BYTE  PUBLIC  'PROG' 
PUBLIC  PEEK 
ASSUME  CS: PGROUP 


AAAA 

PWv 

PEEK  PROC 

f€AR 

AAAA 

VWrO 

55 

PUSH 

BP 

mi 

8B  EC 

MOV 

EP,SP 

m3 

IE 

PUSH 

DS 

***4 

56 

PUSH 

SI 

0**5 

57 

PUSH 

DI 

***6 

51 

PUSH 

CX 

I 

;get 

source  segaent 

»**7 

8E  5E  *4 

MOV 

DS.HORD  PTR  (BP+EXTRA) 

i 

;get 

source  offset 

**«A 

8B  76  *6 

MOV 

SI, WORD  PTR  CBP+EXTRA+2] 

1 

;get 

destination  offset  in  ES 

**«d 

8B  7E  *8 

MOV 

DI.WORD  PTR  [BP+EXTRAM1 

! 

;  get 

byte  count  for  transfer 

e«i« 

8B  4E  *A 

MOV 

CX.HORD  PTR  CBP+EXTRA‘6] 

» 

;»ove 

the  bytes  into  local  buffer 

**13 

F37  A4 

REP 

MOVSB 

Dr.  Dobb's  Journal,  May  1985 


125 

417 


16-Bit  Toolbox 

Listing  Three 


(Listing  Continued,  text  begins  on  page  118) 


W15 

59 

POP 

O 

X 

Wit 

5F 

POP 

01 

W17 

5E 

POP 

SI 

W18 

IF 

POP 

DS 

Wl? 

5D 

POP 

BP 

Wlfl 

C3 

RET 

W1B 

PEEK 

ENDP 

W1B 

PROG 

ENDS 

END 

End  Listing  Three 


Listing  Four 


Gregor  Owen's  Microsoft  Assembler  Bug  of  the  Month. 

Assemble  this  brief  program  for  some  entertaining  error  messages, 
then  comment  out  the  line  "es:lodsb"  and  assemble  it  again. 


microwiff  segment 

assume  cs : microwiff , ds: microwiff , es: microwiff , ss : microwiff 


microwimp: 


es: lodsb 

lods  byte  ptr  es: [si+0] 

mov  ax,es:[si+l] 

microwiff  ends 


end  microwimp 


End  Listings 


126 


Dr.  Dobb’s  Journal,  May  1985 


SOFTWARE  TOOLS  FOR  ADVANCED  PROGRAMMERS 


CANADA 


Telecommunications 


Christensen  Protocols  in  C 
2400  Baud  and  Beyond  ^ 
New  Column:  Unix  Exchange 

Queues,  Bitmaps,  LaserJets 


Dr.  Dobb’s  Journal 

Editorial 

EdKor-in-Cliltf  Michael  Swaine 

Editor  Randy  Sutherland 
Managing  Editor  Frank  DeRose 
Technical  Editor  Alex  Ragen 
Editorial  Assistant  Sara  Noah 
Contributing  Editors  Robert  Blum. 

Dave  Cortesi, 

Ray  Duncan. 

W'  el  Allen  Hoi ub 

Copy  Editor  Polly  Koch 
Typesetter  JeanAring 
Production 
Design/ Production 

Director  Delta  Penna 
Art  Director  Shelley  Rae  Doeden 
Production  Assistant  Alida  Hinton 
Cover  William  Cone 
.  Circulation 

Sub.  Fulfillment  Mgr.  Stephanie  Barber 
Subscription  Mgr.  Maureen  Snee 
Book  Marketing  Mgr.  Jane  Sharninghouse 
Single  Copy  Sale*  Mgr.  Kathleen  Boyd 
Administration 
Finance  Manager  Sandra  Dunie 
Business  Manager  Betty  Trickelt 
Accounts  Payable  Sup*.  Mayda  Lopez -Quintana 
Accounts  Payable  Asst.  Denise  Giannini 
Billing  Coordinator  Laura  Di  Lazzaro 
Advertising 
Advertising  Director 
Stephen  Friedman  141 5)  424-0600 
Advertising  Sales 

Walter  Andrzejewski  (617)  367-8361 
Lisa  Boudreau  1415)  424-0600 
Beth  Dudas  1714)  643-9439 
Michele  Beaty  (31 7)  875-0557 
DDJ  Classifieds 
Alice  Abrams  (415)424-0600 
Advertising  Coordinators 
Jay  Horvath  (415)  424-0600 
Alice  Abrams  (415)  424-0600 


M&T  Publishing,  inc. 

Chairman  of  the  Board  Otmar  Weber 


Director  C  F  von  Quad t 
President  Laird  Foshay 


Entire  contents  copyright  ®  i  985  by  M&T  Publishing, 
Inc.  unless  otherwise  noted  on  specific  articles.  All 
rights  reserved. 


Dr.  Dobb’s  Journal  (USPS  307690)  is  published 
monthly  by  MAT  Publishing,  Inc.,  2464  Embarcadero 
Way.  Palo  Alto.  CA  94303.  (415)  424-0600.  Second 
class  postage  paid  at  Paio  Alto  and  at  additional  entry 
points. 

Address  correction  requested.  Postmaster  Send  Form 
3579  to  Dr.  Dobb's  Journal,  2464  Embarcadero 
Way,  Palo  Alto,  CA  94303.  ISSN  0278-0508 

Subscription  Rates:  $25  per  year  within  the  United 
States,  $44  for  first  ctass  to  Canada  and  Mexico.  $62 
for  airmail  to  other  countries.  Payment  must  be  in  U.S. 
Dollars,  drawn  on  a  US.  Bank.  For  subscription  prob¬ 
lems,  call,  outside  of  CA  1-800-321-3333;  within  CA  I- 
619-485-6535. 

Foreign  Distributors:  ASCII  Publishing,  Inc.  (Ja¬ 
pan),  Computer  Services  (Australia),  Computer  Store 
(New  Zealand),  Computercollectief  (Nederland), 
Homecomputer  Vertriebs  GMBH  (West  Germany). 
International  Presse  (West  Germany),  La  Nacelle 
Bookstore  (Prance),  McGill’s  News  Agency  PTY  LTD 
(Australia),  Progresco  (France). 


People's  Computer  Company 

Of.  Dobb’s  Journal  is  published  by  MAT  Publishing, 
Inc.  under  license  from  People’s  Computer  Company. 
2682  Bishop  Dr.,  Suite  107,  San  Ramon,  CA  94583,  a, 
non-profit,  educational  corporation. 


4 

Aon 


June  1985 
Volume  10,  Issue  6 


CONTENTS 


The  View  from  Mid-Year 

These  are  the  articles  that  generated  the  most  reader  response  in  recent  months; 

January,  “Fatten  Your  Mac”  by  Thomas  LaFleur  and  Susan  Raab,  and  “Archiv¬ 
ing  Files  with  CP/M  80  and  CP/M  86”  by  Ian  Ashdown 

February,  Excerpts  from  Fire  in  the  Valley  by  Paul  Freiberger  and  Michael 
Swaine,  and  “CP/M  Exchange,”  in  which  Bob  Blum  discusses  buffered  disk  I/O 

March,  Richard  Stallman’s  “GNU  Manifesto,”  and  Dave  Cortesi’s  “Tour  of 
Prolog” 

April,  Allen  Holub’s  general  purpose  Quicksort  routine  in  “C  Chest” 

May,  “Solid  Shape  Drawing  on  the  Commodore-64”  by  Richard  Rylander 

This  month,  Dean  Gengle,  our  featured  author,  asks  how  we  are  to  maintain  democ¬ 
racy  in  an  information  age.  There  is  more  to  telecommunications  than  baud  rates 
and  band  widths!  Special  thanks  to  lan  Ashdown  of  byHeart  Software  and  Steven 
Bellovin  of  AT&T  Bell  Laboratories  for  serving  as  referees. 

Next  month  we  deliver  three  hardware  articles:  “Build  a  Custom  PC  or  Clone,” 
“The  Ultimate  Parallel  Print-Spooler,”  and  the  feature  “Designing  a  Real-Time 
Clock  for  the  S-100  Bus.”  In  “16-Bit  Toolbox,”  Ray  Duncan  presents  MSDOS 
installable  device  drivers  with  an  overview  of  the  Unix  device  drivers  which  inspired 
them.  Allen  Holub,  in  “C  Chest”  offers  an  MSDOS  directory  utility  which  includes 
a  multicolumn  print  utility.  “Computer  Calisthenics”  will  announce  the  winners 
and  answers  to  Michael  Wiesenberg’s  recent  puzzle  competition.  We  will  also 
introduce  a  new  column,  “Mac  Toolbox,”  to  provide  the  background  needed  to 
write  programs  that  really  make  use  of  the  Macintosh’s  capabilities. 

August,  we’ll  C. 

In  September  we’ll  take  a  practical  look  at  an  academic  subject:  Algorithms. 

October  Forth  issue:  Leo  Brodie  will  report  on  the  implications  of  the  Novix  chip 
for  programmers.  We  also  expect  to  cover  Dr.  Alan  Winfield’s  Forth  machine:  an 
English  job,  with  racing  stripes.  And  we’ll  review  Neon,  a  Forth-based,  Smalltalk¬ 
like  language  for  the  Mac. 

November:  Programming  Tools  for  Modula-2  Programmers. 

December:  Programmer’s  View  of  GEM. 


Randy  Sutherland 
Referees  Editor 

Ian  Ashdown,  byHeart  Software 
Steven  Bellovin,  AT&T  Bell  Laboratories 

Dr.  Dobb’s  Journal,  June  1985 


Dr.  Dobb’s  Journal 


ARTICLES 


Information  Age  Issues 

by  Dean  Gengle 

52 

Advances  in  telecommunications  carry  risks  to  our  civil 
liberties;  the  problems  and  some  suggested  solutions. 

(Reader  Ballot  No.  1 93). 

Modems:  2400  Bit/Sec  and  Beyond 

by  Dale  Walsh 

56 

Two  approaches  to  increasing  modem  speed:  use  more  of 
the  available  bandwidth;  construct  more  densely  packed 
signaling  alphabets.  (Reader  Ballot  No.  197). 

C  UART  Controller 

by  Don  Gay 

60 

A  program  for  easy  modification  of  the  parameters  of  a 
programmable  USART.  (Reader  Ballot  No.  1 95). 

Christensen  Protocols  in  C 

by  Donald  Krantz 

66 

This  highly-portable  file  transfer  utility  is  a  basic 
Christensen  protocol  engine  with  a  minimal  terminal 
program.  (Reader  Service  No.  196). 

COLUMNS 

Dr.  Dobb's  Clinic 

by  D.  E.  Cortesi 

16 

The  end  of  shadowy  printing;  Z80  and  BASIC  code 
puzzles;  pseudo-random  numbers;  alloc(  )  and  free(  ). 

(Reader  Ballot  No.  1 90). 

C  Chest 

by  Allen  Holub 

26 

Queue  management  routines;  bitmaps  for  grep.c.  (Reader 
Ballot  No.  191). 

Unix  Exchange 

by  Axel  Schreiner 

42 

Tricks  with  the  C  preprocessor.  (Reader  Ballot  No.  192). 

1 6-Bit  Software  Toolbox 

by  Ray  Duncan 

94 

WordStar  for  the  HP  LaserJet;  Microsoft  8086 
MacroAssembler  2.0;  the  TRIM  filter.  (Reader  Ballot  No. 
194). 

CP/M  Exchange 

by  Bob  Blum 

106 

SID2TTY,  an  alternate  console  for  SID;  CP/M  Plus 
inaccuracy.  (Reader  Service  No.  198). 

DEPARTMENTS 

Editorial 

6 

Letters 

8 

Reviews 

92 

Artisoft’s  Envoy 

DDJ  Classifieds 

99 

Of  Interest 

118 

Advertiser  Index 

128 

Dr.  Dobb’s  Journal,  June  1985 


5 

421 


EDITORIAL 


You’ll  find  the  answers  to  this  month’s  Industry  Awareness  quiz  upside 
down  at  the  bottom  of  the  page. 

[  1  ]  How  many  computer  books  have  been  published  in  the  last  five 
years  with  the  title  Up  and  Running ? 

[2]  How  many  C  compilers  are  available  for  the  IBM  PC? 

[3]  How  many  IBM  360/75s  were  there  installed  in  1979?  (Source:  Inter¬ 
national  Data  Corporation.) 

[4]  How  many  DEC  PDP-8s?  (Source:  ibid.) 

[5]  How  many  computer  magazines  folded  in  1984?  (Source:  Marketing 
Technology.) 

[6]  What  do  you  call  a  computer  company  that  drives  away  people  like 
Steve  Wozniak  and  Andy  Hertzfeld,  pretends  that  the  division  keeping  the 
company  alive  doesn’t  exist  and  thinks  that  it  can  make  a  machine  a  home 
computer  or  a  portable  by  fiat?  (Hint:  it  starts  with  an  A.) 

[7]  Adam  Osborne  is  predicting  that  within  a  year  virtually  all  microcom¬ 
puter  software  will  sell  for  under  $100  and  Phillippe  Kahn  has  been  heard 
making  predictions  of  wealth  for  Borland  personnel  that  sound  suspiciously 
like  those  we  heard  from  Osborne  Computer  Company  executives  on  60  Min¬ 
utes  a  few  years  ago.  Is  Osborne  emulating  Kahn,  or  is  Kahn  emulating 
Osborne? 

[8]  By  what  ratio  will  Bill  “The  Cracker’’  Landreth’s  royalties  for  his  book 
Out  of  the  Inner  Circle  exceed  the  fine  he  paid  when  convicted  of  wire  fraud? 
(Hint:  his  fine  was  half  the  dollar  amount  of  the  damage  that  he  says  the  FBI 
did  to  his  equipment  in  confiscating  it.) 

[9]  What  do  you  call  an  Association  for  DAta-Processing  Service  Organi¬ 
zations  that  responds  to  the  vehement  rejection  by  the  Microcomputer  Man¬ 
agers’  Association  of  its  copy-protection  proposal  (a  plan  that  MMA  threat¬ 
ened  to  respond  to  by  boycotting  participating  vendors)  by  saying  “Corporate 
America  would  find  it  hard  to  turn  its  back  on  the  [software]  these  companies 
are  offering”?  (Hint:  it  starts  with  an  A.) 

[  10]  Back  in  February,  San  Francisco  police  lieutenant  Thomas  Suttmeier 
admitted  that  he  had  for  the  preceding  two  months  had  access  to  all  computer 
files  in  the  city’s  criminal  justice  system,  including  the  public  defender’s  files 
of  defense  strategies  and  files  of  the  office  that  handles  civilian  complaints 
against  the  police.  In  fact,  any  police  officer  with  access  to  the  computer  could 
have  tapped  into  the  files  by  typing  “Tom’s  Menus.”  What  can  be  done  to 
prevent  such  abuses  of  power? 


Michael  Swaine 


Suojm  SuiqtXuu  op  ^upjnoM  Aaqj_  ajaq  aotjod  aqi  jnoqB  3up(|B} 
snouas  ag  :[oi]  juBSojjy  :[6]  'F001  ‘ajBuiijsa  aAijBAjasuoa  Ag  :[8]  }SBd 
aqr  aaquiauiaj  jo  saossaaapajd  a8paiMOu:>(OB  ‘ajBtiuii  ‘atBjnuia  jou  op  sanau 
-ajdajjUH  JaqjtaN  :[/.]  tUBSojjy  :[9]  -gg  :[g]  599‘t7£  [fr]  g£  [£]  qtuoui  txau 
uiaqj  SuiA\aiAaj  aJ,aAy  uazop  omj  jsBaj  jy  \l]  "saim  JSB3|  jy  :[  j  ]  :sjaMSuy 


6 


A99 


Dr.  Dobb’s  Journal,  June  1985 


LETTERS 


Prolog 

Dear  DDJ. 

I  enjoyed  reading  your  March  [1985, 
#101]  issue,  especially  the  article  en¬ 
titled  “Programming  In  Logic.”  I  am 
an  avid  believer  in  Prolog,  hoping  to 
see  it  continue  to  catch  on  in  the  U.S. 

I’m  writing  to  you  in  part  to  thank 
you  for  helping  promote  an  under¬ 
standing  of  logic  programming  and 
Prolog.  I  am  also  writing  to  let  you 
know  that  while  the  table  of  sources 
of  Prolog  was  a  terrific  addition  to 
the  article,  it  was  not  complete. 
Arity,  a  spin-off  from  LOTUS  Devel¬ 
opment  Corporation  and  recently 
funded  by  LOTUS,  is  in  the  business 
of  building  sophisticated  Prolog  tools 
and  applications. 

We  take  great  pride  in  having  built 
Arity/Prolog,  a  highly  optimized  and 
extended  version  of  Prolog.  We  have 
two  products  available  now:  the 
Arity/Prolog  Compiler  and  Inter¬ 
preter  and  the  Arity/Prolog  Inter¬ 
preter.  We  are  also  very  excited 
about  our  upcoming  products. 
Sincerely, 

Meredith  Bartlett 
Arity  Corporation 
336  Baker  Avenue 
Concord,  MA  01742 

Dear  DDJ: 

Congratulations  on  your  Prolog  is¬ 
sue.  The  articles,  together  with  Dean 
Schlobohm’s  Tax  Advisor  program, 
provide  an  excellent  introduction  to 
Prolog. 

I  would  just  like  to  add,  for  your 
readers’  information,  the  Quintus 
name  to  John  Malpas’  list  of  sources 
of  Prolog.  Quintus  supplies  a  high- 
performance  Prolog  development 
system  for  the  Sun  workstation,  and 
DEC  Vax  under  Unix  4.2  and  VMS. 
Quintus  Prolog  Release  1.0  features 
an  incremental  compiler,  integrated 


Emacs  text  editor,  comprehensive  de¬ 
bugger,  style  checker,  C  interface, 
on-line  help  system,  and  a  high  de¬ 
gree  of  compatibility  with  DEC- 10/ 
20  Prolog  and  C-Prolog.  In  addition, 
Quintus  distributes  DEC-10/20  Pro¬ 
log  as  an  unsupported  product. 
Sincerely, 

David  Warren 
Quintus  Computer  Systems 
2345  Yale  Street 
Palo  Alto,  CA  94306 

Capitalism  vs. 

Stallmanism? 

Dear  DDJ : 

Although  I  am  no  fan  of  Unix,  I  read 
Richard  Stallman’s  “Manifesto” 
with  delight.  Here  is  a  true  adherent 
to  the  Hacker  ethic!  Mr.  Stallman’s 
militant  advocacy  is  enhanced  by  his 
well-written  arguments.  Many  may 
find  points  to  dispute  in  his  state¬ 
ment,  but  all  will  find  it  thought-pro¬ 
voking  and  interesting. 

I  wish  him  and  his  associates  luck. 
The  computer  industry  needs  to  be 
turned  upside  down  every  so  often. 
Mr.  Stallman  seems  just  the  fellow  to 
do  it  this  time.  Gee — maybe  I  ought 
to  reconsider  my  dislike  of  Unix — I’d 
hate  to  be  left  out  of  the  fun  when 
GNU  arrives! 

Yours, 

James  F.  Glass 

18653  Ventura  Boulevard 

Suite  351 

Tarzana,  CA  91356 
Dear  DDJ\ 

Richard  Stallman’s  “The  GNU  Man¬ 
ifesto”  in  your  March  issue  made  me 
furious.  I’d  love  to  take  5  pages  to  dis¬ 
pute  all  of  his  5  pages,  but  I’ll  confine 
myself  here  to  2  major  points. 

First,  Stallman  doesn’t  believe  in 
property  rights.  As  evidence  on  a 
small  scale,  note  the  omission  of  the 


usual  “Unix  is  a  trademark  of  Bell 
Labs”  in  his  article,  which  was  pres¬ 
ent  in  every  advertisement  in  that  is¬ 
sue.  Stallman  purposely  omits  this 
because  he  doesn’t  recognize  their 
claim  to  the  name  Unix. 

However,  if  it  hadn’t  been  for  all 
the  time  and  money  invested  by  Bell 
Labs  in  development,  then  Unix 
would  not  be  as  widespread,  popular, 
or  valuable  as  it  is  today.  If  Unix  had 
no  value,  then  neither  would  GNU. 
(To  see  this,  reread  Stallman’s  arti¬ 
cle,  substituting  “Glorp”  for  “Unix” 
and  see  if  he  still  makes  sense.)  In 
effect,  Stallman  is  happy  to  take  full 
advantage  of  Bell  Labs’  investment, 
with  no  recompense  to  them.  Would 
you  spend  your  time  and  money  de¬ 
veloping  software,  only  to  have  Stall- 
man  copy  it  and  give  it  away  for  free? 
I  wouldn’t.  (In  fact,  Stallman 
wouldn’t  even  give  you  the  choice.  He 
states:  “the  desire  to  be  rewarded  for 
one’s  creativity  does  not  justify  de¬ 
priving  the  world  in  general  of  all  or 
part  of  that  creativity.”) 

Second,  Stallman’s  explicit  philos¬ 
ophy  is  socialist  redistributionism. 
He  states  that  “all  sorts  of  develop¬ 
ment  can  be  funded  with  a  software 
tax.”  Stallman  wants  to  tax  me  to 
fund  him  to  develop  software  he  will 
give  away  for  free.  Is  this  how  you 
want  your  tax  money  spent?  Not  me. 

Stallman  states:  “the  fundamental 
act  of  friendship  among  program¬ 
mers  is  the  sharing  of  programs”; 
“good  system  software  [should  be] 
free,  just  like  air”;  what  deserves  a 
reward  is  “social  contribution.”  In 
other  words,  the  Good  is  what  is  so¬ 
cial,  shared,  free.  On  the  other  hand, 
he  states:  “marketing  agreements  .  . . 
forbid  programmers  to  treat  others  as 
friends”;  “users  [are]  at  the  mercy  of 
one  programmer  or  company  that 
owns  the  sources”;  software  vendors 


Dr.  Dobb's  Journal,  June  1985 


8 

423 


“[extract]  money  from  users  of  a  pro¬ 
gram  by  restricting  their  use  of  it”; 
“a  person  who  enforces  a  copyright  is 
harming  society.”  In  other  words,  the 
Bad  is  what  is  individual,  owned, 
bought,  sold. 

Stallman  obviously  prefers  the  for¬ 
mer,  which  is  characteristic  of  the 
Marxist  ideals  of  the  Soviet  Union. 
Me,  I  prefer  the  latter,  typified  by  the 
good  old  capitalist  U.  S.  of  A. 

Stallman  says  he  is  looking  for 
contributions.  I  would  be  delighted 
to  offer  a  one-way  plane  ticket  to 
Moscow. 

Sincerely, 

Robert  Schwartz 

P.O.  Box  1637 

Wakefield,  M  A  01880 

Dear  Richard: 

Hurrah!  Three  cheers  for  GNU!  Your 
proposal  and  the  philosophy  behind  it 
are  so  refreshing,  I  almost  couldn’t 
believe  what  I  was  reading  in  this 
month’s  issue  of  Dr.  Dobb’s  Journal. 
As  you  can  see,  I  am  ecstatic  that 
someone  is  finally  doing  something 
about  Unix. 

Since  about  1978  or  so  I  have  been 
going  to  Unix  users’  meetings,  and  I 
remember  meeting  Ron  Cain  at  one 
of  these  meetings  before  he  left  SRI.  I 
obtained  a  Unix  Version  6  system 
about  that  time  to  run  on  a  PDP-1 1  / 
70  for  a  company  contract.  One  of 
my  colleagues  spent  a  few  nights 
writing  some  very  useful  programs; 
one  was  a  combination  nroff/troff 
marriage  that  output  bit-mapped 
fonts  on  a  Versatec  printer.  Another 
rotated  the  nroff/troff  output  by  90 
degrees  so  that  printed  copy  came  out 
sidewise,  but  properly  formatted  on 
the  Versatec  1 1  X8'/2  fan-fold  paper. 
With  that  system  our  little  company 
was  in  business,  and  the  number  of 
reports  and  proposals  done  on  that 
system  was  amazing.  We  had  to  re¬ 
pair  the  file  system  now  and  then,  but 
that  was  a  minor  thing  compared  to 
the  amount  of  work  that  got  done. 

Rather  than  waste  your  time  with 
more  of  my  nostalgia,  I  want  to  tell 
you  that  I  have  had  in  mind  the  same 
idea  as  you  have  proposed.  After 
some  of  my  friends  were  involved  in 
the  secure  Unix  kernel  work  at 
UCLA,  I  began  to  realize  that  it  was 


in  fact  possible  to  write  a  Unix-like 
system  that  would  not  have  the  royal¬ 
ty  problems  that  make  Unix  (even  on 
micros)  so  ridiculously  expensive.  It 
is  a  crime  that  the  micro  community 
does  not  have  access  to  the  hundreds 
of  programs  written  in  C  for  Unix 
systems  and  that  so  many  (inferior) 
wheels  are  being  reinvented.  I  ob¬ 
tained  a  copy  of  the  XINU  book  and 
have  accumulated  a  library  of  arti¬ 
cles  on  other  people’s  experiences  in 
porting  Unix  to  new  machines.  I  had 
just  about  everything  I  needed  except 
a  C  compiler  for  my  (don’t  laugh) 
Commodore  64.  Recently  one  has 
been  announced  by  Abacus  Software, 
so  I  am  getting  all  set. 

The  C64  is  a  short-term  thing;  I 
am  looking  forward  to  getting  a 
68000-based  system  soon.  My  moti¬ 
vation  for  wanting  to  do  a  system  on 
the  C64  was  to  be  able  to  run  good 
programs  (e.g.,  a  full-screen  editor 
like  Emacs)  on  that  system.  Memory 
limitations  will  obviously  rule  out 
multiple  users,  but  if  someone  comes 
along  with  a  hard  disk  interface,  at 
least  limited  multi-processing  would 
be  nice.  Already  there  are  switchable 
memory  banks  available  in  64K  sec¬ 
tions  up  to  512K. 

For  what  it’s  worth,  I  would  like  to 
offer  my  help  in  your  enterprise.  This 
would  most  likely  take  the  form  of 
programming  assistance.  I  suspect 
you  will  get  a  lot  of  other  offers  of 
help  as  well,  now  that  you  have  “gone 
public”  with  your  GNU  concept.  My 
resources  are  somewhat  limited;  how¬ 
ever,  it  may  be  possible  to  get  a 
Southern  California  “GNU  Group” 
going.  Perhaps  you  will  spark  enough 
interest  to  form  a  mailing  list  of 
volunteers. 

Once  again,  I  am  glad  someone 
like  you  has  come  along,  and  I  hope 
there  is  some  way  I  can  help  out. 
Sincerely, 

Rollin  V.  Weeks 
7130  Marymount  Way 
Goleta,  CA  931 17 

Dear  DDJ\ 

After  reading  Richard  Stallman’s 
“Manifesto”  [March  1985,  #101],  I 
felt  compelled  to  respond.  To  begin,  I 
am  certainly  not  opposed  to  the  idea 
of  a  public  domain  version  of  Unix, 


nor  to  public  domain  software  in  gen¬ 
eral.  But  my  initial  reaction  to  Mr. 
Stallman’s  sanctimonious  outpouring 
was  to  feel  rather  insulted.  I  have 
been  a  programmer  for  about  ten 
years  now,  and  I  don’t  believe  that 
programmers  are  any  more  or  any 
less  greedy  than  others  in  this  society. 
In  the  case  of  software  companies 
supposedly  making  large  sums  of 
money  selling  copyrighted  products,  I 
rather  doubt  whether  the  program¬ 
mers  see  very  much  of  the  profits. 

I  believe  Mr.  Stallman  must  be  suf¬ 
fering  from  a  few  other  fantasies  be¬ 
sides  that  of  a  public  domain  Unix.  He 
perhaps  forgets  that  he  is  living  in  a 
capitalist  society.  Of  course,  every¬ 
body  needs  food,  even  more  than  free 
software.  So  food  would  be  distributed 
for  free,  right?  I  have  no  doubt  that 
farmers,  food  processing  workers,  su¬ 
permarket  clerks,  truck  drivers  and 
others  employed  in  the  food  industry 
will  not  let  personal  greed  blind  them 
to  the  logic  of  this  argument.  People 
need  clothing,  housing,  medical  care 
and  education,  too.  All  these  things 
are  necessities  and  as  such  should  be 
guaranteed  to  everyone  ....  As  for 
Mr.  Stallman,  I  wonder  how  he  can 
afford  to  devote  so  much  of  his  time  to 
this  admirable  project.  Perhaps  be¬ 
cause  his  previous  employer  paid  him 
such  a  fat  salary  that  he  can  afford  to 
forget  about  the  dirty  business  of 
making  a  living  for  a  little  while. 

I  am  afraid  that  I  might  sound  al¬ 
most  as  self-righteous  as  Mr.  Stall- 
man.  I  read  Dr.  Dobb’s  for  its  valu¬ 
able  technical  information  and  for 
enjoyment.  If  any  of  your  readers  are 
concerned  about  moral  or  political  is¬ 
sues,  I  would  recommend  that  they 
subscribe  to  the  New  York  Guardian. 
And  I  would  also  recommend  that 
Mr.  Stallman  stick  to  technical  mat¬ 
ters,  for  which  I  am  sure  he  is  highly 
qualified. 

Sincerely, 

David  Kettle 
8  Milepost  Place,  #308 
Toronto,  Ont.  M4H  1E1 

Dear  DDJ: 

I  read  with  excitement  Richard  Stall¬ 
man’s  “The  GNU  Manifesto”  until  I 
reached  his  justifications  for  his  ap¬ 
proach.  I  feel  torn  because,  on  the 


10 


Dr.  Dobb' s  Journal.  June  1985 


one  hand,  I  applaud  his  objective, 
but,  on  the  other,  I  cannot  accept  his 
rationale  of  why  this  is  the  proper 
approach. 

My  problem  is  that  he  says  that 
“social  contribution”  should  be  re¬ 
ward  for  an  individual’s  creativity.  In¬ 
deed,  he  claims  that  a  personal  reward 
is  “destructive”  because  it  deprives  so¬ 
ciety  of  the  potential  benefits  of  an  in¬ 
dividual’s  creativity.  I  am  a  program¬ 
mer  and  the  owner  of  a  business  that 
sells  software  and  from  a  selfish  point 
of  view  would  reject  his  arguments. 
But  I  am  also  a  free  market  advocate 
and  I  reject  his  statements  on  philo¬ 
sophical  grounds  which  I  will  attempt 
to  enumerate. 

First  the  wealth  of  society  is  in¬ 
creased  whenever  individuals  trade 
freely.  If  I  charge  $100.00  for  a  pro¬ 
gram  and  you  freely  decide  to  buy  the 
program,  we  are  both  richer.  I  have 
my  $100.00,  which  I  value  higher 
than  the  copy  of  the  program,  and  you 
have  the  program,  which  you  value 
higher  than  the  $100.00.  If  we  were 
not  both  richer,  then  one  or  both  of  us 
would  not  have  entered  into  the  trade. 

To  say  that  this  is  “destructive”  to 
society  because,  by  charging,  I  deny 
the  benefits  of  my  program  to  society, 
is  to  say  that  whatever  one  creates 
does  not  belong  to  him,  but  belongs  in 
some  larger  sense  to  society,  and, 
therefore,  to  deny  it  to  society  is  be¬ 
ing  “destructive.” 

In  a  narrow  sense  Mr.  Stallman  is 
right.  If  Lotus  Development  gave 
away  “Lotus  1  -2-3”  instead  of  charg¬ 
ing  for  it,  we  would  all  use  it  and  “so¬ 
ciety”  would  be  richer.  The  problem 
is  that  Lotus  Development  would 
never  have  created  “Lotus  1-2-3”  in 
the  first  place.  Further,  the  hundreds 
of  others  who  think  they  can  do  it  bet¬ 
ter  than  Lotus  would  not  go  to  the 
effort  of  attempting  to  make  a  better 
“Lotus  1-2-3”  without  the  promise  of 
financial  reward.  In  this  sense  society 
would  be  poorer  because  of  the  free 
software. 

I  believe  that  an  individual’s  physi¬ 
cal  or  intellectual  product  is  his  own. 
Unknowingly,  he  furthers  the  general 
good  by  selfishly  trading  it  for  the 
most  he  can  get  for  it.  Only  a  lawyer 
would  say  that  one’s  original  idea  is 
not  his  own.  Only  a  dictatorship 


would  steal  it  from  the  author. 

The  proof  is  in  the  pudding.  Today 
we  see  hundreds  of  thousands  of  pro¬ 
grammers  working  at  what  they  love 
and  doing  it  to  maximize  their  own 
rewards.  The  individual  is  supreme. 
The  marketplace  decides  who  wins 
and  loses.  Society  benefits  by  this 
enormous  effort.  If  we  follow  Mr. 
Stallman’s  philosophy,  we  have  to  el¬ 
evate  “society”  to  a  higher  level  than 
the  individual.  This  I  cannot  do.  In¬ 
deed,  wars  have  been  fought  and 
blood  spilled  over  these  very  ideas. 

Yet,  I  wish  him  every  success  with 
GNU.  I  don’t  know  how  he  keeps  food 
on  the  table  during  his  efforts,  but  as 
he  said,  his  reward  is  in  his  love  of 
programming,  the  pride  of  seeing  his 
work  being  used,  the  recognition  of 
his  peers,  and  just  knowing  he  has 
contributed  something  to  the  world. 
For  some,  this  is  more  important  than 
money.  Indeed,  if  given  independent 
means  many  more  would  follow  the 
same  path. 

I  hope  he  succeeds  with  GNU  (I 
love  his  recursive  name!)  because  for 
all  the  reasons  he  mentions,  I  could 
be  a  beneficiary.  I  also  wonder 
whether  the  success  of  a  “free”  oper¬ 
ating  system  will  close  the  door  to 
other  operating  systems  that  might 
be  developed  if  there  were  some  mon¬ 
ey  to  be  made.  He  may  find  that  in 
succeeding  in  his  quest,  in  the  long 
run  he  may  hurt  the  “society”  that  he 
was  hoping  to  enrich.  On  second 
thought,  there  is  nothing  to  worry 
about,  for  if  there  is  a  need  for  a  bet¬ 
ter  operating  system  than  GNU  in  the 
future,  people  will  be  willing  to  pay 
for  it. 

Jim  Harlan 

Cogitate,  Incorporated 

24000  Telegraph  Rd. 

Southfield,  MI  48075 

BU  Patched 

Dear  DDJ: 

I  read  Jim  Rosenberg’s  Letter  to  the 
Editor  [March  1985,  #101]  regard¬ 
ing  my  article  “Archiving  Files  with 
CP/M-80  and  CP/M-86”  [January 
1985,  #99],  and  would  like  to  respond 
to  his  comments  regarding  BU  and 
CP/M. 

Mr.  Rosenberg  is  correct  in  stating 
that  the  BDOS  of  CP/M  v2.2  does  not 


reset  file  attribute  bit  t3-prime  when¬ 
ever  a  file  is  opened,  written  to  and 
closed.  In  this  matter  I  admit  to  being 
mistaken.  I  can  also  confirm  that 
Dave  Cortesi’s  book  Inside  CP/M  is 
also  wrong  in  stating  (p.  222)  that 
“the  BDOS  sets  t3’  to  0  whenever  it 
updates  a  directory  entry,  that  is, 
whenever  the  data  map  of  an  extent  is 
altered.” 

However,  Mr.  Rosenberg  chose  to 
ignore  the  second  part  of  my  state¬ 
ment:  that  a  directory  entry  is 
changed  if  it  is  renamed  (or  created). 
In  this  case  the  BDOS  most  definitely 
sets  t3-prime  to  zero,  and  it  is  this 
fact  upon  which  depends  the  success 
of  both  BU  and  (presumably)  QBAX. 
This  can  be  demonstrated  by  setting 
the  file  attribute  t3-prime  of  a  file 
with  a  disk  utility,  using  either  the 
built-in  “REN”  command  or  BDOS 
function  23,  “Rename  File,”  to 
change  the  file’s  name,  and  then  ex¬ 
amining  t3-prime  again. 

To  quote  Mr.  Rosenberg:  “Our 
own  product,  QBAX,  will  function 
without  patching  for  programs  such 
as  word  processors  and  compilers 
that  change  files  by  rewriting  them 
from  scratch.”  This  is  exactly  what 
BU  will  do.  Since  “BU  is  almost  use¬ 
less  under  2.2  unless  BDOS  is  patched 
to  install  support  for  the  archive  bit,” 
cannot  the  same  be  said  for  QBAX 
under  a  standard  CP/M  BDOS? 

The  answer  is  of  course  no.  BU  is  a 
very  useful  program,  as  a  number  of 
satisfied  users  have  written  to  tell  me. 
Its  one  failure  is  that  it  cannot  detect 
files  that  have  been  updated  in  place 
(i.e.,  changing  records  through  BDOS 
random  access  calls  or  by  appending 
records  via  BDOS  sequential  write 
calls).  To  do  so  would  require  a  modi¬ 
fied  operating  system. 

QBAX,  according  to  a  review  by  Da¬ 
vid  Fiedler  in  the  October  1983  issue  of 
Microsystems,  is  a  fine  commercial 
software  product.  The  review  covered 
QBAXl,  which  does  not  support  hard 
disks.  Since  that  review,  Amanuensis 
has  released  QBAX2,  which  offers  sup¬ 
port  for  hard  disks,  automatic  split  file 
restoration,  version  numbering  and 
date  stamping.  Both  products  appar¬ 
ently  include  the  BDOS  patch,  referred 
to  by  Mr.  Rosenberg,  that  implements 


Dr.  Dobb's  Journal.  June  1985 


11 

425 


the  archive  bit  feature  for  those  files 
updated  in  place. 

I  wrote  BU  as  a  basic  utility  pro¬ 
gram  that  incorporated  and  demon¬ 
strated  a  relatively  obscure  feature  of 
CP/M  v2.2.  Readers  were  invited  to 
add  their  own  enhancements,  since 
space  limitations  prohibited  includ¬ 
ing  features  such  as  QBAX1  and 
QBAX2  offer.  If  the  Microsystems 
review  is  anything  to  go  by,  I  would 
probably  recommend  the  QBAX 
products  to  a  business  user  of  CP/M, 
rather  than  BU.  In  this  matter,  I 
agree  with  Mr.  Rosenberg.  I  do  not 
believe  that  businesses  should  entrust 
their  data  to  unsupported  public  do¬ 
main  utilities. 

Regarding  CP/M’s  PIP  utility, 
some  further  investigation  revealed 
that  it  behaves  exactly  as  you  would 
expect  under  the  explanation  given 
above.  When  a  new  file  is  created  on 
the  destination  disk,  the  archive  bit  is 
initially  zero.  The  archive  bit  can  be 


set  by  BU  or  another  utility,  and 
thereafter  is  unchanged  by  any  file 
operation  except  renaming  the  file. 

In  summary,  I  wish  to  thank  Mr. 
Rosenberg  for  his  critique  of  my  arti¬ 
cle,  although  I  feel  he  did  DDJ' s  read¬ 
ers  a  disservice  by  failing  to  mention 
that  the  archive  bit  is  reset  when  a 
file  is  first  created  or  renamed,  and 
by  strongly  implying  that  BU  does 
not  work. 

Ian  Ashdown 
byHeart  Software 
2  -  2016  West  First  Avenue 
Vancouver,  B.C.  V6J  1G8 
Canada 

Dear  DDJ: 

We  noted  with  interest  Ian  Ash¬ 
down’s  BU  program  published  in  the 
January  (1985,  #99)  DDJ.  We  also 
noted,  as  Jim  Rosenberg  of  Amanu¬ 
ensis  reported  in  a  letter  in  the  March 
issue,  that  the  “archive  bit”  t3’  is  not 
automatically  cleared  by  the  BDOS 


on  file  writes  or  renames. 

After  examining  the  BDOS  we 
came  up  with  a  patch  to  accomplish 
this  under  CP/M  2.2.  With  the  patch 
installed,  any  time  a  file  is  written  to, 
either  randomly  or  sequentially,  the 
“archive  bit”  will  be  cleared  to  zero. 
We  have  used  this  with  a  number  of 
different  programs  including  word 
processors,  SuperCalc  and  Microsoft 
BASIC  (including  writing  a  single 
random  record)  and  have  encoun¬ 
tered  no  problems  with  it. 


14 


Dr.  Dobb's  Journal,  Jun  1985 


Since  a  number  of  your  readers 
may  be  interested  in  having  this  facil¬ 
ity,  I  am  enclosing  a  printout  of  the 
patch  (see  Listing,  below).  This 
patch  assumes  that  the  patch  code  is 
located  outside  of  the  BDOS,  proba¬ 
bly  in  the  BIOS.  The  changes  to  the 
BDOS  can  be  made  by  the  BIOS  on 
each  warm  boot,  if  necessary,  though 
there  are  other  ways  to  accomplish 
this. 

Before  making  the  patches  to  the 


BDOS,  be  sure  that  code  to  be  re¬ 
placed  matches  that  shown  in  the  en¬ 
closed  patch  (the  original  code  is 
commented  out  in  the  patch). 

One  other  approach  which  we  have 
used  to  implement  the  patch  is  to  in¬ 
stall  it  directly  into  the  BDOS  source. 
We  have  used  C.  C.  Software’s  excel¬ 
lent  CP/M  Source  Code  Generator  to 
create  the  BDOS  source  (we  have  this 
software  available,  as  well  as  our  own 
EUREKA!  disk  cataloger).  We  then 


substituted  Z80  instructions  in 
enough  places  to  gather  the  space  we 
needed  for  the  patch  and  reassembled 
the  BDOS  and  patched  it  directly 
onto  the  disk. 

We  hope  that  this  patch  will  make 
BU  even  more  useful  to  your  readers. 
Bruce  Haanstra,  President 
Mendocino  Software  Co., 
P.O.  Box  1564 
Willits,  CA  95490 

DD| 


Letters  Listing  (Text  begins  on  page  8) 


;Desc: Patch  to  CP/M  2  BDOS  to  clear  update  flag  on  close  or  rename  of  a  file 
; Date :02/02/85 
(Time: 13 :51 


;  The  purpose  of  this  patch  is  to  clear  the  update  flag  (8th  bit  of  t3)  when  a 
;  file  that  has  been  changed  is  closed.  Part  of  this  patch  file  will  also 
;  clear  the  update  flag  on  a  rename. 


BDOSBASE 

EQU 

OD800H 

.  **.  SET  THIS  to  THE  ADDRESS  OF  THE 
;  BASE  OF  YOUR  BDOS  *** 

patcharea 

equ 

00000 

.  ***  put  THE  ADDRESS  WHERE  YOUR  PATCH 
;  CODE  IS  LOCATED  HERE  BEFORE 

;  ASSEMBLY  *** 

updf lg 

;  patch  to 

equ 

rename 

11 

;  offset  to  update  flag  byte  in  fcb 
!  ll=t3'  (archive  bit) 

org 

jmp 

i 

;  was  lxi 
;  dad 

;  mov 

BDOSBASE+822h 

patchl 

d  ,  16 
d 

m ,  a 

?  patch  to  clear  update  flag  on 

a  file  that  has  been  changed 

org 

BDOSBASE + 9 ICh 

jmp 

patch2 

;  was  JMP 

BDOSBASE+810h 

(updatel ) 

s 

org 

patcharea 

?  *****  UPDATE 

FLAG  PATCH  ***** 

?  01/06/85  - 

by  BWH 

;  this  patch 

forces  the  directory  entry  being  written  to  have 

its  update 

;  flag  bit  reset  to  0  so  FD  and  MDIR  can  handle  ALL  updated  files. 

i 

patchl : 

;  RENAME  patch 

lxi 

d,  16 

;  move  over  to  desired  name 

dad 

d 

mov 

m ,  a 

;  keep  the  same  user  number 

call 

clruf lg 

;  clear  the  update  flag 

jmp 

BDOSBASE* 8 2 7H 

;  (chgnaml)  go  back  and 

continue 

patch2 : 

;  UPDATE  flag  patch 

call 

BDOSBASE + 5 5Eh 

(  (fcb2hl)  get  address  of  fcb  in 

dir  buffer 

call 

clruf  lg 

offset  to  FD  update  flag  byte 

(0-11)  of  fcb 

jmp 

BDOSBASE+810h 

(updatel)  do  original  stuff 

;  01/27/85  -  broke  out  clearing 

of  update  flag  so  rename  can  do 

it  too 

;  IN  -> 

HL=address  of  base  of  FCB  with  flag  to  clear 

?  USES  -> 

A,  HL,  DE 

clruf lg : 

clear  update  flag  routine 

lxi 

d,updflg 

offset  to  FD  update  flag  byte 

(0-11)  of  fcb 

dad 

d 

mov 

a ,  m 

get  the  current  byte 

ani 

7FH 

clear  8th  bit 

mov 

m,a 

and  put  it  back 

ret 

End  Listing 


Dr.  Dobb’s  Journal,  June  1985 


15 

497 


DR.  DOBB'S  CLINIC 


by  D.  E.  Cortesi 

Printer  Out  of  Shadow 

In  the  April  column,  we  asked  for 
help  finding  competent  maintenance 
for  our  Diablo  printer,  but  we  wrote 
that  column  back  in  February,  and 
the  problem  wouldn’t  wait.  We  post¬ 
ed  the  same  query  on  several  local 
RBBSs  (and  very  glad  we  were  that 
the  Oxgate,  PicoNet,  and  Simms 
boards  exist).  One  Eugene  Jones 
came  through  with  a  recommenda¬ 
tion  of  The  Printer  Works  (1961  Al¬ 
pine  Way,  Hayward,  CA  94545; 
(415)  887-6116).  And  they  did  the 
job:  made  an  informed  diagnosis;  dis¬ 
cussed  it  with  us,  explaining  exactly 
what  they  would  do  and  what  it 
would  cost;  then  did  the  work.  They 
were  thoroughly  professional,  help¬ 
ful,  and  prompt.  May  they  get  every 
broken  Diablo  in  Silicon  Valley. 

But  our  problems  getting  the  Dia¬ 
blo  fixed  have  made  us  wonder:  What 
else  is  hard  to  get  fixed?  Who  else  is 
doing  a  good  job  fixing  things?  If 
you’ve  had  maintenance  adven¬ 
tures  with  happy  endings  or  other¬ 
wise — why  don’t  you  describe  them 
to  us?  Especially  tell  us  about  repair 
people  who  know  their  stuff.  Let  us  in 
on  the  scams,  too  (with  enough  hor¬ 
ror  stories,  we  can  revive  “Dobb’s  Ex 
Machina”),  but  it  would  be  super  if 
we  could  point  to  good  repair  services 
in  every  major  city. 

Watt  Duzzit  Dew 

We  asked  in  January  David  Tilton 
asked  us:  What  does  this  short  Z80 
assembly  routine  do? 


BTS: 

cp 

(hi) 

ret 

z 

rl 

L 

djnz 

BTS 

Id 

L,0 

ret 

Only  Thomas  Cage  of  Crestview,  FL, 
attempted  to  give  us  an  answer  (yea, 
Tom!),  and  while  he  got  the  essence 
of  it,  he  was  wrong  in  detail  (boo, 
Tom!).  Here  is  how  Tilton  described 
it. 

“[The  routine]  actually  outper¬ 
forms  the  CPIR  instruction  of  the 
Z80  in  searching  a  list  of  bytes  for  a 
match  by  using  a  binary  rather  than  a 
linear  search,  but  the  restrictions  on 
the  sequence  of  the  list  and  its  place¬ 
ment  in  memory  make  it  less  than  at¬ 
tractive. 

“On  entry,  A  contains  the  byte  to 
search  for,  B  contains  the  number  of 
levels  in  the  binary  tree,  and  HL 
points  to  the  root  of  the  binary  tree. 
One  of  the  unfortunate  restrictions  is 
that  the  value  of  L  for  the  root  must 
be  01  h.  Since  the  value  of  H  remains 
constant,  it  is  the  value  of  L  on  return 
that  indicates  the  result. 

“It  is  helpful  to  think  of  the  list  as  a 
byte  array  indexed  by  L.  We  start 
with  a  comparison.  If  there  is  a 
match,  the  second  instruction  returns 
with  L  indicating  where  the  match 
occurred. 

“If  there  was  no  match,  the  value  in 
the  tree  was  either  too  large  or  too 
small.  The  carry  flag  will  be  set  if  it 
was  too  large.  The  next  instruction 
multiplies  the  contents  of  L  by  two 
and,  if  the  carry  flag  was  set,  adds 
one.  The  DJNZ  instruction  checks  for 
another  level  in  the  tree  and,  if  there  is 
one,  loops  to  check  the  already-select¬ 
ed  node  in  it.  If  there  is  not  another 
level,  L  is  set  to  zero  to  indicate  that 
the  byte  sought  was  not  in  the  tree. 

“There  are  many  disadvantages  to 
this  arrangement.  It  is  inflexible.  For 
one  thing,  a  full  tree  must  be  main¬ 
tained;  that  is,  the  size  of  the  tree 
must  always  be  some  power  of  two 
less  one.  It  is  significantly  faster  than 
CPIR  only  for  the  larger  trees.  Fur¬ 


thermore,  if  you  can  spare  a  full  page 
of  256  bytes  [to  hold  the  tree]  then 
there  is  an  even  faster  way  of  doing 
the  same  thing: 

BTL: 

Id  L,A 

Id  L,(HL) 

ret 

This  is,  of  course,  simply  a  byte  look¬ 
up  table.  I  thought  I  was  being  really 
clever  with  my  BTS  routine,  but  it 
turns  out  to  be  virtually  useless.” 

Oh,  now,  cheer  up.  You  got  deep 
into  binary  trees,  right?  It’s  very  pro¬ 
found  that  the  successive  carry-bits 
shifted  into  L  are  a  record  of  the  path 
through  a  tree  to  the  desired  node, 
where  one  means  “go  left”  and  zero 
means  “go  right.”  The  binary  num¬ 
ber  that  defines  the  path  to  a  node  is  a 
unique  label  for  that  node;  it’s  basi¬ 
cally  a  “Dewey  Decimal  Number” 
for  the  node. 

There’s  a  discussion  of  this  in 
Knuth’s  Volume  1,  Section  2.3  (espe¬ 
cially  see  the  answer  to  Exercise  15, 
p.  315).  There  also  is  a  connection  to 
data  compression:  if  the  compared 
values  were  wider  than  a  byte,  their 
binary  node  labels  would  be  a  com¬ 
pressed  representation  of  them. 

Watt  Dozen  It  Dew 

OK,  faithful  readers,  you  didn’t  do 
too  well  on  Tilton’s  puzzle;  perhaps 
you  can  do  better  with  one  from  Da¬ 
vid  Ross,  of  Iowa  City,  IA.  He  sends 
the  BASIC  code  you  can  read  in  List¬ 
ing  One  (page  18).  “In  theory,”  he 
says,  “it  does  something  quite  re¬ 
markable.  In  practice,  it  doesn’t  do 
much  of  anything:  it  is  extraordinari¬ 
ly  sensitive  to  roundoff  errors.  Ques¬ 
tion:  what  ought  it  to  do,  and  to  what 
extent  can  it  be  made  to  work?” 

Now,  Tilton  sent  a  full  explanation 


16 


Dr  Dobb's  Journal,  June  1 985 


of  his  puzzle.  Ross  did  not.  That 
means  (since  the  Intern  hasn’t  a  clue) 
that  if  you  don’t  figure  it  out  and  ex¬ 
plain  it,  nobody  will  ever  know  the 
answer. 

Random  Probing 

In  February  we  showed  a  simple  gen¬ 
erator  of  pseudo-random  numbers  (a 
PRNG,  pronounced  “prang”).  We’re 
getting  mixed  feedback  on  that;  at 
least  one  reader  has  written  to  say  it’s 
no  good.  It  would  be  nice  if  someone 
would  subject  it  to  some  rigorous 
testing  and  tell  us. 

In  fact,  the  letters  we  are  seeing  in¬ 
dicate  a  lot  of  ignorance  about  what’s 
good  in  a  PRNG  and  how  to  test  one. 
Add  to  that  a  paper  that  Mike 
Swaine  ran  across:  “Random  Num¬ 
ber  Generation  in  Microcomputers” 
by  Modianos,  Scott,  and  Cornwell  in 
Interfaces ,  Vol.  14,  No.  4  (July-Au¬ 
gust  1984).  We  aren’t  familiar  with 
Interfaces',  it  appears  to  be  a  publica¬ 
tion  of  The  Institute  of  Management 
Sciences. 

Anyway,  Modianos,  et  al.,  studied 
the  characteristics  of  the  PRNGs  in 
various  micro  BASICS.  The  net  of 
their  findings  is  “that  the  random 
number  generators  which  are  intrin¬ 
sic  to  the  Apple  11+  and  He  (using 
either  Applesoft  BASIC,  Integer  BA¬ 
SIC,  or  CP/M  BASIC),  the  Osborne 
Executive,  and  the  IBM  PC  are  so 
flawed  that  we  cannot  recommend 
their  use  for  simulation  studies.”  The 
only  acceptable  PRNGs  they  found 
were  in  the  HP-86,  Apple  III,  and 
(oddly)  the  TRS-80  model  III.  Plus 
one  for  the  Apple  published  by  Hare, 
Faulkner,  and  Sparks  in  Call  APPLE, 
Vol.  6,  No.  1 . 

These  authors  apparently  expend¬ 
ed  a  lot  of  time  and  CPU  cycles  test¬ 
ing  PRNG;  unfortunately,  they  don’t 
discuss  their  methods  in  any  detail. 
Sure,  that  subject  has  been  covered 
many  times  before,  but  not  for  some 
years  in  this  magazine.  Volunteers? 

Object  Module 

By  way  of  filling  up  this  column,  we 
present  a  module  of  C  code  that  has 
helped  us  make  some  quick-and-dirty 
programs.  Sometimes  it’s  easier  to  do 
things  if  you  have  the  ability  to  allo¬ 
cate  a  lot  of  small  objects  and  free 

Dr.  Dobb's  Journal,  June  1985 


them  again  in  any  order.  In  The  C 
Programming  Language,  Kernighan 
and  Ritchie  described  a  pair  of  func¬ 
tions,  alloc(  )  and  free(  ),  to  do  just 
that.  Probably  these  are  in  most  C 
libraries. 

The  compiler  we  use  implements 
an  alloc(  ),  but  contains  no  free(  ). 
Alloc(  )  alone  gives  the  essential  abil¬ 
ity  to  allocate  chunks  of  storage  for 
buffers  and  what-not.  But  we  wanted 
to  make  dozens  or  hundreds  of  little 
objects  and  then  discard  them  again. 
So  we  did  what  you’d  do:  we  rolled 
our  own. 

The  module  for  dispensing  objects 
appears  in  Listing  Two  (page  18).  It 
duplicates  the  functions  of  alloc(  ) 
and  free(  ),  including  quite  a  bit  of 
validation  to  guard  against  bugs  in 
the  using  code.  It  adds  the  useful 
functions  of  objmax(  )  and  obj- 
trim(  ),  which  allow  you  to  size  an 
object  to  match  the  data  it  will  hold. 

The  method  it  uses  is  not  that  of 
Kernighan  and  Ritchie,  but  rather  an 
old  scheme  that  you  may  not  have 
seen  before:  chaining  adjacent  blocks 
by  fencing  them  in  with  length-words. 

The  more  we  use  C’s  #define,  the 
more  impressed  we  are  with  the  flexi¬ 
bility  it  gives.  In  the  Object  module, 
we  used  it  to  define  an  adjective, 
WORDAT,  to  stand  for  the  awkward 
cast  required  to  say,  “the  unsigned 
integer  in  storage  where  this  charac¬ 
ter  pointer  points.” 

The  Object  module  is  definitely 
written  for  8-bit  machines;  it  relies  on 
16-bit  addresses  stored  in  16-bit  inte¬ 
gers.  The  first  step  in  making  it  por¬ 
table  would  be  to  convert  all  the  hex 
constants  to  #defined  names.  Also,  it 
would  be  more  efficient  if  it  started 
the  search  for  a  free  block  at  the 
point  where  it  last  found  or  freed  one. 
You  might  enjoy  making  those  im¬ 
provements. 

The  performance  of  this  design 
won’t  differ  a  great  deal  from  that  of 
Kernighan  and  Ritchie.  You  might 
enjoy  analyzing  the  two  and  working 
out  which  is  faster,  under  the  as¬ 
sumption  of  a  high  turnover  of  many, 
small  objects. 

DDJ 


17 

A  OQ 


Dr.  Dobb's  Clinic  (Text  begins  on  page  16) 
Listing  One 


100  REM  BASIC  PUZZLE  BY  D.  ROSS 
105  REM  MODIFIED  FOR  MBASIC  BY  DEC 
110  DEF  FNL ( X ) =LOG ( X ) /T 
120  INPUT  "K=",K  :  DIM  A$(K+1) 

130  FOR  1=1  TO  K+l  :  A$ ( I )  =  "0"  :  NEXT  I 

140  T  =  LOG (10) 

150  Z  =  ASC ( "0" ) 

160  AO  =  K*FNL ( 2 ) 

170  B  =  1 

180  WHILE  B  >  0 

190  A  =  AO+FNL ( B ) 

200  N  =  INT(A) 

210  IF  N<0  THEN  GOTO  280 
220  L  =  A-N 
230  X  =  1 0  *  L 
240  D  =  I NT ( X ) 

250  A$ ( K-N+ 1 )  =  CHR$ ( D  +  Z) 

260  B  =  B- 1 0 ‘ ( N-AO+FNL ( D ) ) 

270  WEND 

280  FOR  1=1  TO  K+l  :  PRINT  A$(I);  :  NEXT  I 


Listing  Two 


/* 

OBJECT . C 

general  purpose  ob ject-allocation  module 
(8-bit  systems  only,  relies  on  16-bit  addresses) 

Copyright  (C)  1985  David  E.  Cortesi 
430  Sherman  Ave  #212 
Palo  Alto,  CA  94306 
All  rights  reserved 

Permission  granted  to  copy  for  nonprofit  purposes. 

An  "object"  is  a  block  of  storage  of  at  least  two  bytes. 
Functions  in  this  module  manage  a  pool  of  storage  to 
allocate  and  reclaim  objects. 

unsigned  objipit(u)  unsigned  u 

Initializes  a  storage  pool  of  size  u,  returning  the 
size  of  the  largest  object  that  may  be  allocated. 

If  called  a  second  time,  does  nothing  and  returns  the 
size  of  the  largest  object  that  can  now  be  allocated. 

unsigned  objmax( ) 

Returns  the  size  of  the  largest  object  that  can  be 
allocated  from  the  storage  pool  as  it  is  now. 

char  *objget(u)  unsigned  u 

Allocates  an  object  of  size  u  and  returns  its  address, 
if  possible.  If  it  can't,  it  aborts  the  program. 

void  objfree(p)  char  *p 

Frees  the  object  addressed  by  p,  which  must  have  been 
allocated  by  objget().  If  it  wasn't,  or  if  it  is  now 
free,  or  if  it's  been  damaged,  the  program  is  aborted. 

unsigned  objsize(p)  char  *p 

Returns  the  size  of  the  object  addressed  by  p.  Aborts 
if  *p  is  not  a  valid  object. 


End  Listing  One 


18 


Dr.  Dobb's  Journal,  June  1985 


void  objtrim(p,u)  char  *p;  unsigned  u 

Trims  the  object  addressed  by  p  back  to  have  size  u, 
reieasing  excess  space.  Use  in  a  sequence  like, 
p  =  ob jget ( t=ob jmax( ) ) ; 

. . .process  data  *p  to  size  u,  u  <=  t. . . 
objtrim(p,u) ; 

Aborts  if  *p  isn't  a  valid  object  or  if  its  size  is 
less  than  u. 

void  ob jprt ( ) 

Compiled  only  if  DEBUG  is  defined,  this  function 
prints  a  map  of  object-space  on  stderr . 

Allocation  is  first-fit.  Garbage  is  melded  as  it  is  created. 
An  object  is  allocated  as  an  even  number  of  bytes  L,  fenced  in 
by  two  words  WWWW : 

A  B  CD 

...object  (WWWW) (WWWW) <==  L  bytes  ==> (WWWW) (WWWW)  object... 

Words  WWWW  are  essentially  2+L,  where  L  is  the  length  of  the 
object  they  bound.  The  low-order  bit  is  the  in-use  flag. 

If  (W  &  0x0001),  the  object  is  active;  if  not,  it  is  free. 

The  value  K  ==  (WWWW  &  Oxfffe)  ==  2+L  is  used  to  chain  from 
block  to  block.  For  addresses  A,B,C,  and  D  as  shown,  A+K->C, 
B+K->D,  B-2->A,  D-2->C,  D-K->B,  etc  etc.  Address  B  is  the 
object's  address,  returned  by  objget()  and  given  to  objfree. 

The  whole  storage  pool  is  bounded  by  two  sentinel  words,  0003. 
These  appear  to  be  active  blocks  with  K=2,  hence  L=0 ,  and  stop 
a  scan  over  the  pool. 

*/ 

#include  "a:stdio.h"  /*  for  error  messages  */ 

/*efine  DEBUG  1*/  /*  or  don't  --  to  exclude  objprt()  */ 


#define  W0RDAT  * (unsigned  *) 
#define  VOID  /*  not  supported 
static  char  *objbase  =  0, 

*ob j  top ; 

static  unsigned  maxob j , 


/*  using  "char  *"  ptrs  for  words  */ 
in  Aztec  C  */ 

/*  base  of  storage  pool,  uninitialized  / 
/*  upper  limit,  for  integrity  checks  */ 

/*  largest  possible  object  */ 


j , k ;  /*  scratch  words  */ 

static  char  *p,  *q,  /*  scratch  pointers  */ 

minit[]  =  "Objinit",  /*  abort-message  parts  *■/ 

mget [  ]  =  "Objget", 

mtrim[]  =  "Objtrim", 

mvali[]  =  "Object  validation", 

mpool[]  =  "Object  pool  damage"; 


VOID 

{ 


objinit ( u ) 
unsigned  u; 

if  (objbase)  return ( objmax ())  ; 


/*  ensure  an  even  number  of  bytes  in  the  entire  pool  */ 
u  =  (u==0xffff)  ?  Oxfffe  :  (u  +  1)  &  Oxfffe; 

/*  set  out  4  bytes  for  sentinels,  4  for  first  2  WWWW  words  */ 
maxob j  =  u-8; 

/*  ensure  a  reasonable  size  of  pool,  allocate  it  */ 
if  (248  >  maxob j ) 

objerr (minit , 1 ) ; 
if  ( NULL== ( objbase  =  alloc(u)) 
objerr ( mini t , 2 ) ; 


20 


Dr.  Dobb's  Journal,  June  1985 


AO-i 


Dr.  Dobb's  Clinic  (Listing  continued,  text  begins  on  page  16) 

Listing  Two 

/*  set  up  sentinel  words  0003h  at  ends  of  pool  */ 
objtop  =  objbase  +  u  -  2; 

WORDAT  objbase  =  0x0003; 

WORDAT  objtop  =  0x0003; 

/*  fill  pool  with  a  single  free  object  */ 
objbase  +=  2 ; 
k  =  maxobj+2; 

WORDAT  objbase  =  k; 
q  =  objbase  +  k; 

WORDAT  q  =  k; 

return  (maxobj); 

) 

char  *objget(u) 

unsigned  u; 

{ 

/*  eliminate  cases  of  zero  and  too-big  sizes  */ 
if  (u==0)  ob jerr ( mget , 1 ) ; 
if  (u>maxobj)  ob jerr (mget , 2 ) ; 

/*  ensure  even  length,  establish  enventual  K  ==  L+2  */ 

k  =  2  +  ( u  =  ( u  +  l )  &  Oxfffe); 

/*  Scan  to  the  top  sentinel,  looking  for  the  first  free 
object  longer  or  equal  to  the  requested  size.  The  pool 
is  vulnerable  to  damage  --  be  very  suspicious  and  test 
every  link  in  the  chain  before  using  it.  */ 
p  =  objbase; 

while  (0x0003  !=  (j  =  WORDAT  p) ) 

( 

if  (0==(j  &  0x0001))  /*  free  */ 

if  (j  >=  k)  break;  /*  good  size  */ 
q  =  p  +  2  +  (j  &  Oxfffe); 
if  ( (q  <=  p) j | (q  >  objtop) ) 
ob jerr ( mpool , 1 ) ; 

p  =  q; 

} 

/*  if  we  hit  the  sentinel,  the  requested  size  is  not  available 
(could  return  NULL  here  instead  of  aborting)  */ 
if  ( j  ==0x0003 )  ob jerr (mget , 3 ) ; 

/*  set • p-> low  WWWW,  q->high  WWWW,  validate  block  */ 

q  =  p  +  j; 

if  ((q  <=  p ) I | ( q  >  objtop) ) 
ob jerr ( mpool , 2 ) ; 

/*  if  the  wasted  space  in  this  block  would  accomodate 
another  object  of  4  bytes  or  more,  split  off  the 
high  end  as  a  new,  smaller  free  object  */ 
if  ( j  >  (k+8) ) 

( 

j  =  j-k-2;  /*  j  =WWWW  of  excess  object  */ 

WORDAT  q  =  j ; 

q  -=  j ; 

WORDAT  q  =  j ; 
q  -=  2; 

WORDAT  q  =  k; 

WORDAT  p  =  k; 

) 

/*  activate  chosen  block,  return  address  of  data  */ 

WORDAT  p  +=  1 ; 


21 


Dr.  Dobb's  Journal,  June  1985 


WORDAT  c.  +=  1; 
return  ( p+2 ) ; 

} 

unsigned  objsize(ob) 
char  *ob; 

{  /*  This  is  mostly  an  internal  subroutine.  It  validates 

a  user-supplied  object-pointer,  leaving  static  globals 
p,  q,  and  k  set  up  so  that  p->(WWWW)  ...  q->(WWWW) 
and  k==  WWWW  &  Oxfffe. 

p  =  ob-2; 

/*  address  must  fall  within  in  pool,  */ 

if  (p  <  objbase)  objerr (mvali , 1 ) ; 
if  (p  >  objtop)  ob jerr ( mval i , 2 ) ; 

/*  ..must  have  active  lower  WWWW  word,  */ 
k  =  WORDAT  p; 

if  (0==(k.  &  0x0001))  objerr  (mvali  ,  3  )  ; 
k--;  /*  strip  active  bit  */ 

/*  ..showing  size  of  L=2  or  more,  but  not  more  than  max,  */ 
if  (k<4)  ob jerr ( mvali , 4 ) ; 
if  (maxobj  <  k)  ob jerr ( mval i , 5 ) ; 

/*  ..which  must  yield  a  valid  upper-WWWW  address,  */ 
q  =  p  +  k; 

if  ( ( q  <—  P ) | I ( q  >  objtop))  ob jerr ( mval i , 6 ) ; 

/*  . .and  the  upper  and  lower  WWWWs  must  match  */ 

if  (WORDAT  p  ! =  WORDAT  q)  ob jerr ( mval i , 7 ) ; 

/*  object  is  valid,  return  its  data  size  */ 
return  ( k-2 ) ; 

) 

VOID  objfree(ob) 
char  *ob; 

( 

/*  use  objsize()  to  set  up  p,  q,  k  and  validate  block  */ 
objsize ( ob) ; 

/*  object  is  valid,  make  it  free  */ 

WORDAT  p  =  k; 

WORDAT  q  =  k; 

/*  if  object  next  above  is  free,  meld  with  it.  Afterward 
and  regardless,  ensure  that  p->KKKK . . .  q->KKKK  */ 
q  +=  2 ; 
j  =  WORDAT  q; 
if  ( 0== ( j  &  0x0001) ) 

{ 

k  =  k  +  j  +  2  ; 

WORDAT  p  =  k; 

q  +=  j; 

WORDAT  q  =  k; 

) 

else  q  -=2 ; 

/*  if  block  next  below  is  free,  meld  with  it,  too  */ 

P  -=  2; 

j  =  WORDAT  p; 

if  ( 0==( j  &  0x0001 ) ) 

( 

P  -=  j ; 

k  =  k  +  j  +2; 

WORDAT  p  =  k; 

WORDAT  q  =  k; 


Dr.  Dobb’s  Journal.  June  1985 


22 


Dr.  Dobb's  Clinic 

Listing  Two 


(Listing  continued,  text  begins  on  page  16) 


} 

) 

VOID  ob j trim ( ob , sz ) 

char  *ob; 
unsigned  sz; 

{ 

/*  validate  object,  ensure  call  is  to  reduce  it  not  enlarge  it  */ 
j  =  objsize ( ob) ; 
if  (sz  >  j)  objerr (mtrim, 1 ) ; 

/*  ensure  new  size  will  be  even  */ 
sz  =  (sz  +  l)&Oxfffe; 

/*  we  will  create  a  new,  free  object  in  the  high  end  of 
the  present  one  --  its  k  is  k-sz-4 .  */ 
j  =  k  -  sz  -  4 ; 

/*  if  that  block  doesn't  amount  to  8  bytes  of  data,  forget  it  */ 
if  (j  <=  10)  return; 


/*  the  k  of  the  existing  block  will  become  sz+2  */ 
k  =  sz  +  2  ; 


/*  create  the  trimmed  block,  including  active  bit  */ 
WORDAT  p  =  k+1; 
p  +  =  k ; 

WORDAT  p  =  k+1; 

/*  create  the  freed  block  (no  active  bit)  */ 

p  +=  2  ; 

WORDAT  p  =  j; 

WORDAT  q  =  j ; 

/*  if  its  upper  neighbor  is  free,  meld  them  */ 

q  +=  2; 


24 


Dr.  Dobb’s  Journal,  June  1985 


k  =  WORDAT  q; 

if  ( 0== ( k  &  OxOOOl ) ) 

( 

j  =  j  +  k  +  2; 

WORDAT  p  =  j ; 
q  +=  k; 

WORDAT  q  =  j; 

} 

) 

unsigned  objmax( ) 

{ 

p  =  objbase; 
k  =  0; 

/*  scan  the  whole  pool  to  find  the  largest  free  object.  */ 

/*  be  suspicious,  validate  all  chain  links  as  we  go.  */ 
while  (0x0003  !=  (j  =  WORDAT  p)  ) 

{ 

if  (j  &  0x0001) 

j  &=  Oxf f fe ; 

else 

if  (k  <  j)  k  =  j; 
q  =  P  +  j; 

if  ( (q  <=  p)  ||  (q  >  ob j  top ) ) 
ob jerr ( mpool , 3 ) ; 

P  =  q+2; 

} 

return  ( k-2 ) ; 

} 

VOID  objerr(m,n) 

char  *m; 
int  n ; 

{ 

fprintf  (stderr ,  <'\n\n%s  error  #%d\n" , m , n ) ; 
exi  t  (  )  ; 

) 

#i f def  DEBUG 

VOID  ob jpr t ( ) 

{ 

fputs ( "\n\nmap  of  allocation  space  ... \n" , stderr ) ; 

/*  display  what  should  be  lower  sentinel  of  0003,  and  its  address  */ 
p  =  objbase-2; 

fprintf  (stderr,  "3504x->%04x\n",p,  WORDAT  p  )  ; 

P  +-2  ; 

/*  display  bounds  and  WWWW  words  of  each  block  to  upper  sentinel  */ 
while (0x0003  ! =  (j  =  WORDAT  p)) 

{ 

q  =  p  +  (j  &  Oxfffe) ; 
k  =  WORDAT  q; 
fprintf(stderr, 

"3;0  4x— >S504x  ...  3504x->%04x\n"  , 

P  .  4  -  q  -  k )  ; 

if  ((q<=p)||(q  >  ob j  top ) )  break; 

P  =  q+2; 

) 

/*  display  upper  sentinel  unless  chain  was  broken  */ 
if  (j  ==  0x0003) 

fprintf(stderr, "%04x->0003\n" ,p) ; 

else 

f puts ("... stopped  for  invalid  chain\n" , stderr ) ; 

> 

#endif 


End  Listings 


Dr.  Dobb's  Journal,  June  1985 


25 

435 


C  CHEST 


Queues  and  Bit  Maps 


by  Allen  Holub 

Queues 

I’ve  never  been  happy  with  a  store- 
bought  word  processor.  I  cut  my 
teeth  on  vi  and  nroff  on  a  Unix  sys¬ 
tem,  and  I’ve  yet  to  find  an  accept¬ 
able  commercial  version  of  either 
that  will  run  under  MSDOS. 

Because  I  had  an  editor  that  I  could 
live  with,  not  having  nroff  was  my 
greatest  problem.  None  of  the  ver¬ 
sions  of  nroff  from  users  groups  were 
acceptable;  they  invariably  were  sub¬ 
sets  of  the  real  thing,  often  loaded 
with  bugs,  and  at  least  one  of  them 
(roff4)  was  formatted  so  poorly  that  it 
was  unmaintainable.  The  writers  of 
these  programs  also  took  considerable 
liberties  with  nroffs  instruction  set. 

So,  being  a  masochist,  I  wrote  my 
own  nroff.  In  the  course  of  this  pro¬ 
ject,  I  added  several  useful  functions 
to  my  library  and  these  functions  will 
turn  up  in  this  column  from  time  to 
time.  This  month  we’ll  look  at  the 
queue  manager . 

A  queue  is  a  FIFO  (first  in,  first 
out)  data  structure.  The  first  object 
to  be  enqueued  is  also  the  first  object 
to  be  dequeued  (unlike  a  stack  where 
the  first  object  pushed  is  the  last  ob¬ 
ject  popped). 

The  rate  at  which  objects  are  en¬ 
queued  can  be  higher  than  the  rate  at 
which  they  are  dequeued,  as  long  as 
you  never  allow  the  queue  to  get  full. 
Therefore,  you  can  use  queues  to  im¬ 
plement  type-ahead  in  character  I/O 
routines  and  the  like.  The  interrupt 
service  routine  that  talks  to  the  key¬ 
board  UART  puts  characters  into  the 
queue,  and  getc  or  its  system  equiva¬ 
lent  takes  the  characters  out  of  the 
queue.  As  long  as  the  queue  never 
fills  up,  getc  doesn’t  have  to  keep  up 
with  the  data  rate  at  the  UART. 

Queues  have  other  uses.  In  multi¬ 
tasking  operating  systems,  they  hold 
pending  messages  while  a  task  is 


busy.  In  a  word  processing  applica¬ 
tion  (like  nroff),  they’re  used  for  line 
filling.  Input  text  to  nroff  is  unfor¬ 
matted:  there  are  a  random  number 
of  words  on  a  line.  Nroff  collects 
words  until  it  has  enough  to  fill  an 
entire  line,  then  it  prints  the  collected 
line.  My  version  of  nroff  enqueues 
characters  until  it  has  collected  one 
too  many  words.  It  then  dequeues  all 
but  the  last  word  to  produce  an  out¬ 
put  line. 

Listing  One  (page  31)  contains  a 
package  of  queue  management  rou¬ 
tines;  you  need  to  declare  a  pointer  to 
a  QUEUE  somewhere  in  your  pro¬ 
gram  to  use  them.  Like  a  FILE,  a 
QUEUE  is  a  structure  maintained  by 
the  queue  manager.  However,  also 
like  a  FILE,  your  program  need  know 
nothing  about  the  details  of  queue 
management  or  the  contents  of  this 
structure.  I  usually  typedef  a  QUEUE 
as  a  character  pointer  when  1  use  it. 
Now  let’s  turn  to  the  various  rou¬ 
tines. 

typedef  char  *QUEUE; 

QUEUE  *makequeue(  qsize,  objsize  ) 
int  qsize,  objsize; 

Makequeuef  )  is  the  equivalent  to 
fopen(  ).  “Qsize”  is  the  size  of  the 
queue  in  objects  (not  bytes),  and 
“objsize”  is  the  size  of  a  single  object 
in  bytes.  You  can  make  a  queue  of 
anything;  even  a  queue  of  structures 
is  permissible.  Like  fopen(  ),  make¬ 
queuef  )  returns  a  pointer  that  must 
be  passed  to  all  the  other  queue  man¬ 
agement  routines.  A  zero  is  returned 
if  there  isn’t  enough  memory  to  make 
the  queue. 

del_queue(  qp  ) 

QUEUE  *qp; 
int  empty; 


Del_queue(  )  deletes  a  queue  cre¬ 
ated  with  a  previous  makequeuef  ) 
call.  Qp  is  the  pointer  returned  by 
that  makequeue(  )  call.  This  routine 
only  deletes  empty  queues.  If  you 
need  to  delete  a  queue  that  isn’t  emp¬ 
ty,  use  free(qp);  a  one  is  returned  if 
the  queue  is  deleted,  and  a  zero  other¬ 
wise. 

enqueue  (  obj,  qp  ) 
char  *obj; 

QUEUE  *qp; 

enqueue(  )  puts  an  object  into  the 
queue.  “Obj”  points  at  the  object  to  be 
enqueued.  Alhough  it’s  declared  as  a 
character  pointer,  obj  actually  can 
point  at  anything.  “Qp”  is  a  pointer  to 
the  queue  itself,  as  returned  from  a 
previous  makequeue(  )  call. 

dequeue  (  obj,  qp  ) 
char  *obj; 

QUEUE  *qp; 

DequeUe(  )  is  the  opposite  of  en- 
queue(  ).  It  takes  an  object  out  of  a 
queue  pointed  to  by  “qp”  and  puts 
that  object  at  the  memory  location 
pointed  to  by  “obj.” 

car  *show_next(  qp  ) 

QUEUE  *qp; 

int  *sp_used  (  qp  ) 

QUEUE  *qp; 

int  sp_avail  (  qp  ) 

QUEUE  *qp; 

You  use  these  routines  for  getting 
information  about  a  queue.  Show_ 
next(  )  returns  a  pointer  to  the  object 
to  be  dequeued  by  the  next  call  to  de- 
queuef  ),  without  actually  removing 
the  object  from  the  queue.  Sp_ 
used(  )  returns  the  number  of  objects 


26 


Dr.  Dobb  s  Journal,  June  1985 


now  in  the  queue.  Sp_avail(  )  returns 
the  amount  of  space  left  in  the  queue. 
The  sum  of  the  return  values  of 
sp_used(  )  and  sp_avail(  )  is  the 
queue  size. 

The  QUEUE  data  structure  is  a 
block  of  memory  that  includes  a 
header  describing  the  queue  and  the 
queue  itself.  The  mechanism  is  simi¬ 
lar  to  that  used  by  alloc(  )  and 
free(  ).  This  structure  is  shown  in 
Figure  1  (page  27). 

“Start”  points  at  the  beginning  of 
the  queue  proper.  “Head”  and  “tail” 
are  the  offsets,  in  objects,  from  the 
start  of  the  queue  to  the  first  and  last 
valid  objects  in  the  queue.  “Size”  is 
the  maximum  number  of  objects  that 
the  queue  can  hold.  “Nobj”  is  the 
number  of  objects  currently  in  the 
queue.  “Objsize”  is  the  size  of  an  ob¬ 
ject  in  bytes. 

There  are  actually  more  things  in 
the  header  than  are,  strictly  speak¬ 
ing,  necessary.  For  example,  you  can 
determine  the  start  of  the  queue’s 
data  area  by  adding  the  header  size 
to  the  queue  pointer.  Similarly,  you 
can  compute  the  number  of  objects  in 
the  queue  from  head  and  tail.  Howev¬ 
er,  these  extra  fields  do  reduce  the 
amount  of  computation  you  need  to 
do.  If  RAM  is  tight  and  ROM  isn’t, 
take  them  out. 

Makequeue(  )  (Listing  One,  line 
27)  allocates  space  for  the  queue  and 
initializes  the  header.  The  real  queue 
size  is  the  number  of  bytes  required 
for  the  queue  itself  (qsize  X  objsize) 
plus  the  size  of  the  header. 

Del_queue(  )  (line  43)  just  calls 
free  to  return  the  memory  used  by  the 
queue  to  the  free  list  (if  the  queue  is 
empty). 

Objects  are  put  into  the  queue  with 
enqueue(  )  (line  57).  They  are  en¬ 
queued  at  the  tail  and  dequeued  from 
the  head.  The  address  where  the  ob¬ 
ject  is  to  go  is  computed  on  line  71. 
Because  qp->start  is  declared  as  a 
character  pointer,  pointer  arithmetic 
is  defeated  (if  you  add  1  to  a  charac¬ 
ter  pointer,  you  actually  modify  it  by 
1 ,  not  so  with  an  integer  pointer).  The 
object  is  transferred  into  the  queue, 
one  byte  at  a  time,  on  line  75,  and  the 
tail  pointer  is  incremented  on  lines 
77-78.  If  the  pointer  advances  past 


Pointer  returned 
from  makequeue( ) 

▼ 


Header  (  structure  of  type  QUEUE  ) 

▼ 


Figure  1 

The  queue  data  structure 


Pointer 

returned  ^ 

by  makebitmap( ) 


map  size 


the  map 
itself 


I- 


sizeof(int)  bytes 


(map  size  /  8)  bytes 


* 


Figure  2 

The  bit  map  data  structure. 


Dr.  Dobb's  Journal,  June  1985 


27 

437 


the  end  of  the  queue,  it  is  reset  to  the 
beginning  of  the  queue. 

Head  and  tail  are  actually  offsets 
(in  elements)  from  the  starting  ad¬ 
dress  of  the  queue.  In  the  original 
versions  of  these  routines,  head  and 
tail  were  pointers  (which  saved  some 
computation),  but  the  routines  were 
harder  to  debug  and  additional  com¬ 
putation  was  added  in  other  places. 

Objects  are  removed  from  the 
queue  with  dequeue(  )  on  line  82.  It 
works  the  same  way  as  enqueue(  )  ex¬ 
cept  you’re  now  using  the  head  rather 
than  the  tail,  and  you’re  copying  in 
the  other  direction  (from  the  queue 
into  the  object). 

Bit  Maps 

My  version  of  grep  (DDJ,  October 
1984,  #96)  had  several  inefficiencies 
built  into  it.  Some  of  these  inefficien¬ 
cies  were  basic  to  the  algorithms 
used.  As  several  readers  pointed  out, 
a  state  machine  implementation 
would  have  been  faster.  However, 
changing  something  this  fundamen¬ 
tal  would  have  meant  throwing  away 
most  of  the  program. 

Because  the  thing  did  work  after 
all,  1  wasn’t  tempted  to  rewrite  it 
completely.  Other  of  the  suggested 
improvements,  however,  were  more 
reasonable.  In  particular,  Dave  Cor- 
tesi  suggested  using  a  bit  map  to  take 
care  of  character  classes;  his  code, 
which  I  have  transformed  into  a  set  of 
general  purpose  routines,  is  presented 
in  Listing  Two  (page  36).  Listing 
Three  (page  37)  shows  the  various 
changes  needed  to  add  bit  maps  to 
grep. 

Probably  the  biggest  problem  with 
grep.c  is  that  I  modeled  the  program 
on  the  version  given  in  Software 
Tools  in  Pascal.  I’ve  never  been  able 
to  figure  out  why  Kernighan  and 
Plauger  did  some  things  the  way  they 
did.  Pascal  does  support  a  pointer 
type;  why  don’t  they  ever  use  it? 

Anyway,  the  problem  of  character 
classes  is  really  a  problem  of  set  rec¬ 
ognition.  You  want  to  define  a  set  of 
characters  that  are  legally  in  the 
character  class  and  then  to  test  for 
membership  in  that  set.  The  Soft¬ 
ware  Tools  version  creates  a  string  of 
characters  that  are  legal  in  a  charac¬ 
ter  class,  and  then  searches  that 

30 

A'ta 


string  sequentially  when  testing  for 
membership. 

Because  character  classes  are  often 
pretty  big  (i.e.,  [A-Za-z]  has  52  el¬ 
ements),  you  both  waste  memory  and 
slow  down  the  program  if  the  charac¬ 
ter  you're  looking  for  is  at  the  end  of 
the  list.  [A-Za-z]  requires  31  com¬ 
parisons  to  find  a  lower-case  “e,”  the 
most  common  letter  in  English. 

A  better  solution  to  the  set  prob¬ 
lem  is  a  bit  map  in  which  each  bit 
corresponds  to  a  single  ASCII  charac¬ 
ter;  that  is,  bit  0  is  a  NULL,  bit  1  is  a 
control-A,  bit  2  is  a  control-B,  bit  65 
is  an  upper-case  A,  and  so  forth.  So, 
to  put  a  letter  into  the  set,  you  set  the 
corresponding  bit  in  the  bit  map,  and 
to  see  if  a  letter  is  in  the  set,  you  test 
the  corresponding  bit. 

The  set  operation  will  take  a  little 
longer  than  it  used  to,  but  you  need 
set  a  bit  only  once.  The  test  operation 
will  be  much  faster,  and  you  general¬ 
ly  test  bits  a  lot.  Memory  use  is  more 
efficient  too;  a  bit  map  that  can  hold 
all  128  ASCII  characters  needs  only 
16  bytes.  An  equivalent  string  would 
require  129.  On  the  other  hand,  16 
bytes  are  always  required,  even  in  a 
small  character  class. 

Listing  Two  contains  three  routines: 

typedef  char  BITMAP; 

BITMAP  *makebitmap(  size  ) 
unsigned  size; 

This  will  make  a  bit  map  with 
“size”  elements,  requiring  (size/8  + 
2)  bytes,  and  return  a  pointer  to  it. 
Because  calloc(  )  is  used  to  do  the 
memory  allocation,  the  pointer  re¬ 
turned  by  makebitmap(  )  can  be 
passed  to  free(  )  to  delete  the  map.  A 
zero  is  returned  if  makebitmap(  ) 
can’t  get  enough  memory. 

setbit  (  c,  map,  val  ) 
unsigned  c,  val; 

BITMAP  *map; 

This  will  set  bit  “c”  of  the  bit  map 
pointed  to  by  “map”  if  “val”  is  not  a 
zero;  it  will  clear  the  bit  if  “val”  is 
zero.  C  must  be  in  the  range  0  to  size 
—  1,  where  “size”  is  the  argument 
originally  given  to  makebitmap(  ). 
Map  is  a  pointer  returned  by  a  previ¬ 


ous  makebitmap(  )  call.  A  one  is  re¬ 
turned  on  success,  and  a  zero  if  c  is 
out  of  range. 

testbit  (  c,  map  ) 
unsigned  c; 

BITMAP  *map; 

This  will  test  bit  “c”  of  the  bit  map 
pointed  to  by  “map.”  It  returns  a  one 
if  the  bit  is  set,  and  a  zero  if  it  isn’t  or 
if  c  is  out  of  range. 

Listing  Two  also  contains  a 
main(  )  routine  for  testing  the  three 
utility  routines  (and  demonstrating 
how  they  work).  A  bit  map  contain¬ 
ing  32  bits  is  made  on  line  66.  The 
contents  of  the  map  are  printed  to  the 
screen  with  the  for  loop  on  lines  74 
and  75.  The  bit  is  actually  set  on  line 
80. 

The  subroutines  are  relatively 
straightforward.  Makebitmap(  ),  like 
makequeue(  ),  creates  a  header  with 
the  bit  map  itself  appended  to  it  (see 
Figure  2,  page  28).  The  pointer  re¬ 
turned  by  makebitmap(  )  points  at 
this  header,  which  is  a  single  un¬ 
signed  sized  object  (containing  the 
number  of  bits  in  the  map). 

Makebitmap(  )  figures  the  number 
of  bytes  needed  on  line  24  with: 

numbytes  =  (size  >>  3)  +  ((size 

&  0x07)  ?  1  :  0); 

The  right  shift  does  a  divide  by  8,  and 
the  conditional  adds  1  to  the  result  of 
the  divide  if  “size”  isn’t  an  even  mul¬ 
tiple  of  8.  Calloc(  )  is  called  (on  line 
28)  to  get  the  memory  so  the  bit  map 
will  be  initialized  to  all  zeroes  (all 
bits  cleared).  The  additional 
sizeof(unsigned)  is  for  the  header.  A 
pointer  to  the  map  is  returned  on  line 
30. 

Setbit(  )  starts  on  line  32  of  Listing 
Two.  It  tests  for  “c”  out  of  bounds  on 
line  39  (“map”  must  be  cast  into  a 
pointer-to-unsigned  to  get  the  map 
size).  We  then  add  sizeof  (unsigned) 
to  map  to  skip  past  the  map  size  to 
the  map  itself.  The  bit  is  set  on  line  43 
or  cleared  on  line  45.  Testbit(  )  (line 
49)  works  just  like  setbit(  ),  the  only 
difference  being  that  it  returns  the 

bit’s  value  instead  of  modifying  it. 

Reader  Ballot 

Vote  for  your  favorite  feature/article. 

Circle  Reader  Service  No.  191. 

Dr.  Dobb’s  Journal,  June  1985 


C  Chest  (Text  begins  of  page  26) 

Listing  One 


1:  # d e f i ne  DEBUG  1 

2:  # i f de f  DEBUG 
3:  #include  <stdio.h> 
4 :  #endi f 


QUEUE. C: 


General  purpose  queue  management  routines: 


Copyright  (c)  1985,  Allen  I.  Holub.  All  rights  reserved 
This  program  may  be  copied  for  personal,  non-profit,  use  only. 

The  QUEUE  data  structure.  No  external  routine  needs  to  know  anything 
about  how  this  structure  is  put  together.  These  routines  need  only 


14 

*  remember  a 

pointer  to 

the  queue 

(in  a  manner  similar  to 

the 

FILE 

15 

*  pointer  used  by  the  i 

/ o  routines ) . 

16 

*/ 

17 

typedef  struct 

18 

( 

19 

char 

♦star t ; 

/* 

Pointer  to  beginning  of 

queue 

*/ 

20 

i  n  t 

head  ; 

/* 

Index  of  current  head 

*/ 

21 

in  t 

tail; 

/* 

Index  of  current  tail 

*/ 

22 

i  n  t 

size; 

/* 

Max  num  of  objects  queue 

can 

hold 

*/ 

23 

in  t 

nob  j  ; 

/* 

Number  of  objects  now  in 

the 

queue 

*/ 

24 

int 

objsize; 

/* 

Size  of  one  element 

*/ 

25 

)  QUEUE; 

26 

/* - 

—*/ 

27 

QUEUE  *makequeue(  qsize 

,  objsize 

) 

/  *  Make  a  queue  of  the  specified  size  containing  objects  of  the 

*  specified  size.  Return  a  pointer  to  the  queue  or  0  if  there  is 

*  not  enough  memory  to  make  the  queue.  Queues  are  created  using 

*  calloc().  They  require  si zeo f ( QUEUE )  +  (qsize  *  objsize)  bytes. 
*/ 


register  QUEUE  *qp; 

if(  !(qp  =  (QUEUE  *)  ma 1 1 oc ( si zeo f ( QUEUE )  +  (qsize  *  objsize))  )) 
return  0; 


qp->start 
qp->size 
qp->ob jsize 


=  ( char  *  )  (qp  +  1); 
=  qsize  ; 

=  objsize  ; 


qp->head  =  qp->tail  =  qp->nobj 
return(  qp  ); 


del_queue(  qp  ) 
QUEUE  * q  p ; 

( 


Delete  a  queue  and  free  the  memory.  The  queue  will  NOT 
be  deleted  unless  it  is  empty.  Return  1  if  the  queue 
was  deleted,  0  otherwise.  If  you  don't  care  if  the  queue 
is  actually  empty,  use  free(qp). 


i  f (  qp->nobj  ) 

return  0 ; 

f  r  ee (  qp  ); 
return  1 ; 


(Continued  on  next  page) 


Dr.  Dobb's  Journal,  June  1985 


31 

439 


i L  LsiieST  (Listing  continued,  text  begins  on  page  26) 

Listing  One 


57: 

enqueue ( 

ob  j  , 

qp  ) 

58: 

char 

*  o  b  j 

59: 

QUEUE 

*QP 

i 

60: 

[ 

61  : 

/* 

Put  an 

object  into  the 

queue 

.  Obj  is  a  pointer 

62: 

* 

object 

q  p  is  a  pointer 

to  a 

QUEUE.  Return  1  on 

63: 

* 

0  if  there's  no  more  room  in 

the  queue . 

64: 

*/ 

to  the 
success , 


65: 

int  i ; 

/* 

Counter 

*/ 

66: 

char  *  b  p ; 

/* 

points  into  queue 

*/ 

67: 

iff  qp->nobj  >=  qp->size  ) 

/* 

If  the  queue  is  full 

*/ 

68: 

return  0 ; 

/* 

return  failure. 

*/ 

69: 

qp->nob j++ ; 

/* 

One  more  object  in 

*/ 

70: 

/* 

the  queue 

*/ 

71 : 

bp=  qp->start  +  (qp->objsize  * 

qp->tail ) ; 

/*  Get  target  address 

*/ 

72: 

/* 

within  the  queue; 

*/ 

73: 

/* 

then  move  object 

*/ 

74: 

/* 

into  i t : 

*/ 

75:  f or (  i  =  q p->ob j si ze ;  — i  >=  0;  *bp++  =  *obj++  ) 

76:  ; 


77 

if( 

++qp->tail  >=  qp->size) 

/* 

Wrap  around  if  we've 

*/ 

78 

qp->tail  =  0; 

/* 

gone  off  the  end  of 

*/ 

79 

/* 

the  queue . 

*/ 

80 

return  1  ; 

81 

} 

82 

dequeue(  obj 

.  qp  ) 

83 

char  *obj 

» 

84 

QUEUE  *  q  p 

* 

85 

( 

86 

/* 

Get  an  object  from  the 

queue. 

Qp  is 

a  pointer  to  a  QUEUE 

• 

87 

* 

The  dequeued  object  is 

copied 

into 

the  place  pointed  to 

by 

88 

obj.  Return  0  if  the  queue  is 

empty 

and  no  object  was 

89 

* 

dequeued,  1  otherwise. 

90 

*/ 

91:  register  int  i; 

92:  register  char  *bp; 

93: 

94:  if(  qp->nobj  <=  0  ) 

95:  return  0;  /*  queue  empty  */ 

96 :  qp->nob j —  ; 

97:  bp  =  qp->start  +  (qp->objsize  *  qp->head)  ; 

98:  for(  i  =  qp->objsize;  — i  >=  0;  *obj++  =  *bp++  ) 

99:  ; 


100:  if(  ++qp->head  >=  qp->size  ) 

101:  qp->head  =  0; 

102 :  return  1 ; 

103:  } 


104:  /* 
105:  /* 
106:  /* 
107:  /* 
108:  /* 


Little  access  routines: 

Show_next  returns  a  pointer  to  the  object  at  the  head  of  the 
queue;  sp_used  returns  the  number  of  objects  in  the  queue 
s p_a vail  returns  the  number  of  slots  available  in  the  queue. 


*/ 

*/ 

*/ 

*/ 

*/ 


109:  char  *show_next  (qp) 

110:  QUEUE  *qp; 

111:  ( 

(Continued  on  page  34) 


32 


Dr.  Dobb’s  Journal.  June  1985 


C  Chest  (Listing  continued,  text  begins  on  page  26) 

Listing  One 

112:  return(  qp->start  +  (qp->head  *  qp->objsize)  ) 


i  nt 
QUEUE 


sp_used  (qp) 

*qp; 

returnf  qp->nobj  ); 


i  nt 
QUEUE 


sp_avail  (qp) 

*qp; 

returnf  qp->size  -  qp->nobj  ); 


#i f def  DEBUG 


main() 

( 


in  t  num ,  c ,  *ip ; 

QUEUE  *q  p ; 


129 

qp  =  makequeuef  4,  sizeoffint)  ); 

130 

whilef  1  ) 

131 

( 

132 

num  =  c  =  - 1 ; 

133 

ip  =  (int  *)qp->start  ; 

134 

printff  "\n\nqueue:  Zd  %d  Zd 

Zd\n"  ,  i p [ 0 ]  ,  ip[ 1 ] , 

ip[  2 

135 

136 

printf ( "start  =0xZx\n", 

q  p->star t 

) 

137 

printf("head  =Zd\n", 

qp->head 

) 

138 

printff "tail  =Zd\n", 

q  p->ta i 1 

) 

139 

printf("size  =Zd\n", 

qp->size 

) 

140 

printff "obj size  =Zd\n", 

qp->ob jsize 

) 

141 

printf("nobj  =Zd\n", 

qp->nob j 

) 

printff "there  are  %d  slots  left  in  the  queue\n\n", 

s  p_a  vail(qp)  ); 

printf ("(d/e/q) 

while (  c  ! =  'e'  &&  c  !=  'd'  &&  c  !=  'q'  ) 

c  =  getchar(); 

if (  c  ==  'e'  ) 

( 

printf ( "enter  decimal  number 
scanf("%d",  &num  ); 

printff "enqueue(Zd)  returned  %d\n", 

num,  enq ueue ( &num , q p )  ); 

) 

else  iff  c  ==  ' d '  ) 

{ 

printff  "dequeue  returned  Z  d  ,  loaded  %d\n", 

dequeuef  &num,  qp  ),  num  ); 


break ; 


printff"  deleting  queue,  queue  was  Zsempty\n", 

del_q ueue ( qp )  ?  ""  :  "not  "  ); 


#  end i f 


End  Listing  One 

( Listing  Two  begins  on  page  36) 


Dr.  Dobb’s  Journal,  June  1985 


34 

441 


K^flCSl  (Listing  continued,  text  begins  on  page  26) 

Listing  Two 


/*  BITMAP. C 

* 


raakebitmap,  setbit,  testbit:  bit  map  manipulation 
routines . 


*  Copyright  (c)  Allen  I.  Holub,  all  rights  reserved.  This  program 

*  be  copied  for  personal,  non-profit  use  only. 

*/ 


#if def  DEBUG 
finclude  <stdio.h> 
#endi f 

typedef  char  BITMAP; 


BITMAP  *makebitmap(  size  ) 
unsigned  size; 

{ 

/*  Make  a  bit  map  with  "size"  bits.  The  first  entry  in 

*  the  map  is  an  unsigned  int  representing  the  maximum 

*  bit.  The  map  itself  is  concatenated  to  this  integer. 

*  Return  a  pointer  to  the  map  on  success,  0  if  there's 

*  not  enough  memory. 

*/ 

unsigned  *map,  numbytes; 

numbytes  =  (size  >>  3)  +  ((size  &  0x07)  ?  1  :  0  )  ; 


#if def  DEBUG 

printf ( "Making  a  %d  bit  map  (%d  bytes  r equi r ed ) \n" ,  size,  numbytes); 

#endif 

if(  map  =  (unsigned  *)  calloc(  numbytes  +  sizeof (unsigned )  ,1  )  ) 
*map  =  size; 

return  (BITMAP  *)  map; 

) 

setbit(  c,  map,  val  ) 
unsigned  c,  val; 
char  *map; 


/*  Set  bit  c  in  the  map  to  val. 

*  If  c  >  map  size,  0  is  returned,  else  1  is  returned. 

*/ 


if(  c  >=  *(unsigned  *)map  ) 
return  0; 


map  +=  sizeof ( unsigned ) ; 


/*  if  c  >=  map  size  */ 


/*  Skip  past  size  */ 


if(  val  ) 

map[c  >>  3]  |=  1  <<  (c  &  0x07)  ; 

else 

map[c  >>  3]  &=  (1  <<  (c  &  0x07))  ; 

return(  1  ); 


48;  /* -  */ 


49:  testbit(  c,  map  ) 

50:  unsigned  c; 

51  :  char  *map ; 

52:  ( 

53:  /*  Return  1  if  the  bit  corresponding  to  c  in  map  is  set. 

54:  *  0  if  it  is  not. 


a  An 


Dr.  Dobb's  Journal,  June  1985 


55: 

*/ 

56: 

if (  c  > 

=  *(unsigned  *)map  ) 

57: 

return  0; 

58: 

map  +  = 

sizeof(unsigned)  ; 

59: 

return( 

map [  c  >>  3  ]  &  (1  <<  (c  &  0x07))  ); 

60:  ) 

61 :  #if def 

DEBUG 

62:  main() 

63:  ( 

64: 

int 

bitnum,  set,  i,  *map; 

65: 

printf ( 

"Making  a  32  bit  wide  bit  map\n"); 

66: 

i f (  !(map  =  makebitraap(  32  ))  ) 

67: 

printf ( "Can ' t  make  map\n  ); 

68: 

while ( 

1  ) 

69: 

( 

70: 

/*  Print  the  bit  map.  Try  to  print  past 

71: 

*  map  to  make  sure  overflow  detection 

72: 

*  come  back  as  a  0). 

73: 

*/ 

74: 

f or (  i  =  0;  i  <=  32  ;  i++  ) 

75: 

putchar(  testbit(  i,  map  )  ?  'X 

76: 

printf ("\n\nBit  number  :"); 

77: 

scanf("%d",  Sbitnum  ); 

78: 

printf("\nl  to  set,  0  to  clear:  "); 

79: 

scanf("%d",  &set  ); 

80: 

if(  !  se tb i t ( bi t num ,  map,  set)  ) 

81 : 

printf("Bit  out  of  range\n") ; 

82: 

) 

83:  ) 

84:  #endif 

); 


End  Listing  Two 


Listing  Three 


1:  /* - + 

2:  *  GREP:  Changes  needed  to  add  bit  maps  to  character  classes: 

3:  * - + 

4:  */ 

5:  typedef  struct  token  /*  In  tools. h  */ 

6:  ( 

7:  char  tok; 

8:  char  lchar; 

9:  char  *bitmap; 

10:  struct  token  *next; 

11:  ) TOKEN ; 

12:  /* - */ 

13:  TOKEN  *makepat ( arg ,  delim)  /*  In  tools. c  */ 

14 :  char  *arg ; 

15:  int  delim; 

16:  { 

17 :  * 

18:  * 

19:  * 

20:  case  CCL: 


21 

22 

23 

24 

25 


if  (*(arg+l)  ==  NEGATE) 

( 

ntok->tok  =  NCCL; 
arg  +=  2; 

) 

(Continued  on  next  page ) 


Dr.  Dobb’s  Journal,  June  1985 


37 

441 


C  Chest 


(Listing  continued,  text  begins  on  page  26) 


Listing  Three 


26 

27 

28 

29 

30 


else 

{ 

ntok->tok  =  CCL; 
ar  g++ ; 

) 


31 

32 

33 

34 

35 

36 

37 

38 


if(  ntok->bitraap  =  makeb i t raa p ( 1 28 )  ) 

arg  =  dodash(CCLEND ,  arg ,  ntok->bitraap  ); 

else 

{ 

f pr in t f ( s tder r , "No t  enough  memory  for  pat\n") ; 
error  =  1 ; 

) 

break ; 


39:  * 

40:  * 

41 :  * 

42:  ) 

43:  char  *dodash(  delim,  src,  map  )  /*  In  tools. c  */ 

44:  int  delim; 

45:  char  *src,  *map; 

46:  ( 

47:  register  int  first,  last; 

48:  char  *start; 


49: 


start  =  src; 


50:  while(  *src  &&  *src  !=  delim  ) 

51:  ( 

52  :  if (  *src  ! =  ' - ' ) 

53:  setbit(  esc(  Ssrc  ),  map,  1  ); 


54 

55 

56 

57 

58 


else  if(  src  == 
setbit( 


else 


start  || 
' ,  map , 


*( src+1 ) 

l  ): 


delim  ) 


sr C++ ; 


59 

60 
61 
62 

63 

64 

65 

66 

67 

68 


if(  *src  <  *(src  -  2)  ) 

( 

first  =  *  s  r  c  ; 
last  =  *(src-2)  ; 

) 

else 

{ 

first  =  *  ( sr  c  -  2); 
last  =  *src; 


69: 

while (  ++f ir st  <=  last  ) 

70: 

setbit(  first,  map,  1); 

71: 

src++ ; 

72: 

) 

73: 

) 

74: 

return(  src  )  ; 

75: 

) 

76: 

int 

omatch  (linp,  pat,  boln) 

77: 

char 

**linp ,  *boln ; 

78: 

TOKEN 

*pat  ; 

79: 

{ 

80: 

* 

81 : 

* 

82: 

* 

83: 

case  CCL: 

84: 

if(  testbit(  **linp,  pat->bitmap 

tools . c  * / 


(Continued  on  page  40) 


38 


Dr.  Dobb’s  Journal,  June  1985 


C  Chest  (Listing  continued,  text  begins  on  page  26) 

Listing  Three 


85 

advance  =  1 ; 

86 

break ; 

87 

case  NCCL: 

88 

if(  !testbit(  **linp,  pat->bitmap)  ) 

89 

advance  =  1 ; 

90 

break ; 

91 

* 

92 

* 

93 

* 

94 

) 

95 

/* - 

— 

— 

96 

pr  tok( 

head  ) 

/  *  In  tools. c 

*/ 

97 

TOKEN 

♦head ; 

98 

{ 

* 

99 

* 

100 

* 

101 

if  (head->tok  ==  CCL  | |  head->tok  ==  NCCL) 

102 

( 

103 

printf ( "string  (at  OxZx)  =<",  head->bitmap 

104 

for(  i  =  0;  i  <  0x7f  ;  i++) 

105 

if(  testbit(i,  h ead->bi t raa p )  ) 

106 

putchar ( i  )  ; 

107 

printf(">,  "); 

108 

) 

109 

* 

110 

* 

111 

★ 

112 

) 

113 

unmakepa t ( head ) 

/*  In  tools.c 

♦/ 

114 

TOKEN 

♦head ; 

115 

( 

116 

register 

TOKEN  *old_head ; 

117 

while  ( head ) 

118 

{ 

119 

switch  (head->tok) 

120 

( 

121 

case  CCL: 

122 

case  NCCL: 

123 

free(head->bitraap) ; 

124 

/*  no  break,  fall  through  to  default  */ 

125 

default : 

126 

old  head  =  head; 

127 

head  =  head->next; 

128 

free(old  head); 

129 

break  ; 

130 

) 

131 

) 

132 

) 

End  Listings 


40 


Dr.  Dobb’s  Journal.  June  1 985 


UNIX  EXCHANGE 


by  Axel  Schreiner 


This  column  is  reprinted  from  the  German  quarterly 
unix/mail  (Hanser  Verlag,  Munich,  Germany ).  ®  1 984  by 
Axel  T.  Schreiner,  Ulm,  West  Germany.  It  may  be  repro¬ 
duced  as  long  as  the  copyright  notice  is  included  and  ref¬ 
erence  is  made  to  the  original  publication. 

/lib/cpp 

This  column  deals  with  uses  and  abuses  of  the  C  prepro¬ 
cessor.  It  demonstrates  some  techniques  that  can  save  a 
lot  of  work  and  even  more  errors.  The  discussion  applies 
to  programming  in  C  in  general  and  makes  only  very  ele¬ 
mentary  assumptions: 

C  programs  are  run  through  a  preprocessor  before 
they  are  handed  to  the  actual  compiler.  The  prepro¬ 
cessor  performs  (parameterized)  text  substitution 
(#define),  inserts  header  files  (#include),  and  can 
exclude  parts  of  the  source  from  compilation  (#if). 

Because  the  preprocessor  is  independent  of  the  actual 
compiler  and  does  not  know  C  at  all  -one  can  use  it  in 
particular  to  extend  the  C  language.  Only  taste  limits 
one’s  imagination  here  .... 

Excluding  Text 

Every  programmer  writes  occasional  comments.  Some¬ 
times  we  comment  to  exclude  program  parts  from  a  com¬ 
pilation.  Because  in  Standard  C  comments  may  not  be 
nested,  there  is  considerable  temptation  not  to  comment 
such  excluded  program  parts  anymore. 

The  following  technique  for  text  exclusion  is  much 
more  appropriate: 

#ifdef  not_defined 

crash_the_system(NOW); 

/*  this  definitely  goes  wrong  */ 

#endif  not_defined 

Of  course,  the  name  not_defined  should  really  not  be 
defined. 

Vector  Dimensions 

In  principle,  one  can  determine  the  size  of  a  vector  by 
using  the  sizeof  operator.  However,  sizeof  yields  the  size 
in  bytes,  not  in  elements.  The  following  macro  determines 
the  number  of  elements  in  an  arbitrary  vector: 

#define  DIM(x)  (sizeof  (x)  /  sizeof  ((x)[0])) 


sizeof  does  not  really  need  parentheses,  if  it  is  used  to 
determine  the  size  of  an  object  and  not  of  a  data  type.  One 
should,  however,  enclose  macro  parameters  in  parenthe¬ 
ses.  Then  things  work  out  for  a  vector  with  more  than  one 
dimension,  too: 

main(  ) 

{ 

struct  { int  a;  char  b; }  v[  10]  [20]  [30]; 
printf(“%d  %d  %d\n”,  DlM(v),  DIM(v[  1  ]), 
DIM(v[  1  ]  [2])); 

} 

The  program  produces  the  values  10,  20,  and  30. 

Parentheses  should  not  be  necessary  in  this  use  of  sizeof 
because  a  vector  subscript  should  have  precedence  over 
sizeof.  However,  my  copy  of  the  Mark  Williams  CP/M-86 
C  compiler  does  not  seem  to  know  this. 

We  can  carry  these  ideas  somewhat  further.  The  last 
element  of  a  vector  is 

#define  LAST(x)  ((x)[DIM(x)—  1  ]) 

and  the  customary  for  loop  is,  for  example: 

#define  END(x)  ((x)  +  DIM(x)—  1 ) 

int  vector[  10],  *  vp; 

for  ( vp  =  vector;  vp  <  =  EN  D( vector);  +  +  vp) 

The  compiler  evaluates  sizeof  during  its  evaluation  of 
constant  expressions.  This  circumstance  can  be  used  to 
determine  the  length  of  constant  strings  in  an  efficient 
and  flexible  fashion: 

#define  STRLEN(s)  (sizeof  (s)  —  1) 

char  buf[STRLEN(“model”)  +  I  ]; 

strcpy(buf,  “model”); 

There  is  the  danger,  however,  that  STRLEN  is  used  for 
other  objects  (i.e.,  non-strings)  by  mistaking  it  for  strlen. 

Trace 

It  is  well  known  that  a  macro  call  is  not  recognized  in  a 


42 


A  AC 


Dr.  Dobb’s  Journal,  June  1 985 


constant  string.  Less  well  known,  but  more  useful  per¬ 
haps,  is  that  a  macro  parameter  is  recognized  and  re¬ 
placed  within  the  replacement  text  of  a  macro  definition. 
Rather  than 

printf(“variable  =  %d\n”,  variable); 
printf(“formula  =  %f\n”,  formula); 

we  write 

#define  SHOW(val,fmt)  fprintf(stderr,\ 

“SHOW:  val  =  fmt\n”,  val) 

SHOWfvariable,  %d); 

SHOWfformula,  %f); 

The  latter  is  easier  to  use  and  conveys  more  information 
because  val  is  replaced  in  the  format  by  the  entire  macro 
argument. 

A  bit  of  caution  is  required:  if  the  %  operator  is  used 
within  val,  there  will  be  problems  with  the  format.  This 
can  be  corrected  as  follows: 

#define  SHOWfval,  fmt)  fprintffstderr,  “%s\ 

=  fmt\n”,  “val”,  val) 

A  macro  can  be  defined  without  a  replacement  text. 
Uses  of  SHOW  thus  can  be  eliminated  easily  from  the 
compiled  program  altogether.  Alternatively,  we  can  spec¬ 
ify  a  condition: 

#ifdef  DEBUG 

char  debugflag; 

#  define  SHOW(val,fmt)  (debugflag  &&\ 

fprintff . . .  )) 

#else  !  DEBUG 

#  define  SHOWfval, fmt) /*  null  */ 

#endif  DEBUG 

In  this  example,  SHOW  is  always  used  as  a  statement  and 
not  as  an  expression.  Using  &&  rather  than  if  has  two 
advantages:  we  do  not  have  to  use  SHOW  as  a  statement, 
and  use  of  SHOW  does  not  invite  an  unintentional  else, 
debugflag,  by  the  way,  should  be  used  as  a  bit  vector,  e.g.: 

#define  SHOWflevel,  val,  fmt)  (debugflag  &\ 
l<<level  &&  fprintff . . .  )) 

Now  we  can  maintain  different  sets  of  trace  information 
at  levels  0  through  7. 

Global  Variables 

Do  you  like  modular  programs  with  lots  of  sources,  make¬ 
file,  a  central  header  file,  and  the  (feeble)  hope  that  all 
global  declarations  really  match?  Do  you  like  to  lint,  too? 
The  following  technique  simplifies  maintaining  global 
variables.  The  central  header  file  contains  about  the 
following: 


Dr.  Dobb's  Journal,  June  1985 


43 

447 


#ifndef  GLOBAL 
#  define  GLOBAL  extern 

#endif  GLOBAL 

GLOBAL  int  global_variable; 

If  GLOBAL  is  not  defined,  a  variable  declared  GLOBAL  is 
declared  extern. 

Within  exactly  one  of  the  source  files  that  include  the 
header  file,  we  have  to  take  care  that  the  variables  that 
were  declared  extern  elsewhere  are  really  defined.  In  the 
main  source  file,  we  therefore  write: 

#define  GLOBAL  /*  to  define  global  variables  */ 
#include  “definitions.h” 

One  can  even  initialize  global  variables  in  this  context 
without  resorting  to  the  —  m  flag  instructing  the  loader  Id 
to  accept  multiple  definitions: 


#ifdef 

GLOBAL 

# 

define 

INIT(x)  -  x 

#else 

!  GLOBAL 

# 

define 

GLOBAL  extern 

# 

define 

INIT(x) 

#endif 

GLOBAL 

GLOBAL 

int  variable  INIT(  10); 

This  technique  is  not  very  practical  for  aggregates.  The 
following  variant  is  easier  to  use: 


#ifdef 

GLOBAL 

# 

define 

INIT(x)  =x 

# 

define 

GINIT 

#else 

!  GLOBAL 

# 

define 

GLOBAL  extern 

# 

define 

INIT(x)  ; 

# 

undef 

GINIT 

#endif 

GLOBAL 

GLOBAL 

struct  ( int  a;  char  b; }  variable  IN1T(  ) 

#ifdef 

GINIT 

{  10,  ‘b’ }; 

#endif 

GINIT 

This  method  requires  that  the  C  preprocessor  permit  a 
macro  call  with  an  empty  argument  list  and  that  the  C 
compiler  not  complain  about  superfluous  semicolons  be¬ 
tween  global  declarations.  This  method  is  admittedly  no 
longer  very  elegant,  but  it  has  the  significant  advantage 
that  the  text  of  central  definitions  exists  only  once  in  all 
cases. 

/bin/lex 

Now  you  see  it . .  . 

lex  programs  have  lots  in  common  with  fashions:  the 
effect  is  not  always  what  the  pattern  promises.  If  a  func¬ 
tion  generated  by  lex  is  used  as  a  front  end  for  a  parser 

44 


generated  by  yacc,  it  is  sometimes  very  hard  to  decide 
where  to  place  the  blame  for  a  bug:  is  there  a  bug  in  the 
grammar  presented  to  yacc,  or  are  the  patterns  that  were 
processed  by  lex  at  fault? 

The  following  technique1  permits  the  construction  of  a 
source  file  for  lex  that  is  conditionalized  so  that  a  debug¬ 
ging  version  can  be  compiled  at  any  time  without  any 
changes  to  the  source.  To  test  the  results  of  lex,  all  inputs 
that  the  parser  is  to  receive  later  are  first  presented  to  the 
debugging  version.  This  version  of  the  front  end  then 
prints  a  mnemonic  version  of  the  values  that  the  parser 
would  receive: 

%{ 

#ifdef  TRACE 

#  include  “assert.h” 

main(  ) 

{ 

char  *cp; 

assert(sizeof(int)  >  = 
sizeof(char  *)); 

while  (cp  =  (char  *)  yylex(  )) 
printf(“%  - .  1  Os  is  \“%s\”\n”, 
cp,  yytext); 

} 

#  define  token(x)  (int)  “x” 

#else  !  TRACE 

#  include  “y.tab.h” 

#  define  token(x)  x 

#endif  TRACE 

%} 

Normally,  TRACE  is  undefined,  and  the  tokens  (i.e., 
the  values  that  are  to  be  returned  to  the  parser)  are  de¬ 
fined  in  the  file  y.tab.h  generated  by  yacc  as: 

#define  NAME  257 


These  defined  names  are  used  directly  in  the  source  pre¬ 
sented  to  lex  and  are  returned  as  a  result  of  the  function 
yylex(  ). 

If  TRACE  is  defined,  y.tab.h  need  not  yet  exist.  In  this 
case  (i.e.,  in  the  debugging  version),  we  want  to  return  a 
string  as  a  result  of  yylex(  )  that  is  then  printed  by  the 
main(  )  program  included  here.2  Analyzing  the  debug¬ 
ging  output  is  most  easily  accomplished  if  the  output  uses 
exactly  those  words  that  later  will  appear  in  y.tab.h — i.e., 
that  are  a  result  of  %token  statements  in  the  source  pre¬ 
sented  to  yacc. 

We  are  using  the  fact  that  macro  parameters  are  re¬ 
placed  within  strings  in  the  replacement  text  of  a  macro. 
token(x)  returns  either  x  itself  (to  be  passed  on  to  yacc)  or 

Dr.  Dobb's  Journal,  June  1985 


448 


a  string  “x”  for  the  purposes  of  TRACE. 

The  remainder  of  the  lex  program  is  now  quite  obvious: 

%% 

[0  —  9]+  return  token(NUMBER); 

[a  — z_A  — Z]  [a  — z_A  — Z0  — 9]*  return  word(  ); 

[  \t\n  ]  +  ; 

return  token(yytext[0]); 

%% 

struct  reserved  { char  *  text;  int  yylex; }  reserved[  ]  =  { 
{ “begin”,  token(BEGIN) }, 

{ “end”,  token(END) }, 

(char  *)  0 }; 

int  word(  ) 


struct  reserved  *rp; 

for  (rp  =  reserved;  rp— >text;  ++  rp) 
if  (strcmp(yytext,  rp— >text)  ==  0) 
return  rp  — >yylex; 
return  token  (NAME); 

} 

Yes,  there  should  have  '  sen  a  binary  chopped  search, 
but  we  are  dealing  only  with  the  j.  nciples. 


/usr/src/main.c 
Argument  Standards 

Command  arguments  are  always  good  for  surprises. 
Sometimes  several  options  may  be  combined  into  one  ar¬ 
gument;  sometimes  each  option  must  be  a  separate  argu¬ 
ment;  sometimes  a  parameter  value  follows  as  part  of  the 
argument;  sometimes  it  does  not;  sometimes  all  of  the 
above;  sometimes  some  of  the  above  .... 

If  one  consults  the  sources  of  certain  Unix  utilities,  one 
learns  to  appreciate  the  flexibility  of  C  (or  the  infinite 
patience  of  the  C  compiler).  Everybody  does  his  or  her 
own  thing,  and  most  do  it  differently  in  every  program! 
However,  it  would  be  so  simple  to  develop  a  standard  as  in 
Listing  One,  page  48). 

At  show(  ),  argc  contains  the  number  of  arguments  that 
have  not  yet  been  processed,  and  *argv  is  the  first  one  of 
these.  This  argument  can  be  a  single  -  character;  in  some 
ancient  (cat)  and  almost  new  (tar)  utilities,  this  indicates 
that  standard  input  or  output  is  to  be  used  in  place  of  a  file 
argument. 

Flags  can  be  combined  at  will.  If  an  option  requires  a 
value,  it  can  follow  immediately  or  it  can  be  an  argument 
of  its  own. 

Following  a  standard  proposed  in  the  “USENIX  login,” 
an  option  —  serves  to  terminate  processing  of  the  option 
list.  Apart  from  that,  options  must  start  with  -  and  must 
precede  other  arguments.  These  rules,  however,  still  do 
not  cover  all  possibilities  of  pr. 

The  skeleton  above  is  useful  but  anatomically  some¬ 


what  terrifying.  The  following  incarnation  is  perhaps 
more  attractive: 

#include  <stdio.h> 

#include  “main.h” 

#define  show(x)  printf(“x  =  %d\n”,  x) 

#define  USAGE fputs(“cmd  [  —  f]  [— v  #]\n”,\ 
stderr  ),  exit(  1 ) 

MAIN 

{ 

int  f  =  0,  v  =  0; 

OPT 

ARGT: 

+  +  f; 

ARG  V: 

PARM 

v  =  atoi(*argv); 

NEXTOPT 

OTHER 

USAGE; 

ENDOPT 

show(f),  show(v),  show(argc); 
if  (argc) 

puts(*argv); 

} 

The  trick,  of  course,  is  concealed  in  the  header  file 
main.h:  here  the  macros  OPT,  ARG,  PARM,  NEXTOPT, 
OTHER,  and  ENDOPT  must  be  defined  using  exactly 
those  texts  that  were  given  explicitly  in  the  previous  ex¬ 
ample,  as  in  Listing  Two,  (page  50). 

The  definitions  are  not  exactly  beautiful,  especially  if 
they  need  to  be  compacted  so  that  the  C  preprocessor 
accepts  the  lengthy  replacement  texts,  but  they  need  to  be 
developed  only  once  to  make  the  argument  standard 
available  for  all  applications.  An  application  then  is  al¬ 
most  self-documenting: 

MAIN 

is  the  function  header  of  the  main  program. 

OPT 

starts  the  loop  during  which  the  options  are  processed. 
ENDOPT 

completes  this  loop. 

ARG 

within  the  loop  starts  the  processing  of  one  option;  the 
name  of  the  option  (a  single  character)  enclosed  in 
single  quotes  and  a  colon  must  follow. 

PARM 

follows  the  option  specification  if  the  option  has  a  val¬ 
ue  parameter;  the  parameter  itself  is  then  available  as 
*argv. 


Dr.  Dobb’s  Journal,  June  1985 


46 

449 


NEXTOPT 

is  used  once  such  a  parameter  has  been  processed  to 
advance  to  the  next  command  argument. 

OTHER 

must  follow  all  options;  following  this,  one  specifies 
what  should  be  done  if  an  option  could  not  be  recog¬ 
nized.  NEXTOPT  may  be  specified  in  this  case,  too. 
The  unknown  option  itself  is  **argv. 

After  the  OPT  ENDOPT  loop,  argc  contains  the  number 
of  command  arguments  that  have  not  yet  been  processed, 
and  *argv  is  the  first  such  argument.  Arbitrarily  many 
(different)  options  ARG  can  be  specified,  pr  would  be  im¬ 
plemented  approximately  as  follows: 

MAIN 

{ 

do 

{ 

OPT 

ARG  ‘h’: 

PARM 

header  =  *argv; 

NEXTOPT 
ARG  ‘w’: 

PARM 

width  =  atoi(*argv); 
NEXTOPT 
ARG  T: 

PARM 

length  =  atoi(*argv); 
NEXTOPT 
ARG't’: 

tflag  =  1; 

ARG ‘s’: 

PARM 

delimiter  =  **argv; 

+  +  *argv; 

NEXTOPT 
ARG  ‘m’: 


mflag  =  1; 

OTHER 

if  (isdigit(**argv)) 

columns  =  atoi(*argv), 
NEXTOPT 

else 

USAGE,  exit(l); 

ENDOPT 

if  (argc) 

{ 

if  (**argv  =  =  *  +  ’) 

{ 

PARM 

first_page  =  atoi(*argv); 
continue; 

} 

dopr(*argv); 

} 

else 

dopr(“-”); 

}  while  (argc  >  1 ); 

} 

There  is  a  blemish:  -  columns  must  be  secified  as  a  single 
argument  (because  -  alone  refers  to  standard  input). 

Notes 

1  This  technique  was  developed  for  the  book  Introduction 
to  Compiler  Construction  by  A.  T.  Schreiner  and  H.  G. 
Friedman,  Jr.,  Prentice-Hall,  1985. 

2  The  technique  requires  that  a  pointer  to  a  character 

string  be  returned  in  place  of  an  int  value.  This  is  not 
possible  across  all  implementations  of  C;  e.g.,  it  is  proba¬ 
bly  not  allowed  on  the  7300  systems.  We  guard  against  a 
portability  problem  by  using  assert(  ).  DD| 

Reader  Ballot 

Vote  for  your  favorite  feature/article. 

Circle  Reader  Service  No.  1 92. 


#include  <stdio.h> 

#define  show(x)  printf("x  =  %d\n",  x) 

#define  USAGE  fputsf'cmd  [— f]  [-v  #]\n",  stderr),  exit(1 ) 

main(argc,  argv) 
int  argc; 
char  "argv; 

( 

\ntf  =  0,  v  =  0; 

while  ( — argc  >  0  &&  **+  +argv  ===== 

{ 

switch  (*+  +*argv) 

{ 

case  0:  /*  _  •/ 

Listing  One  (Continued  on  page  50) 


48 

450 


Dr.  Dobb’s  Journal.  June  1985 


(Continued  from  page  48) 


-  -‘argv; 
break; 

case 

if  (!  (*argv)  [  1  ])  /*--*/ 

{ 

4-  +  argv,  — argc; 
break; 

} 

default: 

do 

{ 

switch  (**argv) 

{ 

case  T:  /*  -f  */ 

+  +  f; 
continue; 
case  V: 

if  (*+  +*argv) 

;  r  -v#  7 

else  if  (-  -argc  >  0) 

+  +argv;  /*  -v  #  */ 

else 

break; 

v  =  atoi(*argv); 

‘argv  +  =  strlen(*argv)  -1  ; 
continue; 

} 

USAGE; 

}  while  (*+  +*argv); 
continue; 

} 

break; 

} 

show(f),  show(v),  show(argc); 
if  (argc) 

puts(’argv); 


Listing  One 


#define  MAIN  main(argc,  argv)  \ 

int  argc;  \ 

char  **  argv; 

#define  OPT  while  ( — argc  >  0  &&  \  **+  +argv  \ 

{  \ 

switch  (*++  *argv)  \ 

{  \ 

case  0:  \ 

-  -*argv;  \ 

break;  \ 

case'  —  ':  \ 

if  (!  (*argv)  [1] )  \ 

<  \ 

+  +  argv,  —  argc;\ 
break;  \ 

}  \ 

default:  \ 

do  \ 

{  \ 


switch  (**argv)  \ 

{ 

#define  ARG  continue;  \ 

case 

#define  OTHER  continue;  \ 

} 

#define  ENDOPT  }  while(*  +  +  *argv);  \ 

continue;  \ 

}  \ 

break;  \ 

> 

#define  PARM  if(*++*argv)  \ 

\ 

else  if  (-  -argc  >  0  )  \ 

+  +argv;  \ 

else  \ 

break; 


#define  NEXTOPT  ‘argv  +  =  strlen(‘argv)—  1  ; 


Listing  Two 


Dr.  Dobb’s  Journal,  June  1985 


50 

451 


My  first  experiences  with  a  modem,  a  microcom¬ 
puter,  a  television  set,  and  a  telephone  linked  to¬ 
gether — with  me  in  the  loop — were  much  like  my 
first  discoveries  regarding  the  true  significance  of  portions 
of  my  own  anatomy.  These  experiences  combined  the  joys 
of  intellectual  insight,  emotional  pleasure,  physical  in¬ 
volvement,  and  all  the  wonder  we  associate  with  the  notion 
of  “surprise.” 


Information 
Age  Issues 

by  Dean  Gengle 


The  real  issues  in  telecommunications 
dwarf  questions  about  protocol  and 
baud  rate.  This  is  an  essay  on 
responsibility. 


On-Line  Life 

I  conducted  my  early  experiments  in  on-line  living  with  no 
misgivings  at  all.  I  was  able  to  consider  only  the  positive 
possibilities  contained  in  this  new  technical  capability.  Mo- 
democracy!  Distributed  networks!  Grass  roots  anticipatory 
guidance  systems!  It  all  seemed  within  reach  if  only  every¬ 
one  would  use  these  tools.  The  sheer  weight  of  the  new 
experiences  themselves  bolstered  my  initial  faith  that  folks 
would  come  to  use  these  tools  in  the  “right”  way. 

I  remember  distinctly  the  moment  when  the  inevitable 
mental  disappointment  occurred  and  reality  set  in 
about  six  weeks  after  my  first  modem  fix  (ca  1980).  I  was 
logged  on  a  local  bulletin  board  system  (BBS).  1  had  just 
finished  entering  a  private  reply  to  a  particular  message; 
it  was  meant  to  go  directly  to  another  user  of  the  system. 
On  my  screen  flashed  “Hi.”  Weird  response.  Commands 
issued  to  the  remote  computer  no  longer  worked.  Instead, 
the  system  operator  (sysop)  had  taken  over  and  was  typ¬ 
ing  messages  at  me  directly. 

“I’ve  been  watching  your  session,”  he  wrote,  in  the 
most  casual  and  matter-of-fact  manner.  “Thought  I’d  say 
hello.”  In  that  instant,  the  scales  fell  from  my  eyes,  so  to 
speak.  My  illusion  of  privacy  was  shattered,  an  illusion 
maintained  without  my  awareness  by  a  number  of  sup¬ 
porting  factors.  I  was  working  out  of  my  own  apartment, 
a  bedroom  with  a  spare  corner  space,  in  fact.  What  could 
be  more  personal  or  private?  Also,  I’ve  never  had  any 
reason  to  think  that  my  phone  was  tapped,  so  I’ve  always 
regarded  phone  conversations  as  “private”  My  modem  is 
hooked  to  the  phone,  ergo  ....  Besides,  the  remote  com¬ 
puter  system  itself  reinforced  my  illusion  by  allowing 
“private”  messages  between  individuals  and  by  requiring 
a  password  to  read  “mail.” 

All  along,  that  sysop  might  have  been  looking  over  my 
shoulder,  reading  what  I  typed,  aware  of  when  and  how  I 
used  the  system.  The  implications  of  that  made  me  decid¬ 
edly  uncomfortable.  Since  then,  I  have  become  aware  of 
other  sysops  who’ve  turned  their  computers  into  dictator- 
chips.  They  not  only  eavesdrop  at  will  but  run  their  sys¬ 
tems  with  the  firm  style  of  a  Khomeni.  (Confess  or  I  pull 
out  your  passwords!)  Large  systems  and  small  are  run  this 
way.  The  truly  astounding  thing  is  that  some  people  seem 
to  like  that  sort  of  relationship.  Still,  I  hope,  there  num¬ 
bers  are  not  great.  Not  here.  Not  in  the  U.S.  of  A. 


Dean  Gengle,  1150  Bryant  St.,  San  Francisco,  CA  94103 
(415)  861-8733. 


52 

452 


Dr.  Dobb's  Journal,  June  1985 


Info  Liberties 

The  net  result  of  this  is  that  I’ve  been  led,  willy-nilly,  into 
what  has  become  an  ongoing  interest  in  civil  liberties  and 
technology.  I  have  found  that  specific  design  decisions  of 
an  apparently  “purely  technical”  nature  can  and  often  do 
have  an  effect  on  what  we  consider  to  be  our  personal 
liberties  and  freedoms.  Conversely,  social  values  can  af¬ 
fect  what  and  how  new  technology  is  designed. 

The  controversies  that  arise  from  this  interplay  taken 
together  can  be  called  information  age  issues.  They  all 
focus  on  the  handling  of  information:  who  generates  it; 
who  can  store  it;  who  can  ship  it  around;  who  can  have  it; 
who  can  control  it;  who  can  copy  it  and  who  cannot;  what 
is  and  is  not  legitimate  information  in  a  given  instance — 
hence,  “information  age.” 

It  is  becoming  increasingly  difficult  to  draw  a  firm  line 
between  the  idea  of  telecommunications  and  the  idea  of 
computing.  Some  commentators  have  coined  terms  such 
as  telecomputing  and  compunications  to  make  this  point. 
(I  will  spare  the  reader  any  further  use  of  these,  however.) 
Distinctions  between  media  are  blurring  fast.  All  kinds  of 
information  now  can  take  digital  form  to  be  zapped  across 
various  channels,  whether  phone  lines,  fiber  optic  cables, 
cellular  networks,  or  satellite  transponders. 

For  the  time  being,  I  find  it  useful  to  think  of  telecom¬ 
munications  as  any  form  of  electronic  transmission  re¬ 
quiring  temporary  or  permanent  physical  connections. 

The  examples  that  follow  are  representative  of  the  di¬ 
lemmas  we  already  confront.  I  can  only  hint  at  telecom- 
munications-related  conundrums  (or  nightmares)  we 
may  have  to  face  in  the  months  and  years  directly  ahead. 

Freedom  from  Eavesdropping 

The  kind  of  surveillance  conducted  by  the  sysop  I  de¬ 
scribed  earlier  is  not  the  only  kind  that  new  technologies 
have  made  possible.  Signals  of  all  kinds  are  now  being 
intercepted  on  a  widespread  scale.  On  the  governmental 
level,  spy  agencies  around  the  world  engage  in  SIGINT 
(signal  intelligence)  work: 

•  Radio  and  television  broadcasts,  voice  and  data  phone 
calls,  satellite  channels,  international  telex  and  TWX 
and  Western  Union  transmissions,  and  amateur  radio 
broadcasts  are  all  monitored.  The  superpowers  con¬ 
duct  the  most  sophisticated  monitoring,  as  one  would 
expect,  but  the  technology  of  signal  interception  is  get¬ 
ting  cheaper  every  day. 

•  Telephone  company  officials,  FBI  agents,  and  credit 
card  companies  go  on  “fishing  expeditions”  in  local  mi¬ 
crocomputer-based  message  systems.  Ostensibly,  they 
are  looking  for  exchanges  of  “illegal”  data,  such  as  com¬ 
puter  access  numbers  and  passwords,  phone  credit  card 
numbers,  and  so  on.  Who  knows  what  other  subversive 
material  these  official  intruders  might  turn  up? 

•  Although  laws  surrounding  the  tapping  of  voice  tele¬ 
phone  calls  are  stringent  when  they  deal  with  what  may 
and  may  not  be  used  as  trial  evidence  or  when  warrants 
are  required  and  when  they  are  not,  no  such  laws  cover 
eavesdropping  on  private  data  transmissions.  With  large 


computers  doing  the  listening  and  sifting,  it  is  a  simple 

matter  to  monitor  thousands  of  data  phone  calls. 

Meanwhile,  in  the  commercial  sector,  cellular  mobile 
telephone  systems  are  being  marketed  heavily  in  major  met¬ 
ropolitan  areas  across  the  country.  However,  cellular  chan¬ 
nels  can  be  monitored  with  easily  built  “scanners”  similar  to 
those  used  to  monitor  police  and  CB  channels. 

Thus,  as  the  cellular  service  suppliers  themselves  ad¬ 
mit,  those  top  level  business  calls  we  make  from  the  mid¬ 
dle  of  traffic  jams  may  not  be  private.  The  service  suppli¬ 
ers  downplay  this  aspect  of  cellular  mobile 
telecommunications.  After  all,  there  are  666  assigned 
channels,  and  the  channel  assignment  for  a  particular  call 
does  shift  as  the  user  moves  from  cell  to  cell.  But  even  if  it 
isn’t  technically  feasible  to  build  a  scanner  capable  of  fol¬ 
lowing  a  particular  call,  you  probably  shouldn’t  conduct 
sensitive  business  by  cellular  phone  anyway,  according  to 
the  suppliers.  So  much  for  productivity  in  traffic  jams. 

A  few  companies  already  are  planning  to  connect  mo¬ 
dems  and  micros  to  cellular  telephones.  Get  your  elec¬ 
tronic  mail  from  anywhere,  anytime.  There  is  no  reason  to 
think  this  evolution  will  stop  until  someone  makes  a  de¬ 
vice  that  is  a  cellular  phone,  voice  and  data  terminal,  and 
microcomputer,  all  in  one  portable  package.  The  tempta¬ 
tion  to  use  such  a  device  will  grow  right  along  with  the 
temptation  to  listen  in. 

“Freedom  from  eavesdropping”  is  more  apt  as  a  rally¬ 
ing  cry  than  “right  to  privacy”  because  certain  forms  of 
eavesdropping  do  not  intrude  directly  on  our  privacy.  For 
example,  others  can  gather  and  analyze  information 
about  your  phone  calls  even  if  they  do  not  monitor  direct¬ 
ly  the  content  of  the  calls  themselves.  Within  many  corpo¬ 
rate  settings,  this  already  is  being  done. 

Information  about  whom  you  call,  when  you  call,  the 
duration  of  the  call,  and  the  frequency  of  calls  to  particular 
numbers  all  can  be  collected  without  your  knowledge.  To 
some  extent,  the  same  laws  covering  voice  phone  tapping 
hedge  the  ability  of  police  and  government  to  use  such 
information.  However,  no  such  laws  cover  intraorganiza- 
tional  phone  monitoring.  Monitoring  systems  are  marketed 
under  the  rubric  of  “phone  management”  and  “automatic 
call  distribution  and  reporting  systems.” 

One  company  advertisement  for  this  kind  of  system  is  a 
touch  Orwellian  in  its  copy:  “To  check  out  your  staff’s 
performance,  you  can  either  walk  around  the  office.  Or 
get  one  of  [our]  automatic  call  distribution  systems.  They 
actually  print  out  detailed  reports  on  every  single  person’s 
hourly  performance:  Who  needs  a  raise.  And  who  needs 
improvement.” 

Freedom  to  Play  vs.  the  Drive  for  Security 

Given  the  complexity  of  our  society,  we  are  far  more  like¬ 
ly  to  lose  our  valued  freedoms  bit  by  bit  than  all  at  once. 
Freedoms  also  are  more  likely  to  erode  faster  as  a  result  of 
nongovernmental  institutional  decisions. 

As  technicians,  software  designers,  hardware  design¬ 
ers,  and  computer  users,  we  have  as  much  to  lose  through 
the  misapplication  of  technology  as  anyone  else.  We  are 


Dr.  Dobb’s  Journal,  June  1985 


53 

453 


also,  perhaps,  in  a  better  position  to  prevent  misapplica¬ 
tions  than  anyone  else. 

There  is  much  attention  paid  these  days  to  the  ubiquitous 
“hacker,”  whoever  that  may  be.  The  hacker  is  linked  in  the 
public  mind  with  computer  crime.  This  was  not  always  so, 
although  the  reasons  for  this  association  are  many.  Yet, 
whether  one  thinks  of  oneself  as  a  hacker  or  not,  the  solu¬ 
tions  to  the  problems  of  computer  crime  don’t  seem  to  lie  in 
the  direction  of  hiring  more  consultants.  Nor  does  it  make 
much  sense  to  try  to  rehabilitate  the  image  of  the  hacker  by 
making  him  or  her  into  some  sort  of  cultural  hero(ine). 

Along  with  the  hacker  image,  the  public  is  being  sold  a 
bill  of  goods  concerning  computer  crime.  Such  crime,  by 
and  large,  is  portrayed  as  something  an  individual  does  to 
an  organization,  using  a  microcomputer.  The  public  sees 
computer  crime  as  “breaking  and  entering”  large  sys¬ 
tems,  whether  to  take  money  gains,  information  gains,  or 
both.  But  other  kinds  of  computer  abuse  are  not  labelled 
as  “crimes”  or  “criminal”  and,  for  the  most  part,  are  not 
even  mentioned  in  the  mass  media  that  shape  public  con¬ 
sciousness  on  such  matters.  The  daily  papers  do  not  dis¬ 
cuss  the  idea  that  data  eavesdropping  by  government,  cor¬ 
porate,  or  even  private  individuals  on  private  citizens  may 
also  be  criminal.  Nor  does  the  idea  that  the  IRS  or  NSA 
may  be  tapping  the  communications  and  computers  of 
private  individuals  receive  much  consideration.  The  laws 
that  are  discussed  and  passed  are  laws  aimed  at  individual 
abuse,  not  large-scale  institutionalized  abuse. 

Often,  the  quest  for  security  tramples  our  rights  to  priva¬ 
cy  and  individuality.  In  the  drive  for  absolute  security  in  an 
absolutely  efficient  society,  we  may  lose  the  one  thing  we 
need  in  order  to  survive  and  prosper  in  the  global  informa¬ 
tion  economy:  the  freedom  to  play  and  its  attendant 
creativity. 

No  matter  what  the  end  product,  creative  (and  there¬ 
fore  innovative)  ideas  are  nurtured  in  an  atmosphere  of 
play.  The  balance  between  control  and  freedom  must  be 
struck  in  actual  design  decisions  we  make  from  day  to 
day.  The  impact  of  one  implementation  vs.  another  on  our 
overall  freedoms  must  be  part  of  design  decisions. 

High  on  the  list  of  needed  design  developments  in  an 
era  of  mass  telecommunications  is  some  sort  of  public  key 
encryption  and  signature  system  that  is  mass-producible 
and  inexpensive  and  secure  from  any  kind  of  eavesdrop¬ 
ping.  The  jury  still  seems  to  be  out  on  whether  or  not  the 
DES  (Data  Encryption  Standard)  is  really  secure  from 
megacomputer  cracking  power.  This  is  an  area  where 
some  effective  designs  could  improve  all  our  on-line  lives, 
but  it  is  only  one  example. 

The  Future  of  Paranoia 

Future  problems  in  telecommunications  may  make  to¬ 
day’s  problems  seem  relatively  trivial  by  comparison.  As 
expert  systems  take  up  residence  in  our  telecommunica¬ 
tions  networks,  designers  will  have  to  become  even  more 
concerned  with  individual  freedoms — constitutional  and 
civil  liberties — than  they  are  now. 

For  example,  it  is  feasible  to  expect  that  various  infor¬ 
mation  suppliers,  data  banks,  and  mass  information  utili- 

54 


ties  will  be  able  to  learn  about  users  in  sophisticated  ways: 
what  your  interests  are,  what  items  to  call  to  your  attention 
based  on  your  past  use  of  the  system,  what  you  read  and 
what  you  ignore,  what  kind  of  equipment  you  have,  your 
age,  marital  status,  and  economic  position,  and  so  forth. 

It  would  be  nice  to  interact  with  a  system  that  appears 
custom  tailored  to  your  needs  and  use.  However,  it  is  not  a 
big  leap  to  go  from  information  about  how  you’ve  inter¬ 
acted  with,  say,  a  “smart  Source”  or  “intelligent  Compu¬ 
Serve”  to  a  psychological  and/or  political  profile.  This 
sort  of  information  analysis  is  good  not  only  for  marketers 
who  want  to  sell  you  things  but  for  politicians,  power 
junkies,  and  competitors  who  want  to  know  all  they  can 
about  you  for  reasons  of  their  own. 

Approaches  to  Action 

People  directly  involved  in  making  technical  decisions  for 
design  or  implementation  are,  as  I  said  earlier,  in  a  good 
position  to  act  as  ad  hoc  advisers  to  the  general  public,  the 
media,  and  lawmakers  at  local,  state,  and  federal  levels. 
Technical  people,  along  with  artists,  can  act  as  “antennae 
for  the  race”  in  matters  of  sociotechnical  importance.  If 
you  are  involved  in  the  early  design  stages  of  an  expert 
software  system  to  correlate  IRS  data  with  SSI  informa¬ 
tion,  to  take  one  hypothetical  example,  you’ll  know  before 
the  rest  of  us  do  how  that  system  can  be  abused  and  put  to 
uses  never  intended  in  the  first  place. 

We’ve  all  heard  that  the  price  of  liberty  is  eternal  vigi¬ 
lance.  But  just  how  one  remains  vigilant  in  an  age  of  rapid 
technological  change  is  not  so  obvious.  Responses  to  up¬ 
coming  issues  can  take  a  variety  of  forms,  whether  indi¬ 
vidual,  institutional,  or  governmental.  It  has  become  an 
article  of  faith  for  me,  having  studied  and  worked  in  this 
arena  for  several  years,  that  individual  responses  are  pref¬ 
erable  to  institutional  ones  and  institutional  ones  prefera¬ 
ble  to  governmental  intervention.  However,  we  will  need 
all  these  approaches. 

Individual  Responses 

Individual  technicians  and  designers  play  a  large  role  in 
the  creation  of  technical  standards  and  on  standards  bod¬ 
ies.  In  telecommunications,  for  instance,  global  standards 
for  videotext,  viewdata,  and  teletext  systems  have  yet  to 
emerge.  Standards  can  affect  the  kinds  of  competition 
possible  and  hence  the  marketplace.  It  is  not  unheard  of 
for  standards  to  be  adopted,  not  on  their  merit,  but  for 
overtly  political  reasons. 

More  individual  participation  in  the  creation  of  new 
standards,  therefore,  is  one  way  in  which  technicians  and 
designers  can  respond. 

Educating  our  friends  and  neighbors  who  are  not  tech¬ 
nically  inclined  is  another  way  to  respond  to  the  changes 
raining  down.  Explicit  information  on  the  connections  be¬ 
tween  technical  possibilities  and  quality  of  life  is  needed 
on  a  wider  scale  than  ever  before. 

Organizational  Responses 

When  biotechnology  began  to  hint  that  doctors  and  bio¬ 
logical  technicians  could  have  a  profound  influence  on  the 

Dr.  Dobb’s  Journal.  June  1985 


454 


direction  and  quality  of  human  life,  medical  schools  and 
other  establishments  of  higher  learning  began  to  teach 
formal  courses  in  medical  and  biological  ethics. 

Today,  when  computer  technology  has  had — is  hav¬ 
ing — an  equally  profound  effect  on  our  quality  of  life, 
almost  none  of  the  establishments  responsible  for  techni¬ 
cal  education  are  providing  courses  equivalent  to  those 
taken  by  medical  students.  A  few  formal  studies  of  “tech¬ 
nical  ethics”  and  “information  age  ethics”  are  being  con¬ 
ducted,  but  these  are  one-shot  projects,  not  long-term 
educational  efforts.  Therefore,  one  possible  institutional 
response  to  information  age  issues  is  to  provide  courses 
that  combine  the  study  of  ethics  and  morals  with  the 
study  of  information  science  and  the  future  of 
telecommunications. 

Legal  Responses 

It’s  important  that,  in  the  discussion  of  what  is  and  is  not 
“computer  crime,”  we  not  let  high-priced  “security  con¬ 
sultants”  or  politicians  with  their  own  axes  to  grind  make 
our  laws  for  us. 

It  is  especially  important  that  we  not  let  the  rare  occur¬ 
ence  of  a  “hacker  break-in”  distract  us  from  the  larger 
and  potentially  more  serious  issues  at  hand.  The  Privacy 
Act  and  the  Freedom  of  Information  Act,  for  example, 
have  suffered  consistent  erosion  over  the  last  10  years  by 
administrative  decree  and  bureaucratic  red  tape. 

As  more  government  information  is  put  on  line  and 
made  accessible  bv  computer,  no  one  is  talking  about  our 


rights  to  access  information  that  is  not  private  and  that 
was  collected  legally  at  taxpayers’  expense.  Census  data 
and  Library  of  Congress  information  come  to  mind.  (You 
can  get  census  data  now,  certainly,  if  you  have  certain 
kinds  of  computers  and  are  willing  to  pay  a  lot  for  it.)  The 
government  is  probably  the  largest  single  source  of  useful 
information  in  this  country.  Making  that  information 
available  to  the  micro-using  public  is  an  issue  worthy  of 
Congressional  attention. 

Last  Words 

I  don’t  have  any.  I  am  willing  to  bet  that  some  of  the 
people  who  read  this  piece  are  engaged,  even  now,  in  de¬ 
bates  on  design  that  will  come  to  wider  attention  only  after 
a  particular  piece  of  outrageous  hardware  or  software  has 
been  manufactured  and  distributed.  Even  now,  you  proba¬ 
bly  can  think  of  a  dozen  or  so  issues  I’ve  not  been  able  to 
cover  here  because  of  my  own  ignorance  or  lack  of  space. 

In  this  business  of  telecommunications,  there  are  no 
last  words.  Therefore,  for  all  our  sakes,  I  urge  you  to  add 
yours  to  the  heap. 

Dean  Gengle  is  a  software  documentation  specialist  and 
consultant  at  CommuniTree  Group  in  San  Francisco.  Fie 
is  the  author  of  The  Netweaver’s  Sourcebook.  ddj 


Dr.  Dobb's  Journal,  June  1985 


55 

455 


Modems:  2400  Bit/Sec 
and  Beyond 


by  Dale  Walsh 


Electronic  data  communication,  in 
its  simplest  form,  employs  bina¬ 
ry  signaling  common  in  logic  and 
microprocessor  circuits.  A  “high” 
signal  is  one,  and  a  “low”  signal  is 
zero.  We  have  seen  dramatic  in¬ 
creases  in  how  fast  electronic  logic 
devices  can  switch  binary  logic  levels. 
Each  new  computer,  it  seems,  is  sev¬ 
eral  times  faster  than  its  predecessor. 
This  is  achieved  primarily  by  reduc¬ 
ing  large-scale  integration  (LSI)  de¬ 
vices  in  size  to  increase  device  speeds. 
We  in  the  communications  field 
would  say  “they  kept  binary  signal¬ 
ing  but  increased  signal  bandwidth.” 


(2)  construct  densely  packed  signal¬ 
ing  alphabets  so  that  each  signaling 
interval  uniquely  specifies  more  than 
one  data  bit. 


Available  Bandwidth 

The  available  telephone  channel 
bandwidth  is  approximately  200-3200 
Hz.  Lower  speed  modems  carve  out 
narrow  slices  near  the  center  where 
channel  distortions  are  at  a  minimum. 
But  as  you  move  out  from  the  middle, 
things  get  sticky.  Band-edge  charac¬ 
teristics  vary  widely  from  call  to  call 
and,  near  the  limits,  are  extremely 
hostile  to  data  signals.  Modem  evolu- 


Phone 

Channel 


103  Answer  \ 


103  Originate 


212  Originate 


212  Answer 


SIGNAL  BANDWIDTHS 
Figure 


We  usually  associate  bandwidth 
with  frequency  limits:  our  hearing 
bandwidth  is  from  20  Hz  to  12,000 
Hz,  for  example.  But  bandwidth  also 
limits  how  fast  things  can  move.  Run 
in  water  and  you  appreciate  that  wa¬ 
ter  is  a  narrow  bandwidth  medium,  at 
least  for  physical  movement. 

In  their  efforts  to  keep  pace  with 
the  demand  for  higher  speeds,  mo¬ 
dem  designers  immediately  collided 
with  a  limited  resource:  by  and  large, 
signal  bandwidth,  set  by  the  tele¬ 
phone  channel,  is  fixed.  Two  ap¬ 
proaches  will  increase  speed:  ( 1 )  use 
more  of  the  available  bandwidth,  and 


Dale  M.  Walsh,  U.S.  Robotics  Inc., 
477  East  Butterfield  Rd.,  Lombard, 
IL  60148. 


tion,  as  a  result,  favored  development 
of  complex  signal  alphabets  before  the 
final  assault  on  unused  bandwidth. 

The  Bell  103  and  other  dial-up,  full 
duplex  modems  belong  to  a  class 
called  Frequency  Division  Multiplex¬ 
ing  (FDM)  modems.  They  send  and 
receive  simultaneously  using  two 
non-overlapping  frequency  bands 
much  like  two  radio  stations  tuned  at 
different  frequencies.  By  protocol, 
the  calling  modem  uses  the  lower  fre¬ 
quency  band;  hence  the  terms  “Origi¬ 
nate”  and  “Answer”  bands.  The  fig¬ 
ure  (page  57)  shows  these  bands  for 
the  Bell  103  and  Bell  212  modems. 

Signal  States 

“One  if  by  land,  two  if  by  sea,”  and 
Paul  Revere  was  on  his  way.  The 
flash  of  light  was  a  data  symbol,  and 
one  bit  per  data  symbol  did  the  job. 


56 


Dr.  Dobb's  Journal,  June  1985 


456 


The  300  bit/sec  103  modem  uses  a 
similar  scheme:  one  tone  for  a  one 
and  a  different  tone  for  zero.  It’s  a 
fairly  simple  scheme  and  easily  im¬ 
plemented  with  today’s  technology. 
The  Originate  and  Answer  bands 
each  use  about  300  Hz  of  the  roughly 
3,000  Hz  bandwidth  that  is  available. 

To  quadruple  data  rates  to  1200 
bit/sec,  modem  designers  made  three 
changes.  First,  they  changed  the  ba¬ 
sic  signaling  scheme  from  frequency 
modulation  (as  in  FM  radio)  to  phase 
modulation;  the  latter  more  efficient¬ 
ly  uses  available  bandwidth.  Second, 
they  increased  the  signaling  alphabet 
from  two  states  (0  and  1)  to  four 
states  (0,  1,  2,  3);  hence,  each  data 
symbol  uniquely  signals  two  bits.  Fi¬ 
nally,  they  doubled  the  baud  rate  (the 
rate  at  which  data  symbols  are  sent 
from  transmitter  to  receiver)  from 
300  to  600;  this  boosted  the  Originate 
and  Answer  bandwidths  to  approxi¬ 
mately  800  Hz  each. 

These  changes  significantly  in¬ 
creased  modem  complexity.  But  LSI 
technology  rode  to  the  rescue.  Faster 
microprocessors  handled  the  modula¬ 
tion  and  demodulation  tasks.  More 
importantly,  the  complicated  212  an¬ 
alog  filters  that  separate  high  and  low 
(Answer  and  Originate)  bands  were 
reduced  to  single  LSI  chips. 

2400 

The  move  from  1200  to  2400  bit/sec 
distinctly  ups  the  ante.1  Designers  de¬ 
cided  not  to  increase  signal  band¬ 
width  further,  primarily  so  that  they 
could  use  2 1 2  filter  chips  in  2400  bit/ 
sec  modems  and  so  that  2400  mo¬ 
dems  could  conveniently  fall  back  to 
a  212  mode. 

Instead,  they  increased  speed  by 
changing  the  signaling  scheme  from 
phase  modulation  to  quadrature  am¬ 
plitude  modulation  (QAM).  In  QAM, 
you  can  increase  the  signal  alphabet 
by  varying  both  signal  phase  and  am¬ 
plitude.  A  2400  bit/sec  modem  sends 
16  phase-amplitude  combinations. 
Each  combination  corresponds  to  four 
data  bits.  Therefore,  the  modem  baud 
rate  between  transmitter  and  receiver 
is  the  same  for  both  212  and  2400,  but 
each  2 1 2  data  symbol  carries  two  data 
bits  while  each  2400  modem  data 
symbol  carries  four  data  bits. 


Performance 

What  does  this  cost?  First,  design 
complexity  quadruples.  The  design  is 
more  exacting  and  permits  fewer 
simplifying  short  cuts. 

Second,  an  inherent  performance 
loss  is  associated  with  using  16  differ¬ 
ent  signals  rather  than  four.  Imagine 
a  signal  system  that  flashes  one  of 
four  colors,  each  color  representing 
two  bits.  To  convert  to  16  colors 
would  degrade  the  system’s  ability  to 
distinguish  colors,  especially  when 
they  are  surrounded  by  other  inter¬ 
fering  color  flashes. 

However,  the  performance  differ¬ 
ence  between  2 1 2  and  2400  modems 
is  not  as  great  as  one  might  expect. 
The  2400  modems  use  adaptive 
equalizers  that  “learn”  telephone 
channel  characteristics,  compensat¬ 
ing  for  call-to-call  variations.  An 
adaptive  equalizer  is  a  little  like  an 
auto-focusing  camera  device  that 
also  balances  the  color  spectrum  and 
corrects  lens  imperfections.  Because 
a  sharply  focused  receiver  signal  is 
more  noise  tolerant,  the  2400  receiver 
can  achieve  80-90  percent  of  its  po¬ 
tential  noise  immunity  on  every  call. 
Most  212  designs  use  a  compromise 
equalizer  (“one  size  fits  all”)  ap¬ 
proach  that  sacrifices  performance 
for  design  simplicity.  This  difference 
significantly  narrows  the  perfor¬ 
mance  gap  between  the  two  modems. 

But,  more  to  the  point,  how  well 
the  2400  modem  works  depends  on 
its  performance  potential  compared 
to  the  performance  limits  imposed  by 
the  U.S.  telephone  network.  After 
reading  a  recent  survey  of  signal  lev¬ 
els,  noise,  and  distortions  on  the  U.S. 
network,  one  can  conclude  that  well- 
designed  2400  modems  will  perform 
successfully  on  a  very  high  percent¬ 
age  of  such  connections.  Indeed,  this 
is  borne  out  by  units  already  in  the 
field:  a  limited  test  using  a  U.S.  Ro¬ 
botics  Courier  2400  modem  on  long 
distance  connections  yielded  95  per¬ 
cent  successful  calls. 

Higher  Speeds 

Doubling  speed  from  2400  to  4800  is 
not  as  straightforward  as  going  from 
1 200  to  2400  because  2400  bit /sec  is 
the  maximum  practical  speed  for 
FDM  dial-up  modems  (like  the  Couri¬ 


er  2400).  There  are  two  barriers. 
First,  network  changes  will  make  ex¬ 
tending  the  signal  alphabet  beyond  16 
states  highly  questionable.  Second, 
the  remaining  option  (to  use  more  of 
the  available  signal  bandwidth)  chews 
up  more  bandwidth  than  is  available! 
At  4800  and  above,  the  Answer  and 
Originate  bands  overlap,  and  design 
complexity  escalates  dramatically. 

It  is  possible  to  mix  Transmit  and 
Receive  signals  in  the  same  frequency 
band.  Each  end  must  ignore  its  own 
transmitter  and  pick  up  the  remote 
transmitter.  This  is  called  echo  can¬ 
cellation.  Echo  cancellation  and  adap¬ 
tive  equalization  (mentioned  above) 
are  similar  in  that  they  both  “learn” 
line  conditions  and  adaptively  com¬ 
pensate.  But  echo  cancellation  re¬ 
quires  more  computations  with  about 
twice  as  many  bits  of  arithmetic.  Sev¬ 
eral  European  modems  successfully 
use  echo  cancellation,  but  they  are 
fairly  expensive  compared  to  2400 
bit/sec  modems. 

CCITT  recommendation  V.32  de¬ 
fines  a  new  dial-up  9600  bit/sec  mo¬ 
dem  that  includes  a  4800  bit/sec 
mode.  The  Originate  and  Answer 
bands  completely  overlap,  and  each 
occupies  90  percent  of  the  available 
bandwidth.  Originally,  the  modem 
was  conceived  for  local  access  to  digi¬ 
tal  data  networks.  However,  added 
features  now  allow  long  haul  opera¬ 
tion  on  international  circuits  and  in 
large  countries  like  the  U.S. 

Recommendation  V.32  is  very  new 
and  subject  to  change  as  the  first  mod¬ 
els  are  introduced.  Measured  by  com¬ 
putations  per  second  and  bits  of  reso¬ 
lution,  these  modems  are  roughly  64 
times  more  complex  than  2400  bit/sec 
modems.  For  this  reason,  it  will  be 
quite  some  time  before  V.32  modems 
can  compete  in  the  high-volume  mo¬ 
dem  market. 

Notes 

1  The  discussion  here  concerns  dial¬ 
up,  full  duplex  modems  in  the  103 
and  212  class.  Leased-line  modems 
typically  operate  4,800-14,400  bit/ 
sec. 

DDJ 


Dr.  Dobb's  Journal,  June  1985 


57 

457 


C  UART  Controller 


by  Don  Gay 


This  article  describes  a  program 
that  allows  easy  modification 
of  the  parameters  of  a  pro¬ 
grammable  USART.  The  program, 
written  in  C,  is  readily  adapted  to  a 
number  of  different  UART  chips. 
This  particular  program  is  for  the 
Signetics/National  Semiconductor 
2651  USART. 

Recently,  I  constructed  a  serial 
port  board  to  let  me  use  a  modem  on 
my  microcomputer.  After  looking  at 
some  of  the  designs  in  Interfacing  to 
S-1 00/IEEE  696  Microcomputers' 
and  checking  the  local  availability  of 
UART  chips,  I  decided  to  implement 
the  design  on  page  183  of  the  book, 


to  systems  that  have  an  autoexecute 
on  boot:  anyone  can  boot  up  in  what¬ 
ever  mode  is  required  without  modi¬ 
fying  the  BIOS. 

An  examination  of  the  listing  re¬ 
veals  four  command  line  switches. 
These  allow  the  user  to  change  the 
baud  rate  (-b),  number  of  stop  bits 
(-s),  parity  (-p),  and  word  length 
(-1 ).  All  invalid  switches  are  ignored, 
as  are  values  out  of  range  for  any  of 
the  parameters.  After  all  command 
line  switches  have  been  effected,  the 
program  shows  the  values  of  all  the 
port  parameters.  Trying  to  execute 
the  program  with  no  switches  on  the 
command  line  produces  a  “Usage” 


A  tasty  little  morsel  from  Wahroonga. 


which  makes  use  of  the  265 1  USART. 

This  USART  is  fully  programma¬ 
ble  with  respect  to  baud  rate,  stop 
bits,  parity,  word  length,  and  so  on, 
and  changes  to  my  BIOS  would  pro¬ 
gram  the  USART  at  boot  time.  How¬ 
ever,  I  needed  more  flexibility.  When 
transferring  files  to  an  adjacent  ma¬ 
chine,  I  would  have  appreciated  a 
simple  way  to  change  the  USART  pa¬ 
rameters,  namely,  the  baud  rate,  re¬ 
setting  it  for  higher  rates. 

SETP.C 

The  Listing  on  page  62  shows  the  re¬ 
sults  of  my  attempt  at  solving  this 
problem.  The  program  is  called  with 
a  number  of  command  line  switches, 
which  can  be  used  to  modify  the  port 
parameters.  I  have  designed  the  pro¬ 
gram  to  run  entirely  from  the  com¬ 
mand  line  to  make  it  easily  adaptable 


Don  Gay,  136  Fox  Valley  Road,  Wah¬ 
roonga,  2076,  N.S.  W.,  Australia. 


description,  followed  by  the  current 
port  parameters.  Here  is  an  example 
of  how  the  program  typically  is 
called: 

A>  setp -bl 200 -si  -pn-18 

This  would  configure  the  USART  to 
transmit/receive  at  1200  baud,  1  stop 
bit,  no  parity,  and  a  character  length 
of  8  bits. 

USART  Programming  Details 

A  2651  USART  uses  four  consecutive 
I/O  ports: 

(1)  Transmit/Receive  holding  register 

(2)  Status  register 

(3)  Mode  register  1  and  2 

(4)  Command  register 

The  USART  parameters  are  con¬ 
trolled  by  writing  to  the  mode  regis¬ 
ters.  By  reading  the  values  in  the 
mode  registers,  you  can  determine 
the  current  parameters  of  the 
USART. 

There  are  actually  two  mode  regis- 


60 


Dr.  Dobb's  Journal,  June  1985 


ters  at  the  same  I/O  address,  each 
accessed  by  a  consecutive  read  or 
write.  Read  from  the  mode  register 
once  and  you  will  get  the  contents  of 
one  of  the  mode  registers;  read  from 
it  again  and  you  will  get  the  contents 
of  the  other  mode  register.  A  similar 
situation  exists  for  writing.  After  a 
reset,  the  first  read/ write  accesses 
the  first  mode  register.  As  long  as 
reading  and  writing  are  done  in  pairs, 
you  will  always  know  with  which  reg¬ 
ister  you  are  dealing. 

The  command  register  controls  the 
level  of  *RTS  and  *DTR  lines,  as  well 
as  enabling  and  disabling  transmit/ 
receive.  Because  the  values  written  to 
the  command  register  need  not 
change  once  the  port  has  been  initial¬ 
ized,  the  program  does  not  allow  al¬ 
teration  of  the  command  register 
from  the  command  line  switches. 
These  switches  alter  only  the  con¬ 
tents  of  the  mode  registers. 

As  is  usually  the  case  with  pro¬ 
grammable  peripheral  interfaces, 
each  bit  has  a  special  significance  in 
the  controlling  mode  registers.  Fig¬ 
ures  1  and  2  (at  right)  show  the  im¬ 
portance  of  each  bit  in  the  mode  reg¬ 
isters  of  the  2651. 

The  USART  is  configured  in  this 
program  in  asynchronous  mode  with 
internal  receive  and  transmit  clocks. 
This  is  probably  the  most  common 
mode  used  with  ordinary  devices, 
such  as  modems  and  terminals,  so 
these  characteristics  are  “hard  cod¬ 
ed”  in  the  program.  Refer  to  the  265 1 
documentation2  to  find  out  other 
ways  to  operate  the  port. 

References 

1  Sol  Libes  and  Mark  Garetz.  Inter¬ 
facing  to  S-IOO/IEEE  696  Micro¬ 
computers.  California:  Osborne/ 
McGraw-Hill,  1981. 

2  National  Semiconductor  Data 
Sheet.  “INS2651  Programmable 
Communications  Interface.”  Octo¬ 
ber  1980. 


DD) 

(Listing  begins  on  next  page) 


MI-7 

MI-6 

MI-5 

Ml -4 

MI-3 

MI-2 

MI-1 

M1-0 

\ 

A  A  A . 

A 

_ / 

A.  B. 

C. 

D.  E. 

Stop  Bits  Parity 

Parity 

Word  Length  Mode  and  Baud 

Type 

Control 

Rate  Factor 

A.  Stop  Bits 

00  =  Invalid  code. 

01  =  1  stop  bit. 

10  =  1.5  stop  bits. 

11=2  stop  bits. 

B.  Parity  Type 

0  =  Odd. 

1  =  Even. 

C.  Parity  Control 

0  =  Disabled. 

1  =  Enabled. 

D.  Word  Length 

00  =  5  bits. 

01=6  bits. 

10  =  7  bits. 

11=8  bits. 

E.  Mode  and  Baud  Rate  Factor  00  =  Synchronous  mode  (not  used). 

01  =  Synchronous  mode  (not  used). 

10  =  Asynchronous  mode  16x  rate. 

11  =  Asynchronous  mode  64x  rate. 

Figure  1 

Mode  Register  1  Format 


M2-7 

M2-6 

M2-5 

M2-4 

M2-3 

M2-2 

M2-1 

M2-0 

\ _ A _ A _ A _  7 

A 

B. 

C. 

D. 

Not  Used  Transmit  Receive  Baud  Rate  Selection 

Clock  Clock 


A. 

B. 

C. 

D. 


Not  Used 

Transmit  Clock  0  =  External  clock 
1  =  Internal  clock 


Receive  Clock  0  =  External  clock 
1  =  Internal  clock 

Baud  Rate  Selection  (only  for  async  1 6x  mode) 


0000  =  50  baud 
0001  =  75  baud 
0010  =  110  baud 
0011  =  134.5  baud 
0100  =  150  baud 
0101  =  300  baud 
0110  =  600  baud 
0111  =  1200  baud 


1000  =  1800  baud 

1001  -  2000  baud 

1010  =  2400  baud 

1011  =  3600  baud 

1100  =  4800  baud 

1101  =  7200  baud 

1110  =  9600  baud 

1111  =  19200  baud 


Reader  Ballot 

Vote  for  your  favorite  feature/article. 
Circle  Reader  Service  No.  1 95. 


Figure  2 

Mode  Register  2  Format 


Dr.  Dobb’s  Journal,  June  1985 


61 

459 


C  UART  Controller  Listing  (Text  begins  on  page  60) 


/ttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt/ 


/*  %/ 

/t  Serial  Port  Con-figuration  Program  <2651  PCI)  */ 
/*  */ 
/*  Source  filename  :  SETP.C  */ 
/*  Program  Language  :  C/80  3.0  (Software  Toolworks)  t/ 
/t  Program  Author  :  Don  Gay  %/ 
/t  Last  Revision  :  June  11,  1984  %/ 
/*  */ 


/ ttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt / 


•include  printf.h  /%  Header  for  formatted  output  */ 

•define  REV  DATE  •■11/06/84" 


•define 

POR T_ BASE 

0 

/* 

2651  PCI  base  address 

*/ 

•def i ne 

M0DE_REG 

P0RT_BASE+2 

/* 

Mode  register 

*/ 

•define 

CMND_REG 

PORT_BASE+3 

/% 

Command  register 

*/ 

•def i ne 

DISABLE 

0x32 

/* 

Disable  UART  reev/xmit 

*/ 

/* 

Force  *DTR  and  *RTS  low 

*/ 

•def i ne 

ENABLE 

0x37 

/* 

Enable  UART  reev/xmit 

*/ 

/*  Store  legal  values  for  all  paramters  */ 

char  tbaudr C 3  =  €"50" , "75" , " 1 10" , " 134. 5" , " 150" , "300" , "600"  , 

" 1 200 “ , " 1 800 " , " 2000 “ , " 2400 " , " 3600 " , " 4800 " , 

" 7200 " , " 9600 " , " 1 9200 " > , 
tsbitsCT  =  € ”0" , " 1 " , " 1 . 5" , "2" } , 
tparitCT  =  € "N" , "0" , "N" , "E" > , 
twordl C 3  =  €"5", "6", "7", "8">; 

int  baud_rate,  stop_bits,  parity,  word_length; 

main  large , argv) 
int  arge; 
char  largvtl; 

€ 

printf  ("\nSerial  port  configuration  program  as  of  7.s\n" ,  REV_DATE)  ; 
get_prm();  /*  get  current  port  parameters  %/ 

if  (argc>l)  /*  process  the  command  line  */ 

while  large —  >  1) 

process (argvCargc  3 ) ; 

else  /t  Give  some  help  %/ 

showusage 1 ) ; 

show_prm 1 ) ;  /*  Display  parameters  */ 

set_prm 1 ) j  /*  Re-program  PCI  */ 

> 

process (arg)  /*  Try  to  match  the  command  line  */ 

char  targ;  /*  argument  with  a  valid  switch  */ 

<  /%  If  valid,  set  up  new  value  %/ 

int  n; 

i f  1  targ  ==  ' — ’ )  € 

switch! toupper 1 *++arg ) )  < 

case  ’ B’ :  n=match (++arg , baudr , 16) ; 

if  In  >=0)  baud_r ate=n ; 

else  error ("baud  rate  value", arg); 

break; 

case  ’S’:  n=match (++arg, sbits, 4) ; 

if  (n>=0)  stop_bits=n; 

else  error ("stop  bits  value", arg); 

break ; 

case  ’P’:  n=match(++arg,parit,4) ; 


Dr.  Dobb’s  Journal,  June  1985 


4cn 


C  UART  Controller  Listing  (Listing  continued,  text  begins  on  page  60) 


if  (n>=0)  parity=n; 

else  error ("parity  val ue" , arg > ; 

break ; 

case  ’L':  n=match (++arg, wordl ,4) ; 

if  (n>=0)  word_length=n; 

else  error ("word  length  value" , arg) ; 

break ; 

default  :  error ( "swi tch ", arg) ;  break; 

} 

> 

el  se 

error ( "swi tch " , arg ) ; 

> 

match (value, table, size) 
char  (value, 

•tabled; 
int  size; 

C 

int  ndx; 


/%  Check  if  switch  value  is  valid  */ 
/*  Command  line  value  */ 

/%  Table  of  valid  arguments  X/ 

/X  No.  of  entries  in  table  X/ 


ndx=0; 

while  (  (ndx<size)  ScSc  (strcmp  (val  ue,  tabl  eCndx  1 )  !=  0)) 

ndx++; 

return (  ndx<size  ?  ndx  :  —1  ) ; /•  Return  —1  if  not  found  X/ 


get_prm()  /X  Read  mode  ports  to  determine  X/ 

<  /X  current  port  parameters  */ 

int  model,mode2; 


> 


model  =  i nput (MODE_REG) ; /*  get  mode  register  1  X/ 
mode2  =  input (MODE_REG) ; /X  get  mode  register  2  X/ 


baud_rate 
stop_bi ts 
word_l ength 
parity 


getbi ts (mode2, 3, 4) ;  /X  isolate  bit  fields  X/ 
getbi ts (model ,7,2) ; 
getbi ts (model ,3,2) ; 
getbi ts (model ,5,2)  ; 


getbi ts (x , p , n)  /X  get  n  bits  from  position  p  */ 

unsigned  x,  p,  n;  /X  Ref:  KScR  pg  45  X/ 

< 

return  (  (x  >>  (p+l-n))  Sc  "-(~0  <<  n)  )  ; 

> 


set_prm()  /X  Re-program  PCI  with  new  parmameters  */ 

< 

int  model,  mode2; 


model  =  0x02  ! 

/X 

set  asynch.  16x  mode 

*/ 

(  stop_bits 

<< 

6) 

1 

/X 

merge  parameters 

X/ 

(  (parity 

<< 

4) 

Sc 

0x30)  : 

(  (word_l ength 

<< 

2) 

Sc 

0x  0c 

:  >; 

mode2  =  0x30  ! 

/X 

set  internal  clock 

X/ 

baud  rate; 

1 

/X 

merge  baud  rate 

X/ 

output (CMND_REG,  DISABLE)  ; 

/X 

disable  recv/xmit 

X/ 

output (MODE_REG, model ) ; 

/X 

send  new  parameters 

X/ 

output (MODE_REG, mode2) ; 

output (CMND_REG, ENABLE) 

5 

/X 

now  enable  PCI 

X/ 

Dr.  Dobb's  Journal.  June  1985 


64 

461 


shoH_parm ( )  /*  Display  current  parameters 


printf  (  " \nBaudrate  :  -/.s\n",baudrtbaud_rate])  j 
printf (  “Stopbits  :  %s\n" , sbi tsCstop_bi tsl ) ; 
print-f  <  "Parity  s  ”>; 

swi  tch  (par  i  ty )  <. 

case  0:  printf ( "None\n" ) ;  break; 
case  1:  printf ( "Odd\n" ) ;  break; 
case  2:  printf (“None\n") ;  break; 
case  3:  printf ("Even\n") ;  break; 

> 


> 


printf  (  "Wordlength  :  Xs\n“,wordl Cword_length]) ; 


*/ 


show_usage ( ) 


printf  (" 

\nUsage:  SETPORT 

C-Bb 

printf  (" 

where  — Bb 

sets 

printf  (" 

-Ss 

sets 

printf  (" 

-Pp 

sets 

printf  (" 

-LI 

sets 

> 


C— Ss3C— Pp3C— LI 3\n\n" ) ; 

Baudrate,  b  =  50. . 19200\n " ) ; 

Stopbits,  s  =  0,  1,  1.5,  2\n">; 

Parity,  p  =  0,  E,  N\n") ; 

wordLength,  1  =  5,  6,  7,  8\n”); 


error (type, value)  /*  Warn  command  line  argument  error  */ 

char  ttype,  lvalue; 

< 

pr  i ntf  (  "  1 1 1  egal  %s  ignored  :  5Cs\n"  ,  type,  val  ue)  ; 

> 


i nput (port ) 

/%  Return  input  from  I/O  port  %/ 

int  port 

r 

5 

ttasm 

pop 

b 

; Return  address 

pop 

h 

; Port  number 

push 

h 

; Restore  stack 

push 

b 

mov 

a,  1 

; Get  port  into  A 

sta 

inadr 

;  Modify  next  instruction - + 

in 

0 

; Dummy  input  instruction  i 

inadr 

equ 

*-i 

; Actual  port  gets  poked  here  <+ 

mov 

1  ,a 

mvi 

h,  0 

ttendasm 

> 


output (port, byte)  /%  Output  to  I/O  port  t/ 

int  port,  byte; 

#asm 


pop 

b 

; Return  address 

pop 

d 

;Byte  to  output 

pop 

h 

; Port  number 

push 

h 

; Restore  stack 

push 

d 

push 

b 

mov 

a,  1 

; Get  port  into  A 

sta 

outadr 

;  Modify  next  instruction - + 

mov 

a,  e 

; Get  byte  to  output  ! 

out 

0 

; Dummy  output  instruction  ! 

outadr 

equ 

*-l 

; Actual  port  gets  poked  here  < — + 

mov 

1  ,  a 

mvi 

h,  0 

#endasm 

> 


End  Listing 


65 

462 


Dr.  Dobb's  Journal.  June  1985 


Christensen  Protocols  in  C 


by  Donald  Krantz  a  bout  tw0  years  ag°- when  1  ac- 

only  to  port  the  source  for  the  file 

quired  my  first  8-inch  ma- 

transfer  program. 

/  V  chine,  I  needed  a  file  transfer 

I  put  the  8088s  aside  and  continued 

program  to  load  data  between  the  8- 

with  my  life.  As  time  passed,  I  used 

inch  machine  and  my  5'/4-inch  ma- 

the  file  transfer  program,  which  by 

chines.  I  started  looking  around  for  a 

now  had  the  name  XFR,  and  gradual- 

version  of  MODEM7  on  8-inch  disk. 

ly  started  adding  bells  and  whistles. 

Before  1  found  one,  I  ordered  a 

Some  time  later,  I  had  occasion  to 

couple  of  8088  co-processors  for  my 

write  XMODEM  in  C  for  my  portable 

systems.  As  this  development  prom- 

BBS  series.  I  thought  I’d  just  take  the 

ised  to  complicate  my  file  transfer 

old  XFR  program  and  do  a  little  sur- 

problems  considerably,  I  decided  to 

gery;  I  figured  the  whole  thing  ought 

write  a  file  transfer  utility  in  C  so  I 

to  take  no  longer  than  a  couple  of 

would  have  a  single,  portable  pro- 

hours. 

gram  for  all  my  systems. 

By  this  time,  I  had  switched  all  of 

I  started  writing  in  SuperSoft  C, 

my  new  program  development  to  Eco 

because  that  was  what  1  had  back 

C,  which  is  closer  to  the  Unix  Version 

then,  and  several  weeks  later  had  a 

7  standard  than  was  SuperSoft.  As  I 

version  working  on  my  Zorba  CP/M 

started  work  on  XMODEM,  I  found 

machine.  I  thought  I’d  be  in  Fat  City 

more  and  more  places  where  the  ba- 

when  the  co-processors  came. 

sic  structure  of  XFR  made  porting  the 

The  most-often-ported  of  telecommunications  pro- 

grams  meets  the  most-often-called-portable  of 

languages. 

Well,  the  co-processors  arrived,  I 

thing  less  and  less  attractive.  Still,  I 

wired  them  in,  and  I  started  planning 

took  out  the  digital  chain  saw  and 

the  big  port.  I  was  unpleasantly  sur- 

started  to  work  on  XFR. 

prised  to  find  that  the  co-processors 

Some  of  the  items  I  took  out  in- 

had  no  assembler  or  documentation 

elude  an  assembly  language  CRC 

and  wouldn’t  read  my  5‘4-inch  IBM 

generator,  interrupt-driven  receive 

disks.  As  far  as  I  could  tell,  I  had  no 

FIFO,  XDIR-style  directory,  wildcard 

means  of  getting  any  new  programs 

filename  expansion  for  the  batch 

or  files  into  the  system  short  of  writ- 

mode,  file  eraser,  disk  drive  logger, 

ing  a  program  with  DEBUG.  This  was 

half  duplex  and  host  terminal  mode, 

unacceptable,  especially  since  I  had 

and  a  data  line  monitor  for  watching 

promised  myself  that  I  would  never 

control  characters.  What’s  left  is  a 

learn  nor  use  8088  assembly  lan- 

basic  Christensen  protocol  engine 

guage  and  had  given  myself  a  waiver 

with  a  minimal  terminal  program. 

The  advantage  is  that  the  file  transfer 

primitives  are  now  pretty  much  inde- 

pendent  of  the  operating  system  and 

Donald  Krantz,  2845  42nd  Ave. 

C  compiler — to  the  extent  allowed  by 

South,  Minneapolis,  MN  55406. 

the  protocol — and  can  be  dropped 

Dr.  Dobb's  Journal,  June  1985 

66 

463 

into  other  environments  easily. 

The  basic  features  supported  by 
XFR  are  dumb  terminal  emulation 
with  host  system  terminal  attributes 
and  checksum  or  CRC  file  transfer  in 
single  or  batch  file  mode.  I’ll  run 
through  the  Listing  (page  71)  and 
discuss  features  and  portability  con¬ 
siderations  on  a  function-by-function 
basis.  First,  let  me  mention  up  front 
that  my  goal  was  operation  at  19.2K- 
baud,  so  I  have  violated  some  soft¬ 
ware  development  standards  in  the 
interest  of  speed,  specifically  by  using 
C’s  interfunction  GOTO  to  reduce 
conditionals  in  loops.  Also,  Z80  im¬ 
plementations  of  C  tend  to  access  glo- 
bals  faster  than  automatics,  so  glo- 
bals  perhaps  are  overused. 

Function  main(  ) 

This  is  basically  a  menu  processor. 
Most  of  the  functions  do  returns  rath¬ 
er  than  using  the  longjmp(  )  to  resume 
the  menu  loop.  It’s  a  trivial  job  to  add 
features  to  the  menu  if  you  plan  on 
actually  using  this  as  a  terminal  pro¬ 
gram  and  need  more  functions. 

Function  term(  ) 

This  is  a  simple  dumb  terminal.  For 
you  MSDOS  or  Unix  folks  who 
haven’t  memorized  the  CP/M  BDOS 
function  calls,  the  function  _bdos(  6, 
OxFF  )  is  a  specialized  version  of  get- 
char(  )  that  immediately  returns  a 
NULL  if  no  character  is  waiting.  I’ve 
seen  a  similar  function,  sometimes 
called  inkey(  )  or  keyscan(  ),  in  other 
C  libraries.  The  presence  or  absence 
of  term(  )  doesn’t  affect  the  file 
transfers  at  all,  but  because  you  usu¬ 
ally  should  be  able  to  communicate 
with  the  remote  system  to  set  up  the 
file  transfer,  it’s  nice  to  have  some 
sort  of  dumb  terminal. 

Function  transmit!  ) 

This  function  has  the  only  cheezy 
user  interface  in  the  program.  It  se¬ 
lects  batch  file  transfer,  as  opposed  to 
single  file  transfer,  by  looking  for  a 
comma  at  the  end  of  the  input  line 
when  asking  for  the  transmit  file¬ 
name;  should  it  find  a  comma,  it  will 
select  batch  mode  and  ask  for  the 
next  name.  Most  versions  of  MO¬ 
DEM?  either  have  a  command  option 
switch  for  this  purpose  or  recognize 


that  you  want  batch  mode  if  an  am¬ 
biguous  filename  is  specified.  The 
original  XFR  used  the  latter  method. 
Unfortunately,  there  is  no  portable 
way  to  expand  filenames  and  get  a 
list  of  matching  files. 

If  you  have  a  system  that  will  ex¬ 
pand  wildcards  on  the  command  line 
and  pass  matching  names  through 
the  argc/argv  method,  you  could  eas¬ 
ily  convert  this  program  from  inter¬ 
active  to  command  line  mode.  Other¬ 
wise,  on  CP/M  systems,  if' you  have 
access  to  the  BDOS  calls,  you  can  use 
the  function  unparse(  )  to  convert 
CP/M  fcb  names  to  a  normal  format 
and  pass  them  one  at  a  time  to 
trans(  ).  The  one  advantage  the  cur¬ 
rent  method  has  over  wildcard  expan¬ 
sion  is  that  it  allows  sending  files  with 
dissimilar  names.  However,  if  you  do 
use  the  CP/M  “search  for  file”  and 
“search  for  next”  calls,  be  sure  to  col¬ 
lect  all  the  names  before  you  allow 
the  program  to  proceed. 

Function  trans(  ) 

This  is  the  file  transmission  primitive 
executive  (if  that’s  not  a  contradic¬ 
tion  in  terms).  A  filename  is  passed 
in,  in  a  format  compatible  with  the 
fopen(  )  function.  If  batch  mode  is  se¬ 
lected,  the  name  is  sent  to  the  remote 
system,  followed  by  the  file  itself. 
This  function,  and  the  supporting 
functions  it  uses,  are  all  pretty  much 
system  independent  within  the  con¬ 
fines  of  the  protocol. 


fixed  1 1-character  field  in  CP/M  fcb 
format.  This  part  of  the  protocol  has 
a  slightly  different  flavor  to  it  than 
the  rest,  so  I  suspect  that  somebody 
other  than  Ward  Christensen  did  it. 
If  you’re  not  interested  in  compatibil¬ 
ity  with  MODEM7,  the  size  of  the 
name  is  fixed  by  the  macro  NAME- 
SIZE.  You’re  safe  up  to  about  30 
characters  or  so  before  you  have  to 
start  hunting  for  other  items  affected 
by  the  name  size  that  NAMESIZE 
doesn’t  control. 

Function  receive!  ) 

This  is  the  file  reception  primitive  ex¬ 
ecutive.  All  it  really  does  is  figure  out 
if  you  want  batch  mode  or  single  file 
mode.  It’s  not  possible  to  tell  what  the 
other  system  has  selected  just  by  lis¬ 
tening  to  the  line.  If  an  asterisk  is  en¬ 
tered  in  the  receive  filename  specifi¬ 
cation,  batch  mode  is  selected  while 
preserving  any  drivespec.  Those  with 
non-CP/M  systems  could  remove  the 
drivespec  material,  but  this  probably 
won’t  be  necessary.  It’s  triggered  by  a 
colon  in  the  second  character  position 
of  the  filespec. 

Because  the  Christensen  protocol  is 
receiver  driven,  you  have  to  have 
made  a  choice  whether  to  use  check¬ 
sum  or  CRC  error  detection  before 
you  initiate  the  receive  mode.  Some 
versions  of  MODEM7  will  hunt  back 
and  forth  between  CRC  and  checksum 
mode  to  match  what  the  other  system 
uses,  but  this  program  doesn’t. 


Function  txrec( ) 

This  function  sends  a  single  record  to 
the  remote  system  and  does  any  re¬ 
transmission  necessary  due  to  errors. 
The  record  size  is  fixed  at  128  bytes 
by  the  macro  RECSIZE.  If  you  don’t 
need  compatibility  with  the  rest  of 
the  world,  you  could  try  optimizing 
performance  by  increasing  the  size  of 
the  record,  if  you  have  a  good  com¬ 
munication  link,  or  decreasing  the 
size  of  the  record  if  you  use  noisy 
lines.  The  macro  RECSIZE  fixes  all 
the  parameters  necessary  for  change 
of  record  size. 

Function  txname!  ) 

This  function  sends  a  filename  to  the 
remote  system  when  batch  mode  is 
selected.  The  protocol  calls  for  a 


Function  rxbatch(  ) 

This  takes  over  as  receive  executive  if 
batch  mode  is  selected.  It  receives  a 
filename  from  the  remote  system  and 
converts  it  from  the  fcb  format  used 
for  transmission  back  to  a  format 
suitable  for  use  with  C  functions.  The 
name  is  then  passed  to  the  file  receive 
function  rxfile(  )  to  pick  up  the  body 
of  the  file. 

Function  rxnamef  ) 

This  is  the  companion  to  txname(  ). 
Enough  said. 

Function  rxfile!  ) 

This  handles  reception  of  an  entire 
file.  This  function  is  the  time-critical 
part  of  the  program,  which  is  why  I 
|  didn’t  break  it  into  smaller  modules. 


67 


Dr.  Dobb's  Journal.  June  1985 


It  doesn’t  matter  if  the  program  is 
slow  when  transmitting,  but  when  re¬ 
ceiving  you  have  to  catch  all  of  the 
characters  sent  from  the  other  end  as 
they  come  in.  I  put  off  the  computa¬ 
tion  of  checksum  or  CRC  to  the  end 
of  the  record  for  this  reason,  instead 


of  building  it  into  the  receive  charac¬ 
ter  function  where  it  probably  be¬ 
longs. 

It’s  also  important  not  to  output 
messages  to  the  display  when  a  packet 
transfer  is  in  progress.  One  of  the  ver¬ 
sions  of  MODEM7.1 1  that  is  floating 


around  can’t  run  above  1 200  baud  in 
receive  mode  because  it  makes  a  mes¬ 
sage  output  call  immediately  after  re¬ 
ceiving  the  packet  header  and  so 
misses  a  couple  of  characters. 
Function  make(  ) 

This  opens  a  file  to  receive  to.  The 


1111111 

6543210987654321  <- Bit  numbers 


CRC  accumulator,  1 6  bits 

-  !x:x:x:x:x!xix!x:x!x:xix;x:x!x!x!  <—  Data  byte 

!x!x:xlxix:x:x!x! 

- ►  :  0  i  0  :  0  :  1  :0:0i0:0:0i0:1  :  0  :  0  :  0  :  0  :  1  :  CRC  polynomial 

t 

Controls  whether  XOR  takes  place 


Figure  1. 

A  1  shifted  out  of  the  left  end  of  the  CRC  accumulator  causes 
the  CRC  polynomial  to  be  exclusive-ORed  with  the  post-shift 
CRC  accumulator.  The  CRC  polynomial  appears  to  have  no 
relation  to  the  polynomial  equation  in  the  text  until  you  realize 
that  it  has  been  shifted  left  along  with  the  accumulator. 


Byte  Number 
1 
2 

3 

4 


Definition 

SOH  (Start  of  Header)' 

Record  number  mod  256' 

[One's  complement  of  record  number' 
Data  byte  1 


131  Data  byte  1 28 

1 32  CRC  high  bytef 

1 33  CRC  low  bytet 

'  Not  included  in  CRC  or  checksum  calculation 
+  Included  as  zeros  in  CRC  calculation 


Figure  2. 

Christensen  packet  makeup  is  shown  for  a  CRC  error  detection 
packet.  In  checksum,  byte  1 33  is  not  sent,  and  byte  1 32  is  the 

checksum. 


Programmers  writing  MODEM7- 
compatible  programs  traditionally 
learn  about  the  protocol  by  getting  as 
many  copies  of  the  source  to  MO¬ 
DEM?  as  possible  and  tracing  pro¬ 
gram  flow.  In  violation  of  that  tradi¬ 
tion,  I  have  written  down  my 
interpretation  in  English,  dock’s 
Fourth  Law  states  that  this  must 
have  been  done  before,  but  the  corol¬ 
lary  to  that  law  states  that  you  can’t 
find  out  where,  even  if  it  has. 

Ward  Christensen  (according  to 
legend)  developed  the  protocol, 
which  was  extended  by  several  other 
people  who  ought  to  receive  credit 
but  whose  names  I  don’t  have.  Ward 
has  been  around  so  long  that  I  was 
surprised,  and  perhaps  a  little  indig¬ 
nant,  to  find  that  he  is  still  alive.  He  is 
responsible  for  some  of  the  more  clev¬ 
er  public  domain  programs,  so  I’m 
willing  to  bet  that  he  didn’t  do  some 
of  the  peculiar  things  in  the  Christen¬ 
sen  protocol — such  as  the  filename 
transfer. 

There  are  two  levels  to  the  Chris¬ 
tensen  (also  called  XMODEM)  proto¬ 
col:  file-at-a-time  and  batch  transfer. 
The  batch  transfer  mode  provides  for 
the  unattended  transmission  of  mul¬ 
tiple  files,  including  the  filename. 

The  protocol  uses  two  different  er¬ 
ror  detection  schemes:  checksum  and 
CRC  (Cyclic  Redundancy  Code). 
Older  versions  of  MODEM7  use  the 
checksum  method,  while  the  newer 
versions  use  the  more  accurate  CRC 
method.  Most  programs  that  can  do 
CRC  will  also  do  checksum. 

Only  the  data  bytes  in  a  packet  are 
included  in  the  calculation  of  the 
error  detection  code.  The  protocol 
handles  errors  in  headers  and 
handshakes  by  ignoring  garbage 
characters  or  assuming  the  worst. 

Checksums  are  calculated  by  add¬ 
ing  all  data  bytes  mod  256,  with  any 
carry  ignored. 

The  CRC  polynomial  used  is  the 


Dr.  Dobb’s  Journal,  June  1985 


68 

465 


original  version  of  XFR  made  a  back¬ 
up  file  if  a  file  would  be  overwritten, 
but  file  renaming  isn’t  portable  in  C 
(the  current  version  will  happily  de¬ 
stroy  an  old  file,  even  if  reception  is 
terminated  prematurely).  I  left  the 
code  in  a  false  conditional  assembly 
block  because  I  thought  it  rather 

CCITT  X'6  +  X12  +  X5  +  1.  I’m 
told  that  there  are  people  who  know 
what  that  means  (I’m  taking  the 
word  of  an  old  XMODEM  listing).  In 
simple  language,  the  procedure  for 
generating  the  CRC  is  this:  A  byte  to 
be  added  to  the  CRC  is  fed  into  the 
generator,  one  bit  at  a  time,  high  bit 
first.  The  bit  is  shifted  into  a  16-bit 
CRC  accumulator  low  end.  If  the 
high  bit  shifted  out  of  the  CRC  accu¬ 
mulator  is  a  1,  the  CRC  accumulator 
is  exclusive-ORed  with  0x1021.  The 
process  repeats  for  all  eight  bits  of  the 
input  character.  See  Figure  1  (page 
68). 

In  the  case  of  the  Christensen  pro¬ 
tocol,  after  all  the  data  bytes  in  a  giv¬ 
en  packet  are  sent  through  the  CRC 
generator,  two  zero  bytes  are  sent 
through  at  the  end.  I’m  guessing  that 
these  bytes  take  the  place  of  the  CRC 
bytes  in  the  packet,  although  I  don’t 
know  why  this  is  needed.  I  do  know 
that  it  doesn’t  come  out  right  unless 
you  do  this,  and  for  an  empirical  kind 
of  guy  like  myself  that’s  plenty  good 
enough. 

At  the  single  file  level  of  the  proto¬ 
col,  both  systems  are  told  to  begin, 
and  the  transmitter  waits  for  the  re¬ 
ceiver  to  send  a  sync  byte.  The  initial 
sync  byte  tells  the  transmitting  sys¬ 
tem  whether  CRC  or  checksum  will 
be  used.  If  checksum,  the  sync  byte  is 
a  NAK.  If  CRC,  the  sync  byte  is  a  ‘C.’ 
If  the  receiver  is  thinking  CRC  and 
the  transmitter  isn’t  capable  of  it, 
both  systems  hang  until  they  time 
out.  Some  programs  send  five  or  six 
‘C’  sync  bytes  and,  if  not  answered, 
then  send  NAKs  in  case  the  transmit¬ 
ter  is  an  old  version. 

In  CRC  mode,  once  the  systems  are 
in  sync,  the  ‘C’  is  dropped  in  favor  of 
ACK/NAK  handshaking  for  subse¬ 
quent  records. 

Once  in  sync,  the  transmitter  sends 
a  packet  consisting  of  an  SOH  char¬ 
acter,  the  record  number  mod  256, 


clever;  it’s  a  good  feature  if  your  C 
library  supports  renaming.  The  func¬ 
tion  link  takes  the  arguments  (  new- 
name,  oldname  ). 

Function  parse(  ) 

This  takes  a  filespec  useable  by  C 
functions  and  converts  it  to  the  CP/M 


fcb  format  needed  by  the  Christensen 
protocol.  On  systems  with  path¬ 
names,  you’ll  have  to  hack  this  func¬ 
tion  into  shape  to  deal  with  filename 
preambles.  It  will  operate  on  names 
that  don’t  follow  CP/M  conventions 
in  most  circumstances,  although  the 
use  of  periods  will  give  some  unusual 


the  one’s  complement  of  the  record 
number,  a  128-byte  data  block,  and 
the  error  detection  byte(s).  See  Fig¬ 
ure  2  (page  68). 

The  receiver  must  accept  the  entire 
packet  at  once.  Most  versions  of  MO¬ 
DEM?  wait  for  the  SOH,  so  a  missed 
SOH  is  an  error  from  which  it  is  diffi¬ 
cult  to  recover.  After  reception,  con¬ 
ditions  that  will  cause  record  rejec¬ 
tion  are:  the  record  number  doesn’t 
match  the  one’s  complement  of  the 
record  number;  the  error  detection 
number  doesn’t  match;  SOH  is  not  in¬ 
tact;  and  the  record  number  either  is 
not  the  record  number  expected  or  is 
one  less  than  the  record  number  ex¬ 
pected  (indicating  a  previous  ACK 
was  trashed).  If  the  record  is  reject¬ 
ed,  the  receiver  sends  a  NAK  to  the 
transmitter,  which  retransmits  the 
record.  If  the  record  is  accepted,  the 
receiver  sends  ACKs  at  about  one- 
second  intervals  until  the  next  SOH. 

This  process  repeats  until  all  re¬ 
cords  are  transmitted.  At  the  end  of 
file,  the  transmitter  sends  EOT  at 
one-second  intervals  until  the  receiv¬ 
er  ACKs  the  EOT. 

Record  numbers  are  "natural” 
numbers;  that  is,  the  first  record  is 
numbered  1,  as  opposed  to  the  com¬ 
puter  natural  0. 

Batch  level  of  the  protocol  includes 
a  filename  header  in  front  of  the  file. 
Filenames  always  use  checksum  er¬ 
ror  detection,  even  if  CRC  mode  is  se¬ 
lected  for  the  rest  of  the  file.  The  file¬ 
name  is  sent  as  it  would  appear  in  a 
CP/M  file  control  block  (fcb),  a  fixed 
block,  1 1  characters  long,  with 


blanks  expanding  the  dot  should  the 
filename  be  less  than  1 1  characters. 
It  is  important  in  CP/M  systems  to 
make  sure  that  any  high  bits  are 
stripped  from  the  filename.  See  Fig¬ 
ure  3  (page  69). 

When  preparing  to  receive  a  name, 
the  receiver  sends  NAKs  at  about 
one-second  intervals  until  the  trans¬ 
mitter  responds  with  an  ACK  fol¬ 
lowed  by  the  first  character  of  the  fi¬ 
lename.  The  receiver  then 
handshakes  with  ACKs  until  the 
transmitter  sends  EOF.  This  may 
mean  that  more  than  1 1  characters 
are  received  (because  of  line  noise). 
When  the  receiver  gets  the  EOF,  it 
responds  with  the  checksum  of  the 
name,  including  the  EOF  byte.  If  the 
checksum  matches,  the  transmitter 
sends  an  ACK,  and  the  single  file  pro¬ 
cess  takes  over.  If  the  checksum 
doesn’t  match,  the  transmitter  sends 
a  NAK,  and  the  process  repeats. 

If  the  transmitter  sends  an  EOT  in 
place  of  any  name  character,  it 
causes  batch  transfer  mode  to  termi¬ 
nate.  This  is  the  normal  end  to  a 
batch  transfer. 

Both  the  transmitter  and  the  receiv¬ 
er  should  be  able  to  recognize  CAN 
(Cancel)  in  place  of  ACK,  NAK,  EOT, 
SOH,  or  EOF.  In  practice,  timeouts 
will  replace  some  tests  for  CAN. 

All  transmit  and  receive  loops 
should  have  timeouts  and  retries  built 
into  them  to  avoid  hanging  if  the  two 
systems  get  out  of  sync. 

Standard  UART  setting  for  the 
Christensen  protocol  is  eight  bits,  no 
parity,  one  stop  bit. 


TEST.DOC  becomes:  . . D  ;  0  :  C  ! 

VERY.  LONG.  NAM  becomes:  :V:E:R:Y:L:0:N:G:NiA:M 
NAME  becomes:  :  N  :  A  i  M  :  E  :  :  :  :  :  :  : 

Figure  3. 

CP/M  filename  expansion 


69 

dfifi 


Dr.  Dobb’s  Journal,  June  1985 


results.  If  the  NAMESIZE  macro  is 
resized,  parse!  )  will  still  operate  in  a 
reasonable  manner. 

Function  fillbuf(  ) 

This  loads  the  transmit  file  buffer 
with  a  record.  I  modularized  this  sec¬ 
tion  because  soon  I’ll  add  the  ability  to 
extract  members  from  a  library  file. 
On  systems  capable  of  file  sizes  that 
aren’t  multiples  of  system  records, 
this  function  pads  the  last  record  with 
zeros.  Note  that  this  code  will  never 
be  executed  on  a  CP/M  system. 

Function  unparse(  ) 

This  undoes  what  the  parse!  )  func¬ 
tion  does.  Notice  that  a  period  always 
is  inserted  into  a  filename  unless  the 
filename  is  less  than  (NAMESIZE-3) 
characters  long. 

Function  updcrc(  ) 

This  adds  a  character  to  the  CRC  or 
checksum  accumulator.  This  uses  the 
CCITT  CRC  polynomial,  but  charac¬ 
ters  are  fed  into  the  generator  high 
bit  first.  I’m  not  certain,  but  I  think 
that  makes  it  incompatible  with  com¬ 
munications  chips  that  do  a  hardware 
CRC,  in  case  you  have  thoughts  in 
that  direction.  At  any  rate,  look  into 
it  before  you  spend  a  lot  of  time  on  it. 


Remaining  Functions 

In  CP/M  systems,  all  of  the  above 
functions  should  work  as  is.  The  rest 
of  the  functions  are  all  machine  de¬ 
pendent — or  potentially  so. 

The  init(  )  and  quit!  )  functions 
are  called  at  the  beginning  and  end  of 
the  program.  I  had  code  to  set  up  and 
remove  an  interrupt-driven  FIFO  in 
these  functions;  UART  initialization 
or  I/O  channel  acquisition  would 
probably  go  here  as  well. 

The  I/O  primitives  are  set  up  for 
an  I/O-mapped  Intel  8351 A  UART. 
These  functions  are  the  ones  most 
likely  to  require  customization  to 
make  the  program  work. 

How  does  the  whole  thing  per¬ 
form?  On  a  Z80  running  at  3.58 


MHz,  the  program  will  easily  do 
transfers  in  checksum  mode  at  19.2K 
baud.  I’m  reasonably  sure  it  will  do 
the  same  in  CRC  mode,  but  I  haven’t 
the  resources  to  test  CRC  at  that 
speed.  If  anyone  does  do  a  bench¬ 
mark,  I’m  interested  in  the  result. 

As  far  as  error  handling,  the  pro¬ 
gram  is  not  ready  for  an  end  user,  but 
a  reasonable  person  would  have  to 
work  at  it  to  blow  the  thing  up.  Con¬ 
sider  this  a  starting  point,  and  if  any¬ 
one  gets  it  up  on  8-inch  MSDOS  for 
the  Bigboard  and  Co-Power  88,  let 
me  know. 

Source  is  available  on  TCOG  BBS 
(612)  724-7779  300/1200  baud. 

DDJ 


Protocols  Listing  (Text  begins  on  page  66) 


/« - 

XFR 

By  Donald  G.  Krantz  3/84 
(Cl  Copyright  1984  Donald  G.  Krantz 
All  Rights  Reserved 


Function  abort( ) 

This  scans  the  console  for  an  opera¬ 
tor-requested  abort.  As  far  as  I  know, 
this  function  is  called  inside  any  loop 
where  the  system  can  hang  (but  I 
never  guarantee  anything  about  a 
program).  The  BDOS  keyboard  scan 
is  the  same  as  in  term!  )• 


XFR  is  a  packet  protocol  File  transferer  similar  to  modem7.  It  is 
designed  for  19. 2K  baud  transfers  "nose-to-nose"  between  computers 
and  works  very  well  in  modem  use  at  low  speeds.  XFR  is  compatible 
with  most  versions  of  Modem7. 


-*/ 


♦include  "stdio.h"  /*  ECO  C  */ 

/» - 

ASCII  and  XMODEM  control  characters 
- «/ 


Function  error(  ) 

This  is  called  when  fatal  transmission 
or  reception  errors  occur.  It  ties  up 
loose  ends  then  jumps  back  to  the 
menu. 

Functions  wait(  ),  swait(  ),  and 
waitcan(  ) 

These  do  different  delays  while  wait¬ 
ing  for  characters.  You  could  com¬ 
bine  them  all  into  one  function  if 
you’re  willing  to  pay  the  overhead  for 
more  conditionals.  At  high  baud 
rates,  file  reception  timing  is  pretty 
critical,  but  for  1200  baud  or  so  there 
won’t  be  any  problems. 


•def i ne 

SOH  1 

/* 

start  of  header 

*/ 

•define 

EOT  4 

/*• 

end  of  transmission 

»/ 

•def i ne 

ACK  6 

/* 

true  acknowledge 

*/ 

•define 

NAK  0x15 

/» 

false  acknowledge 

»/ 

•def i ne 

CRC  ’C'  . 

/* 

request  CRC  mode 

*/ 

•define 

CAN  0x18 

/* 

cancel  transmission 

*/ 

•def i ne 

EoF  26 

/» 

end  of  file  (used  for  name) 

*/ 

•define 

BADNAME  0x75 

/* 

received  bad  name  checksum 

»/ 

•def i ne 

BELL  7 

/» 

conso 1 e  bell 

*/ 

•define 

NEWLINE  10 

/* 

1 i nefeed  char 

*/ 

•def i ne 

CR  13 

/» 

carriage  return  char 

*/ 

•define 

BS  8 

/* 

backspace  character 

»/ 

•def i ne 

ESCTERM  'E'-'B' 

/* 

escape  from  terminal  mode 

•  / 

/» - 

User  accessable  system  equates 

- »/ 


•define  RETRY  S  /*  no.  of  retrys  before  abort  */ 

•define  RECSIZE  128  /**  transfer  record  size  */ 

•define  NAMESIZE  II  /«  filename  fixed  length  */ 

•define  T IlLt  printfl  "\n\t\t\t  XFR  -  File  Transfer  Utility") 
•define  VERSION  pr intf ( *\n\t\t\t  Z0RBA  Version  2.00  as  of  3/85"l 
•define  CLS  printfl  "\033E")  /*  clear  screen  *•/ 


Dr.  Dobb's  Journal,  June  1985 


71 

467 


Protocols  Listing 

(Listing  continued,  text  begins  on 

page  66) 

♦define  ERROR  -1 

♦define  MAG IC_NUMBER  10000 

/»  time  constant  -  machine 

dep.  */ 

♦define  VOID  int 

/«  function  type 

»/ 

/»  ♦define  LINK 

/  "  file  rename  supported 

*/ 

/« - 

Machine  dependant  port  addresses  -  ZORBA  Version  (Intel  825IA) 

- H/ 


♦define 

RXDATA 

0x20 

/» 

rec'd  data  UART 

port 

*/ 

♦def i ne 

TXDATA 

0x20 

/« 

tx'ed  data  UART 

port 

*/ 

♦define 

UARTCMD 

0x21 

/" 

Command/Status 

port 

*/ 

/* - 

Global  variables 

- #/ 


char  "1 ine  s 

/« 

char  "buffers 

/» 

FILE  "fds 

/" 

unsigned  recs 

/" 

char  checksums 

/" 

unsigned  crcaccum; 

/» 

char  crcs 

/« 

char  batch s 

/» 

Jmp_env  to_menus 

/« 

struct  t 

/» 

char  name_partt  32 
}  list!  32  Is 


scratch  input  1 ine 

"/ 

record  buffer 

»/ 

file  POINTER! 

*/ 

record  number 

*/ 

checksum  accumulator 

*/ 

global  crc  bytes 

*/ 

CRC  on/off  flag 

»/ 

batch  on/off  flag 

»/ 

long  Jump  envelope 

»/ 

batch  1 i st  array 

Is 

*/ 

/» - 

Function  type  declarations 

- */ 


VOID  main!),  term!),  transmit!!,  transll,  txname!),  txreclls 
VOID  receive!),  rxnameli  rxfilel),  make!),  error!),  clrcrclli 
VOID  updcrcl),  abort!),  sleep!),  quit!),  initl),  tx(),  rxbatchlli 
char  "index!),  "parse!),  "unpa  sells 

int  fillbufl),  wait!),  swaitl),  waitcan!),  rxstat!),  rx ( ) ; 
int  txstat ( ) s 

/» - 

Executable  code  follows. 

main!)  is  (of  course)  the  master  program  loop.  Uses  setjmpl) 
to  un-nest  function  calls  on  error  exits. 

References  globalss  crc,  ttys 
Modifies  globalss  crc,  ttys 

- »/ 


VOID  main!  ) 
t 

char  menus 


menu  =  FALSE s 

/« 

initial  1 y  expert 

mode 

"/ 

init  < )  s 

TITLES 

VERSIONS 

/« 

initial i ze  modem 

and  UART 

«/ 

printfl  "NnType  'M' 

for  MENU  display....")! 

setjmpl  &to  menu  )s 
while!  TRUE") 

/" 

menu  loop 

»/ 

printfl  "\n\nModei  %s",  crc  ?  "CRC"  i  "Checksum"  Is 
i f I  menu  ) 

{ 


printf!  "\n\n\tS  Send  file  R  Receive  fi le\n"  Is 


printf!  "\tT  FDX  term 
printfl  *\tM  toggle  menu 

) 

printf!  "\n\nCommand s  "  )s 
switch!  toupper!  getchar!) 
€ 

case  'S' s 

transmi t ( ) 
break  5 
case  'R ' s 

rece i ve ( ) s 
break  s 
case  'T' s 

term (  )  s 


V  CRC/Checksum\n"  )  s 
X  Ex i t \n"  ) s 

)  ) 

/»  send  f i I e ( s )  »/ 

/•  RX  filets)  »/ 

/"  dumb  terminal"/ 


( Continued  on  page  74) 


72 

468 


Dr.  Dobb's  Journal,  June  1 985 


Protocols  Listing  (Listing  continued,  text  begins  on  page  66) 


case 

•V1 

break i 

/** 

toggle 

crc 

*/ 

case 

•M 

crc  =  !crci 
break i 

1  . 

/» 

toggle 

menu 

<*/ 

case 

'X 

menu  =  imenu: 
break  j 
'  ! 

/• 

qu  i  t 

»/ 

defau  1 1 

quit  (  )  i 

/** 

opt i on 

bad 

*/ 

put  char!  BELL 

1  : 

/# - 

term!)  is  the  dumb  terminal  section.  Escape  by  typing  ESCTERM 
character  (see  ASCII  tdefines). 

Some  machine  dependent  stuff. 

- #/ 


VOID  term! ) 
t 

char  chi 

/»  scratch  char 

CLSi 

printf! 

"\n\n"  li 

wh i 1 e  ( 

TRUE  ) 

/*  the  loop. 

*/ 

t 

i f 1  rxstat  ( )  ) 

/*  if  char  coming  in 

*/ 

putchar!  rxil  );/»  display  local  */ 

ifi  (ch  =  bdos (  6.  OxFF  I)  !=  NULL  ) 

iff  ch  ==  ESCTERM  I  /»  check  exit  */ 

return s 

e  I  se 

tx<  ch  )  ;  /»  disp  remote  **/ 


) 


) 


/* - 

transmit!)  is  the  transmission  executive.  It  requires  no  input 
parameters.  This  function  takes  filenamelsl  as  input  from  the 
user,  one  line  at  a  time.  A  comma  at  the  end  of  a  filename 
signals  the  intention  to  enter  anther  filename  on  the  next 
line.  More  than  one  filename  entered  sets  "batch"  mode.  3S 
names  maximum,  for  no  especially  good  reason. 


Modifies  globals:  buffer,  batch! 


«/ 


VOID  transmi t ( ) 
t 

int  ct  =  Oi 
int  i  ; 

pr int f (  "\n"  I  i 
while!  TRUE  I 
t 

printf!  "Transmit  File  specifications  "  ); 
gets!  list!  ct  I.name_part  I: 

if!  index!  list!  ct  ].name_part,  )  ==  NULL  ) 
break  j 

“index!  listC  ct  I.name_part,  )  =  '\0'i 

ct  +  +s 

> 

batch  =  (ct  ?  TRUE  s  FALSE)! 
for!  i =0  i  i  <=  ct  i  i ++  ) 
t 

while!  rxstat!)  )  /»  gobble  garbage  */ 

rx(  I  i 

transl  listC  i  I.name_part  ) ;  /»  send  file  »/ 

) 

if!  batch  ) 

{ 


wh i 1 e (  swa it!)  !=  NAK  : 

1  /« 

await  name  request 

»/ 

abort ( ) i 

/» 

operator  abort  scan 

«/ 

t  X (  ACK  ); 

/• 

handshake  NAK 

»/ 

s 1 eep (  )  i 

/* 

decent  interval 

»/ 

t  X (  EOT  )i 

/* 

end  of  transmission 

»/ 

) 

> 


(Continued  on  page  76) 


Dr.  Dobb’s  Journal,  June  1985 


74 

469 


Protocols  Listing 


(Listing  continued,  text  begins  on  page  66) 


/« - 

transit  does  transmission  of  a  file. 

References  globalsi  buffer,  fd,  batchi 
Modifies  globalsi  fd,  rec,  crci 

- 


VOID  transl  in_name  ) 


char  »in_name; 

char  name!  NAMES IZE+13; 

/*  expanded  name 

»/ 

char  ch; 

/*  scratch  character 

»/ 

reg i ster  char  » i ; 

/*  buffer  pointer  (index) 

»/ 

int  y; 

/»  scratch  counter 

•/ 

rec  «  1 ; 

/*  uses  "natural"  numbering 

*/ 

if!  (fd  »  f open  I  in_name,  "rb"  ))  ==  NULL) 
t 


> 


printfl  "Unable  to  open:  *s\n",  in_name  ); 
returns 

} 

if(  batch  )  /*  need  to  send  name?  */ 

txnamel  parse)  in_name,  name  )  ); 
printfl  "\nTransm i tt ing:  %s",  in_name  ); 
while!  rxst at  I )  ) 
rxll; 

while!  TRUE  )  /*  handshake  w/  receiver  */ 

f 

printfl "NnSyncroniEing:  "  >i 

if!  (ch  =  waitcanl  10  ))  ==  NAK  ) 

t 

printfl  "Received  Checksum  Request\n"  )j 
crc  =  FALSE; 
break  ; 

) 

if!  ch  ««  CRC  ) 

< 

printfl  "Received  CRC  Request \n"  ); 
crc  =  TRUE; 
break  ; 


while!  f i 1 Ibuf  I )  ) 

/» 

file 

not  empty? 

»/ 

txrecl  buffer  )  ; 

/* 

send 

record 

*/ 

txl  EOT  ); 

while!  (ch  =  waitcanl  1)1 
£ 

1*  ack  : 

1 

t  X  I  EOT  ) ; 

■v 

/• 

show 

end 

*/ 

J 

fclosel  fd  >; 

/* 

dump 

f  i  le 

»/ 

printfl  "\n%s  transferred. " , 

,  in_name  I ; 

/* - 

txnamel)  transmits  a  file  name  from  the  CP/M  fcb  format 
parameter  'name'. 

References  globalsi  buffer,  checksum,  crc; 

Modifies  globalsi  crc; 

- »/ 


VOID  txnamel  name  I 
char  "names 


register  int  i ; 

/» 

scratch  counter 

*/ 

char  ch; 

/* 

scratch  char 

*/ 

char  crcsav; 

/* 

holds  state  of  global  ' 

1  crc' 

*/ 

crcsav  =  crc; 

/** 

we  always  use  checksum 

for 

»/ 

crc  *  FALSE; 

/« 

name  error  detection 

«/ 

whi le 1  TRUE  I 
£ 

/* 

main  loop  for  retries 

•/ 

whi lei 

rxstat 1  I 

I 

rxll; 

i  =  0; 

/« 

retry  count 

*/ 

whi lei 

TRUE  I 

t 

printfl  "\nUaiting  for  filename  request"); 
if!  waitcanl  RETRY  I  —  NAK  I 
break  ; 

if!  i ++  >  RETRY  I 


76 

470 


Dr.  Dobb’s  Journal,  June  1985 


error!  "Can't  send  filename"  is 
printf!  ”  -  Received  garbage"  I; 

) 

t x (  ACK  );  /*  handshake  name  request  */ 

sleep! is  /*  decent  interval  */ 

printf!  "NnSending  name:  "  ); 

ctrcrcl):  /*  clear  checksum  accumulator  */ 

for!  i  =  0  ;  i  <  NAMES IZE  :  i++  )/*  name  loop  */ 
t 

tx (  nameC  ill: 

putchar!  nameC  i  3  > ;  /*  local  echo  */ 

wait!  10  );  /"wait  for  ACK  */ 

) 

tx!  EoF  ):  /*  name  terminated  w/EoF*/ 

if!  wait!  10  )  ==  checksum  I  /*  handshake  */ 
l 

printf!  "  -  Name  sent  OK"  I: 


t  x (  ACK  )  : 

/« 

handshake 

OK  i 

checksum*/ 

crc  =  crcsav; 

/* 

replacce 

'crc 

'  */ 

return ; 

/« 

normal  exit 

»/ 

BADNAME  I  : 

/* 

handshake 

bad 

chksm  «/ 

printf!  "  -  Bad  name  transfer"  ); 

) 

) 


/* - 

txrecl)  transmits  a  single  record,  with  retransmit  on  error  from 
receiver.  Input  is  a  pointer  to  the  I/O  buffer. 

References  globals:  rec.  crcaccum,  checsum,  crci 
Modifies  globals:  rec: 

- »/ 


VOID  txrec!  buf 
char  *bufi 
t 

register  int 
unsigned  cr: 

while! 

I 


i  i 


TRUE  ) 

/* 

do  i t  unt i 1  r i ght 

«/ 

printf!  "\rTransmi tt i ng 

record  %d  " ,  rec  I  : 

t  x (  SOH  ) i 

/* 

start  of  header 

*/ 

tx!  rec  1: 

/* 

rec  » 

*/ 

t  x (  'rec  ) ; 

/» 

1 ' s  comp 

»/ 

clrcrcl I i 

/» 

clear  CRC  accum 

»/ 

for!  i  =  0  :  i  ( 

RECSIZE  : 

i  ++  ) 

tx!  buf C 

i  3  >; 

/* 

send  record 

»/ 

updcrc (  0  I ; 

/» 

finish  up  CRC 

»/ 

updcrc!  0  ): 

/* 

aga  i  n 

*/ 

cr  =  crcaccum; 

/* 

save  crc  lobyte 

*/ 

if!  crc  ) 

/* 

send  hi  byte  first 

*/ 

tx! 

tx! 


crcaccum 
cr  I  i 


>>  8  )  : 


) 

e  I  se 

tx!  checksum  I: 
if!  wa i tcan ( 1 0 )==ACK  ) 
break : 

printf!  "Error  in  transmi ss i on\n 


rec++ : 


/*  quit  if  correct 
) : 

/*  bump  record  count 


»/ 


»/ 


/* - 

receive!)  is  the  receive  executive.  It  requires  no  input 
parameters  except  'crc'  should  be  set.  Does  not  "hunt"  from 
CRC  to  checksum. 

References  globals:  line: 

Modifies  globals:  line; 


-«/ 


VOID  rece i ve ( I 
t 

printf!  "\n\nReceive  File  specification:  "  I; 
gets  I  line  I  : 
i f (  rxstat !  I  I 


rxll: 

/*  gobble  trash 
!*  NULL  )/»  'batch'  set  by  a 

«/ 

if! 

i  ndex (  line. 

•  *  •  ) 

«/ 

rxbatch ! 

line  1 

l  ;  /*  aster i sk 

*/ 

e  1  se 

/*  in  input  file  name 

*/ 

rxf  i  1  e  ( 

line  )  i 

/*  otherwise  nobatch 

*/ 

( Continued  on  next  page) 


Dr.  Dobb’s  Journal,  June  1985 


77 


X71 


(Listing  continued,  text  begins  on  page  66) 


/* - 

rxbatch!)  is  the  batch  mode  reception  executive. 

References  Globals:  line; 

Modifies  Globals:  line; 

- ,/ 


VOID  rxbatch I ) 
t 

char  fcbt  NAMESIZE  +13;  /*  used  by  unparse (I  »/ 

char  name!  NAMESIZE  +53;  /*  unparsed  name  */ 

while!  TRUE  I 
{ 

while!  rxstatl)  )  /*  gobble  trash  »/ 

rx  1 1  ; 

printf!  "\nWaiting  for  file  name:  "  ); 

rxname I  fcb  I;  /*  receive  file  name  into  fcb  »/ 

unparse!  name,  fcb  ):  /*  make  "standard"  */ 


if!  line!  1  3  ==  ' :  '  ) 

line!  2  3  =  '\0'  ; 

e  I  se 

I ineC  0  3  =  ' \0 ' ; 
strcat !  line,  name  )  ; 

rxfile!  line  );  /*  receive  file  »/ 

) 

} 


/* - 

rxname!)  loads  a  CP/M  style  fcb  with  a  filename  from  remote 
sender.  On  receipt  of  an  EOT  instead  of  a  name  character, 
aborts  to  master  menu  via  longjmpl). 

- */ 

VOID  rxname!  fcb  ) 


char  *fcb; 

/» 

points  to  CP/M  style  fcb 

»/ 

char  "fcbptr; 

/* 

index  to  fcb 

»/ 

register  int 

i ; 

/* 

scratch  counter 

«/ 

Int  ch; 

/» 

scratch  char  (must  hold  ERR) 

»/ 

char  chksum; 

/** 

checksum  accumulator 

«/ 

while!  TRUE  I 

fcbptr 

*  fcb; 

'*  a  1 i gn  i ndex 

»/ 

i  -  RETRY  » 

S; 

/*  retry  for  name  h.s. 

*/ 

while! 

r 

TRUE 

) 

/*  Handshake  NAK 

»/ 

X. 

abort ( ) ; 

/*  check  operator  abort 

*•/ 

txl 

NAK  1  ; 

if! 

swa it! 

1  -=  ACK  ) 

break  ; 
if!  ! (  I  —  )  ) 

error!  "Timed  out  waiting  for  name"  ); 
3 

chksum  =*  EoF;  /«  init  checksum  */ 

for!  i  =  0  ;  i  (  34  ;  i ++  I  /*  accept  noise  »/ 

t 

if!  (ch  =  swait ( ) I  ==  EOT  ) 

{ 

t X (  ACK  I; 
printf! 

"\nAI I  transfers  completed"  I; 
longjmpl  8to_menu,  0  ); 

) 

if!  ch  ™  EoF  )  /*  End  of  name  chars  */ 
break ; 

if!  ch  1=  ERROR  )  /*  not  timeout  */ 

t 

*!fcbptr++)  =  ch  8  0x7F; 
put char  I  ch  ); 
chksum  +=*  ch  8  0x7F; 

) 


abort ( ) ; 

/* 

operator  abort 

*/ 

t  x (  ACK  )  ; 

/« 

handshake  name 

char  »/ 

J 

fCbC  NAMESIZE  3=0; 
do  t 

/* 

terminate  name 

field  »/ 

abort !  )  ; 

/» 

operator  abort 

»/ 

txl  chksum  ) ; 

/« 

handshake  checksum  •/ 

>  wh i 1 e (  (ch  =  swa it!)) 

== 

ERROR  ) ; 

(Continued  on  page  80) 


78 


Dr.  Dobb’s  Journal,  June  1 985 


Protocols  Listing  (Listing  continued,  text  begins  on  page  66) 


) 


i f (  ch  ==  ACK  ) 
return; 

printfl"  -  Checksum 


/*  ACK  is  good  name 
error\nRetry ing  file  name: 


*/ 


I  l 


/* - 

rxfilel)  receives  a  file.  Input  parameter  is  the  name,  including 
drivspec  if  any,  to  3ave  incoming  data  under.  To  maintain  speed 
the  function  is  rather  long  to  reduce  function  calls  where 
possible.  Nave  'crc'  set  before  entry. 

References  global s:  buffer,  fd,  rec,  crcaccum,  checksum,  crc: 
Modifies  globals:  buffer,  rec: 

- */ 


VOID  rxf i I e (  name  ) 
char  "name : 

{ 


char  ch; 

/* 

scratch  handshake  var 

»/ 

char  response; 

/» 

ACK/NAK/CRC  handshake 

»/ 

char  crcrlo; 

/* 

rec'd  CRC  low  byte 

»/ 

char  crcrh i  ; 

/<* 

rec'd  CRC  hi  byte 

»/ 

char  r 1 ; 

/» 

current  record  number 

*/ 

char  r2; 

/• 

I's  comp  record  number 

«/ 

unsigned  int  J; 

/* 

wait  loop  timer 

*/ 

i  nt  i  ; 

/• 

scratch  counter 

*/ 

register  char  "bptr; 

/* 

buffer  index 

*/ 

whi lei  rxstat I )  ) 
rx  I )  ; 

make!  name  ); 

/• 

open/make  backup 

«/ 

printfl  "\n"  ); 

rec  =  1 ; 

/* 

uses  natural  numbering 

*/ 

if!  crc  ) 

/« 

set  initial  handshake  to 

•/ 

response 

=  CRC; 

/« 

CRC  or  checksum 

*/ 

e  1  se 

response 
whi le(  TRUE  ) 

■r 

=  NAK; 

record  receive  loop 

»/ 

bptr  =  buffer; 

/*  align  index 

*/ 

printfl  " 

\rUa i t i ng 

for  record  %d  " ,  rec 

1  ; 

fori  i = 1  i  i  <=  RETRY  *5  ;  i ++  I 
t 


) 


abort ( )  : 

txl  response  I;  /*  send  handshake  »/ 

if  (  (ch  -  await  (!)  --  SOH  ) 

break:  /*  SOH  indicatees  rec  »/ 
if(  ch  »»  EOT  I  /*  EOT  indicates  done  •/ 
t 

printfl  "\n*s  received  OK",  name  >: 
fc lose (  fd  ) : 

txl  ACK  ):  /»  handshake  */ 

return:  /*  normal  exit  */ 

) 

if!  ch  ===  CAN  )  /»  Xmit  request  abort  */ 
t 

fclose  I  fd  )  ; 

error  I "SrRece i ved  cancel  request"); 

) 

if!  i  ==  RETRY  *  5  )  /»  timeout  exit  */ 

error!  "Can't  sync  to  sender*  ); 


rl  =  await  I);  /«  record  number  »/ 
r2  ■  swaitl);  /»  1 • s  comp  record  t  «/ 
while!  bptr  -  buffer  <  RECSIZE  )  /»  test  count  **/ 
i 


»(bptr+»)  =  swaitl);  /*  accept  char  */ 

} 

if!  crc  )  /*  get  hibyte  CRC  */ 

crcrh i  «  swa i t I ) : 

crcrlo  “  swaitl);  /»  lobyte  CRC  or  chksm  */ 

response  =  NAK ;  /«  init  response  */ 

if!  I *T I  &  OxFF )  ! “  Ir2  S  OxFF)  ) 

C 


printfl  "-  Record  numbers  don't  matchkn"  ); 
cont i nue ; 


clrcrcl);  /*  calc  check sum/CRC  »/ 

fori  J  -  0  ;  J  <  RECSIZE  ;  J++  ) 
updcrcl  buffer!  J  ]  ); 


(Continued  on  page  82) 


80 


Dr.  Dobb’s  Journal,  June  1985 


A71 


Protocols  Listing  (Listing  continued,  text  begins  on  page  66) 


) 


updcrc!  0  ) i  /»  required  to  finish  */ 

updcrc!  0)i  /"  off  CRC  -  why?  "/ 

if!  crc  I  /»  CRC  test  */ 

if(  (crcrlo  +  (crcrhi  <(  8)1  !«  crcaccum  ) 
t 


printf!  "-  Bad  CRC  receivedkn"  li 
cont inue i 

> 

if(  !crc  I  /"  checksum  test  "/ 

if(  crcrlo  !=  checksum  I 
t 

printfl"-  Bad  checksum  rece i ved\n" I s 
cont i nue i 

> 

if(  (rl  ==  (rec  -  I)  &  OxFF  I  I  /•  duplicate?  "/ 

{ 

printfl  ’  -  Received  duplicate  record\n"  1; 
response  *  ACK i  /«  dup  is  OK  -  ACK  was  "/ 
continue;  /»  trashed  -  ignore  it  »/ 

) 

ifl  rl  !=  (rec  S  OxFF)  )/»  fatal  sequence  error  "/ 
error!  "File  record  numbering  error"  ); 
rec++;  /»  bump  record  count  »/ 

fori  J  =  0  ;  J  (  RECSIZE  ;  J+  +  I  /»  write  data  »/ 
put cl  buffer C  J  3,  fd  ); 

response  «  ACK;  /*  normal  loop  end  "/ 


/* - 

make!)  opens  a  file  for  output.  It  renames  any  existing  file 
with  the  same  name  to  ".BAK",  and  erases  any  previous  backup. 

Input  parameter  is  filespec  in  "normal"  (compressed)  format. 

Note;  file  rename  by  the  link!)  function  isn't  supported 
by  several  CP/fl  C  Compilers,  and  others  call  this  function 
rename!),  or  reverse  the  arguments.  At  any  rate,  you  can  determine 
how  to  handle  backups  in  your  own  system  and  take  any 
appropriate  action. 

References  globals:  fd; 

Modifies  globals;  f d  ; 

- »/ 


VOID  make!  name  ) 


char  "name; 


t 

tlfdef  LINK 


char 

bakt 

NAMESIZE  +53; 

/» 

holds  .BAK  filespec 

»/ 

/" 

does  this  name  exist? 

*/ 

if! 

f 

(fd 

=  fopen!  name. 

Up  n 

) )  I =  NULL  1 

V 

fclose!  fd  ); 

/» 

we  were  Just  checking 

"/ 

strcpy!  bak ,  name  ) 

;  /"  save  old  name 

•  / 

if!  index!  name 

l 

•  • 

'  )  )/"  extension  spec'ed? 

*/ 

strcpy I 

index!  bak .  ' . '  ) ,  ".BAK"  ) ; 

e  1  se 

strcat ( 

bak 

;,  ".BAK"  ); 

uni  ink  I  bak  ) ; 

/» 

dump  any  old  .BAK  file 

*/ 

link!  bak ,  name 

) ; 

/»  rename  new  .BAK  file 

»/ 

tend  if 

} 

if! 

(fd 

=  fopen!  name. 

"wb" 

1  ) )  ==  NULL  1 

error!  "Can't  create  file  -  Aborting"  I; 


3 


/» - 

parse!)  expands  non  -  ambiguous  filespecs  to  the 
standard  CP/M  fcb  format,  excluding  drive  byte. 

Sets  the  globals 

'batch'  if  ambigous,  and  'driv'  for  drivespecs. 

Inputs  are  "normal"  filespec  (inspecl,  and  exanded  fcb. 
- »/ 


char  "parse!  inspec,  fcb  ) 
char  "inspec,  "fcb; 
t 

register  int  i  ;  /*  fcb  index  »/ 

int  inptr;  /*  input  spec  index  */ 

(Continued  on  page  84) 


82 


Dr.  Dobb’s  Journal,  June  1985 


Protocols  Listing  (Listing  continued,  text  begins  on  page  66) 


3 


for!  i  =  0  i  i  <  NAMESIZE 
fcbt  i  3  =  '  '  s 


if  ( 

e  I  se 


inspect  I  3 
inptr  • 


2i 


) 


i++  )/*  blank  fill  name 

/*  check  for  drivspec 
/*  point  past  drivespec 


inptr  =  Oi 

I  -  Oi 

while!  TRUE  I 

switch!  inspect 
t 


/*  index  to  start 
/*  pointer  into  fcb 


inptr++  3  I 


case  '\0'i  /*  end  of  input  spec 

fcbt  NAMESIZE  I  =  Oi 
return!  fcb  )i 

case  /«  extension  spec'ed 

i  *  NAMESIZE  -  3i  /»  extension 
break  ; 

defau 1 1 1 


if!  i  <  NAMESIZE  ) 

fcbt  i++  3  =  toupper! 
inspect  inptr  -13); 


»/ 

<*/ 

*/ 

*/ 

«/ 


*/ 


*/ 

»/ 


/« - 

fill buf C I  loads  the  I/O  buffer  with  a  record  from  the  input  file. 
Returns  TRUE  if  data  was  available,  FALSE  if  no  data  left. 

References  globals:  buffer,  fdi 
Modifies  globals:  bufferi 

- 

int  f i I  I buf ( ) 
i 

register  int  it  /*  scratch  counter  »/ 

int  errorchk  i  /*  holds  EOF  in  Eco  C  */ 

for!  i  =  0  i  i  <  RECSIZE  i  i ♦+  I 
t 

if!  (errorchk  *  getc!  fd) I  =■  EOF  ) 
break  i 

buffert  i  3  «  errorchk  i 

) 


if!  i  - 

=  0  1 

/*  no  data  read 

»/ 

return!  FALSE  )i 

for  (  ; 

i  <  RECSIZE  ;  i ++  1 
bufferC  i  3  =  0 ; 

/*  zero  fill  at  EOF 

»/ 

return  1 

TRUE  )  i 

/« - 

unparse!)  reassembles  a  filename  from  a  CP/M  fcb  into  "normal"  or 
cmpressed  form,  so  that  our  C  functions  can  deal  with  them. 

Inputs  are  a  pointer  to  a  string  to  receive  the  name  (name),  and 
a  pointer  to  a  CP/M  3tyle  fcb  entry  (buf I. 
- #/ 


char  »unparse(  name, 

buf 

I 

f 

char  *name,  *bufi 

register  int  ii 

/*  'name'  index 

*/ 

int  Ji 

/*  'buf'  index 

*/ 

i  =  0 1 

/»  'drive less'  name  «*/ 

for!  J  =  0  i 
£ 

J  < 

NAMESIZE  ;  J++  ) /*  transfer  chars 

*/ 

if! 

buf  C 

J  ]  ! 

!=  '  '  I  /*  (skip  spaces) 

«/ 

name! 

i+»  3  -  buf C  J  3  &  0x7F : 

if! 

J  — 

NAMES 1ZE-3  I  /«  don't  forget  dot 

«/ 

nameC 

i++  3  «  ' . • i 

J 

nameC  i  3  * 

'\0' 

i 

/*  terminate  string 

»/ 

/*  eat  terminal  dot 

«/ 

if!  * ( i ndex ( 

name ,  '  .  1 

1  I  +  II  =■  ■ \0 '  I 

* ( i ndex ( 

name , 

,  ' . '  I  I  -  ’ \0 ' j 

> 

return!  name 

I  ; 

/»  return  pointer 

«/ 

/* - 

clrcrc!)  clears  the  crc  accumulator.  Not  much  to  it,  actually. 

{ Continued  on  page  86) 


84 


Dr.  Dobb's  Journal,  June  1985 


,17c 


Protocols  Listing  (Listing  continued,  text  begins  on  page  66) 


References  globals: 

Modifies  globals:  crcaccum,  checksum: 
- */ 


VOID  clrcrcl  I 
t 

crcaccum  = 
checksum  =  0: 

> 

/» - 

updcrcl)  updates  the  crc  accumulator,  if  'crc'  is  TRUE,  else 
updates  the  checksum. 

'x'  is  the  byte  to  be  added  to  CRC  or  checksum. 

CCITT  po I ynomi a  I . 

References  globals:  crc: 

Modifies  globals:  crcaccum,  checksum: 

- */ 


VO  10  updcrcl  x  ) 
char  x : 

{ 

unsigned  shifter,  i,  flag: 


> 


i f (  crc  ) 
f 

for  ( 
t 


) 

> 


shifter  «  0x80  :  shifter  :  shifter 

flag  =  (crcaccum  &  0x8000); 
crcaccum  (£■*  I: 

crcaccum  1=  ((shifter  &  x)  ?  I 
ifl  flag  ) 

crcaccum  *=  0x1021; 


e  I  se 

checksum  +=  x; 


>>=■=  I  ) 


0) : 


/* - 

abort!)  scans  console  for  *X  abort  code.  Exit  via  error!). 

System  Dependent.  If  not  supportable,  convert  to  null  function 
or  delete  and  use  a  preprocessor  macro  to  make  it  go  away. 
- */ 


VOID  abort ( ) 
t 

if (  _bdos(  6,  OxFF  I  ==  'X'-'e'  ) 

error!  "VrOperator  requested  abort" 


) 


)  ; 


/« - 

error!)  does  error  abort  cleanup 


References  globals:  fdi 


«/ 


VOID  error!  str  ) 
char  *str: 
t 


fc 1 ose (  f d  ) : 

/*  dump  any  open  files 

*/ 

t  x (  CAN  ) : 

/*  alert  remote 

•/ 

putcharl  BELL  ); 

/*  alert  operator 

*/ 

pr intf 1 "\n\nError 

-  IsNnPress  any  key  to  continue",  str 

1  : 

getchar ( )  s 

/**  pause 

*/ 

longjmp!  8to_menu, 

0  ) ;  /*  rejoin  menu 

*/ 

/» - 

sleep!)  does  a  short  delay  to  account  for  transmission  line 
latency,  etc. 

- »/ 


VOID  sleep! ) 
t 

register  unsigned  int  i; 

for!  i =0  :  i  <  MAGIC_NUMBER  ;  i ++  I 
: 

*  (Continued  on  page  88) 


86 


Dr.  Dobb's  Journal,  June  1985 


Protocols  Listing  (Listing  continued,  text  begins  on  page  66) 


/* - 

waitl)  waits  for  a  character  -  timeout  built  in.  Timeout 
condition  causes  error  return  to  menu. 

Input  parameter  'time'  controls  duration  of  wait. 
- 

i nt  waitl  time  I 


Int  time; 

register  unsigned  int  is 

/*  loop  timer 

«/ 

int  Js 

/<•  timeout  count 

*/ 

J  “ 

/*  timeout  count 

•/ 

i  «  Os 

/*  inner  timeout  count 

*/ 

whi le  I  Irxstat  1 1  ) 

if!  i ++  >  MAGIC  NUMBER 

*  3  )/*  about  1.5  secs 

»/ 

if  I  ! 

(time--)  1 

/»  check  retry  count 

•/ 

error  I 

"Receiver  timed  out"  Is 

e  1  se 

/»  more  tries  available 

*/ 

abort  I  I 

s  /*  scan  for  "X 

»/ 

if!  ! J 

I  /*  \n  first  timeout 

•/ 

pr int f I  "\n"  Is 

pr intf I 

"\rtimeout  %d  ",  *+J 

I  s 

> 

i  =  Os 

return  I  rx  I  I  Is 

/»  send  back  char 

*/ 

/* - 

swaitl)  does  a  short  wait  for  a  char  from  rx.  returns  ERROR  on 
a  timeout,  or  char  received. 

- #/ 


i nt  swa it!) 
t 


register  unsigned  int  is 

/•  loop  timer 

»/ 

i  *  0  s 

while!  i ++  <  MAGIC_NUMBER  I 

/*  loop  t iming  test 

*/ 

if!  rxstatl)  I 

/*  anything  yet? 

*/ 

return  I  rx  I  I  I 

s  /*  yes,  return  char 

*/ 

return  I  ERROR  1  s 

/*  t i meout  exit 

*/ 

/* - 

waitcanl)  waits  for  a  character  -  If  recv's  CAN  aborts  to  main 
menu.  Uses  wait!)  for  timing  loop,  so  timeout  also  returns 
to  main  menu.  Input  argument  'time'  controls  duration  of  wait. 
- */ 


int  waitcanl  time  I 
int  timei 
I 

char  chi 

if!  Ich  =  waitl  time  II  ==  CAN  ) 

error!  "Received  cancellation  request*  I; 
returnl  ch  I s 

) 


/«- 


The  following  functions  are  machine  dependant  and  should  be 
modified  to  run  on  your  machine. 


ssssa==s=a 


/» - - - - - 

initl)  initializes  globals.  In  addition,  UART  initialization 
goes  here  as  well,  if  required.  No  input  parameters.  May  have 
some  machine  dependant  stuff  here. 

References  globals: 

Modifies  globals:  crc,  batch,  buffer,  lines 
- «/ 


VOID  initl) 
t 

buffer  “  alloc!  RECSIZE  Is  /»  transfer  buffer  •/ 


Dr.  Dobb's  Journal,  June  1985 


88 

477 


1  ine 

=  a  1  loci  81  ) ; 

/* 

scatch  input  line 

»/ 

crc  = 

/» 

CRC  mode 

*/ 

batch 

=  TRUE; 

/* 

not  really  required 

*/ 

/* - 

r  x ( )  receives  a  char  from  uart .  No  timeout  checking  is  performed. 
Also,  no  status  check  is  done.  Bad  news  if  you  call  this  without 
checking  status  first...  Done  that  way  to  speed  up  things. 


ZORBA  Version 


»/ 


int  rx  ( ) 

{ 

return (  inpl  RXDATA  )  ); 

) 


/» - 

rxstat I  I  returns  TRUE  if  char  waiting  for  input 

ZORBA  Version  (interrupt  fifo) 


int  rxstat I ) 

{ 

return!  inp(  UARTCMD  )  &  2  Is 

} 


•  / 


/» - 

txl  ch  )  transmits  the  char  ’ch',  and  calls  the  CRC/checksum 
update.  A  timeout  is  built  in  for  transmission  faults. 


ZORBA  Version 


*/ 


VOID  txl  ch  ) 
char  ch; 
t 


register  unsigned  int  i; 

/* 

timing  loop  counter 

«/ 

i  =  0; 

whi lei  itxstat  I )  ) 

/* 

ready? 

*/ 

if!  i ++  >  MAGIC 

NUMBER  )/* 

loop  about  3  secs 

*/ 

error  I 

"UART  not  ready??"  ) ; 

outp I  TXDATA ,  ch  ) ; 

/« 

to  UART 

*/ 

updcrcl  ch  ) ; 

/* 

st  i  r  CRC  a  little 

•/ 

/• 


txstatl)  returns  TRUE  if  tx  register  is  empty 
ZORBA  Version 


*/ 


int  txstatl) 

{ 

return!  inpl  UARTCMD  I  &  4  ); 

) 


/• - 

quitl)  does  exit  processing. 

- 

VOID  quitl) 

{ 

exit  I  0  )  ; 

) 


End  Listing 


89 


A~r  a 


Dr.  Dobb's  Journal,  June  1985 


Envoy,  Version  1.1 
Company:  Artisoft  Inc.,  2450  E. 
Speedway,  Suite  4,  Tucson, 
AZ  85719  (602)  327-4305 
Computer:  IBM  PC  family,  16-bit 
compatibles,  8-bit  CP/M 
Price:  $49.95 

Circle  Reader  Service  No.  144 
Reviewed  by  David  W.  Carroll 

The  trend  in  microcomputer  software 
is  toward  high-volume,  high-quality, 
low-cost  software  (under  $100).  Bor¬ 
land  International  pioneered  this 
“volks-software”  concept  with  Turbo 
Pascal  and  Sidekick,  and  other  com¬ 
panies  are  now  following  this  path. 

Commercial  telecommunications 
software  typically  costs  $200  and 
more,  but  Artisoft  of  Tucson,  is  now 
offering  its  Envoy  communications 
package  with  versions  that  run  on  al¬ 
most  any  microcomputer  system  for 
only  $49.95. 

“With  over  a  dozen  major  com¬ 
mercial  programs  available,  who 
needs  another?”  you  might  well  ask. 
First,  the  price  of  Envoy  represents  a 
breakthrough.  Second,  the  program 
is  small,  fast,  and  easy  to  use.  Third, 
Envoy  offers  some  unique  features. 
Finally,  compatible  versions  are 
available  for  almost  any  type  of  mi¬ 
crocomputer  and  operating  system. 

Operation 

Envoy  is  a  menu-driven  communica¬ 
tions  program.  This  means  that  it  has 
two  modes:  command  and  terminal. 
In  command  mode,  the  current  menu 
is  shown  on  the  screen.  In  terminal 
mode,  you  are  on  line  with  your  serial 
port  or  modem,  and  Envoy  is  trans¬ 
parent  to  all  keyboard  input  except 
for  your  terminal  mode  “exit”  char¬ 
acter,  usually  escape  <Esc>.  Envoy 


Copyright  by  David  W.  Carroll 


requires  you  to  set  your  terminal 
mode  exit  character  when  you  start 
each  session.  The  program’s  main 
menu  appears  below: 

***  Main  Menu  *** 

<T>  — Smart  Terminal 

<E>  —  ENVOY  file  transfers 

<X>  —  XMODEM  file  transfers 
<P>  — Port  Parameters 

<U>  — Utilities 
<Ret>  —  Previous  menu 
<Esc>  —  Exits  ENVOY 

Enter  your  selection  -> 

The  program  offers  dumb  terminal 
operation,  ASCII  file  send  and  buffer 
capture,  and  protocol  file  transfers. 
Envoy  supports  both  the  Ward  Chris¬ 
tensen  XMODEM  protocol  and  the 
little  known  ANSI  standard  X3.28 
file  transfer  protocol. 

The  ANSI  protocol  is  designed  so 
that  either  computer  in  a  data  link 
can  act  as  master  while  the  other  is  a 
slave.  This  allows  multiple  file  trans¬ 
fers  and  remote  system  utility  opera¬ 
tion  (you  can  use  the  remote  system’s 
utility  menu).  The  transfer  protocol 
is  faster  than  XMODEM  and  provides 
greater  error  checking. 

This  menu  appears  when  you  select 
Smart  Terminal: 

***  Smart  Terminal  *** 

Capture  Buffer  is  Off 

< T >  — Terminal  Mode 

<S>  —  Send  Text  File 

<M>  — Modem  Functions 
<0>  — Terminal  Mode  Options 
<C>  — Capture  Buffer  On 
<B>  — Buffer  Utilization 

<W>  — Write  Buffer  to  Disk 
<D>  —  Display  Buffer 


<U>  — Utilities 
<Ret>  —  Previous  menu 
<Esc>  —  Exits  ENVOY 

Enter  your  selection  ->  B 

Capture  Buffer  57362  bytes 
Remaining  Space  57362  bytes 

[Enter  any  key  to  continue] 

Envoy  uses  an  autodial  menu  and 
even  supports  simple  “script”  files  for 
autologon.  It  will  support  almost  any 
modem  because  of  its  free-form  mo¬ 
dem  command/phone  number  direc¬ 
tory.  The  directory  consists  of  a  file 
with  entries  created  using  a  word  pro¬ 
cessor  or  editor  (like  WordStar  or  ED- 
LIN).  A  few  typical  directory  entries 
for  a  Hayes  Smartmodem  follow: 

Redial  [A/I] 

Disconnect  [_+  +  +  _ATH0] 
Information  [ATDT1-555-1212] 

Here  is  a  sample  directory  listing: 

***  Modem  Functions  *** 

<A>  — Redial 
<B>  — Disconnect 
<C>  — Information 
<D>  ARTISOFT,  Inc. 

<E>  — CompuServe 
<Ret> —  Previous  menu 
<Esc>  —  Exits  ENVOY 

Enter  your  selection  -> 

Envoy  scripts  can  include  key¬ 
board  control  characters,  delays,  and 
character-matching  strings.  The  spe¬ 
cial  characters  *  ,  _,  and  I  provide 
these  features  when  inserted  in  script 
entries.  Question  marks  indicate 
strings  to  match. 

You  can  set  the  terminal  mode 


Dr.  Dobb’s  Journal,  June  1985 


92 

479 


communications  parameters,  which 
include  stripping  line  feeds,  local 
echo  mode,  and  delay  of  up  to  9  sec 
between  each  line  sent  for  ASCII  text 
file  transfers: 

***  Terminal  Mode  Options  *** 

<L>  — Send  LFs  On 
<D>  — Delay  Lines  (0) 

<E>  — Local  Echo  On 
<X>  —  Change  Exit  Key 
<Ret>  —  Previous  menu 
<Esc>  —  Exits  ENVOY 

Enter  your  selection  -> 

You  may  also  set  port  parameters 
(speed,  parity,  etc.)  from  within 
Envoy. 

Full  operating  system  utilities  are 
available  from  within  Envoy,  and 
PC/MSDOS  users  have  full  access  to 
DOS  2.0  subdirectory  commands  and 
paths.  The  program  even  supports  re¬ 
mote  system  operation  (unattended). 
This  menu  is  for  the  IBM  PC  version 
of  Envoy  PC: 

***  Utilities  *** 

<D>  — Directory 

<  L>  —  Log  to  another  disk 
<E>  — Erase  files 

<T>  —  Type  files 

<C>  —  Copy  a  file 

<R>  — Rename  a  file 

<P>  — Print  files 

<N>  —  Change  directory  path 

<S>  —  Create  a  directory  path 

<X>  —  Remove  a  directory  path 

<  Ret>  —  Previous  menu 
<Esc>  —  Exits  ENVOY 

Enter  your  selection  — > 

Envoy  is  written  in  assembly  code; 
that  means  it  is  fast  and  compact. 
The  program  takes  a  total  of  7-9K 
disk  space,  depending  on  the  version. 
That  is  small  enough  to  put  on  every 
floppy— -not  much  more  space  than 
the  old  standby  MODEM7  program 
requires — and  Envoy  offers  many 
more  features. 

Evaluation 

Envoy  PC  operates  reliably  in  all 
modes.  My  only  complaints  were  re¬ 


garding  the  effort  required  to  make 
new  directory  entries.  I  found  it 
clumsy  to  exit  the  program,  start  up  a 
word  processor,  and  enter  numbers  in 
the  directory  file.  The  lack  of  a  real¬ 
time  directory  entry  mode,  similar  to 
what  Crosstalk  and  Smartcom  II  pf- 
fer,  was  irritating.  I  also  didn’t  like  to 
reenter  the  required  modem  com¬ 
mands  in  each  directory  entry, 
though  this  is  a  minor  point. 


Overall,  the  Envoy  communica¬ 
tions  programs  are  well  designed, 
easy  to  use,  and  versatile.  For  a  rea¬ 
sonable  price,  they  provide  virtually 
every  communications  feature  you 
could  require. 

DD| 


93 

AHCI 


Dr  Dobb’s  Journal,  June  1985 


16-BIT  SOFTWARE  TOOLBOX 


by  Ray  Duncan 

IBM  PC  WordStar  and  the 
HP  LaserJet 

We  at  Laboratory  Microsystems  re¬ 
cently  purchased  an  HP  LaserJet 
printer  to  produce  our  various  soft¬ 
ware  manuals.  After  buying  the  La¬ 
serJet,  we  were  dismayed  to  find  that 
it  is  essentially  impossible  to  configure 
IBM  PC  WordStar  to  properly  use  the 
printer’s  boldface,  italics,  and  various 
fonts.  WordStar  allows  the  user  to 
patch  in  only  four-character  escape 
sequences,  while  nearly  every  impor¬ 
tant  LaserJet  control  sequence  is  five 
bytes  or  longer.  When  we  contacted 
the  HP  dealer  about  this  problem,  his 
response  was,  “Well,  you’ll  just  have 
to  switch  to  Spellbinder;  it  supports  all 
the  LaserJet  capabilities.” 

Because  we  have  around  10  Mb  of 
documentation  files  that  we  would 
have  to  convert  if  we  switched  word 
processors,  this  option  didn’t  sound 
too  attractive.  My  first  attempt  at 
getting  around  the  problem  was  to 
write  a  character  filter  that  read  a 
WordStar  document  file,  translated 
the  various  control  codes  (such  as  A  B) 
into  the  proper  LaserJet  escape  se¬ 
quences,  and  sent  the  result  to  the 
printer.  This  worked  acceptably  as 
far  as  print  quality,  but  it  required  an 
exit  from  WordStar  whenever  you 
wanted  to  print  something,  which 
made  WordStar’s  capability  of  back¬ 
ground  printing  while  editing  useless. 

It  began  to  look  as  if  we  would 
have  to  face  the  facts  and  do  some 
heavy-duty  disassembly  and  patching 
of  WordStar.  On  a  long  shot,  I 
dropped  a  note  to  MicroPro  Techni¬ 
cal  Support,  asking  them  if  they 
knew  of  anyone  who  had  already 
worked  out  the  necessary  patches. 
Much  to  my  amazement,  I  got  an  im¬ 
mediate  phone  call  from  a  nice  lady 
at  MicroPro  who  said  that  a  LaserJet 
version  of  WordStar  was  already 


available  to  licensed  users  for  $25.00. 
I  duly  sent  off  my  check  and  in  a  few 
days  received  a  diskette  containing  a 
new  WS.OVL  file  and  a  batch  file  that 
performs  some  patches  to  the 
WS.COM  file. 

After  following  the  installation 
steps  in  the  accompanying  documen¬ 
tation,  I  ended  up  with  a  WordStar 
that  supports  italics,  boldface,  double¬ 
strike,  underline,  over-strike,  micro¬ 
justification,  superscripting,  sub¬ 
scripting,  variable  character  pitch, 
variable  line  height,  and  multiple 
copies  on  the  HP  LaserJet.  It’s  a  little 
slow  (only  2-3  pages/min  through¬ 
put)  and  has  a  few  bugs  (a  AY  at  the 
start  of  a  line  makes  it  go  crazy),  but  it 
works  pretty  well.  You  can  get  this  en¬ 
hancement  disk  by  sending  a  polite 
letter,  including  your  WordStar  ver¬ 
sion  3.3  serial  number  and  $25.00,  to 
LaserJet  Technical  Support,  Micro¬ 
Pro  International  Corp.,  33  San  Pablo 
Ave.,  San  Rafael,  CA  94903. 

Microsoft  8086 
MacroAssembler  2.0 

Many  moons  after  Microsoft  an¬ 
nounced  version  2.0  of  the  8086  Ma¬ 
croAssembler,  it  has  appeared  in  the 
IBM  Product  Centers  in  the  last  few 
weeks  without  fanfare.  Along  with 
the  new,  higher  version  number 
comes  a  new,  higher  price  ($175.00). 
Silly  me.  I  thought  that  because  I  had 
bought  version  1  with  its  zillions  of 
bugs  I  might  be  entitled  to  a  reduced 
cost  update,  but  the  Product  Center 
salesman  nearly  killed  himself  laugh¬ 
ing  when  I  brought  that  up.  Finally, 
gritting  my  teeth  and  hoping  that  the 
new  version  would  be  a  little  more  ro¬ 
bust  than  the  old,  I  hauled  out  my 
well-worn  MasterCard  and  took 
home  a  copy. 

If  you  choose  to  go  the  same  route, 
what  will  you  get  for  your  money? 


Well,  the  first  thing  you  get  is  a  Ma¬ 
croAssembler  that  has  an  even  more 
severe  case  of  bloat  than  the  first  ver¬ 
sion  (the  EXE  file  is  76,544  bytes 
compared  to  67,584  bytes  for  version 
1).  The  8087  mnemonics  are  now 
fully  supported,  along  with  most  of 
the  additional  80286  mnemonics  that 
would  be  used  in  that  CPU’s  real 
mode.  The  performance  of  the  Ma¬ 
croAssembler  has  improved  consider¬ 
ably:  a  106K  source  file  (approxi¬ 
mately  3200  lines),  which  required 
13  min  57  sec  to  assemble  with  ver¬ 
sion  1,  is  assembled  in  4  min  13  sec 
with  version  2. 

Version  2  provides  a  new  version  of 
the  Linker  (which  has  also  suffered 
more  bloat  to  the  tune  of  about  6K),  a 
documented  Library  manager,  and  a 
new  program  called  SALUT  (Struc¬ 
tured  Assembly  Language  Utility), 
which  converts  an  assembly  language 
source  file  containing  high-level  con¬ 
trol  structures  into  a  source  file  that 
can  be  fed  to  the  assembler.  Although 
IBM  pushes  SALUT  pretty  hard  in 
the  MacroAssembler  manual,  I  see 
no  evidence  in  the  BIOS  listings  or 
distributed  PCDOS  assembler  source 
files  (such  as  VDISK.ASM)  that  IBM 
programmers  ever  use  it.  Perhaps 
IBM  is  adopting  some  of  Apple’s  poli¬ 
cies  for  “the  rest  of  us.” 

The  manual  for  the  MacroAs¬ 
sembler  has  been  drastically  over¬ 
hauled.  The  previous  “encyclopedia” 
format,  which  devoted  a  page  to  each 
of  the  assembler  directives  and  8086 
mnemonics  with  examples,  has  been 
abandoned.  The  new  manual  is  heavi¬ 
ly  oriented  to  operation  of  the  assem¬ 
bler  and  associated  utilities  (every¬ 
thing  is  called  “sessions”  now)  and 
provides  almost  no  programming 
guidance  whatsoever.  A  copy  of  Rec¬ 
tor  and  Alexy’s  The  8086  Book  or  the 
Intel  iAPX  86  User’s  Manual  will  be 


Dr.  Dobb’s  Journal,  June  1985 


94 

481 


indispensable  to  people  who  start  out 
with  the  new  Macro  Assembler  and 
no  previous  experience. 

Some  of  the  known  bugs  in  version 
1  appear  to  have  been  fixed;  these  in¬ 
clude  the  operand  order  in  the  SHL 
and  SHR  operators,  the  XOR  that 
used  to  work  like  OR,  the  packed 
decimal  data  generated  by  the  DT 
pseudo-op,  the  failure  of  DUP  to  work 
properly  inside  the  invocation  of  a 
STRUC,  and  the  lack  of  error  mes¬ 
sages  and  generation  of  “funny”  op¬ 
codes  for  some  nonexistent  instruc¬ 
tions  (such  as  CMP  ES,0).  Error 
detection  (and  the  quality  of  the  error 
messages)  seems  generally  improved. 

Some  bugs,  however,  remain.  I’ve 
provided  a  few  examples  in  Listing 
Two  (page  104).  A  major  problem 
seems  to  be  inconsistent  handling  of 
signed  and  unsigned  values  by  the 
logical  operators.  This  is  “explained” 
to  some  extent  by  a  comment  in  the 
manual  on  page  2-28:  “When  rela¬ 
tional  operators  are  used,  results  are 
produced  by  comparing  17-bit  signed 
numbers,  where  the  17th  bit  can  con¬ 
tain  only  a  sign  bit.”  This  accounts 
for  the  behavior  of  the  LT  and  GT  op¬ 
erators,  but  if  it  were  strictly  true,  the 
expression  “— 1  EQ  OFFFFH”  would 
return  a  false  flag  (which  it  doesn’t, 
as  you  can  see  by  the  listing).  Any¬ 
way,  it’s  a  mystery  to  me  why  Micro¬ 
soft  thought  it  should  support  within 
expressions  a  17-bit  data  type  that 
has  no  physical  equivalent  in  the 
8086  processor  family.  Wouldn’t  it 
make  more  sense  to  have  the  assem¬ 
bler  evaluate  expressions  to  the  same 
results  that  you  would  get  with  in-line 
machine  code? 

The  TRIM  Filter 

In  the  last  few  months,  we  have  print¬ 
ed  three  new  “filters”  for  MSDOS: 
CLEAN,  TEE,  and  TK.  In  this 
month’s  column,  we  print  another  fil¬ 
ter,  named  TRIM,  contributed  by  A. 
K.  Head  of  Melbourne,  Australia 
(Listing  One,  page  96).  I  have  taken 
the  liberty  of  retyping  and  reformat¬ 
ting  the  program  from  his  original 
listing,  and  I  have  added  some  error 
messages;  hence,  credit  for  the  con¬ 
cept  and  implementation  should  go  to 
Mr.  Head,  while  blame  for  any  bugs 
or  other  deficiencies  specially  intro¬ 


duced  by  publication  in  DDJ  is  mine. 

In  spite  of  its  brevity,  TRIM  is  a 
fairly  sophisticated  filter  that  can 
perform  one  of  several  operations: 

•  Exclude  all  the  characters  outside  a 
given  range  of  columns  from  each 
line  of  a  file 

•  Exclude  all  the  characters  inside  a 
given  range  of  columns  from  each 
line  of  a  file 

•  Delete  trailing  blanks  from  each 
line  of  a  file 

•  Delete  blank  lines  from  a  file 

Documentation  for  the  use  of 
TRIM  appears  in  the  beginning  of  the 
source  listing  itself.  You  can  use 
TRIM  in  combination  with  SORT  and 
various  other  commands  by  means  of 
the  Unix-like  pipes  of  MSDOS  2.x  to 
perform  complex  operations.  For  ex¬ 
ample,  the  following  command  line 
will  isolate  the  filename  and  exten¬ 


sion  from  each  line  of  a  directory  list¬ 
ing,  sort  the  list,  and  leave  the  result 
in  a  file  named  SORTED. DIR: 

A>DIR  1  TRIM  1,121  SORT 
>SORTED.DIR 

Incidentally,  you  can  download  the 
source  for  all  the  programs  printed  in 
this  column,  as  well  as  many  other 
useful  MSDOS  utilities,  from  the 
Laboratory  Microsystems  Bulletin 
Board  System  at  (213)  306-3530 
(300  or  1200  baud).  This  BBS  is  up 
between  6  PM  and  9  AM,  Pacific 
Time,  on  weekdays  and  for  24  hours 
on  weekends  and  holidays.  DDJ 


95 

482 


Dr.  Dobb’s  Journal,  June  1985 


16-Bit  Toolbox  (Text  begins  on  page  94) 
Listing  One 


name  trim 

page  55,132 

title  'TRIM  -  excerpt  lines  of  a  file' 

TRIM  -  excerpts  selected  columns  from  each  line 

of  a  file  and  writes  them  to  the  selected 
output  device  or  file. 

A  "filter"  for  MS-DOS  or  PC-DOS  version  2  or  higher, 
after  the  fashion  of  Unix.  Reads  from  the  standard  input 
(redirectable)  and  writes  to  the  standard  output  (redirectable) . 
Error  messages  are  directed  to  the  standard  error  device. 

TRIM  can  be  (and  usually  would  be)  used  in  a  pipe,  e.g. 

|  TRIM  7,45  | 

transmits  only  the  characters  in  columns  7  to  45  (inclusive) 
of  each  line.  A  minus  sign  reverses  the  action,  e.g. 

|  TRIM  -7,45  | 

transmits  all  characters  except  those  in  columns  7  to  45. 
Special  actions: 

|  TRIM  0  |  deletes  trailing  spaces  from  lines,  and 

|  TRIM  -0  j  also  discards  empty  lines 

By  A.  K.  Head,  6  Duffryn  Place,  Melbourne,  Australia  3142 
reformatted  and  error  handling  added  by  Ray  Duncan 


command 

equ 

8  Oh 

buffer  for  command  tail 

fcbl 

equ 

5ch 

default  file  control  block  #1 

fcb2 

equ 

6ch 

default  file  control  block  #2 

buf len 

equ 

16384 

buffer  length,  alter  to  taste 

cr 

equ 

Odh 

ASCII  carriage  return 

If 

equ 

Oah 

ASCII  line  feed 

ff 

equ 

Och 

ASCII  form  feed 

eof 

equ 

Olah 

End-of-file  marker 

tab 

equ 

09h 

ASCII  tab  code 

blank 

equ 

2  Oh 

ASCII  blank 

DOS  2.x  pre-defined  handles 

stdin 

equ 

0000 

standard  input  file 

stdout 

equ 

0001 

standard  output  file 

stderr 

equ 

0002 

standard  error  file 

stdaux 

equ 

0003 

standard  auxilliary  file 

stdprn 

equ 

0004 

standard  printer  file 

cseg 

segment 

para  public  'CODE 

assume 

cs : cseg, ds: cseg 

org 

100H 

7 

start  .COM  at  100H 

start: 

jmp 

near  ptr  trim 

paraml 

dw 

0 

r 

command  parameter  #1 

param2 

dw 

0 

f 

command  parameter  #2 

sign 

dw 

0 

/ 

nonzero  if  "-"  in  command 

count 

dw 

0 

7 

column  count,  current  line 

topin 

dw 

0 

r 

chars  in  input  buffer  -  1 

char 

db 

0 

i 

current  character 

Dr.  Dobb’s  Journal,  June  1 985 


96 

483 


16-Bit 


(Listing  continued,  text  begins  on  page  94) 


Listing  One 


trim 


proc 

far 

xor 

si,  si 

1 

initialize  buffer  pointers 

xor 

di,di 

mov 

bx,fcbl+l  ; 

addr 

of  parsed  parameter  1 

call 

getprm 

! 

convert  it 

cmp 

ax,  0 

je 

trunc 

! 

zero 

parameter,  go  truncate 

t 

trailing  blanks  etc. 

mov 

paraml, 

ax  ; 

save 

first  parameter 

mov 

bx,fcb2+l  ? 

addr 

of  parsed  parameter  2 

cmp 

byte  ptr  [bx], blank  ; 

is  it  present  at  all? 

jne 

trimO 

t 

yes, 

proceed 

jmp 

err3 

i 

no. 

exit 

trimO : 

call 

getprm 

;  convert  it 

mov 

param2 , ax 

;  save  2nd  parameter 

cmp 

ax, paraml 

;  is  end  column  <  start 

jnb 

triml 

jmp 

err3 

;  yes,  exit  with  error  : 

triml: 

mov 

count, 0 

;  starting  a  new  line, 

initialize  column  counter 


trim2 : 

inc 

count 

call 

cin 

mov 

al ,  char 

and 

al , 07fh 

cmp 

al,cr 

je 

trims 

mov 

ax, count 

cmp 

sign,  0 

jne 

trim4 

cmp 

ax, paraml 

jb 

trim2 

cmp 

ax,param2 

ja 

trim2 

trim3 : 

call 

cout 

jmp 

trim2 

trim4 : 

cmp 

ax, paraml 

jb 

trim3 

cmp 

ax,param2 

ja 

trim3 

jmp 

trim2 

trims : 

call 

cout 

call 

cin 

call 

cout 

jmp 

triml 

trunc: 


truncl:  mov  count ,0 

xor  bp /bp 


;  count  characters 
;  read  a  character 
;  is  it  carriage  return? 

;  (ignore  high  bit  in  case 
;  this  is  Wordstar  file) 

;  yes,  found  end  of  line 
;  fetch  current  char  count 
;  is  this  include  or  .exclude  call? 
7  jump,  -,  exclude  range 
;  proceed,  +,  include  range 
;  is  column  counter  within 
;  desired  range? 

;  no,  discard  this  char. 

;  no,  discard  this  char. 

7  yes,  use  this  character 
?  get  next  char. 

7  is  column  counter  outside 
7  of  excluded  range? 

7  yes,  use  this  character 

;  yes,  use  this  character 
7  no,  discard  this  character 

;  found  end  of  line 
7  write  carriage  return 
7  read  presumed  line  feed 
7  write  line  feed 

?  come  here  if  zero  parameter 
?  to  delete  trailing  blanks  from 
;  all  lines.  If  -  sign  was  in 
7  command  parameter,  also  delete 
7  empty  lines  completely. 

?  initialize  column  counter 
;  init  line  pointer 


trunc2:  call 
mov 
and 
cmp 
je 
mov 
mov 


byte  ptr  ds: [line+bp] ,al 


read  a  character 
is  it  carriage  return 
(ignore  high  bit  in  case 
this  is  Wordstar  file) 
yes,  go  process  end  of  line 
transfer  char,  to  forming  line 


100 


Dr.  Dobb's  Journal,  June  1985 


inc 

bp 

emp 

char, blank 

;  is  character  a  space  code? 

je 

trunc2 

;  yes,  get  next  char 

mov 

count , bp 

;  no,  update  column  count 

jmp 

trunc2 

;  get  next  char. 

t rune 3 : 

xor 

bp,  bp 

;  text  string  now  in  LINE 

call 

cin 

;  discard  line  feed 

emp 

count, 0 

;  was  line  empty? 

jne 

trunc4 

;  no,  go  output  it 

emp 

sign,  0 

;  deleting  empty  lines? 

jne 

truncl 

;  yes,  discard  this  one 

jmp 

trunc5 

;  no,  send  cr-lf  sequence 

trunc4 : 

;  now  transfer  LINE  to  BUFOUT 

mov 

al,byte  ptr  ds:[bp+line] 

mov 

char, al 

;  get  next  char  and 

call 

cout 

;  send  it  to  output 

inc 

bp 

emp 

bp , count 

;  entire  line  sent  yet? 

jb 

trunc4 

;  no,  send  another  char 

trunc5 : 

mov 

char, cr 

;  send  carriage  return 

call 

cout 

mov 

char, If 

;  and  line  feed 

call 

cout 

jmp 

truncl 

exit: 

emp 

di,  0 

;  output  buffer  empty? 

je 

exitl 

;  yes 

call 

outbuf 

;  no,  flush  it 

exitl : 

mov 

ax, 4c00h 

;  exit  with  return  code=0 

int 

21h 

;  if  no  errors  were  encountered 

err: 

;  print  error  message  and  exit. 

;  DS:DX  =  addr  of  message 
;  CX  =  length  of  message 

;  AL  =  return  code 

push 

ax 

;  save  return  code 

mov 

ah, 4 Oh 

;  function  40  =  write 

mov 

bx, stderr 

;  handle  for  error  output 

int 

21h 

pop 

ax 

;  retrieve  return  code 

mov 

ah, 4ch 

;  function  4C  =  exit 

int 

21h 

errl : 

mov 

dx, offset 

errlmsg  ;  print  "output  device  error 

mov 

cx, errllen 

mov 

al,  1 

;  return  code  =  1 

jmp 

err 

err2 : 

mov 

dx, of fset 

err2msg  ;  print  "disk  is  full". 

mov 

cx, err21en 

mov 

al ,  2 

;  return  code  =  2 

jmp 

err 

err3 : 

mov 

dx, of fset 

err3msg  ;  print  "bad  parameter" 

mov 

cx, err31en 

mov 

al ,  3 

;  return  code  =  3 

jmp 

err 

err4 : 

mov 

dx, of fset 

err4msg  ;  print  "input  device  error" 

mov 

cx, err41en 

mov 

al ,  4 

;  return  code  =  4 

jmp 

err 

trim 

endp 

cout 

proc 

near 

;  output  contents  of  "char" 

;  with  autobuffering 

mov 

al , char 

mov 

byte  ptr  [di+bufout] , al 

inc 

di 

Dr.  Dobb’s  Journal,  June  1 985 


101 

485 


16-Bit  (Listing  continued,  text  begins  on  page  94) 


Listing  One 


cmp 

di,buflen 

;  buffer  full  yet? 

jb 

coutl 

call 

outbuf 

;  write  buffer 

coutl: 

ret 

;  back  to  caller 

cout 

endp 

outbuf 

proc 

near 

;  write  buffer  to  std  output 

mov 

ah, 4 Oh 

;  function  40  =  write 

mov 

bx, stdout 

;  predefined  handle 

mov 

cx,  di 

;  number  of  characters 

lea 

dx,bufout 

;  DS:DX  =  buffer  addr 

int 

21h 

;  request  DOS  service 

jc 

errl 

;  jump,  device  write  error 

cmp 

ax,  di 

jne 

err2 

;  jump,  disk  is  full 

xor 

di,di 

;  initialize  output  buff  pointer 

ret 

?  back  to  caller 

outbuf 

endp 

cin 

proc 

near 

;  input  next  char  with  buffering 

inc 

si 

;  bump  input  buffer  pointer 

cmp 

si , topin 

;  buffer  exhausted? 

jbe 

cin2 

;  no,  jump 

mov 

ah, 3fh 

;  yes,  read  some  more  data 

mov 

bx, stdin 

;  predefined  handle 

mov 

cx,buflen 

;  max  length  to  read 

lea 

dx, buf in 

;  DS:DX  =  input  buffer  addr 

int 

21h 

;  request  DOS  service 

jc 

err4 

;  jump,  input  device  error 

cmp 

ax,  0 

;  end  of  file? 

jne 

cinl 

jmp 

exit 

;  yes,  goto  success  exit  point 

cinl : 

dec 

ax 

;  save  offset  of  top  of  data 

mov 

topin, ax 

xor 

si,  si 

;  zero  input  buffer  pointer 

cin2 : 

mov 

al,byte  ptr 

[si+bufin] 

mov 

char, al 

;  get  next  char 

ret 

cin 

endp 

getprm 

proc 

near 

;  convert  numeric  parameter  to 
;  binary  and  return  it  in  AX 

xor 

ax,  ax 

;  initialize  forming  answer 

mov 

cl, [bx] 
cl, 

;  get  first  char 

cmp 

;  is  it  minus  sign? 

jne 

getp2 

;  no ,  j  ump 

inc 

sign 

;  yes,  set  flag  and 

getpl : 

inc 

bx 

;  bump  command  string  pointer 
;  past  the  sign 

mov 

cl , [bx] 
cl, 'O' 

;  get  next  char 

getp2 : 

cmp 

;  at  least  1  legal  digit? 

jb 

getp6 

;  no,  exit 

cmp 

ja 

cl,  '9' 
getp6 

;  no,  exit 

jmp 

getp4 

getp3 : 

inc 

bx 

;  advance  through  string 

mov 

cl, [bx] 
cl, 'O' 

cmp 

;  make  sure  legal  digit  0-9 

jb 

getp5 
cl,  '9  ' 

;  not  digit,  jump 

cmp 

ja 

getp5 

;  not  digit,  jump 

mov 

dl,  10 

;  previous  answer  *  10 

mul 

dl 

getp4 : 

sub 

cl, 'O' 

;  add  in  the  new  digit 

xor 

ch,  ch 

add 

ax,  cx 

cmp 

ah,  0 

;  new  answer  >  255? 

je 

getp3 

;  no,  keep  converting 

jmp 

err3 

;  yes,  illegal  parameter,  exit 

getp5 : 

cmp 

byte  ptr[bx] 

, blank  ;  if  not  digit,  must  be  blank 

102 

dftfi 


Dr.  Dobb's  Journal,  June  1985 


jne 

getp6  ;  exit,  bad  parameter 

ret 

;  back  to  caller 

getp6 : 

jmp 

err3  ;  ...  since  too  far  to  reach 

getprm 

endp 

;  direct  with  conditional  branch 

errlmsg 

db 

cr ,  If 

db 

'trim:  output  device  error' 

db 

cr ,  If 

errllen 

equ 

(this  byte) - (of fset  errlmsg) 

err2msg 

db 

cr,  If 

db 

'trim:  disk  is  full.' 

db 

cr,  If 

err21en 

equ 

(this  byte) - (of fset  err2msg) 

err3msg 

db 

cr,  If 

db 

'trim:  bad  parameter' 

db 

cr,  If 

err31en 

equ 

(this  byte) - (of fset  err3msg) 

err4msg 

db 

cr,  If 

db 

'trim:  input  device  error' 

db 

cr,  If 

err41en 

equ 

(this  byte) - (of fset  err4msg) 

buf  in 

equ 

this  byte  ;  data  is  read  here 

;  from  the  standard  input 

bufout 

equ 

bufin+buflen  ;  data  to  be  written  to 

;  standard  output  is  built  here 

line 

equ 

bufout+buflen  ;  temporary  line  buffer 

cseg 

ends 

end 

start 

End  Listing  One 


103 


Dr.  Dobb's  Journal,  June  1985 


AA7 


16-Bit  ^Listing  continued,  text  begins  on  page  94) 

Listing  Two 


1 

name  optest 

2 

page  55,132 

3 

title  ’OPTEST  --  test  HASH  bugs’ 

3 

Demonstrate  some  anomalies  in  the  Macro  Bssembler  v.  2. 

5 

6 

7 

Ibis  was  run  on  HASH  .EXE  file  dated  7-18*81 

0000 

cseg  segment  para  public  ’C00E’ 

8 

assume  cs:cseg,ds:cseg,es:cseg 

9 

0100 

org  100b 

10 

11 

SHR  and  SHL  don’t  distinguish  between  signed  and  unsigned  data 

12 

0100 

0001 

du  -2  shr  1 

13 

0102 

0001 

du  2  shr  1 

11 

0101 

0002 

dw  -1  shl  1 

15 

0108 

0002 

du  1  shl  1 

18 

17 

EQ  and  NEQ  seem  to  recognize  the  decimal  and  hex  equivalents.. 

19 

0108 

rrrr 

du  -1  eq  Offffh 

19 

oi  on 

0000 

du  -1  ne  Offffh 

20 

21 

but  the  comparison  operators  don’t  ... 

22 

010C 

rrrr 

du  1  gt  -1 

23 

010E 

0000 

du  1  gt  Offffh 

21 

0110 

0000 

du  1  It  -1 

25 

0112 

rrrr 

du  1  It  Offffh 

28 

27 

on  M0U  instructions,  if  source  operand  is  missing,  no 

28 

error  message  is  produced  and  machine  code  is  generated 

29 

as  though  the  source  operand  uere  an  immediate  "O’ . 

30 

0111 

30  0000 

cmp  ax, 

31 

32 

generates  the  same  machine  code  as 

33 

0117 

30  0000 

cmp  ax,0 

31 

35 

Assembler  generates  erroneous  machine  code  uhen  last  digit  of 

38 

literal  is  "B"  or  “0"  and  the  default  radix  is  hexadecimal 

37 

0010 

.radix  18 

38 

0118 

83  EB  08 

sub  bx,0a 

39 

0110 

83  EB  00 

sub  bx,0b  ;  generates  same  code  as  SUB  BX,0 

10 

0120 

83  EB  0C 

sub  bx,0c 

11 

0123 

83  EB  00 

sub  bx,0d  ;  generates  same  code  as  SUB  BX,0 

12 

0128 

83  EB  0E 

sub  bx,0e 

13 

11 

0129 

cseg  ends 

15 

end 

End  Listings 


104  Dr.  Dobb' s  Journal,  June  1985 


CP/M  EXCHANGE 


by  Robert  Blum 


Alternate  Console  for  SID  provide  a  standard  way  of  interacting  monitor.  The  patch  in  the  Listing 
Necessity  was  the  mother  of  inven-  with  the  BDOS  to  direct  application  (page  107)  is  only  23  bytes  long,  the 

tion  that  spurred  Bridger  Mitchell  of  program  messages  to  one  display  de-  remainder  of  the  code  providing  the 

Plu*Perfect  Systems  to  develop  this  vice  while  providing  an  additional  necessary  relocation  and  memory 

useful  modification  for  ZSID,  SID,  or  path  for  any  messages  coming  from  protection  services. 

DDT.  Apparently  during  the  develop-  the  debugging  program.  This  pro-  In  operation,  each  time  SID  calls 
ment  and  debugging  of  a  CRT  screen-  gram  dynamically  patches  the  SID  for  BDOS  console  I/O  service, 

handling  routine,  Mitchell  realized  debugger  to  redirect  all  of  its  console  SID2TTY  changes  the  IOBYTE  to  re- 

that  he  had  to  find  an  alternate  path  input  and  output  to  the  logical  TTY:  fleet  a  console  assignment  of  TTY: 

for  the  messages  coming  from  the  de-  device  while  allowing  the  program  and  restores  it  to  the  original  value 

bugging  program.  Each  time  SID  dis-  under  test  to  continue  using  the  CRT:  afterwards.  To  protect  high  memory, 

played  a  message,  it  altered  the  con-  device  undisturbed.  If  you  are  using  the  patch  is  relocated  below  SID  and 

text  of  the  original  screen,  making  it  either  ZSID  or  DDT,  you  should  use  also  below  any  .SYM  and  ,UTL  files, 

practically  impossible  to  decide  ZSID2TTY  and  DDT2TTY,  respec-  which  should  be  loaded  first.  Operat- 

whether  the  screen-handling  routine  tively,  to  patch  the  debugger.  ing  procedures  are  well  documented 

was  functioning  properly.  Even  By  using  a  second  terminal  as  the  within  the  program  source  listing, 
worse,  performing  an  instruction  TTY:  device  to  “command”  SID,  you 

trace  completely  destroyed  the  screen  can  debug  programs  without  interfer-  CP/M  Plus  Inaccuracy 

contents:  as  each  instruction  execut-  ing  with  standard  console  input  and  The  CP/M  Plus  documentation  states 

ed,  a  new  line  of  information  from  output.  Assuming  the  usual  CON:  that  the  fourth  field  of  a  RSX  header, 

SID  was  displayed,  scrolling  yet  an-  =CRT:  device  assignment,  a  pro-  labeled  PREVIOUS,  contains  a  16-bit 

other  data  line  off  the  top  of  the  CRT  gram’s  standard  output  will  appear  pointer  to  the  previous  RSX  in  the 

screen,  until  the  screen  was  filled  on  the  video  monitor  exactly  as  if  the  chain  or  to  memory  location  5  if  no 

with  nothing  but  messages  from  the  debugger  were  not  in  use;  program  other  RSXs  are  active.  This  suggests 

debugger.  This  was  no  help  at  all  in  input  is  entered  on  the  standard  key-  that  the  PREVIOUS  pointer,  in  fact, 

determining  how  the  routine  under  board.  All  debugger  input  comes  always  points  to  the  NEXT  jump  in¬ 
test  was  interacting  with  the  CRT.  from  the  TTY:  console,  and  debugger  struction  of  the  previous  RSX  in  the 

Mitchell  developed  SID2TTY  to  output  appears  on  the  TTY:  video  chain.  Thus,  compatibility  is  main¬ 

tained  throughout  the  system,  and 
each  RSX  is  chained  in  both  forward 
and  backward  directions. 

In  actuality,  though,  this  pointer  is 
to  location  7,  the  last  byte  of  the  jump 
NEXT  field  in  the  previous  RSX,  or  to 
memory  location  7  if  no  other  RSXs 
are  active.  This  inaccuracy  has  no  ef¬ 
fect  on  CP/M  Plus’s  operation  or  on 
any  RSX  currently  supplied  by  DRI. 
But  beware  if  you  are  planning  to 
write  any  custom  RSXs  that  might 
use  reverse  chaining. 

DD] 


Reader  Ballot 

Vote  for  your  favorite  feature/article 
Circle  Reader  Service  No.  1 98. 


Dr  Dobb's  Journal,  June  1985 


106 

489 


CP/M  Exchange  Listing 


SID2TTY.ASM  v  1.1  01/10/84  B.  Mitchell  (Plu*Perfect  Systems) 


A  Debugging  Console  for  SID,  ZSID  and  DDT 
SID2TTY  is  a  small,  public-domain  program  that 

dynamically  patches  the  SID  debugger  (Data  Resources,  Inc.)  to 
redirect  all  of  SID's  console  input  and  output  to  the  logical  TTY: 
device.  ( ZSID2TTY  and  DDT 2 TTY  are  the  ZSID  and  DDT  versions.) 

By  using  a  second  terminal  as  the  TTY:  device  to  "command"  SID, 
one  can  debug  programs  without  interfering  with  standard  console 
input  and  output.  Assuming  the  usual  CON:=CRT:  device  assignment, 
a  program's  standard  output  will  appear  on  the  video 
monitor  exactly  as  if  the  debugger  were  not  in  use,  and  program 
input  will  be  entered  on  the  standard  keyboard.  All  debugger 
input  comes  from  the  TTY:  console,  and  debugger  output  appears 
on  its  video  monitor. 

The  patch  itself  is  just  23  bytes  —  it  changes  the  IOBYTE  each  time 
SID  calls  for  BDOS  console  i/o  service  and  restores  it  afterwards. 
The  patch  is  relocated  to  protected  high  memory,  below  SID  (and 
below  any  .SYM  and  .  UTL  files,  which  should  be  loaded  first). 

Running  SID2TTY  will  display  a  help  message. 

Source  file:  SID2TTY .ASM  for  assembly  with  ASM  or  MAC. 

The  ZSID  and  DDT  versions  are  obtained  by 
changing  three  equates. 

Requirements:  CP/M  2.2  -  3.0  with  IOBYTE  implemented  in  bios. 

8080,  8085  or  Z80  CPU. 

SID  v.  1.4  or  ZSID  v.  1.4  or  DDT  v2.2. 

External  terminal  connected  to  TTY:. 

Installation:  Set  the  equates  for  SID,  ZSID  or  DDT. 

If  your  TTY:  device  needs  initialization  or 
de-ini t ial i zat ion  ,  insert  the  code  in  place 
of  dummy  routines. 

Bugs:  Exiting  from  SID  with  “C  leaves  the  console 

redirected  to  TTY:  (unless  the  BIOS  improperly 
restores  the  IOBYTE  on  warmboots) .  The 
recommended  fix  is  simply  to  exit  with  a  "G0". 

Should  you  happen  to  hit  “C,  reload  SID  and  reset 
the  IOBYTE  to  its  original  value,  then  exit  with 
~C  from  the  CRT  terminal. 


Remarks : 


Version : 


Rev i s ions  : 


1.  Although  it  would  be  possible  to  trap  the 
warmboot  and  restore  the  IOBYTE,  it's  difficult  to 

do  correctly.  Trapping  0001h  violates  the  basic  CP/M 
addressing  convention,  making  it  impossible  to 
run  other  resident  modules  that  need  to  locate  the  BIOS 
and  BDOS.  Trapping  the  warmboot  at  the  BIOS  jump 
vector  does  preserve  addressing,  but  it  still  requires 
special  efforts  to  maintain  compatibility  with  any  higher 
resident  modules,  and  the  trap  must  remove  itself 
without  disturbing  them.  Since  the  application  here 
is  manual,  interactive  debugging,  it  seems  best  to 
favor  compatibility  and  do  a  manual  restoration. 

2.  The  assembler  portion  of  SID/ZSID  can  be  removed 
with  the  "-A"  command  to  gain  680h  bytes,  but 

this  must  be  done  BEFORE  installing  the  patch 
with  the  "gl03"  command. 

1.0  —  10  October  1984  B.  Mitchell 

1.1  —  10  January  1985  B.  Mitchell 


Please  forward  any  revisions  and  improvements 
to  the  author . 

(Continued  on  next  page) 


107 


A(\r\ 


Dr.  Dobb’s  Journal,  June  1985 


CP/M  Exchange  Listing  (Listing  continued,  text  begins  on  page  106) 


Author:  Bridger  Mitchell 

Plu*Perfect  Systems 

Box  1494,  Idyllwild  CA  92349 

(714)  659-4432 

Note:  Not  tested  on  CP/M  3.0. 


VERS 

equ 

1$1 

r 

FALSE 

equ 

0 

TRUE 

equ 

NOT  FALSE 

ADDRESS 

equ 

0FFFFH 

9 

;  exactly  ONE 

of  the  next  3  equates  must  be  TRUE  — 

r 

SID 

equ 

TRUE 

ZSID 

equ 

FALSE 

DDT 

equ 

FALSE 

CR 

equ 

0dh 

LF 

equ 

0ah 

BELL 

equ 

07h 

9 

IOBYTE 

equ 

0003h 

BDOS 

equ 

0005h 

9 

org 

100h 

START: 

jmp 

HELP 

jmp 

INSTALL 

jmp 

DEINITTTY 

9 

signature :db 

'NEXT  PC'  ; common  SID/ZSID/DDT  code 

s  iglen 

equ 

S-signature 

IF  SID 

9 

mods i z 

equ 

1800h  ;size  of  debugger  module 

sigad 

equ 

0E42h  ;offset  to  signature 

ca 1 lbd 

equ 

06A4H  ;offset  to  call_bdos  routine 

f  n2ad 

equ 

1004h  ;offset  to  addr  of  function  2  bdos  call 

fnl0ad 

equ 

0FF2H  ;  ditto  fn  10 

f nllad 

equ 

1065h  ;  ditto  fn  11 

9 

usage : 

db 

CR, LF, ' SID2TTY  v.  ' 

db 

vers/10+' 0 ' , 1 . ' , ( vers  mod  10)+'0' 

db 

'  —  Redirect  SID  console  i/o  to  TTY : ' ,CR , LF 

db 

'  —  Usage  — ' ,CR, LF 

db 

'Load  SID,  then  .SYM  and  .UTL  files', CR,LF 

db 

'  #1*  filename. SYM' ,CR,LF 

db 

'  #  R  ' ,CR , LF 

db 

'  #ISID2TTY .COM' ,CR, LF 

db 

'  # R '  ,CR , LF 

db 

'  #G103  =  =  >>  SID  '  ' s  console  is  now  TTY : ' ,CR , LF 

db 

'  IIfilename.COM  (or  .HEX)  to  debug', CR,LF 

db 

'  #  R ' ,CR , LF 

db 

'  #D3,3  ==>>  display  IOBYTE  for  reference ' ,CR , LF , 

db 

'To  exit  from  SID,  deinitialize  the  TTY:  (if  needed)  by' 

db 

'  # I S I D2TTY . COM ' ,CR , LF 

db 

'  #R0 ’ , CR , LF 

db 

'  #G1 06 ' ,CR , LF 

db 

'Then  use  "G0"  (not  "“C")  to  exit  from  SID.',CR,LF 

db 

' (This  message  can  be  displayed  under  SID  by  the  command 

db 

CR , LF , '  G100  or  C100  before  loading  ''filename'')' 

db 

CR, LF , LF , 

9 

er rmsg : 

db 

CR, LF, BELL, 'Can' ' t  find  SID  v  1.4!$' 

ENDIF 

; IF  SID 

IF  ZSID 


(Continued  on  page  110) 


Dr.  Dobb's  Journal.  June  1985 


108 


491 


CP/M  Exchange  Listing  (Listing  continued,  text  begins  on  page  106) 


modsiz  equ 
sigad  equ 
callbd  equ 
fn2ad  equ 
fnlBad  equ 
fnllad  equ 
/ 

usage:  db 

db 
db 
db 
db 
db 
db 
db 
db 
db 
db 
db 
db 
db 
db 
db 
db 
db 
db 
db 

i 

errmsg:  db 

ENDIF 

IF  DDT 

/ 

modsiz  equ 
sigad  equ 
callbd  equ 
fn2ad  equ 
fnlBad  equ 
fnllad  equ 

usage:  db 

db 
db 
db 
db 
db 
db 
db 
db 
db 
db 
db 
db 
db 
db 
db 
i 

errmsg :db 

ENDIF 


22B0h 
1682h 
0EAFH 
1880b 
18  6Eh 
18Elh 

CR , LF ,  LF ,  1 ZSID2TTY  v.  ' 

vers/10  +  ' 0 (vers  mod  10)+'0' 

'  —  Redirect  ZSID  console  i/o  to  TTY : ' ,CR, LF, LF 

'  —  Usage  — ' ,CR, LF 

•Load  ZSID,  then  .SYM  and  .UTL  files', CR,LF 
•  #1*  filename. SYM' ,CR,LF 

'  #R ’ ,CR , LF 

'  IIZSID2TTY.COM' ,CR,LF 
'  #  R '  ,CR , LF 

'  #G103  ==>>  ZSID ' ' s  console  is  now  TTY : ' ,CR , LF 

'  #1  filename .COM  (or  .HEX)  to  debug', CR,LF 

'  #  R ' ,CR , LF 

'  #D3 , 3  ==>>  display  IOBYTE  for  reference ' ,CR , LF , LF 

'To  exit  from  ZSID,  deinitialize  the  TTY:  (if  needed)  by ' , CR, LF 
'  # I ZS I D2TTY .COM ' ,CR,LF 

'  #R0 ’ , CR, LF 

'  #G106 ' ,CR , LF 

'(This  message  can  be  displayed  under  ZSID  by  the  command:' 

CR, LF , 1  G100  or  C100  before  loading  ''filename'')' 

CR , LF , LF , ' $ ' 

CR, LF, BELL  , 'Can' ' t  find  ZSID  v  1.4!$’ 

; IF  ZSID 


1000h 

0A71h 

06A2h 

0BCEh 

0C25h 

0BBCh 

CR , LF , LF , ' DDT2TTY  v.  ' 
vers/10+' 0 (vers  mod  10)+'0' 

'  --  Redirect  DDT  console  i/o  to  TTY : ' ,CR , LF , LF 

'  —  Usage  — ' ,CR, LF 

' A>DDT  DDT2TTY .COM ' ,CR, LF 

'  -G103  ==>>  DDT ' ' s  console  is  now  TTY : ' , CR, LF 

'  -Ifilename.COM  (or  .HEX)  to  debug', CR,LF 
'  -R' ,CR, LF 

'  -D3 , 3  ==>>  display  IOBYTE  for  re f er ence ’ ,CR , LF , LF 

'To  exit  from  DDT,  deinitialize  the  TTY:  (if  needed)  by',CR,LF 
’  -IDDT2TTY.COM' ,CR,LF 
'  -R0 ’ ,CR, LF 

'  -G106 ' ,CR , LF 

'(This  message  can  be  displayed  under  DDT  by  the  command:' 

CR, LF, '  G100  before  loading  ''filename'')' 

CR , LF , LF , ' $ ' 

CR , LF , BELL  , 'Can '' t  find  DDT  v  2.2!$’ 

; IF  DDT 


;  Display  usage  message  &  quit. 

;  This  is  executed  if  SID2TTY  is  run  as  a  .COM  file 

;  It  can  also  be  C'd  or  G'd  from  the  debugger. 

r 

HELP:  lxi  d, usage 

print:  mvi  c,9 

call  bdos 

( Continued  on  page  112) 


110 


Dr.  Dobb's  Journal,  June  1985 


CP/M  Exchange  Listing  (Listing  continued,  text  begins  on  page  106) 


lxi  h,0  ;is  debugger  in  use  on  default  stack? 

dad  sp 

mov  a,h 

cpi  2 

rnc  jreturn  to  caller  (ccp) 

rst  7  jreturn  to  debugger 

? 

ERR:  lxi  d,errmsg 

jmp  print 

r 

r 

;  Search  for  debugger  signature  in  high  memory. 

;  Search  downward  to  find  first  image  of  the  debugger, 

;  in  case  other  copies  are  lying  around.  This  also 
;  allows  other  resident  modules  to  exist  above  the 
;  debugger.  Debugger  is  always  located  on  a  page  boundary. 

J 

INSTALL: 

lhld  1 

lxi  d  (mods iz  +  3 ) 

dad  d  jstart  at  max  possible  addr 

search:  push  h 

lxi  d,sigad 

dad  d 

call  compar 

pop  h 

jz  found 

mov  a,h 

cpi  10h  ;quit  looking  at  1000h 

jc  err 

dcr  h  ;try  1  page  lower 

jmp  search 

compar:  lxi  d, signature 

mvi  b,siglen 

complp:  ldax  d 

cmp  m 

rnz 

inx  h 

inx  d 

dcr  b 

jnz  complp 

ret 

f 

;  Debugger  located.  Calculate  run-time  addresses  &  patch 
;  into  code  and  debugger. 

! 

found:  push  h  ;  save  base  addr  of  debugger 

lxi  d,callbd  ;put  3  run-time  addresses  into  code 

dad  d 

xchg 

lxi  h,patchl  ;1:  the  call_bdos  routine  address 

mov  m,e 

inx  h 

mov  m,d 

lhld  6  ;2:  the  protect  address/base  of  high  code 

shld  patch2 

lxi  d,-codelen  ;protect  the  code 

dad  d  ; (don't  trace  from  here  to  lpend) 

shld  6 

push  h  jsave  run-time  base  of  high  code 

lxi  d , saviob-code 

dad  d 

shld  patch3  ;3:  the  save-iobyte  address 

pop  d  ;get  destination 

push  d 

lxi  h,code  ;move  the  code  into  place 

mvi  b,codelen 

(Continued  on  page  114) 


Dr.  Dobb’s  Journal,  June  1985 


112 

49.? 


CP/M  Exchange  Listing  (Listing  continued,  text  begins  on  page  106) 


lp: 

mov 

a  ,m 

stax 

d 

inx 

h 

inx 

d 

dcr 

b 

jnz 

IP 

9 

lpend 

pop 

b 

; patch 

debugger's  console  io  calls 

inx 

b 

inx 

b 

inx 

b 

;  . .  to 

point  at  code  entry  ('divert' 

pop 

d 

;  de  = 

base  of  debugger 

lxi 

h , f n 10ad 

;  index 

into  debugger  module 

dad 

d 

mov 

m ,  c 

;plug 

3  diversion  addresses  into  cal 

inx 

h 

mov 

m ,  b 

lxi 

h , f n2ad 

dad 

d 

mov 

m,  c 

inx 

h 

mov 

m ,  b 

lxi 

h , f nllad 

dad 

d 

mov 

m ,  c 

inx 

h 

mov 

m,b 

» 

jmp 

INITTTY 

;do  any  user-supplied  initialization 

;  The 

REDIRECTION  CODE,  which 

is  moved 

up  just  below  the  debugger 

9 

patch2 

equ 

$+1 

CODE: 

jmp 

ADDRESS 

; jmp  to  debugger  entry 

divert : 

lxi 

h,  iobyte 

mov 

a ,  m 

patch3 

equ 

$  +  1 

sta 

ADDRESS 

;save  the  iobyte 

ani 

0f  ch 

;  assign  CON:  to  TTY:  for  debugger 

mov 

m ,  a 

patchl 

equ 

$  +  1 

call 

ADDRESS 

(•debugger's  call  bdos  routine 

sav iob 

equ 

5  +  1 

mvi 

a ,  00 

(restore  user's  iobyte 

sta 

iobyte 

mov 

a  ,  1 

;get  bdos  return  param  back  to  A 

ret 

codelen 

equ  $-code 

;  User- 

suppl ied 

TTY:  initialization  routine. 

;  Use 

this  to 

set  baud 

rate,  channel  parameters,  etc. 

INITTTY 

: 

r st  7 

(return  to  debugger  on  TTY:  device 

;  User- 

suppl ied 

TTY:  de¬ 

initialization  routine. 

;  Use 

this  to 

restore 

TTY:  device  to  regular  settings. 

DEINITTTY: 

rst  7 

(return  to  debugger  on  TTY:  device 

END 

End  Listing 


114 


Dr.  Dobb's  Journal,  June  1985 


OF  INTEREST 


by  Alex  Ragen 


If  you’ve  ever  traveled  outside  the 
United  States,  you’ve  probably  no¬ 
ticed  that  the  household  electricity 
varies  from  country  to  country.  The 
line  voltage  may  be  1 1 0  or  1 20  or  220 
or  240  volts,  and  the  frequency  may 
be  either  50  or  60  Hz.  Even  if  your 
electric  shaver  or  hair  dryer  has  a  lit¬ 
tle  switch  somewhere  to  flip  back  and 
forth  among  all  the  possible  combina¬ 
tions,  you  still  may  find  that  you  can’t 
use  it  because  the  plug  doesn’t  fit  the 
wall  receptacle.  It  turns  out  that  al¬ 
most  every  country  in  the  world  has  its 
own  unique  plug  shape,  which  is  not 
only  different  but  also  superior  to  ev¬ 
ery  other  country’s  plug  shape.  Does 
this  remind  you  of  anything? 

Software  and  hardware  expend  a 
tremendous  amount  of  computing  en¬ 
ergy  in  converting  between  lan¬ 
guages,  protocols,  disk  formats,  oper¬ 
ating  systems,  and  so  on.  You  can  run 
CP/M  80  or  MSDOS  on  your  Macin¬ 
tosh  or  use  your  IBM  PC  (and  compa¬ 
tibles,  of  course!)  to  write  Apple  II 
disks  or  run  a  68000  board.  Whatever 
designers  intended  users  to  do  on  one 
configuration,  they  have  managed  to 
implement  on  another  as  well.  This 
Babel  of  standards  and  not-so-stan- 
dards  is  chaotic,  confusing,  pointless, 
and  counterproductive — and  we’ve 
all  had  lots  of  fun  and  worked  hard 
getting  around  it. 

Perhaps  one  day  we’ll  live  in  a  per¬ 
fect  world.  Everything  will  interface 
with  everything  else,  Macintosh  add¬ 
ons  won’t  be  called  Mac-this  or  Mac- 
that,  and  a  word  processor  will 
emerge  that  is  so  good  that  nobody 
will  want  to  write  another  one. 

C  Language 

Lattice  Inc.  is  offering  an  upgrade  kit 
to  users  of  Microsoft’s  MSDOS  C 
compiler.  For  $150  and  the  original 
Microsoft  diskettes,  Lattice  will  pro¬ 


vide  version  2.20  of  its  C  compiler, 
the  C-SPRITE  debugger,  and  a  type¬ 
set  manual.  The  regular  retail  price 
of  the  package  is  $675,  and  the  offer 
is  good  until  July  31,  1985.  Contact 
Lattice  Inc.  at  P.O.  Box  3148,  Glen 
Ellyn,  IL  60138  (312)  858-7950. 

Reader  Service  No.  101. 

QTools,  a  programmer’s  toolbox  of 
19  tools  adapted  from  Unix  to 
PCDOS,  is  available  for  $49.95  post¬ 
paid  from  QCAD  Systems,  1164 
Hyde  Ave.,  San  Jose,  CA  95129 
(800)  538-9787  or  (408)  255-5574  in 

California.  Reader  Service  No.  103. 

A  new  C  language  scientific  sub¬ 
routine  library  that  provides  IBM  PC 
users  with  1 1 2  pretested  and  precom¬ 
piled  mathematical  and  statistical 
subroutines  is  available  from  Wiley 
Professional  Software.  The  subrou¬ 
tines  include  the  most  commonly  used 
operations  such  as  differentiation, 
polynomials,  probability,  numerical 
integration,  regression,  differential 
equations  and  others.  Over  400  pages 
of  documentation  provide  a  descrip¬ 
tion  of  methodology,  notes  on  special 
considerations,  source  code,  test  pro¬ 
grams,  and  results.  The  library  re¬ 
quires  the  Lattice  C  compiler,  version 
2. 1 2  or  later,  and  a  two-drive  I BM  PC. 
Contact  Leslie  Bixel,  Wiley  Profes¬ 
sional  Software,  605  Third  Ave.,  New 
York,  NY  10158  (212)  850-6788. 

Reader  Service  No.  105. 

TGL  Inc.  has  released  version  II  of 
The  Converter,  an  IBM  PC  program 
that  automatically  converts  UCSD, 
MT  +  ,  and  MS-Pascal  source  pro¬ 
grams  into  equivalent  C  programs. 
Ted  Lewis  of  TGL  points  out  that  The 
Converter  correctly  handles  nested 
procedures,  separately  compiled 
units  or  modules,  and  intrinsic  func¬ 
tions;  it  also  enables  developers  to 
move  quickly  from  UCSD  or  MSDOS 
to  Unix.  Contact  Mr.  Lewis  at  TGL 


Inc.,  4400  Sulphur  Springs  Rd.,  Cor¬ 
vallis,  OR  97330  (503)  745-7476. 

Reader  Service  No.  107. 

Thunder  Software  has  announced 
version  2.0  of  its  Thunder  C,  the  only 
C  compiler  available  for  the  Apple 
Pascal  and  Apple  ProDOS  environ¬ 
ments.  Thunder  C  generates  native 
6502  assembly  language  routines, 
and  its  developers  rate  the  new  ver¬ 
sion  as  300  percent  faster  than  the  old 
one.  It  runs  under  Apple  Pascal  1 . 1  or 
1.2,  although  the  ProDOS  version  re¬ 
quires  an  external  ProDOS  macro  as¬ 
sembler.  Price  is  $49  plus  $3  ship¬ 
ping.  The  company  also  offers 
LINKIT,  a  linking  loader  and  library 
generator;  XREF,  a  Pascal  cross-ref¬ 
erence  utility;  and  ASSYST,  a  6502 
assembler.  Contact  Thunder  Soft¬ 
ware  at  P.O.  Box  31501,  Houston, 
TX  77231  (713)  728-5501.  Reader 

Service  No.  109. 

Hippo-C  is  the  only  (so  far)  C  com¬ 
piler  for  the  Macintosh  with  a  source- 
level  debugger  -or  so  its  developers 
claim.  Level  1  provides  access  to  over 
380  Toolbox  and  Quickdraw  routines 
and  features  a  compiler,  editor,  stan¬ 
dard  C  library,  on-line  tutorial,  de¬ 
bugger,  linker,  shell  command  pro¬ 
cessor,  many  sample  programs,  and 
over  200  pages  of  documentation.  It’s 
priced  at  $149.95.  Level  2  includes 
all  Level  1  features  plus  an  optimiz¬ 
ing  compiler,  assembler,  and  full 
floating-point  support.  It  sells  for 
$399.95.  Contact  Hippopotamus 
Software  at  1250  Oakmead  Pkwy., 
Sunnyvale,  CA  94086  (408)  738- 

1  200.  Reader  Service  No.  111. 

Modula  2 

Release  1.10  of  Modula-2/86  for 
PCDOS  is  now  available  from  Logi¬ 
tech.  A  library  of  programming  tools 
is  also  available.  Contact  Chris  Cale 
at  Logitech,  805  Veterans  Blvd., 


Dr.  Dobb's  Journal,  June  1985 


118 

495 


Redwood  City,  CA  94063  (415)  365- 
9852.  Reader  Service  No.  113. 

Modula-2  for  Z80-based  CP/M 
systems  is  available  from  Hoch- 
strasser  Computing  AG,  Leonhard- 
shalde  21,  CH-8001  Zurich,  Switzer¬ 
land,  phone  01/47  55  48.  The  cost  is 
sFr  400  Or  about  $1  50.  Reader  Service 
No.  115. 

MSDOS 

Guess  who  is  throwing  its  hat  into  the 
cutthroat  world  of  the  IBM  PC  add¬ 
on  market?  None  other  than  Intel, 
which  has  established  a  Personal 
Computer  Enhancement  Operation. 
The  first  two  products  are  its  well- 
known  8087  and  80287  math  co¬ 
processors  (what’s  next,  the  8086?). 
Contact  Intel  at  520N.E.  Elam 
Young  Pkwy.,  Hillsboro,  OR  97124- 
6497  (503)  629-7369.  Reader  Service 

No.  117. 

PC-68K  is  an  upgrade  package — 
plug-in  board  and  software — that 
adds  development  support  for  Motor¬ 
ola’s  68000  family  to  IBM’s  PC  XT 
and  PC  AT  running  PCDOS.  PC-68K 
provides  a  symbolic  debugger,  link¬ 
er/locator,  Motorola  compatible 
macro  assembler,  and  IEEE  floating¬ 
point  package.  Pascal  and  C  compil¬ 
ers  and  communications  utilities  are 
extra-cost  options.  The  board  fea¬ 
tures  an  8  MHz  68000  cpu  and  256  - 
1 024K  of  RAM.  Prices  start  at  $2995 
for  the  256K  version.  Contact  Carrie 
Ann  Moran  at  Language  Resources, 
4885  Riverbend  Rd.,  Boulder,  CO 
80301  (303)  449-8087.  Reader  Service 
No.  119. 

The  Buddy  System  enables  a  sup¬ 
port  person  located  at  a  central  facili¬ 
ty  to  “take  over”  a  remotely  located 
IBM  PC  in  order  to  resolve  a  custom¬ 
er’s  problem.  The  developer  has  been 
using  it  internally  for  three  years  to 
support  its  products  and  now  offers  it 
to  others  at  $199.  You  load  the  Bud¬ 
dy  System  when  you  turn  on  your 
PC;  thereafter,  it’s  dormant  until  you 
activate  the  “copilot”  mode  and  con¬ 
tact  the  support  center  via  modem. 
The  support  person  then  observes 
you,  the  user,  as  the  application  is  ex¬ 
ecuted  and  can  even  take  control  of 
the  application  to  show  you  what  to 
do.  A  number  of  other  features,  such 
as  help,  screen  snapshot,  screen  copy 


to  file,  and  record/ replay  support  ses¬ 
sion,  are  designed  to  increase  the  pro¬ 
ductivity  of  the  support  operation. 
Contact  Edward  Murphy  at  Solva¬ 
tion  Inc.,  302  Turnpike  Rd.,  South- 
boro,  MA  01722  (617)  481-9390. 

Reader  Service  No.  121. 

Phoenix  Software  Associates  has 
introduced  an  IBM  PC/AT-compati- 
ble  ROM  BIOS  that  was  developed  un¬ 
der  strict  controls  specifically  to  en¬ 
sure  originality  and  to  avoid  copyright 
infringement  suits.  The  company  had 
already  developed  versions  for  the  PC 
and  PC/XT.  The  PC/AT  version  is  of¬ 
fered  to  OEMs  for  unlimited  use  li¬ 
censing  at  a  price  of  $440,000.  The 
PC  and  PC/XT  versions  are  $290,000. 
Contact  Richard  Levandov  at  1420 
Providence  Hwy.,  Suite  101,  Nor¬ 
wood,  MA  02062  (617)  769-7020. 

Reader  Service  No.  123. 

Ryan  McFarland’s  RM/Cobol  is 
now  available  for  the  IBM  PC/IX 
user.  It  was  already  available  for  the 
IBM  PC  under  PCDOS,  as  well  as  for 
the  IBM  Series  1  under  Unix  and  the 
IBM  370  computers  under  VM/CMS. 
The  new  RM/Cobol  is  priced  at  $230 
for  the  runtime  version  and  $750  for 
the  full  version.  Contact  Ryan 
McFarland  Corp.,  609  Deep  Valley 
Dr.,  Rolling  Hill  Estates,  CA  90274 
(213)  541-4828.  Reader  Service  No.  125. 

A  full  ANSI  Fortran  77  compiler 
for  the  IBM  PC  has  been  announced 
by  Lahey  Computer  Systems.  The 
complete  package,  including  a  250 
page  manual,  customer  telephone  sup¬ 
port,  and  newsletters,  costs  $477  and 
requires  256K  RAM  and  an  8087  co¬ 
processor.  The  compiler  claims  Lat¬ 
tice  C  compatibility.  Contact  the  ven¬ 
dor  at  31244  Palos  Verdes  Dr.  West, 
Suite  243,  Rancho  Palos  Verdes,  CA 
90274  (213)  541-1200.  Reader  Service 
No.  127. 

Scroll  and  Recall  lets  the  IBM  PC 
user  scroll  back  through  27  pages  of 
previously  displayed  screens  while 
saving  you  DOS  commands,  which 
you  can  then  recall,  edit,  and  reuse 
without  having  to  type  them  in  again. 
The  cost  is  $69.  Contact  Dennis  P. 
Olenick,  Opt-Tech  Data  Processing, 
P.O.  Box  2167,  Humble,  TX  77347 
(713)  454-7428.  Reader  Service  No.  129. 

The  GTP  Program  Development 
System  is  a  productivity  tool  for  Tur¬ 


bo  Pascal  programmers.  Basically,  it 
generates  the  Turbo  Pascal  state¬ 
ments  for  handling  screen  data  entry. 
It  requires  PCDOS  or  MSDOS  version 
■  2.0  or  higher,  and  lists  for  $99.95. 
Contact  AEF  Software,  P.O.  Box  928, 
Katy,  TX  77449  (713)  391-8570. 

Reader  Service  No.  131. 

FREECOPY  is  functionally  equiva¬ 
lent  to  DISKCOPY,  which  is  distrib¬ 
uted  as  part  of  PCDOS.  Although  it  is 
basically  DISKCOPY  reverse  engi¬ 
neered,  the  author  claims  not  to  have 
violated  the  proprietary  rights  of  ei¬ 
ther  IBM  or  Microsoft.  Commented 
source  code  is  included.  The  author  is 
placing  the  program  in  the  public  do¬ 
main  but  does  request  a  $25  contri¬ 
bution.  Contact  Donald  Buresh, 
Squire  Buresh  Associates,  18  Doro¬ 
thy  Rd.,  Millbury,  MA  01527  (617) 
865-3435.  Reader  Service  No.  133. 

Modular  Bridge  is  a  product  that 
allows  you  to  transfer  files  between 
PCDOS  and  the  PICK  PC-XT  operat¬ 
ing  system.  Contact  Modular  Soft¬ 
ware,  P.O.  Box  204,  Union  City,  GA 
30291  (404)  964-7171.  Reader  Service 
No.  135. 

HFORMAT  and  HTEST  are  two 
programs  that  address  the  problems 
of  formatting  hard  disks  on  the  PC 
family.  The  programs  test  and  per¬ 
form  the  equivalent  of  factory  for¬ 
matting  on  the  drives.  Contact  Mar¬ 
cus  Kolod  at  Kolod  Research,  P.O. 
Box  68,  Glenview,  IL  60025  (312) 
29  1  -  1  586.  Reader  Service  No.  137. 

A  Universal  PROM  programmer 
for  the  IBM  PC  is  offered  for  $250  by 
Advanced  Microcomputer  Systems 
of  6802  N.W.  20th  Ave.,  Ft.  Lauder¬ 
dale,  FL  33309  (305)  975-9515. 

Reader  Service  No.  139. 

HelpDOS  is  a  menu-driven  on¬ 
screen  reference  for  PCDOS  and  in¬ 
cludes  a  technical  dictionary.  It  re¬ 
quires  PCDOS  or  MSDOS,  version  2.0 
or  later,  and  is  available  for  $49.95 
from  Help  Technologies,  P.O.  Box 
50834,  Palo  Alto,  CA  94303  (415) 
856-3431.  Reader  Service  No.  141. 

Superkey,  a  resident  keyboard  en¬ 
hancer  for  data  encryption  and  mac¬ 
ro  processing,  has  been  announced  by 
Borland  International,  the  Turbo 
Pascal  people.  The  program  is  basi¬ 
cally  a  shortcut  to  routine  entry  of  of¬ 
ten-used  commands,  like  those  re- 


119 


a  rwc 


Dr.  Dobb  s  Journal,  June  1985 


quired  for  logging  onto  RBBS  services 
or  formatting  letters  and  spread¬ 
sheets.  The  user  can  bring  up  Super¬ 
key,  move  around  in  the  program, 
and  exit  with  single-keystroke  opera¬ 
tions,  as  well  as  pull  its  full-screen 
macro  editor  down  on  top  of  the  main 
program.  It  also  provides  on-line 
help,  automatic  recall  of  the  last  20 
entered  commands,  cut  and  paste  ca¬ 
pability,  data  encryption/decryption, 
and  screen  burn-in  protection.  Con¬ 
tact  Borland  International,  4113 
Scotts  Valley  Dr.,  Scotts  Valley,  CA 
95066  (408)  438-8400.  Reader  Service 
No.  143. 

SEE  is  an  editor  for  “professional 
programmers”  from  Prologic  Corp., 
31324  Via  Colinas,  Suite  111,  West- 
lake  Village,  CA  91362  (818)  991- 
5062.  Reader  Service  No.  145. 

A  new  version  of  PC/Forth  for  the 
PC/AT,  running  under  80286  XENIX 
3.0,  has  been  announced  by  Labora¬ 
tory  Microsystems,  3007  Washing¬ 
ton  Blvd.,  Suite  230,  P.O.  Box  10430, 
Marina  del  Rey,  CA  90295  (213) 
306-74  1  2.  Reader  Service  No.  147. 

The  Echo  PC2  is  a  speech  synthe¬ 
sizer  card  for  the  IBM  PC.  The  manu¬ 
facturer,  having  previously  provided 
similar  products  for  the  Apple,  claims 
to  be  the  first  to  produce  one  for  the 
IBM  PC.  It  features  two  speech 
modes:  a  limited  vocabulary  (about 
700  words)  in  a  natural-sounding  fe¬ 
male  voice  and  an  unlimited  vocabu¬ 
lary  in  a  robotic-sounding  voice.  Some 
400  English  language  and  pronuncia¬ 
tion  rules  have  been  utilized  to  pro¬ 
duce  the  computer-synthesized 
speech.  Software  can  control  pitch 
and  volume.  The  board  fits  into  the 
PC’s  short  slots  and  comes  with  a 
speaker,  software,  and  an  instruction 
manual.  The  price  is  $  1 49.95.  Contact 
Street  Electronics  Inc.,  1 140  Mark 
Ave.,  Carpinteria,  CA  93013  (805) 
684-4593.  Reader  Service  No.  149. 

BIS7705  allows  an  IBM  PC  to  emu¬ 
late  all  the  functions  of  a  Honeywell 
7700/7705  terminal.  The  price  is 
$695,  and  it’s  available  from  IE  Sys¬ 
tems  Inc.,  1 12  Main  St.,  Newmarket, 
NH  03857  (603)  659-5891.  Reader 

Service  No.  151. 

CP/M  80 

DSD80  is  a  fully  DDT-compatible 
Dr.  Dobb's  Journal,  June  1985 


full-screen  symbolic  debugger  for 
8080,  8085,  and  Z80-based  CP/M  80 
systems.  DSD80  includes  port  I/O, 
string  searching,  and  symbol  defini¬ 
tion.  The  Z80  instruction  set  is  fully 
supported  using  either  extended  Intel 
or  Zilog  mnemonics.  There  is  an  on¬ 
line  help  facility,  and  the  program 
comes  with  a  50-page  user’s  manual. 
The  price  is  $125.  Contact  Soft  Ad¬ 
vances,  P.O.  Box  49473,  Austin,  TX 
78765  (512)  478-4763.  Reader  Service 
No.  153. 

CP/M  users  needn’t  feel  left  out  in 
the  cold  by  the  latest  fad  of  pop-up 
programs.  Poor  Person  Software  has 
announced  Write-Hand-Man,  which 
includes  a  notepad,  phone  book,  desk 
calendar,  file  and  directory  viewing 
programs,  and  a  communication  pro¬ 
gram.  Users  familiar  with  CP/M  pro¬ 
gramming  can  add  new  functions. 
Write-Hand-Man  requires  CP/M  2.2 
and  will  run  on  all  CP/M  machines. 
The  price  is  $49.95.  The  program  is 
available  from  Poor  Person  Software, 
3721  Starr  King  Circle,  Palo  Alto, 
CA  94306  (415)  493-3735.  Reader 

Service  No.  1 55. 

MB+  Tools  is  a  set  of  program¬ 
mer  productivity  tools  for  Pascal 
MT  +  .  It’s  currently  available  for 
CP/M  80  and  CP/M  86,  and  it  should 
be  available  for  MSDOS  late  this 
year.  The  price  is  $175.  Contact  Mi¬ 
chael  Nunamaker,  Minnow  Bear 
Computers,  P.O.  Box  2233,  Cham¬ 
paign,  IL  61820-8233  (217)  398- 

6883.  Reader  Service  No.  157. 

Macintosh 

MacCharlie  is  not  a  trampburger  but 
a  $985  box  that  attaches  to  the  Mac¬ 
intosh  and  allows  it  to  run  programs 
written  for  the  IBM  PC.  MacCharlie 
also  allows  Macintosh  users  to  con¬ 
nect  to  IBM  PC  serial  networks  and  to 
use  IBM  PC-compatible  printers. 
Contact  Robert  Barrett  at  Dayna 
Communications,  50  South  Main  St., 
Suite  530,  Salt  Lake  City,  Utah 
84144  (801)  531-0600.  Reader  Service 
No.  153. 

Mighty  Mac  is  a  personal  information 
manager  priced  at  $99  from  Ad¬ 
vanced  Logic  Systems  at  1195  E.  Ar- 
ques  Ave.,  Sunnyvale,  CA  94086 
(408)  730-0307.  Reader  Service  No.  155. 

FileMaker  (the  third  product  in  the 


MACWARE  line)  is  a  single-file  data 
base  system  that  allows  a  user  to  cre¬ 
ate  a  variety  of  custom-designed  re¬ 
ports  and  forms  that  include  text  and 
graphics.  FileMaker  takes  full  advan¬ 
tage  of  the  “visually  active”  Macin¬ 
tosh  environment  and  functions  with¬ 
in  the  Macintosh  Office  recently 
announced  by  Apple.  Contact  Fore¬ 
thought  Inc.,  1973  Landings  Dr., 
Mountain  View,  CA  94043  (800) 
MACWARE.  Reader  Service  No.  129. 


DDJ 


121 

aQ7 


acking  Hardware 


uild  the  Ultimate 
rint  Spooler 

dd  a  Real-Time  Clock 


ake  MSDOS 
'ork  Like  Unix 


TT  i i®,  m 1 1 


rurbo  Pascal  vs 
he  Standard 


.i'.giLc.  Editorial  - 

£%V;  Mltor-ta-CWef  Michael  Swaine  ;  -.Vi.- 
Editor  Randy  Sutherland  v&fc 
Maeagiug  Editor  frank  Dittos?:  -3^ 
Tec  Weal  Editor  Alex  JStdgen  '  \  7  .:pr..;;: 
Editorial  Assistant  Sara  MwA  :  '  > 

%;  Contrtbuttag  Editors  .Robert 
Ks«”:'icT.:i-T.v-5:  .AV-'l..  ■  AjhteeiOn’iesi  : 

•  =  -:U  V  ^toyfiuacoit. 

.••••Si:  v  *  llent&Iub  "icSS^sfc 

:;f:;  -r;  twWta*  Vollylfah  ^^  ;:^ 
Jean  Arbtg  i?.';;^:  3V';r 
b':=.‘v*-v-.':i’r-  .  .  Production . 

-;U  Oeija/Prodtetie* 

‘  '  .-.r  v"  Director  SeUafetma  /  -/ . 

■y  .  Art  PiHbttor  Shelley  Rae  Hoeden  \  £t. 

Production  Assistant  Aiidd  Hinton 
rtS&u'ii  SS®*  Cartoon  Rand' Ren/roe  •:•.... 

Circulation  . 

?*  Sub.  FutfUtoientMgr,  StiphaMettarber  '■■X0- 
/y-  Subscription  Mgr.  M&ttree0.Sn«e . 

Beak  Marketing  Mgr.  Jane  Sharningkouse 
Slugfe  Copy  Sbles  Mgr.  KatMkenttofd  ':o\ v-r 

$  ••  Eloancc  Manager  Sonera  Dunrt  .  ..y 
.  Business  Manager  Betty  Trkkeu 
Accounts  Payable  Supr.  UaydaLope:-Quir.xana 
'  tecoB&  PayiAla  Aaat.  Demie  XJiannim  f 
Biding  Coordinator  Uetura  Di  tarzare...-: 
Advertising 

ISpiTv.rJv-V-- ’V1-- VUMNOttalMgr BWcior  y  V -UV.  yCl 

1  ,  Stephen  frlefrnan  .MJ)  424-0600  y„- 

Shawn  Hot, t  ‘(4151*24-0600  VU.%tf?i' 

^^^ditiollltSaiO  -  v  •S-.V’ 

ITo/rer  Andnejewski  kl7j  SS7-B36 1 

Lisa  Boudreau  (415)  424-0600  : 

.  BethDudos  (714)643-9439  . 

•'  Michele  Beaty  (317)675-0557  T 

'■■■  $  DDiCUssWeds  .  ytS&g. 

TimOrti i  {413)424-0660 :  :;J-> 

Advertising  Coordinators 

•  i;* /ay  Horvath  (415)  424-0600  £•»•;& . 

Alice  Abrams  :(4J5)  424-0600 


July  1985 
Volume  10,  Issue  7 


Michael  Enright 
Robert  Tripp 


Cover 


The  cover  cartoon  by  Rand  Renfroe  was  colored  on  a  Via  Video  Graphics 
System  by  Doyle  Puppo  and  Associates. 


Editor's  Note 


In  C  chest  this  month  Allen  Holub  offers  an  MSDOS  version  of  the  Unix 
directory  utility  Is.  As  the  magazine  went  into  production,  we  discovered  that 
the  program  did  not  operate  exactly  as  promised.  The  command  Is  /,  which 
was  supposed  to  list  the  contents  of  the  root  directory  from  another  directory, 
did  not  function  properly  under  DOS  2.x.  Also,  when  run  on  a  Compaq,  Is  did 
not  underline  directories,  but  printed  them  in  half  intensity.  This  is  probably 
due  to  the  particular  ANSI. SYS  file  used. — Ed. 


M&T  Publishing,  Inc. 

Chttirmsn  of  the  Board  Otmar  Weber. 

Director  C.F.  van  Qiiadl 
Presiim  l.aird  Foshey 

cCy., 


Entire  contents  copyright  '*  1985  by  M&T  Publishing, 
Inc.  unless  othonWse  noted  on  specific  articles.  A}1 
lights  reserved.  • 

Dr.  Dobb’s  Journal  (USPS  307690)  is  published 
homirfv  b>  MAT  Publishing,  Inc.,  246* Embatcarfefo 
Ifray,  Palo  AKo,  CA  94303.  (415)  424-0600.  Second 
lisas  postage  paid  at  Palo  Alto  and  at  .additional  entry 


thcr  countries.  Foreign  subscriptions  must  be  pre-pa id 
f  US.  Dollars,  drawn  on  a  US.  Batik.  For  subscription 
tobieras.  call:  outside  of  CA  800-32!  -3^33;  wfflnnUA 
lft-«5.p«23  or  566-6947. 

foreign  Distributor:  Worldwide  Media  Scrvice,  Inc., 
86  Park  Ave.  South,  New  York,  NY  10016. 
86-l52£>Ta.EXi62ft*30<Wyt) 


P  eople'  $  Computer  Company  V 

Detib’s  Journal  is  published  by  M  AT  Publishing, 
.  under  license  fropi  People's  Computer  Company, 
12  Bishop  Dr.,  Sake  107,  Sen  ftantott,  CA94583.S 
i-profit,  educational  corporation,  : 


Dr.  Dobb’s  Journal,  July  1985 


Dr.  Dobb's  Journal 


ARTICLES 


Build  a  Custom  PC  or  Clone 

by  Jim  Kronman 

42 

From  individual  components  you  can  assemble  a  PC  XT 
work-alike  that  is  totally  IBM  compatible  and  costs 
substantially  less.  (Reader  Ballot  No.  192). 

The  Ultimate  Parallel  Print  Spooler 

by  Don  Rindsberg 

46 

Build  a  parallel  print  spooler  with  a  60K  buffer,  software 
refresh  of  RAM  and  a  peek-a-boo  ROM.  (Reader  Ballot  No. 
193). 

Designing  a  Real-Time  Clock 
for  the  S-100  Bus 

by  Alan  Wilcox 

56 

Construct  a  clock  board  for  your  S-100  system  that  prints 
the  time,  date  and  filename  as  a  header  on  your  programs. 

(Reader  Ballot  No.  1 94). 

COLUMNS 

Dr.  Dobb's  Clinic 

by  D.  E.  Cortesi 

12 

A  design  error  in  the  8-bit  version  of  Turbo  Pascal;  Turbo 
compared  to  Standard  Pascal.  (Reader  Ballot  No.  1 90). 

C  Chest 

by  Allen  Holub 

20 

Ls,  a  Unix-like  MSDOS  directory  utility;  multi-column 
printing;  a  new  MSDOS  access  function  for  Lattice  C. 

(Reader  Ballot  No.  191). 

Realizable  Fantasies 

by  Michael  Swaine  and  Bob  Albrecht 

92 

Progress  reports  on  fantasies  presented  to  date.  (Reader 
Ballot  No.  195). 

1 6-Bit  Software  Toolbox 

by  Ray  Duncan 

94 

MSDOS  installable  device  drivers.  (Reader  Ballot  No.  196). 

Computer  Calisthenics 

by  Michael  Wiesenberg 

110 

Answers  to  puzzles  presented  in  previous  columns.  (Reader 
Service  No.  1 97). 

DEPARTMENTS 

Editorial 

6 

Letters 

8 

DDJ  Classified 

115 

Of  Interest 

120 

Advertiser  Index 

128 

Dr.  Dobb’s  Journal,  July  1985 


5 

501 


EDITORIAL 


We  do  this  for  you. 

“We:”  that’s  us,  the  editorial  staff;  “this”  is  the  magazine.  It’s  that  “you” 
that  makes  things  tricky. 

“You”  are  a  statistical  construct.  You  are  40,000  readers  with  at  least 
40,000  opinions  on  any  random  subject,  40,000  ideas  about  what  Dr.  Dobb’s 
Journal  should  be. 

You  are  a  systems  programmer  for  IBM.  A  DoD  Ada  specialist.  You  are  the 
DP  Manager  for  a  large  corporation.  You  are  the  only  programmer  for  a  small 
firm.  You  are  an  independent  software  developer.  A  consultant.  You’re  a  struc¬ 
tural  engineer  who  happens  to  write  software.  A  researcher.  A  teacher.  A  stu¬ 
dent. 

You  program  in  a  high-level  language  because  you’re  a  high-level  thinker, 
and  you  use  assembly  language  only  to  tighten  the  bolts.  You  have  concluded 
that  all  serious  computer  languages  are  fundamentally  equivalent,  so  why 
doesn’t  everyone  just  use  Pascal?  You  think  that  computer  languages  differ 
widely  in  power,  efficiency  and  naturalness,  so  why  doesn’t  everyone  use  Forth 
(since,  as  surely  everyone  knows,  Forth  code  is  invariably  faster  and  tighter 
and  more  readable  than  anything  else)?  You’ve  forgotten  in  the  past  year  that 
there  is  any  high-level  language  but  C. 

You  refuse  to  program  in  a  high-level  language,  pouring  all  your  creative 
efforts  into  high-torque  68K  assembly  language  code.  8086/8088  code.  Z80/ 
8080  code.  You  know  the  6502  inside  out.  You  know,  even  if  you  are  in  the 
vanishing  minority,  that  the  6809  is  all  the  processor  anyone  really  needs.  You 
know  things  about  the  80286  that  they  haven’t  discovered  at  Intel  yet. 

You  know  what  you  like  about  DDJ.  The  DDJ  articles  for  1985  that  you 
were  most  interested  in  (so  far)  included  pieces  dealing  with  the  Unix,  CP/M 
and  MSDOS  operating  systems;  the  C  and  Prolog  languages;  the  IBM  PC,  the 
PC/AT,  the  Mac,  the  Commodore  64,  Z80  machines  and  “machines  in  the 
68000/16000  class  with  virtual  memory.” 

You’re  intelligent,  knowledgeable,  creative  and  opinionated,  but  let’s  face 
it:  you  are  not  particularly  consistent.  Neither,  consequently,  are  we. 

This  is  the  hardware  issue  of  Dr.  Dobb’s  Journal  of  “software  tools  for 
advanced  programmers.”  Why  are  we  publishing  hardware  construction  arti¬ 
cles  in  a  software  magazine? 

We  are  doing  it,  of  course,  for  you.  The  “Fatten  Your  Mac”  article  in 
January  was  one  of  our  most  popular  articles  to  date,  and  other  hardware- 
related  pieces  have  been  well-received. 

We  have  a  broad  constituency,  trying  as  we  do  to  address  all  advanced 
programmers,  and  magazine  space  is  heartbreakingly  limited.  So  we  try  to  be 
as  broad  as  possible  without  sacrificing  depth.  We  publish  generalizable  code 
whenever  we  can,  emphasizing  good  algorithms. 

But  the  canvas  tears  when  stretched  to  include  hardware  articles.  Although 
there  is  much  non-hardware  material  in  this  issue,  we  have  this  month  com¬ 
mitted  a  sizeable  portion  of  the  magazine  to  hardware-related  material.  We 
offer  this  collection  of  hardware  articles  with  a  question:  how  would  you  like 
to  see  us  cover  hardware  in  the  future?  With  an  occasional  article  when  it 
seems  appropriate?  With  an  annual  hardware  issue  like  this?  Or  a  hardware 
issue  of  a  different  sort?  In  separate  monographs/project  papers,  leaving  the 
magazine  pages  for  software  only?  Or  not  at  all? 


Michael  Swaine 


6 


Dr.  Dobb's  Journal.  July  1985 


ppipusMiiTKT 


"'/uVXAAAAAAAj 


(oper=  =ffadd)) 
result(lval,  lvaI2); 


Small  C 

Dear  DDJ: 

I  don’t  know  if  anybody’s  still  keep¬ 
ing  track  of  these  things,  but  I’ve 
found  a  Small  C  bug.  The  source 

int  lptr;  /*  global  integer  */ 

routinef  )  { 
int  r; 

char  bytes[2]; 

1  ptr  +  =  r  —  bytes; 


compiled  to  (Z80  mnemonics) 

.  . .  (usual  stuff) 

LD  HL,  (LPTR) 

PUSH  HL;;;  (save  left  side  of 
+  =  statement) 

LD  HL,4 
ADD  LH,SP 
CALL  CCGINT 
EX  DE,HL;; 

LD  HL,2 
ADD  HL,SP 
CALL  CCSUB 

POP  DE  (retrieve  left  side  of 

+  =  statement) 
ADD  HL,DE  (add  it) 

LD  A,L  (forget  what’s  going 

on  .  .  .  .) 

LD  (DE),A  (should  be  LD 
(LPTR),HL) 


which  is  entirely  the  wrong  idea  and 
has  very  nasty  effects  (the  bug  also 
occurs  with  local  destinations  for  the 
assignment,  but  with  somewhat  less 
dangerous  results).  The  problem  dis¬ 
appeared  when  I  removed  the  last 
code  line  from  plung2(  )  (aka  plnge2 
in  “The  Small-C  Handbook”  by 
James  E.  Hendrix)  in  cc31.c: 

if  ((oper=  =ffsub)  1 


In  my  copy  of  the  compiler,  this  is  the 
only  place  result(  )  is  used,  so  I  re¬ 
moved  it  from  cc33.c.  The  compiler 
compiled  itself  in  this  shape,  and  suc¬ 
cessfully  compiled  a  reasonably  large 
program,  so  maybe  this  is  the  correct 
fix,  but  I’m  not  deeply  convinced. 

Fixing  the  compiler  is  a  real  adven¬ 
ture  for  me,  because  I  don’t  know  how 
it  works.  I  do  wonder  what  solemn 
oath  bound  the  original  authors,  so 
that  no  trace  of  comment  is  allowed  to 
stain  the  pure  pages  of  code  .... 
Cordially, 

Gregor  Owen 
Software/ Hardware 
35  Admiral  St. 

Port  Jefferson  Station, 

New  York,  NY  11776 
Other  fixes  and  a  status  update  for 
Small  C  will  appear  in  the  August 
issue — Ed. 

Iconoclasm 

Dear  DDJ. 

By  your  response  to  Frank  Gaude’s 
criticism  of  icons,  it  appears  as  though 
you  don’t  fully  appreciate  his  point.  In 
fact,  your  apparent  praise  of  ‘active, 
growing  icons’  only  strengthens  his  ar¬ 
gument.  What  will  we  have  if  soft¬ 
ware  developers  everywhere  start  cre¬ 
ating  systems  with  user-definable 
icons?  We  will  have  the  second  stage 
in  the  evolution  of  language,  as  icons 
were  the  first.  What  is  more,  we  will 
have  multitudinous  languages,  with 
each  programmer  or  user  designing 
the  icon  interface  to  [his]  own  person¬ 
al  taste.  Nobody  will  be  able  to  sit 
down  at  somebody  else’s  system  and 
run  it.  We  will  have  the  Tower  of  Ba¬ 
bel  all  over  again.  Confusion.  Insan¬ 
ity.  Let’s  not  do  it  all  over  again. 

For  those  who  insist  upon  perform¬ 


ing  all  operations  with  a  single  key¬ 
stroke,  there  are  menus. 

Another  thing:  metaphors.  This 
isn’t  really  related  to  icons,  except 
that  it’s  tangled  up  in  your  discussion 
of  them.  I  wouldn’t  quibble  with  any¬ 
one  who  said  that  the  purpose  of  soft¬ 
ware  was  to  entertain,  educate,  or  to 
simply  get  the  job  done  ...  or  to  ob¬ 
fuscate,  mislead,  and  lie.  But  ‘The 
purpose  of  software  is  to  realize  met¬ 
aphors’?  Could  you  say  that  again, 
please?  Could  you  illustrate  with  an 
example? 

Well,  gee,  now  that  I’ve  gotten  go¬ 
ing  it’s  just  too  hard  to  stop.  One 
more  thing.  I  greatly  doubt  that  all  of 
your  readers  know  that  Richard 
Conn  wrote  ZCPR3.  As  a  conse¬ 
quence,  I  would  have  thought  it  to  be 
a  prudent  editorial  decision  to  add  a 
note  to  that  effect  to  his  review  [DDJ 
#  1 03,  May  1 985  ]  of  the  Ampro  com¬ 
puters,  which  comes  bundled  with 
ZCPR3.  What  do  you  think? 

(By  the  by,  I  mean  no  criticism  of 
ZCPR3.  It’s  the  best  damn  thing  to 
come  down  the  pike  since  the  Z80. 
Conn — and  Echelon — are  to  be  ap¬ 
plauded  roundly.) 

Sincerely, 

Dreas  Nielsen 
234  NW  30th  St. 

Corvallis,  OR  97330 
Indeed  we  should  have  identified 
Richard  Conn’s  affiliation. — Ed. 

CP/M 

Dear  DDJ: 

I  have  been  using  CP/M  2.2  for  sever¬ 
al  years,  and  acquired  CP/M  Plus 
about  9  months  ago.  I  am  pleased 
with  it,  but  I  have  run  up  against  one 
difficulty,  namely  that  the  SAVE  pro¬ 
gram  does  not  correctly  save  ,REL 
files  (produced  by  Microsoft’s  BAS- 
COM)  loaded  by  MICROSOFT’S  L80 
relocating  loader.  I  wrote  a  short  BA- 


Dr.  Dobb's  Journal,  July  1985 


8 

503 


SIC  program  to  list  ASCII  files,  in¬ 
cluding  line  numbers;  after  finishing 
a  listing  it  prompts  the  operator  for 
another  file  name.  L80  loads  the 
.REL  file,  and  shows  a  starting  ad¬ 
dress  of  013CH.  (The  starting  ad¬ 
dress  of  CP/M  programs  is  0100H.) 
If  01 3C  is  used  as  the  starting  ad¬ 
dress  when  the  program  is  saved  (us¬ 
ing  CP/M  Plus’s  SAVE  program)  it 
does  not  run  properly.  It  returns  to 
the  operating  system  after  a  few  sec¬ 
onds,  having  done  nothing  visible.  (It 
does  not  even  ask  for  a  file  to  list.)  If 
the  file  is  saved  with  a  starting  ad¬ 
dress  of  0100H,  the  resulting  .COM 


program  does  execute.  It  lists  the  file, 
and  returns  to  ask  for  another  file 
when  the  first  file  is  completed.  How¬ 
ever,  it  does  not  operate  properly: 
with  each  line  it  prints  a  set  of  char¬ 
acters  that  do  not  belong  to  the  file, 
the  same  set  of  characters  for  each 
line.  If  loaded  and  saved  under  CP/M 
2.2  it  operates  properly,  even  when 
moved  to  a  CP/M  Plus  diskette.  How¬ 
ever,  if  the  .REL  file  is  loaded  by  L80 
with  the  /G  option  the  program  oper¬ 
ates  properly,  even  under  CP/M  Plus. 

If  the  .REL  file  is  loaded  by  Digital 
Research’s  LINK,  the  resulting  .COM 
program  executes  and  lists  the  file 


properly,  but  hangs  without  return¬ 
ing  to  request  more  input  when  it  has 
finished  listing  the  file.  Recovery  re¬ 
quires  rebooting  the  system. 

I  have  written  to  Digital  Research 
and  Microsoft,  as  well  as  the  vendor 
from  whom  I  bought  the  computer,  a 
MAX80,  and  have  not  received  any 
assistance.  Have  you  heard  about  this 
problem?  Or  might  some  of  your 
readers  have  some  information  that 
might  be  helpful. 

Sincerely  yours, 

Maynard  B.  Neher 
16637  Diaz  Drive 
San  Diego,  CA  92128 

Tiny  BASIC 

Dear  DDJ\ 

I  have  enjoyed  your  journal  very 
much  over  the  years.  I  especially 
liked  your  articles  on  Tiny  BASIC 
and  Small-C.  I  implemented  Tiny- 
BASIC  on  the  6800  after  your  first 
articles.  I  wrote  a  Small-C  for  the 
6809  when  you  started  the  Small-C 
series  and  did  one  for  the  68000  later. 
When  you  published  the  article  on 
Tiny  BASIC  for  the  68000  in  the  Feb¬ 
ruary  issue  [DDJ  #100  February 
1985]  I  couldn’t  resist  downloading 
the  code  and  trying  it. 

1  have  a  suggestion  for  improve¬ 
ment  which  I  would  like  to  pass 
along.  The  RND  function  does  not 
generate  very  good  random  numbers. 
I  wrote  the  enclosed  test  program 
which  generates  pairs  of  1  digit  num¬ 
bers.  These  should  be  evenly  distrib¬ 
uted  over  the  100  possible  combina¬ 
tions.  If  you  try  the  program  you  will 
see  that  there  are  several  pairs  of  dig¬ 
its  that  don’t  appear  at  all. 

I  have  enclosed  an  improved  RND 
function  which  eliminates  this  prob¬ 
lem.  It  is  only  a  first  try  and  there  is 
room  for  improvement  in  two  areas. 
First  it  is  restricted  to  a  limit  of 
65535  since  I  didn’t  bother  to  imple¬ 
ment  a  32-bit  multiply.  Second  it  is 
based  on  the  multiplicative  algorithm 
and  I  didn’t  spend  any  effort  picking 
the  best  multiplier.  It  should  have  a 
sequence  length  of  4,294,967,296 
however. 

John  R  Byrns 
1953  Governors  Ln. 

Hoffman  Estates,  I L  60195 

DDJ 


cn/i 


Dr.  Dobb's  Journal,  July  1985 


DR.  DOBB'S  CLINIC 


Random  Moves 

We  are  still  collating  the  letters  we've 
received  on  that  pseudo-random  num¬ 
ber  generator  (PRNG)  we  showed  in 
February.  Don’t  use  it.  We  promise 
emended  and  improved  versions  in 
Forth  and  assembly  language  next 
month. 

Turbo  and  CP/M  Plus 

The  estimable  Turbo  Pascal  product 
has  a  design  error  that  makes  its  8-bit 
version  unusable  under  CP/M  Plus 
(CP/M  3.0)  and  dubious  under  CP/M 
2.2.  When  you  compile  a  program  to 
form  a  .com  file.  Turbo  includes  sev¬ 
eral  kilobytes  of  runtime  support  in 
the  output,  along  with  the  code  gen¬ 
erated  from  your  Pascal  source. 
That’s  not  the  problem;  runtime  sup¬ 
port  code  is  necessary. 

Part  of  the  job  of  that  runtime  li¬ 
brary  is  to  initialize  the  program’s  en¬ 
vironment  when  it  is  loaded  and  be¬ 
gins  execution.  Anyone  who  has 
written  assembly  code  for  CP/M 
knows  what  must  be  done:  the  stack 
pointer  must  be  set  up  and  the  size  of 
available  storage  discovered.  The  usu¬ 
al  way  of  sizing  storage  is  to  load  the 
BDOS  entry  address  from  location  6. 

It’s  a  universal  CP/M  convention 
that  system  programs  can  reserve 
storage  for  themselves  at  the  top  of 
the  program  area  by  reducing  that 
address.  DDT  and  SID  do  it;  XSUB 
does  it;  spoolers  and  key-macro  pro¬ 
grams  do  it;  and  CP/M  Plus  has  ele¬ 
vated  it  from  an  informal  convention 
to  an  elaborate  system  of  Resident 
System  Extensions  (RSXs).  As  a  re¬ 
sult,  the  top  of  storage  is  a  dynamic 
value  in  CP/M.  It  can  change  from 
day  to  day  if  the  BIOS  expands  and 
from  moment  to  moment  as  system 
programs  are  loaded. 

The  Turbo  compiler,  however, 
treats  the  size  of  storage  as  a  con¬ 


stant.  At  compile  time,  it  places  in 
the  runtime  code  a  constant  number 
reflecting  the  size  of  the  program  as 
the  compiler  found  it;  when  the  .com 
is  executed,  it  expects  storage  to  be 
exactly  the  same  size.  If  programs 
compiled  under  Turbo  V.3  find  less 
storage  than  the  compiler  had,  they 
abort  with  the  message  “Not  enough 
memory.”  This  has  nothing  to  do 
with  how  many  bytes  the  program 
needs.  Indeed,  our  test  program 
didn’t  need  any;  it  simply  wrote  “hel¬ 
lo”  10  times.  If  we  compile  it  when  no 
RSXs  are  present  but  run  it  when  one 
is,  it  aborts.  Run  under  SID,  it  aborts. 
It’s  a  strange  oversight  in  an  other¬ 
wise  solid  product. 

Turbo  and  Standard  Pascal 

Understand,  please,  that  we  like  Bor¬ 
land  and  its  marketing  policies.  Its 
products  give  good  value  for  the  mon¬ 
ey,  and  Borland  backs  them  with  sup¬ 
port  that  is  much  better  than  average. 
Between  Borland  and  the 
“shareware”  pioneers,  a  whole  new 
level  of  price/performance  is  being 
established  in  the  software  industry. 
So  spare  us  the  letters  about  how 
Borland  has  done  so  much  for  soft¬ 
ware,  etc.,  etc.  We  appreciate  all 
that. 

Turbo  Not  Standard 

But  we  are  getting  just  a  tad  tired  of 
the  claim  that  Borland’s  Turbo  Pas¬ 
cal  is  “standard.”  It  is  definitely  not 
standard  Pascal,  which,  in  a  compiler 
that  seems  destined  to  dominate  the 
micro  world,  is  a  damn  shame.  Its 
price  and  good  user  interface  com¬ 
mend  it  for  school  use,  but  we  cer¬ 
tainly  couldn’t  recommend  it  for  that 
purpose,  and  we  doubt  any  college 
computer  science  department  would 
accept  it  despite  its  friendliness. 

Let’s  talk  about  the  Pascal  stan¬ 


dard,  how  Turbo  violates  it,  and  what 
that  costs  its  users. 

The  Ignored  Standard 

There  is  a  Pascal  standard.  In  the 
U.S.,  it  is  sanctioned  by  the  IEEE 
Computer  Society  and  the  American 
National  Standards  Institute 
(ANSI);  abroad,  by  the  International 
Standards  Organization  (ISO).  Al¬ 
though  ANSI  finalized  it  in  Decem¬ 
ber  1982,  it  was  available  at  least  two 
years  earlier  in  drafts  that  differed 
from  each  other  in  only  the  minutest 
points  and  that  nobody  expected 
would  change  in  any  significant  way 
prior  to  final  approval. 

The  standard  language  omits  fea¬ 
tures  that  many  people  want.  Al¬ 
though  it  defines  a  minimum  Pascal, 
it  explicitly  opens  the  door  to  exten¬ 
sions.  It  defines  an  extension  as  “a 
modification  to  . . .  this  standard  that 
does  not  invalidate  any  program  com¬ 
plying  with  this  standard  .  . .  except  by 
prohibiting  the  use  of  one  or  more  par¬ 
ticular  spellings  of  identifiers.”  In 
other  words,  you  may  add  any  fea¬ 
tures  you  like  to  Pascal  as  long  as  you 
compile  standard  programs  correctly. 
The  last  clause  even  lets  you  add  new 
reserved  words  to  the  language,  words 
that  could  be  user-defined  names  un¬ 
der  the  standard. 

The  standard  lays  two  burdens  on 
the  developer.  First,  you  must  docu¬ 
ment  variations  from  the  standard  in 
certain  ways;  that’s  usually  no  prob¬ 
lem.  The  other  is  that  a  translator 
must  “be  able  to  determine  whether  or 
not  a  program  violates  .  .  .  this  stan¬ 
dard  . . .  and  report  the  result  of  this 
determination  to  the  user.”  In  other 
words,  you  must  warn  the  user  when  a 
program  is  not  standard  Pascal. 
That’s  essential  to  portability;  other¬ 
wise  what  you  intend  to  be  a  standard 
(hence,  portable)  program  can  slip 


Dr.  Dobb's  Journal,  July  1985 


12 

505 


into  an  unstandard  state  that  is  unde¬ 
tectable  until  you  attempt  to  port  it. 

Standard  Pascal  is  a  small  language 
but  still  bigger  than,  say,  Fortran  IV; 
more  flexible  than,  say,  COBOL;  and 
easier  to  teach,  to  use,  and  to  imple¬ 
ment  than,  say,  Ada.  If  there  were 
general  compliance  with  the  standard, 
we  could  teach  people  a  single  lan¬ 
guage  they  could  use  anywhere,  at 
least  for  small  and  medium  problems. 
If  you  moved  to  a  new  implementa¬ 
tion,  you  would  have  to  learn  the  pe¬ 
culiar  extensions  it  supported,  but  you 
could  still  do  useful  programming 
while  you  learned.  And  programs  you 
wrote  using  the  standard  language 
would  be  portable  anywhere. 

It’s  ironic  that  many  newer  and  less 
forgiving  standards  (e.g.,  for  graphics, 
for  terminal  escape  sequences,  for  var¬ 
ious  buses)  garner  wide  respect  and 
conformance,  while  the  modest  and 
unconfining  Pascal  standard,  draws 
only  scorn  from  developers. 

Turbo  Versus  the  Standard 

The  Turbo  manual  ( Turbo  Pascal 
Version  3.0  Reference  Manual,  Bor¬ 
land  International,  Inc.,  1985) 
doesn’t  contain  the  kinds  of  docu¬ 
mentation  called  for  by  the  standard, 
but  it  does  have  Appendix  D,  “Turbo 
vs.  Standard  Pascal”  (p.  319).  This 
shows  at  once  that  Borland  is  un¬ 
aware  of  the  ANSI/ISO  standard. 
“The  Turbo  Pascal  language,”  it 
says,  “follows  the  Standard  Pascal 
defined  by  Jensen  &  Wirth  in  their 
User  Manual  and  Report 

We  hate  to  disturb  such  blissful 
slumber,  but  the  language  has 
changed  in  the  10  years  since  the  User 
Manual  and  Report  was  published. 
The  ANSI/IEEE  committee  convened 
in  1979  and  published  its  first  draft 
standard  in  1980,  two  years  before 
Turbo  was  born. 

Turbo  follows  its  outdated  stan¬ 
dard  “with  only  minor  differences  in¬ 
troduced  for  the  sheer  purpose  of  effi¬ 
ciency.”  Efficiency  for  whom  or 
what?  For  users  who  want  to  port 
programs  or  to  port  their  hard-won 
knowledge  of  the  language?  Surely 
not!  What  then?  Efficiency  in  the  ex¬ 
ecution  of  the  generated  object  pro¬ 
grams?  We  will  examine  the  case  for 
that  as  we  go;  it  doesn’t  hold  up. 


Could  the  changes  be  to  improve 
the  efficiency  of  the  compiler  itself? 
Very  possibly;  we’ll  see  that  the  elim¬ 
ination  of  some  hard  cases  may  have 
let  the  compiler  be  a  little  simpler. 

Or  could  the  manual  be  referring 
to  the  efficiency  with  which  the  de¬ 
velopers  can  bring  a  product  to  mar¬ 
ket?  Did  Turbo  drop  the  hard  parts 
so  the  developers  could  finish  their 
compiler  more  quickly? 

New  and  Dispose 

Turbo  v.  1 .0  skipped  the  standard  way 
of  managing  dynamic  storage,  the 
procedures  New  and  Dispose.  V.2 
added  them  but  in  incomplete  form. 
Turbo  supports  the  basic  features: 
New (ptr)  creates  a  new  one  of  what¬ 
ever  ptr  is  declared  to  address,  and 
Dispose(ptr)  reclaims  the  space  used 
by  whatever  ptr  points  to. 

The  standard  allows  a  special  case 
for  allocating  variant  records  (records 
that  may  have  different  lengths  de¬ 
pending  on  a  tag  field).  Suppose  that 
ptr  bases  a  variant  record  type  and 
that  tag  is  one  of  the  constants  that 
select  among  the  variants.  New(/?r/-) 
will  allocate  space  to  hold  the  longest 
variant,  but  New  {ptr, tag)  should  allo¬ 
cate  just  enough  space  to  hold  the  tag 
variant.  Dispose(/Ur,/ag)  is  then  re¬ 
quired  to  release  such  a  record. 

Turbo  omits  these  special  forms. 
Why?  Not  to  enhance  the  user’s  effi¬ 
ciency;  it  will  seriously  complicate 
your  life  if  you  try  to  import  to  Turbo 
a  program  that  uses  this  feature.  Nor 
for  the  program’s  efficiency;  the 
shortest  variant  of  a  record  is  often 
the  most  common  one,  and  lacking  a 
way  to  allocate  short  records,  you  can 
waste  a  lot  of  dynamic  storage 
sometimes  enough  to  make  a  pro¬ 
gram  infeasible. 

Turbo  didn’t  have  to  ban  these 
cases.  It  could  have  accepted  them 
while  ignoring  the  tag  parameters. 
Then  standard  programs  would  at 
least  compile.  But  why  not  support 
them  in  full?  The  standard  says  tag 
must  be  a  constant;  therefore,  Turbo 
could  still  tell  at  compile  time  how 
many  bytes  New  allocates  or  Dispose 
releases.  The  extra  compiler  code 
couldn’t  be  large,  and  the  cases  have 
no  effect  on  the  runtime  library. 

But  the  manual  adds  insult  to  inju¬ 


ry.  “The  restriction,”  it  chirps,  “is 
easily  circumvented  by  using  the 
standard  procedure  GetMem.” 
Wrong  on  every  count!  GetMem  is 
not  “standard”;  that  name  is  unique 
to  Turbo.  Nor  is  GetMem  type- se¬ 
cure,  as  New  is.  Plus  it  requires  a 
count  of  bytes  to  allocate,  so  to  use  it 
you  must  know  in  detail  how  Turbo 
allocates  space  in  variant  records. 
Then  heaven  help  you  if  you  change 
the  record  type  but  forget  to  change 
the  GetMem  calls!  (Turbo  has  a  non¬ 
standard  SizeOf  function,  but  it 
won’t  give  you  the  size  of  a  particular 
record  variant.) 

The  Page  Procedure 

The  standard  specifies  a  list  of  proce¬ 
dures  and  functions  that  a  compiler 
must  predefine.  Turbo  has  all  the 
trigonometric  functions,  all  but  two 
of  the  I/O  procedures  (discussed 
below),  and  dozens  of  nonstandard 
routines  for  “turtle”  graphics,  special 
I /O  operations,  DOS  calls,  and  so  on. 

But  it  lacks  the  simple  procedure 
Page.  Page(f),  where  f  is  a  text  (AS¬ 
CII)  file,  is  supposed  to  append  a 
newline  if  the  file  isn’t  currently  at 
the  head  of  a  line  then  “cause  an  im¬ 
plementation-defined  effect  on  the 
textfile  f,  such  that  subsequent  text 
.  .  .  will  be  on  a  new  page  if  the  text- 
file  is  printed  on  a  suitable  device.” 
Like  the  other  Pascal  I/O  routines, 
Page  with  no  parameter  works  on  the 
file  output  by  default. 

Page  may  not  seem  like  much  of  a 
loss,  but  its  absence  puts  the  user  in  an 
infuriating  bind.  Like  the  other  Pascal 
I/O  procedures,  Page  cannot  be  du¬ 
plicated  by  user  code.  User  proce¬ 
dures  can’t  have  default  parameters, 
and  there  is  no  way  to  perform  the  es¬ 
sential  test  of  a  file  variable  to  see  if  it 
is  at  the  head  of  a  line.  (A  Page  that 
always  writes  a  newline  will  some¬ 
times  force  an  extra  blank  sheet  where 
the  standard  Page  will  not.) 

What  do  you  do  to  import  a  stan¬ 
dard  program  that  relies  on  Page? 
You  find  every  use  of  Page  and 
change  it  to  call  one  of  two  proce¬ 
dures  that  you  must  write  yourself— 
procedures  that  can’t  duplicate  the 
standard  actions  perfectly.  If  the  out¬ 
put  isn’t  satisfactory,  you  must  dig 
into  the  program’s  logic.  Changing 


14 


Dr.  Dobb's  Journal,  July  1985 


I  the  logic  of  an  imported  program,  one 
written  perhaps  by  total  strangers, 
because  of  a  stupid  omission  from  a 
compiler  is  not  a  good  use  of  time.  If 
you  write  a  program  that  you  will  la¬ 
ter  export,  you  must  include  your 
homebrew  PageNamedFile  and  Pa- 
geDefault  procedures,  surrounded 
with  big  comment  blocks  explaining 
how  to  alter  them  to  use  Page  if  it 
should  be  available. 

What  would  Page  cost:  64  bytes  of 
code  in  the  runtime  library  and  maybe 
32  bytes  of  table  space  in  the  compil¬ 
er?  Its  overhead  would  be  undetect¬ 
able  in  a  compile  and  zero  at  runtime. 
Why  did  Turbo  omit  it?  We’ll  never 
know.  Appendix  D  says  it’s  because 
“the  CP/M  operating  system  does  not 
define  a  form-feed  character,”  but 
that  is  pure  waffle.  If  there  is  one 
thing  common  to  all  CP/M  systems, 
it’s  ASCII,  whose  form  feed  is  respect¬ 
ed  by  every  printer  we  know. 

Get  and  Put 

Wirth  gave  a  mathematician’s  defini¬ 
tion  of  I  /O.  He  began  with  a  rigorous 
definition  of  the  file  data  type.  Then 
he  chose  a  minimal  set  of  operators 
on  that  type,  operators  whose  actions 
are  regular  enough  to  be  useful  in  for¬ 
mal  verifications  of  program  correct¬ 
ness.  Finally  he  defined  all  file  opera¬ 
tions  in  terms  of  these  minimal 
operators.  The  standard  follows  the 
User  Manual  and  Report  in  giving  a 
rigorous  logician’s  definition  of  file 
I/O,  all  based  on  the  operators  Get 
and  Put. 

What  do  these  fundamental  opera¬ 
tions  do?  First,  understand  that  to 
Wirth  a  file  variable  is  a  species  of 
pointer  variable.  He  says  that  a  file  is 
a  sequence  of  objects,  while  a  file  vari¬ 
able  is  a  pointer  to  only  one  of  the  ob¬ 
jects  in  the  sequence.  The  declaration 

fr  :  file  of  real; 

asserts  two  things:  somewhere  there 
is  a  sequence  of  real  numbers  (possi¬ 
bly  empty),  and  under  the  right  cir¬ 
cumstances  fr  will  address  one  of 
them.  The  statement 

Reset(fr); 

sets  up  the  circumstances;  fr  now  ad¬ 


dresses  the  first  real  number  in  the 
file.  For  comparison,  recall  that 

pr :  “real; 

sets  up  a  variable  pr  that  points  to  a 
single  real  number,  as  in 

x  :=  3.5  *  pr“; 

Therefore,  Reset  (fr)  sets  things  up  so 
that  fr  addresses  a  real  number: 

x  :=  3.5  *  fr*; 

Thus  did  Wirth  unify  the  treatments 
of  I/O  and  dynamic  storage. 

Standard  Get  and  Put 

The  Get  procedure  advances  a  file 
one  unit  in  its  sequence.  After 
Get  (fr),  fr  addresses  the  next  real 
number  in  the  sequence. 

Rewrite  opens  a  file  for  output;  the 
statements 

nr :  file  of  real; 

Rewrite(nr); 

create  an  empty  sequence  of  real 
numbers  and  make  nr  point  to  a  real 
of  undefined  value.  You  may  use  nr 
exactly  like  any  other  pointer  to  reals: 

nr'  :=  sqr(pr')  +  sqr(fr'); 
pr'  :=  sqrt(nr'); 

The  statement 

Put(nr); 

appends  the  current  referent  of  nr  to 
its  file  and  makes  nr  point  to  unde¬ 
fined  space  again. 

Do  you  find  these  ideas  confusing, 
difficult,  baffling?  Do  you  think  they 
make  for  an  inflexible  system?  Or  do 
you  agree  that  they  are  simple,  logi¬ 
cal,  consistent,  and  highly  useful? 

The  Borland  programmers  did  not. 
Turbo  does  not  support  Get  and  Put, 
nor  does  it  permit  a  file  variable  to  be 
used  as  a  pointer.  It  offers  only  the 
Read  and  Write  procedures  (which 
to  Wirth  are  composite  operations 
defined  in  terms  of  Get  and  Put). 

Simplicity? 

Appendix  D  offers  four  excuses  for 


the  omission.  One  is  that  Read  and 
Write  are  “easier  to  understand.”  A 
computer  science  teacher  would  never 
say  that.  Good  teachers  explain  the 
basis  of  things.  You  cannot  explain 
the  basis  of  Read  and  Write  without 
referring  to  Get  and  Put;  you  can  state 
their  actions  as  arbitrary  rules  to  learn 
by  rote,  but  you  can’t  explain  them. 

Of  course,  if  you  are  already  an  ex¬ 
perienced  programmer — one  used  to 
Fortran  or  BASIC,  perhaps — you 
might  find  the  Read  and  Write  pro¬ 
cedures  more  familiar  than  Get  and 
Put,  and  you  might  confuse  familiar¬ 
ity  with  simplicity.  That’s  a  common 
error,  as  anyone  will  attest  who  has 
tried  to  explain  the  arbitrary,  but  to¬ 
tally  familiar,  rules  of  English  to  a 
foreigner. 

Versatility? 

One  of  the  three  other  excuses  is  that 
Read  and  Write  are  “far  more  versa¬ 
tile”  than  Get  and  Put.  That’s  rub¬ 
bish,  but  our  hypothetical  ex-Fortran 
user  wouldn’t  realize  it  without  close 
study  of  Pascal. 

With  Put,  you  can  assign  and  reas¬ 
sign  an  output  value  as  often  as  you 
like;  the  value  isn’t  committed  to  the 
file  until  Put  is  issued.  That  concept 
isn’t  in  the  thinking  of  a  PL/I  user;  in 
most  languages,  once  you  associate 
data  with  a  file,  it’s  gone.  Once  you 
comprehend  the  idea,  however,  you 
can  find  lots  of  uses  for  writing  data 
without  putting  it. 

Get  is  even  more  useful.  If  you’ve 
read  Kernighan  and  Plauger’s  Soft¬ 
ware  Tools ,  you  may  recall  how  often 
they  resort  to  the  ungetc(  )  procedure 
to  replace  the  character  last  read 
from  a  file.  With  Pascal  standard  in¬ 
put,  you  can  look  at  the  next  value 
coming  from  a  file  without  commit¬ 
ting  yourself  to  removing  it  from  the 
file.  You  simply  test  the  value  ad¬ 
dressed  by  the  file  variable.  If  you 
want  it,  you  assign  it  somewhere  else 
and  issue  Get.  You  may  also  leave  it 
for  another  part  of  the  program  to 
use,  or  discard  it  with  Get.  Here’s  a 
procedure,  free  of  side  effects,  to  strip 
blanks  from  an  input  file: 

procedure  fstrip(var  f:  text); 

begin 

while  (not  eof(f)) 


Dr.  Dobb’s  Journal,  July  1985 


16 

507 


fine  two  comparison  routines: 


and  (’  ’  =  fA)  do 
Get(f) 

end; 

Afterward,  either  the  next  element  of 
the  file  is  a  nonblank  or  the  file  is  fin¬ 
ished.  Try  writing  a  comparable 
function  in  C  or  BASIC — or  in  Turbo! 
It  takes  either  ungetc(  )  or  your  own 
input  function  with  a  static  variable. 

Because  Get  and  Put  are  standard 
and  highly  useful,  you  may  assume 
that  Pascal  programmers  make  use  of 
them.  A  lot  of  Pascal  programs  are 
floating  around  on  minis  and  main¬ 
frames  and  in  textbooks.  The  odds 
are  good  that  if  you  try  to  import  one 
to  Turbo,  you’ll  fail  because  the  pro¬ 
gram  uses  Get  or  Put.  You  may  ex¬ 
pect  to  find  as  well  that  the  assump¬ 
tions  behind  Get  and  Put  are  woven 
deeply  into  the  program’s  logic;  most 
programs  will  need  major  changes  to 
run  without  them. 

Less  Overhead? 

Appendix  D  offers  the  excuse  that, 
without  Get  and  Put,  “variable  space 
overhead  is  reduced,  as  file  buffer 
variables  are  not  required.”  True,  the 
compiler  would  have  to  allocate  a 
buffer  the  size  of  one  file  data  item  in 
addition  to  the  sector  buffer  it  needs 
for  DOS  I/O.  Alternatively,  it  could 
round  the  size  of  a  data  item  up  to  a 
whole  number  of  DOS  sectors,  add  one 
sector,  and  allocate  a  single  buffer  of 
that  size  (this  ensures  a  complete  data 
item  will  always  fit  in  the  buffer). 

Banning  standard  use  of  the  file 
variable  lets  the  compiler  get  by  with 
a  single  sector  buffer.  Does  that  re¬ 
duce  “variable  space  overhead”?  Of 
course  not!  Now  you  have  to  define 
the  variables!  You  need  extra  vari¬ 
ables  to  hold  data  items  before  they 
are  written  to  the  file,  instead  of  mere¬ 
ly  assigning  and  reassigning  until  a 
Put  is  done.  You  need  variables  to 
hold  items  after  reading  them  from 
the  file  and  before  using  them  in  ex¬ 
pressions.  And  if  it  turns  out  the  items 
shouldn’t  have  been  read,  Turbo  of¬ 
fers  no  ungetc(  ),  so  you  get  to  define 
one — again  adding  code  overhead. 

Speed ? 

Appendix  D’s  premier  excuse  is  that 
“Read  and  Write  give  much  faster 

17 

<sna 


I/O.”  Do  they?  True,  a  naive  imple¬ 
mentation  of  Get  might  often  copy 
data  from  a  sector  buffer  to  an  item 
buffer.  But  Read  requires  copying 
data  to  a  user-defined  variable, 
whereas  a  smart  implementation  of 
Get  could  avoid  moving  data  at  all. 
Many  times  when  the  file  data  type  is 
smaller  than  half  a  sector  (the  typical 
case),  Get  need  do  no  more  than  in¬ 
crement  a  pointer,  check  for  end  of 
sector,  and  return.  That’s  quick! 

If  a  new  sector  must  be  read,  a  Get 
implementation  might  have  to  move 
part  of  the  last  sector  from  the  end  of 
the  buffer  to  its  head  before  reading 
more  data.  But  that  move  always  in¬ 
volves  less  than  a  single  data  item! 
Under  MSDOS,  when  the  file  data 
type  is  composite  (record,  set,  or  ar¬ 
ray),  a  clever  compiler  would  push 
the  whole  burden  onto  the  DOS.  It 
would  allocate  no  sector  buffer  at  all, 
just  an  item  buffer,  and  use  “handle” 
I/O  to  read  or  write  only  as  many 
bytes  as  needed. 

A  good  implementation  of  Get  and 
Put  might  entail  15  percent  more 
code  than  the  present  Turbo  library, 
but  by  moving  less  data,  it  might  well 
execute  in  25  percent  less  time. 

Procedure  Parameters 

One  of  the  least-regarded  features  of 
standard  Pascal  is  the  ability  to  pass 
a  procedure  or  function  as  a  parame¬ 
ter.  Let’s  say  “routine”  instead  of 
“procedure  or  function.”  Then  the 
feature  sounds  simpler:  the  ability 
to  pass  one  routine  the  name  of  an¬ 
other.  Its  purpose  is  to  let  a  routine’s 
behavior  be  parameterized  along 
with  its  data.  Unfortunately,  neither 
Jensen  nor  Wirth  nor  the  standards 
committee  could  find  a  good  example 
of  its  use  except  “finding  the  mini¬ 
mum  of  a  function  by  bisection.” 
Now  really,  what  can  an  honest  pro¬ 
grammer  say  to  that  but  “Gah, 
Wha?” — meaning,  surely  you  can 
omit  such  an  abstruse  feature  from  a 
language  without  anyone’s  noticing! 

Let’s  try  to  suggest  some  examples 
to  show  why  it  should  stay.  The  first 
might  be  a  generalized  sort  that 
takes,  as  a  parameter,  the  name  of  a 
function  that  compares  two  data 
items  and  returns  True  if  one  is  less 
than  or  equal  to  the  other.  Now  de¬ 


function  Up(a,b:real):  Boolean; 
begin  Up  :=  (a  <  =  b)  end; 

function  Down(a,b:real):Boolean; 
begin  Down  :  =  (  b <  =  a  )  end; 

The  identical  generalized  routine  will 
sort  ascending  if  we  pass  it  the  name 
of  Up  and  descending  if  we  pass  it 
Down. 

Notice  that  if  we  make  Up  and 
Down  take,  not  the  data  to  be  com¬ 
pared,  but  ordinal  indices  to  the  data, 
and  if  we  also  pass  a  procedure  Swap 
that  interchanges  data  items  given 
their  indices,  we  will  have  removed 
all  dependencies  on  data  type  from 
the  general  sort  routine.  We  need 
only  pass  it  the  Up  or  Down  and  the 
Swap  that  are  appropriate  to  the  data 
type  we  want  to  sort. 

As  another  example,  consider  a  sit¬ 
uation  where  a  service  routine  can  de¬ 
tect  an  error,  for  which  the  correct 
recovery  action  depends  on  the  condi¬ 
tions  under  which  the  routine  was 


Dr.  Dobb’s  Journal,  July  1985 


called.  There  are  three  ways  to  han¬ 
dle  this:  (1)  You  can  write  a  version 
of  the  routine  for  each  recovery 
method;  (2)  you  can  pass  the  routine 
an  error-recovery  action  number  and 
put  a  big  error-handling  case  state¬ 
ment  in  it;  or  (3)  you  can  pass  the 
routine  the  name  of  an  error-han¬ 
dling  procedure.  Each  client  may 
then  contain  a  local  procedure  to  do 
error  recovery  in  its  own  style.  The 
service  routine  remains  general, 
while  the  recovery  actions  are  local¬ 
ized  in  those  parts  of  the  program 
that  establish  the  need  for  them. 

Turbo  Pascal  is  not  alone  in  ban¬ 
ning  procedure  parameters;  it’s  the 
most  ignored  feature  of  the  language. 
Why?  It  seems  like  a  fairly  simple 
thing  to  implement — at  first.  At  run¬ 
time,  it  amounts  to  just  pushing  the 
address  of  a  routine  on  the  stack.  At 
compile  time,  the  compiler  must 
check  that  the  parameter  list  of  the 
routine  that  is  passed  matches  the  pa¬ 
rameter  list  that  the  receiving  routine 
declared,  which  is  easier  to  do  than  to 
describe. 

The  real  problem  is  the  implemen¬ 
tation  of  Pascal’s  rules  for  the  scope 
of  variable  names.  When  procedure 
parameters  are  forbidden,  the  dy¬ 
namic  scope  of  a  name  matches  its 
lexical  scope;  the  compiler  can  tell 
from  the  program  text  exactly  what 
names  are  accessible  at  any  point  in 
the  program.  When  procedure  pa¬ 
rameters  are  allowed,  cases  occur 
where  the  referent  of  a  name  can  be 
resolved  only  by  extra  stack  linkages. 
That  complicates  the  runtime  code 
for  stack  management  and  for  proce¬ 
dure  call  and  exit. 

Although  Borland  may  seem  to 
have  made  a  good  tradeoff  in  drop¬ 
ping  procedure  parameters,  the  usual 
arguments  can  be  made  for  the  fea¬ 
ture:  it’s  useful  and  likely  to  appear  in 
published  programs,  especially  in  the 
more  sophisticated  ones,  those  that 
are  at  once  most  valuable  and  hardest 
to  modify.  We  might  add  that,  inas¬ 
much  as  Turbo  threatens  to  be  the 
single  most  common  implementation 
of  Pascal  ever,  Borland  has  an  obliga¬ 
tion  to  support  the  whole  language. 

The  stack  management  is  not  that 
complicated  anyway.  Full  Pascal 
stack  linkage  no  doubt  would  slow 


Dr.  Dobb  s  Journal.  July  1985 


down  an  8080  or  Z80  implementa¬ 
tion  (for  once  there’s  a  legitimate 
reason  for  restricting  the  CP/M  ver¬ 
sion!),  but  that  is  not  true  of  the  8086. 

What  would  be  the  penalty  for  full 
Pascal  in  the  MSDOS  version  of  Tur¬ 
bo?  The  compiler  would  be  slightly 
more  complicated,  as  noted.  And  the 
runtime  code?  Studies  have  shown 
that  the  overwhelming  majority  of 
references  are  to  local  and  global 
names  and  to  parameters.  The  trou¬ 
blesome  references,  to  names  defined 
in  intermediate  scopes,  are  very  rare. 
(It’s  tempting  to  modify  the  scope 
rules  to  forbid  just  these  references, 
but  that  would  create  incredibly  sub¬ 
tle  portability  bugs.)  If  the  generated 
code  optimizes  common  usages  and 
penalizes  the  rare  ones,  the  cost 
should  be  small. 

Summary 

The  Pascal  standard  defines  a  nice 
little  language,  but  despite  the  lan¬ 
guage’s  popularity  and  the  standard’s 
age,  the  features  of  the  language  and 
the  liberality  of  the  standard  are  still 
misunderstood  or  unappreciated.  The 
justifications  in  the  Turbo  manual’s 
Appendix  D  just  don’t  hold  water; 
likely  the  real  reason  Turbo  is  non¬ 
standard  is  that  the  Borland  pro¬ 
grammers  were  in  a  hurry  to  get  to 
market  so  they  dropped  some  fea¬ 
tures  that  were  obscure  (to  them)  or 
of  little  use  (to  people  who  don’t  un¬ 
derstand  Pascal  well).  Yet  Turbo 
Pascal  has  many  admirable  features. 
If  it  added  to  them  full  compliance 
with  the  Pascal  standard,  it  would  be 
without  question  the  finest  imple¬ 
mentation  of  Pascal  for  personal 
computers  and  among  the  finest  on 
any  computer. 

DDJ 

Reader  Ballot 

Vote  for  your  favorite  feature/article. 

Circle  Reader  Service  No.  1 90. 


18 

509 


As  part  of  my  ongoing  struggle  to 
turn  MSDOS  into  Unix,  I  write  a  lot 
of  Unix-like  utilities.  This  month 
we’ll  look  at  a  better  MSDOS  directo¬ 
ry  utility  called  “Is”  (after  the  Unix 
model).  Ls  uses  several  subroutines 
useful  in  their  own  right.  A  multico¬ 
lumn  print  utility  and  an  MSDOS  in¬ 
terfacing  utility  (which  corrects  vari¬ 
ous  deficiencies  in  the  one  supplied 
by  Lattice)  are  two  of  these. 

How  To  Use  Ls 

Ls  prints  a  sorted  directory  in  a  mul¬ 
ticolumn  format.  The  columns  are 
read  like  a  magazine;  that  is,  an  en¬ 
tire  column  is  read  from  top  to  bot¬ 
tom,  then  the  next  column  is  read.  Ls 
prints  volume  labels,  which  are  in 
boldface,  first.  Next  it  prints  directo¬ 
ries;  they  are  underlined.  Then  it 
prints  the  files.  Finally,  it  prints  the 
amount  of  space  used  by  the  directo¬ 
ry  (instead  of  the  space  left  on  the 
disk),  along  with  a  file  and  directory 
count.  The  figure  (page  21)  shows 
various  sample  outputs  from  ls. 

Usage  is:  ls  [options]  <file  list>. 
The  legal  command  line  options  are: 
-a 

List  all  files,  including  hidden  files. 
Like  Unix,  all  files  whose  names  be¬ 
gin  with  a  period  (  .  )  are  considered 
invisible  (even  if  the  hidden  attribute 
bit  isn’t  set);  -a  causes  these  files  to 
be  printed  too. 

-c-  num  > 

Print  the  output  in  <num>  columns 
instead  of  the  default  five.  Up  to  six 
columns  can  fit  on  a  screen.  The 
command 

ls  -cl  *.c  >foo.bat 

is  useful  for  making  batch  files.  No 
spaces  are  allowed  between  -c  and  the 
number 

-d 


List  only  directories  (no  files). 

-e 

Sort  by  extension  and  then  by  file¬ 
name.  This  groups  all  the  .c  files  to¬ 
gether  and  all  the  ,obj  files,  and  so  on. 
The  filenames  are  sorted  within  their 
extension  categories  (i.e.,  bar.c  pre¬ 
cedes  foo.c,  and  both  precede  bar.obj). 
-f 

List  only  files  (no  directories). 

-I 

List  in  long  format.  -1  lists  the  file  size, 
the  create  time  and  date,  and  the  file’s 
attributes.  Possible  attributes  are: 

D  -  (D)irectory 
H  -  (H)idden 
L  -  Volume  (L)abel 
M  -  File  has  been  (M)odified 
since  last  backup 
R  -  (R)ead  only 
S  -  (S)ystem 

-s 

Suppress  directory  name-underlining 
and  label-boldfacing  so  that  you  can 
direct  the  output  to  a  printer  without 
the  extra  escape  sequences.  Note  that 
directories  sort  to  the  top  of  the  list 
because  the  graphics  require  an  ESC 
(which  has  a  smaller  value  than  any 
letter)  at  the  beginning  of  the  name 
string.  Because  the  ESC  is  no  longer 
present,  the  directory  names  will  be 
mixed  in  with  the  filenames  when  you 
use  -s. 

-u 

Print  directory  unsorted. 

You  may  combine  options  (ls  -ac3s 
*.c)  or  list  them  separately  (ls  -a  -c3 
-s  *.c).  You  can  place  them  anywhere 
on  the  command  line  (ls  *.c  -1  is  the 
same  as  ls  -1  *.c). 

The  <file  list>  is  a  list  of  the  files 
you’re  looking  for.  You  can  list  files 
explicitly  (ls  foo.c  bar.c)  or  with  am¬ 
biguous  references  (ls  *.c).  A  directo¬ 
ry  may  precede  the  filename  (Is 


\src\tools\Is.c).  You  can  use  /  as 
well  as  \  to  separate  directory  names 
(ls  /src/tools/ls.c).  If  you  request  a 
single  directory,  then  ls  lists  the  con¬ 
tents  of  that  directory.  If  no  <file 
list>  is  given,  ls  prints  all  files  in  the 
current  directory. 

To  make  the  underlining  and  bold¬ 
facing  work,  you  must  find  the  line 
“device  =  ANSI. SYS”  in  your  con- 
fig.sys  file  when  the  system  boots.  If 
you  don’t  do  this,  your  screen  will  look 
funny  unless  you  use  the  -s  option. 

Getting  a  Directory 

You  can  access  MSDOS  directories  in 
two  ways.  A  set  of  low-level  DOS 
function  calls  “Search  for  First  En¬ 
try”  (Oxll)  and  “Search  for  Next 
Entry”  (0x12)  work  much  like  the 
equivalent  CP/M  functions.  (An  ex¬ 
ample  of  CP/M  directory  accessing 
appeared  in  the  March  1985  C 
Chest.)  Because  functions  11  and  12 
force  you  to  construct  an  FCB,  and 
they  search  only  the  current  directo¬ 
ry,  they’re  pretty  hard  to  use. 

Luckily,  DOS  versions  2  and  higher 
provide  two  easier-to-use  functions: 
“Find  First”  (0x4e)  and  “Find  Next” 
(0x4f).  When  you  pass  Find  First  a 
pointer  to  a  string  holding  a  filename, 
it  loads  information  about  the  re¬ 
quested  file  into  the  current  disk 
transfer  area  (DTA).  If  you  give  an 
ambiguous  file  reference,  subsequent 
files  that  match  the  reference  are  re¬ 
trieved  with  a  series  of  Find  Next 
calls.  Find  Next  must  use  the  same 
DTA  as  Find  First  used. 

If  an  error  is  encountered  (such  as 
not  finding  the  requested  file),  the 
carry  bit  is  set  when  the  DOS  inter¬ 
rupt  returns,  and  an  error  code  is  put 
into  the  AX  register.  DOS  v.2  sets  the 
AX  register  to  0  on  success,  but  DOS 
v.3  doesn’t  seem  to  do  this;  you  actu¬ 
ally  have  to  look  at  the  carry  bit. 


20 

510 


Dr.  Bobb’ s  Journal,  July  1985 


These  two  routines  have  several 
nice  features.  The  name  can  contain 
ambiguous  file  references  and  direc¬ 
tory  specifiers,  so  you  can  search  for 
a  file  anywhere  in  the  file  system. 
You  can  even  use  constructs  like  “. .” 
or  ,\.  .”  if  you  want.  Perhaps  nicer, 
you  can  use  /  as  well  as  \  to  separate 
directory  names  in  a  complete  file 
reference.  (This  is  actually  true  for 
all  the  low-level  MSDOS  file  func¬ 
tions.  The  insistence  on  that  idiotic 
backslash  is  an  anomaly  of  the 
MSDOS-supplied  shell,  command- 
.com.  Why  couldn’t  they  use  /  to  sep¬ 
arate  directories  and  -  for  command 
line  switches?)  Another  nice  feature 
is  that  you  can  specify  a  disk  id  as 
part  of  the  filename  (c:/foo). 

All  is  not  wonderful,  though.  The 
two  directory-searching  routines  do 
have  problems.  Unix  directories  are 
just  files.  The  only  difference  be¬ 
tween  a  Unix  directory  and  any  other 
file  is  the  value  of  the  directory  attri¬ 
bute  bit.  MSDOS  uses  a  similar  mech¬ 
anism,  except  that  directories  are 
special:  they  can’t  be  accessed  like 
other  files,  they  have  a  length  of  zero, 
and  so  forth.  However,  they  will  show 
up  in  a  directory  search  as  if  they 
were  files  (with  an  attribute  bit  that 
says  you’re  looking  at  a  directory). 

So,  if  you  request  a  directory  from 
Find  First,  you’ll  get  a  single  listing 
with  the  matching  directory  name. 
You  won’t  get  a  listing  of  the  files  in 
that  directory;  to  do  this,  you  have  to 
specify  /*.*  after  the  directory  name. 
A  search  for  “. .”  will  return  a  struc¬ 
ture  that  actually  contains  the  name 
of  the  parent  directory. 

A  second  problem  is  the  root  direc¬ 
tory.  MSDOS  doesn’t  think  that  the 
root  directory  exists.  If  you  request  / 
or  \  from  Find  First,  it  comes  back 
with  a  file-not-found  error.  Similarly, 
requesting  b:  produces  the  same  error. 
You  need  to  ask  for  a  file  (or  *.*). 

Another  problem  is  the  volume  la¬ 
bel.  A  filename  returned  by  DOS  has 
all  the  blank  padding  removed  and  a 
period  inserted  between  the  name 
and  extension.  Unfortunately,  DOS 
also  puts  the  period  into  a  volume  la¬ 
bel.  If  a  disk  is  labeled  LONGLABEL, 
the  DOS  directory  search  will  yield 
LONGLABE.L  as  the  volume  label. 
Ls  doesn’t  do  anything  to  correct  this 


problem. 

The  final  problem  is  actually  a  de¬ 
ficiency  in  the  Lattice  C  I/O  library. 
(The  people  at  Lattice  claim  that  the 
next  revision  of  the  compiler  fixes 
this,  but  that  doesn’t  help  us  now.)  To 
use  Find  First/Next,  you  need  to 
mess  with  the  DTA  (the  place  where 
the  directory  will  end  up  after  the 


function  call).  Unfortunately,  your 
disk  I/O  system  uses  the  DTA.  If 
you’re  not  doing  any  file  I/O,  you  can 
put  the  DTA  where  you  want  it  (with 
a  DOS  function  Ox  la  call)  and  then 
forget  about  it.  But,  if  you’re  working 
with  files  too,  you’ll  have  to  put  the 
DTA  back  where  it  came  from  to  do 
file  accesses. 


C:\SRC\LS  Is/ 

HARDFILE  GAMES  SRC  AUTOEXEC.BAT 

BIN  INCLUDE  TEXT  COMMAND.COM 

DOS  LIB  UTIL  CONFIG.SYS 

3  files  (22 1 72  bytes,  21  K),  8  directories 
C:\SRC\LS  Is  -la/ 


HARDFILE 

0 

12-20-84 

17:22:18 

LM 

BIN 

0 

1 2-24-84 

15:13:04 

D 

DOS 

0 

1 2-20-84 

17:23:04 

D 

GAMES 

0 

1 2-24-84 

15:20:32 

D 

INCLUDE 

0 

1 2-24-84 

15:17:56 

D 

LIB 

0 

12-24-84 

15:22:26 

D 

SRC 

0 

12-24-84 

15:13:54 

D 

TEXT 

0 

1 2-24-84 

15:20:58 

D 

UTIL 

0 

1 2-24-84 

15:17:58 

D 

AUTOEXEC.BAT 

95 

4-09-85 

17:42:50 

M 

COMMAND.COM 

22042 

8-14-84 

8:00:00 

CONFIG.SYS 

35 

1-22-85 

22:19:06 

M 

IBMBIO.COM 

8964 

7-05-84 

15:00:00 

RHS 

IBMDOS.COM 

27920 

7-05-84 

15:00:00 

RHS 

3  files  (59056  bytes,  58  K),  8  directories 
C:\SRC\LS  Is  -a/text/drdobbs 

FOO  LS.NR  SAVE.BAT 

CROOT.NR  QBITMAP.NR  SORT.  NR 

5  files  (56197  bytes,  54  K),  3  directories 
C:\SRC\LS  Is  -c  1  fe/text/drdobbs 

SAVE.BAT 

CROOT.NR 

LS.NR 

CL-BITMAP.NR 
SORT.  NR 

5  files  (56197  bytes,  54  K) 

C:\SRC\LS  Is  -d/text/drdobbs 

FOO 

1  directory 

Figure  1. 

Sample  Outputs  from  Ls 


Dr.  Dobb's  Journal,  July  1985 


21 

511 


DOS  function  0x2f  returns  the  cur¬ 
rent  DTA  in  ES:BX.  Unfortunately, 
the  Lattice  DOS  interface  functions 
(bdos,  int86,  etc.)  won’t  let  you  get  at 
the  ES  returned  by  the  get  DTA  func¬ 
tion.  They  push  all  the  registers,  do 
the  DOS  call,  then  restore  the  regis¬ 
ters,  overwriting  the  ES  returned  by 
DOS.  Nor  will  a  subsequent  seg- 
read(  )  call  return  the  value  you  need 
for  ES. 

My  solution  to  this  problem  was  to 
write  a  more  sensible  DOS  interface, 
which  we’ll  talk  about  in  a  moment. 
Although  Is  doesn’t  need  to  put  the 
DTA  back,  an  example  of  how  to  re¬ 
store  the  DTA  is  given  in  Listing  Five 
(page  41). 

Program  Description 

Ls  itself  appears  in  Listing  One  (page 
25).  MAXD1R  (line  12)  is  the  maxi¬ 
mum  number  of  files  that  will  be  list¬ 
ed;  change  this  if  you  need  more  than 
132  (132  names  are  6  columns  by  22 
lines). 

Ls  uses  two  subroutines  that  have 
appeared  in  previous  columns:  qsort 
(DDJ  #102,  April  1985)  to  sort  the 
directory  and  getargs  (DDJ  #103, 
May  1985)  to  parse  command  line  ar¬ 
guments.  The  argument  table  for  get¬ 
args  is  on  lines  27-37  of  Listing  One 
(getargs. h  on  line  2  is  used  by  getargs, 
as  we  discussed  back  in  May). 

The  global  variables  on  lines  19-26 
are  set  by  the  various  command  line 
switches.  The  variables  on  lines  39-44 
are  more  general  purpose. 

Directories  are  stored  in  an  argv- 
like  array  of  pointers  to  character 
strings:  Dirs  (line  39).  Dirv  and  Dire 
(40-41)  operate  like  argv  and  arge. 
Total  (line  42)  is  the  total  size,  in 
bytes,  of  all  listed  files.  Numfiles  and 
Numdirs  (43-44)  are  the  number  of 
files  and  directories  found, 
respectively. 

Find_first(  )  and  find_next(  ) 
(lines  47-77)  do  the  DOS  calls.  They 
use  the  DOS  interface  function  given 
in  Listing  Three  (page  34). 

The  REGS  structure  is  not  the 
structure  of  the  same  name  defined  in 
the  dos.h  supplied  by  Lattice.  This 
version  of  REGS  is  defined  in  my- 
dos.h  (Listing  Two,  page  33,  lines 
14-23).  REGS  is  actually  a  functional 
superset  of  the  Lattice  REGS  struc- 

22 

M5 


ture  (that  is,  you  can  use  the  REGS 
defined  in  mydos.h  to  talk  to  the  Lat¬ 
tice  interface  functions,  but  you  can’t 
use  the  Lattice  REGS  to  talk  to  the 
dos(  )  function  in  Listing  Three). 

REGS  has  fields  for  all  the  registers 
you  need  to  access  MSDOS,  including 
the  segment  registers.  It  is  a  union 
that  defines  the  normal  registers  so 
that  you  can  easily  access  the  high, 
low,  or  both  bytes.  The  two  register 
images  are  superimposed  (i.e.,  defini¬ 
tions  for  byte  offsets  are  superim¬ 
posed  over  definitions  for  word  off¬ 
sets).  For  example,  given  a  pointer  p 
to  a  REGS  structure,  p— *-x.ax  access¬ 
es  the  AX  register,  p-*-h.ah  gets  the 
high  byte  (the  AH  register),  and 
p-*h.al  gets  the  low  byte  (the  AL 
register).  You  can  retrieve  only  the 
AX,  BX,  CX,  and  DX  registers  in 
this  way.  The  others  (SI,  DI,  ES,  CS, 
SS,  DS)  are  all  accessed  through  the 
x  part  of  the  union  (p— *x.es). 

The  interface  function,  dos(  ) 
(Listing  Three),  is  called  with 
dos(regp);  where  regp  is  a  pointer  to 
a  REGS  type  structure.  It  returns  the 
status  register,  as  returned  by  DOS. 
You  can  use  dos(  )  only  with  the 
small  and  one  of  the  medium  (the  P) 
memory  models  because  it  assumes  a 
16-bit  pointer. 

Dos(  )  copies  all  fields  of  *regp  ex¬ 
cept  x.ss  and  x.cs  into  their  equivalent 
registers  (lines  59-66  of  Listing 
Three).  DOS  is  invoked  with  the  INT 
21  on  line  68,  then  all  the  registers  are 
copied  back  into  *regp.  The  routine 
returns  with  registers  restored  to  their 
original  state  (i.e.,  before  the  dos(  ) 
call).  The  function  gregs(  )  (lines 
93-124)  gets  the  register  contents  but 
doesn’t  do  a  DOS  call;  it’s  used  for  ini¬ 
tializing  a  REGS  structure. 

Find_first(  )  and  find_next(  )  both 
return  0  on  success  or  the  error  code 
passed  back  by  DOS  on  failure.  When 
they  succeed,  the  current  DTA  will  be 
loaded  with  a  shuffled-around  version 
of  the  directory  entry  for  the  request¬ 
ed  file.  You  use  the  FILE_INFO 
structure,  typedefed  in  mydos.h  (List¬ 
ing  Two,  lines  30-39),  to  access  this 
information.  Mydos.h  also  has  a 
bunch  of  macros  for  extracting  stuff 
from  packed  fields  (lines  59-79). 
With  these,  you  can  get  at  the  various 
fields  of  the  creation  time  and  date,  as 


well  as  test  for  specific  attributes. 

Dirtoa(  )  (Listing  One,  lines 
86-135)  converts  a  FILE_INFO 
structure  into  an  ASCII  string  (see  the 
figure).  The  ANSI  escape  sequences 
used  for  underlining  and  boldfacing 
are  added  to  the  string  in  dirtoa(  ). 

The  final  directory-related  routine 
is  fixup_name(  )  (Listing  One,  lines 
174-223).  This  routine  takes  care  of 
all  the  problems  with  accessing  the 
root  directory  and  getting  the  files 
inside  a  specified  directory  that  we 
discussed  earlier.  It  appends  *.*  or 
/*.*  onto  directory  names  where 
necessary. 

Add_entry(  )  (Listing  One,  lines 
225-253)  puts  the  strings  returned 
from  dirtoa(  )  into  the  Dirv  array.  It 
also  processes  the  -a  and  -f  flags.  If 
an  attribute  bit  is  set  when  you  call 

find _ first(  )  or  find_next(  ),  then 

only  those  files  that  have  the  indicat¬ 
ed  attributes  will  be  found;  the  -d  op¬ 
tion  is  processed  on  line  281  using 
this  mechanism.  Unfortunately,  be¬ 
cause  there’s  no  way  to  request  files 
(only  directories),  -f  must  be  pro¬ 
cessed  by  hand  in  add_entry(  )  (lines 
241-246). 

The  only  remaining  nonobvious 
function  in  ls.c  is  ecmp(  )  (lines 
147-162),  which  is  used  by  qsort  to 
do  a  sort  by  extension.  It  skips  over  to 
the  extension  parts  of  the  two  strings, 
compares  the  extensions,  and  if  they 
match,  goes  back  and  compares  the 
actual  filename.  Refer  to  the  April  C 
Chest  for  more  information  on  how 
qsort  works. 

Multicolumn  Printing 

The  multicolumn  printing  is  done  by 
ptext(  )  (Listing  Four,  page  37), 
which  prints  out  an  argv-like  array  of 
pointers  to  strings  in  multicolumn  for¬ 
mat.  Ptext(  )  originally  was  written 
for  a  version  of  the  Unix  print  utility 
pr  and  works  quite  well  in  that 
application. 

Ptext(  )’s  calling  syntax  is: 

ptext(linec,  linev,  outfile,  numcols, 

colwidth,  numrows); 
char  **linev; 

FILE  *outfile; 

Linev  is  the  array  of  string  pointers, 
linec  is  the  number  of  pointers  in  the 

Dr.  Dobb's  Journal,  July  1985 


array,  outfile  is  the  stream  to  which 
output  will  be  sent,  numcols  and 
numrows  are  the  number  of  rows  and 
columns,  respectively,  and  colwidth  is 
the  width  of  a  single  column. 

Ptext(  )  is  called  in  Listing  One  on 
line  170.  Here  num_cols  is  the  num¬ 
ber  of  columns.  The  column  width  is 
80/Num_cols.  The  number  of  rows 
is  computed  with  (dirc/Num_cols) 
+  (dire  %  Num_cols  !=  0);  dire  is 
the  total  number  of  names  to  be 
printed.  Dire  %  Num_cols  !=  0  eval¬ 
uates  to  1  if  Num_cols  doesn’t  divide 
evenly  into  dire  and  to  0  if  it  does  di¬ 
vide  evenly. 

Several  practical  problems  having 
to  do  with  multicolumn  printing  will 
vary  from  application  to  application. 
The  first  problem  is  a  lone  carriage 
return.  Some  text  formatters  under¬ 
line  text  by  printing  the  text  itself, 
then  a  carriage  return,  then  a  line 
containing  all  the  underscores.  When 
you  have  several  columns,  you  can’t 
print  the  carriage  return  because  that 
takes  you  to  the  left  edge  of  the  page 
rather  than  the  left  edge  of  the  cur¬ 
rent  column.  The  problem  is  solved  in 
ptext(  )  by  backspacing  to  the  correct 
place.  If  your  printer  doesn’t  support 
a  backspace,  you’ll  have  to  modify 
this  part  of  the  code  (lines  93-103). 

The  next  problem  is  ESC  sequences 
(which  take  up  no  space  in  the  out¬ 
put).  Different  sequences  are  com¬ 
posed  of  different  numbers  of  charac¬ 


ters.  Rather  than  try  to  recognize 
various  escape  sequences,  ptext(  ) 
just  assumes  that  all  sequences  use 
four  characters  (one  for  the  ESC  and 
three  normal  characters  that  follow). 
This  assumption  lets  us  handle  the 
various  Set  Graphics  Rendition 
(SGR)  sequences  correctly  (we  need 
these  for  the  underlining  and  boldfac¬ 
ing),  but  it  won’t  handle  the  escape 
sequences  needed  to  change  fonts  on 
printers  and  the  like.  So,  you  may 
need  to  make  this  part  of  ptext(  ) 
more  sophisticated  (the  relevant  code 
is  on  lines  1 17-129). 

The  third  problem  is  with  the  AN¬ 
SI. SYS  driver  provided  by  IBM  (at 
least  with  DOS  v.3  running  on  a  PC/ 
AT — I’d  appreciate  someone  testing 
other  versions  of  DOS  on  other  ma¬ 
chines  and  sending  me  the  results).  If 
you  try  to  underline  the  left-most 
character  on  a  line,  the  entire  line 
ends  up  underlined.  If  you  explicitly 
turn  off  underlining,  the  nonunder- 
lined  characters  overwrite  the  under¬ 
scores,  but  underscores  still  extend 
from  the  last  character  you  wrote  to 
the  physical  end  of  line  on  the  screen 
(i.e.,  from  the  place  where  you  wrote 
a  carriage  return  to  the  right  edge  of 
the  screen).  To  make  matters  worse, 
the  underline  attribute  sticks  on,  so 
subsequent  lines  end  up  underlined 
too.  I  didn’t  want  to  rewrite  AN- 
SI.SYS,  so  I  kludged  a  solution  to  this 
problem  in  ptext(  ).  If  “IBM”  is  #de¬ 


fined  at  the  top  of  the  module,  a  space 
will  be  inserted  as  the  left-most  char¬ 
acter  of  every  line.  I  realize  this  isn’t 
a  real  solution,  but  it  works  every¬ 
where  I’ve  needed  to  use  ptext(  ). 

While  we’re  on  the  subject  of  AN¬ 
SI. SYS,  I  found  another  bug  a  few 
days  ago.  The  “Erase  in  Display”  and 
“Erase  in  Line”  leave  the  cursor  in 
the  wrong  place.  Erase  in  Display 
should  home  the  cursor;  instead,  it 
leaves  the  cursor  at  the  left  edge  of 
row  2.  Similarly,  Erase  in  Line  puts 
the  cursor  at  the  left  edge  of  the  next 
line;  it  shouldn’t  move  the  cursor  at 
all.  Obviously,  the  driver  is  just  print¬ 
ing  blanks  to  erase  the  line,  but  it 
should  put  the  cursor  back  where  it 
belongs  when  it’s  done. 

The  Erase  in  Line  problem  is  par¬ 
ticularly  bad  when  you  try  to  erase  the 
bottom  line:  the  screen  scrolls  up  a 
line  when  the  cursor  is  sent  past  the 
bottom,  so,  even  if  you  put  the  cursor 
back  where  it  belongs,  you  lose  the  top 
line  of  the  screen.  If  anyone  knows 
any  other  bugs  in  ANSI. SYS  (or  has 
fixes  for  them,  maybe  a  new  version  of 
ANSI. SYS),  please  send  them  in  so  I 
can  put  them  in  the  column. 

This  month  we  talked  about  direc¬ 
tories;  next  month  we’ll  continue  the 
discussion  with  a  version  of  the  Unix 
utility  “make,”  which  automates  the 
recompilation  of  modular  programs. 


C  Chest  (Text  begins  on  page  20) 

Listing  One 

1 

2 

3 

4 

5 

6 

7 

8 
9 

10 

11:  #define  CARRY  0x01  /*  Carry  flag  mask  for  an  8086  */ 

12:  #define  MAXDIR  132  /*  Largest  directory  which  will  be  printed  */ 

13:  #define  BOLDFACE  "\033[lm"  /*  Ansi  esc  sequence  to  turn  bold  face  on  */ 

14:  #define  UNDERLINE  "\033[4m"  /*  "  underline  on  */ 

15:  #def ine  ALL_0FF  "\033[0m"  /*  "  attributes  off  */ 

16: 

17:  #define  EOS(s)  while(*s)  s++  /*  Position  s  at  the  end  of  the  string  */ 


^include  <stdio.h> 
#include  <getargs.h> 
#include  <mydos.h> 


/* - + 

*  LS.C:  An  MSD0S  directory  utility 

* 

*  (c)  Copyright  1985,  Allen  I,  Holub.  All  rights  reserved. 

*  This  program  may  be  reproduced  for  personal,  non-profit  use  only. 

*  - + 


*/ 


Dr.  Dobb’s  Journal,  July  1985 


24 

513 


C  Chest  (Listing  Continued,  text  begins  on  page  20) 

Listing  One 


18:  /*- 


-*/ 


19 

20 
21 
22 

23 

24 

25 

26 

27 

28 

29 

30 

31 

32 

33 

34 

35 

36 

37 


51 : 
52: 
53: 
54: 
55: 
56: 
57: 
58: 

59 

60 
61 

62 

63 


lnt 
Int 
int 
lnt 
lnt 
int 
lnt 
lnt 

ARG  Argtab[ ] 


Long  f mt 
Unsorted 
File9_only 
No_graphics 
Di rs_on 1 y 
Lis  t_a  1 1 
N  u  m_c  o 1 s 
F.y  t  «nrf 


/*  Global  variables  set  by  command  line  */ 
/*  switches.  */ 


( 

'a  '  , 

BOOLEAN, 

&Li st_al 1 , 

"List  all  files  (including  hidden)" 

), 

( 

'  c  '  , 

INTEGER, 

&Num_cols , 

"Print  output  in  <num>  columns" 

)  . 

{ 

'd'  , 

BOOLEAN, 

&Dirs_only , 

"List  only  directories" 

). 

( 

'  e  '  , 

BOOLEAN  , 

&Ext_sort , 

"Sort  by  extension" 

)  , 

{ 

'i  ’  . 

BOOLEAN, 

8Files_only, 

"List  only  files" 

)  , 

{ 

'1'  . 

BOOLEAN , 

SLongfmt , 

"Print  directory  in  long  format" 

). 

( 

's'. 

BOOLEAN, 

&N°_graphics 

"Suppress  ANSI  graphics  chars" 

). 

{ 

'u'  , 

BOOLEAN, 

SUnsorted , 

"Print  directory  unsorted" 

). 

/* 

* 

* 

* 

* 

* 

*/ 


Get  directory  information  for  the  indicated  file. 
Ambiguous  file  references  are  ok  but  you  have  to  use 
find_next  to  get  the  rest  of  the  file  references. 

In  this  case,  The  regs  structure  used  by  find_first 
must  be  passed  to  find_next.  0  is  returned  on  success, 
otherwise  the  DOS  error  code  is  returned. 


regp->h .ah 
regp->x . At 
regp->x . cx 


(char)  FINDFIRST 
(short)  filespec 
attributes 


38: 

Idef ine 

TSIZE  (sizeof(Argtab)/sizeof(ARG)) 

« 

39: 

static 

char 

*Dirs [ MAXDIR ] 

/* 

Place 

to 

put  directory 

*/ 

40: 

static 

char 

**Dirv 

= 

Dirs 

/* 

Like  argv 

for  directory 

*/ 

41 : 

static 

int 

Dire 

at 

0 

/* 

Like  arge 

for  directory 

*/ 

42: 

static 

long 

Total 

= 

0L 

/* 

Sum  of 

sizes  of  all  files 

*/ 

43: 

static 

int 

Numf iles 

= 

0 

/* 

Number 

of 

files  found 

*/ 

44: 

static 

int 

Numdirs 

BI 

0 

/* 

II 

II 

directories  " 

*/ 

45: 

extern 

char 

*malloc( ) ; 

46: 

/* - 

— 

--- 

— 

— 

— 

-*/ 

47: 

find  f irst( 

filespec,  attributes 

regp  ) 

48: 

char 

*f i 1 es  pec  ; 

49: 

short 

attributes ; 

50: 

REGS 

♦regp; 

return  (int)(  (dos(regp)  S  CARRY)  ?  regp->x.ax 


0  ); 


64 

65 

66 

67 

68 

69 

70 

71 

72 

73 

74 

75 

76 

77 

78 

79 

80 
81 
82 

83 

84 

85: 

86: 

87: 

88: 

89: 


/*- 


find_next  (  regp  ) 
REGS 
{ 


♦regp.; 

/* 

* 

* 

* 

* 

* 

*/ 

regp->h . ah  =  FINDNEXT  ; 

return  (int)(  (dos(regp)  &  CARRY) 


Get  the  next  file  in  an  ambiguous  file  reference.  A 
call  to  this  function  must  be  preceded  by  a 
find_first  call.  The  regp  argument  must  be  the 
same  register  image  used  by  the  find_first  call. 

0  is  returned  on  success,  otherwise  the  error  code 
generated  by  DOS  is  returned. 


) 

/* - 

doscall(  id,  regp  ) 
REGS  *regp; 


r egp->x .ax 


o  ): 


) 


regp->h.ah  =  id 
dos (  regp  ); 


/*  Do  the  DOS  system  call  */ 
/*  specified  by  "id",  *regp  */ 
/*  is  modified  to  reflect  the  */ 
/*  results  of  that  call  */ 


/* - 

dirtoa(  s,  p, 
char 

FILE_INF0 

( 


longfmt  ) 
*  s  ? 


-*/ 


-*/ 


-*/ 


26 

514 


Dr.  Dobb’s  Journal,  July  1985 


90 

91 

92 

93 

94 

95 

96 

97 

98 

99 
100 
101 
102 

103 

104 

105 

106 


/*  Convert  a  FILE_INF0  structure  to  an  ascii  string.  In  longfmt 

*  all  information  about  the  file  (name,  size,  date  &  time, 

*  mode)  is  used.  Possible  modes  are: 

* 

*  R  -  Read  only  H  -  Hidden 

*  S  -  System  L  -  Volume  label 

*  D  -  Directory 

*  M  -  archive  bit  is  set  (file  has  been  modified) 

* 

*  If  longfmt  isn't  true,  only  the  name  is  printed.  Maximum 

*  string  length  in  long  format  is  52,  in  short  format  is  21 

*  Volume  label  names  are  printed  in  bold  face  (since  this  will 

*  put  an  ESC  in  the  name  the  volume  label  will  sort  to  the  front 

*  of  the  list).  Directories  are  printed  underlined  (these  will 

*  immediately  follow  the  directories).  The  number  of  characters 

*  in  the  string  (not  including  the  terminating  null)  is  returned. 
*/ 


107:  char  *startstr  =  s; 

108:  int  i; 


109 

110 
111 
112 

113 

114 


i f (  !No_graphics  &&  (IS_LABEL(p)  ||  IS_SUBDIR( p) )  ) 

sprlntf(s,  "Xs%s%s",  IS_LABEL(p)  ?  BOLDFACE  :  UNDERLINE, 
p->fi_name,  ALL_0FF  ); 

else 

sprintf(s,  "%s",  p->fi_name  ); 

E0S(s); 


115 

116 

117 

118 

119 

120 
121 
122 

123 

124 

125 

126 

127 

128 

129 

130 

131 

132 

133 

134 

135 

136 


) 

/* 


if(  longfmt  ) 

( 

for(  i  =  st r len ( p->f i_name ) ;  i++  <  12  ;  *s++  =  '  '  ) 

;  /*  Pad  out  the  name  field  */ 
sprintf (  s,  "  %61d  ",  p->fi  fsize  ); 

E0S(s)  ; 

sprintf(s,  "%2d-%02d-%02d  %2d : %02d : %02d"  , 

C_DAY(p),  C_YEAR( p )-1900, 
C_M I N ( p ) ,  C_SEC( p)  ); 

=  ’R’  ; 

-  ’H'  ; 

-  'S'  ; 

-  'L'  ; 

=  'D'  ; 

=  'M'  ; 

) 

return(  s  -  startstr  )  ; 


C_M0NTH(p) 
C_HR(p), 


*s  +  + 

_  1  1  # 

if( 

IS  READONLY ( p ) 

) 

*s  +  + 

if( 

IS  HIDDEN(p) 

) 

*s++ 

if( 

IS  SYSTEM(p) 

) 

*s  +  + 

if( 

IS  LABEL(p) 

) 

*s++ 

if( 

IS _SUBDIR( p) 

) 

*s  +  + 

if( 

IS_DIRTY ( p ) 

) 

*s++ 

■*/ 


137:  haswild(s) 


138:  char  *s; 

139:  ( 

140:  /*  Return  true  if  s  has  a  '*'  or  '?'  in  it  */ 

141:  for(  ;  *s  ;  s++) 

142:  if(  *s  ==  '*'  | |  *s  —  ' ?'  ) 

143:  return  1; 

144:  return  0; 

145:  ) 

146:  /* - */ 


147:  ecmp(  sip,  s2p  ) 

148:  char  **slp,  **s2p; 

149:  ( 

150:  /*  Comparison  routine  for  sorting  a  directory  by  extension  */ 


151 :  char  *sl,*s2; 

152:  int  rval ; 


153: 

for(  si  =  *slp;  *sl  && 

*sl  ! 

154: 

155: 

f or(  s 2  =  *s2p;  *s2  && 

• 

*s2  ! 

156: 

157: 

if(  rval  -  strcmp(sl, 

3  2 )  ) 

158: 

return  rval  ; 

159: 

4 

f or ( sl=*sl p ,  s 2=*s2p  ; 

*sl  — 

160: 

161  : 

162:  ) 

return(  (*sl  ==  ? 

0  :  * 

' . 1  &&  *sl  !=  '  ' ;  sl++  ) 
' . '  &&  *s2  !=  '  ' :  s2++  ) 


/* 

If  the 

extensions 

don't 

match  */ 

/* 

return 

the  strcrap 

value 

*/ 

*s2 

&& 

*sl  && 

*sl  ! =  ’a'; 

sl++, 

s2++) 

si) 

- 

(*s2  = 

*=  1  .  1  ?  0  : 

*s2) 

)! 

Dr.  Dobb’s  Journal,  July  1 985 


27 

515 


L  X^nCSl  (Listing  Continued,  text  begins  on  page  20) 

Listing  One 


163:  /* - 

164:  printdlr ( dire  ,  dirv) 


165:  int  dire  ; 

166:  char  **dirv  ; 

167:  ( 

168:  if(  lUnsorted  ) 

169:  qsort(  dirv,  dire,  sizeof (*dirv) ,  Ext_sort  ?  «ecmp  :  0); 

170:  ptext(  dire,  dirv,  stdout,  Num_cols,  80/Num_cols, 

171:  (dirc/Num_cols)  +  (dire  %  Num  cols  !»  0)  ); 

172:  ) 

173:  /• - */ 


174: 
175: 
176: 
177: 
178: 
179: 
180: 
181  : 
182: 


char  *fixup_name(  name, 
char  *name; 

REGS  *regs; 

FILE_INF0  '*inf  o ; 

( 


regs,  info  ) 


/*  If  the  name  specifies  an  implicit  file  (ie.  it  asks  for 

*  the  directory  rather  than  the  files  in  the  directory), 

*  modify  it  to  ask  for  files  (eg.  becomes  "..\*.*"). 

*/ 


183:  static  char  buf[80],  *p  ; 


184: 

if( 

! f ind_f irst( 

name,  ALL, 

185: 

1 

186: 

/*  If  the 

requested 

187: 

*  a  sub 

directory , 

188: 

*/ 

regs)  ) 

name  was  found,  and  that  name  is 
append  \*.*  to  the  requested  name 


189: 

190: 


if (  ! IS_SUBDIR( inf o )  ) 
return(  name  ) ; 


191 

192 

193 

194 

195 


) 

else 


196:  ( 

197: 

198: 

199: 

200: 

201: 


202: 

203: 

204: 

205: 

206: 

207: 

208  i 

209  ^ 
210: 
211: 
212: 
213: 
214: 
215: 
216: 


217: 

218: 

219: 

220: 

221 : 

222:  ) 
223:  ) 


224:  /* 


strncpy(  buf,  name,  80  ); 
strncat(  buf,  "\\*.*",80  )j 
return  (  buf  )j 


/*  If  you  didn't  find  anything,  and  a  non-explicit 

*  directory  is  request  e-d  (ie.  " . .  \ "  or  "b:"  )  then 

*  set  up  the  name  to  get  the  root  directory  on  the 


*  indicated  disk. 

*/ 

i f (  name [ 1 ]  '  ) 

buf [ 0 ]  =  name [0 ] ; 
buf[l]  =  i 

if(  !*(name  +=  2)  ) 

bu  f [ 2  ]  - 
buf [ 3  ]  = 
buff  4  ]  - 
buf [ 5  ]  =  0  ; 

return(  buf  ); 

) 

else 

buff  2  ]  -  0  i 


for(  p  =  name  ;  *p  ;  P++  ) 

if(  l(*p  -=  ' . '  ||  *P 
return(  name  ) 


/*  Take  care  of  the  */ 

/*  disk  designator  first  */ 


/*  If  the  current  dir  */ 
/*  on  another  disk  is  */ 
/*  requested...  */ 


•w  ||  *P  —  ’/’)) 


strncat(  buf,  80  ); 

return(  buf  ); 


*/ 


225:  add_entry(  p  ) 

226:  FILE_INF0  *p; 

227:  f 


28 

SIB 


Dr.  Dobb’s  Journal.  July  1985 


228 

229 

230 

231 

232 

233 

234 

235 

236 


/♦  Add  an  entry  to  the  directory  array  (dirv)  and  update 

*  various  associated  globals.  Hidden  files  are  not 

*  printed  unless  -a  was  given  on  the  command  line. 

*  (which  will  cause  List_all  to  be  set).  As  with 

*  Unix,  file  names  starting  with  the  character  are 

*  also  not  printed  unless  -a  is  specified.  If  -d  was 

*  given  on  the  command  line  (Dirs_only  will  be  true) 

*  and  files  won't  be  printed. 

*/ 


237:  char  buf[64); 

238:  register  int  strlenj 


239:  i f (  ! Lis t_al 1  88  ( IS_HIDDEN( p)  ||  *p->fi_name  =='.')  ) 

240:  return  1; 


241  : 

if  ( 

I  S_SUBDI R ( p )  ) 

/* 

Entry  is  a  directory 

*/ 

242: 

Numdi r s++ ; 

/* 

Adjust  directory  count 

.  */ 

243: 

else 

i  f  (  Dir  s_o  n 1 y  ) 

/* 

Entry  is  a  file 

*/ 

244: 

return  1; 

/* 

Don 't  print  files 

*/ 

245: 

else 

i f  (  !IS_LABEL(p)  ) 

/* 

Entry  is  a  file 

*/ 

246: 

Numf iles++; 

/* 

Adjust  file  count 

*/ 

247:  strlen  =  dirtoa(  buf.  p,  Longfmt  ); 

248:  if(  ++Dirc  >  MAXDIR  ||  !(*Dirv  =  malloc(  strlen  +  1  ))  ) 

249:  return  0; 

250:  strcpy(  *Dirv++,  buf  ); 

251:  Total  +=  p->fi_fsize; 

252:  return  1; 

253:  ) 

254:  /♦ - */ 


255:  main(argc  ,  argv) 


256:  int 
257:  char 
258:  { 
259: 

260: 

261  : 


argc  ; 

♦♦argv ; 

/*  Get  a  directory.  Given  a  list  of  files  on  the  command  line 

*  (file  names  may  be  ambiguous)  will  print  a  sorted  directory 

*  of  those  files  along  with  a  total  file  and  byte  count. 


262:  ♦/ 


263: 

static 

REGS 

regs 

264: 

static 

FILE_INF0 

info 

265: 

static 

char 

♦name 

;  /♦  Needed  for  DOs  calls  ♦/ 

;  /♦  DOs  puts  dirs  here  »/ 

"*.♦";  /♦  File  name  ♦  / 


266 

267 

268 


gregs  ( 

Sregs  ); 

/♦ 

regs . x  .  dx 

=  (word)  Sinfo; 

/♦ 

doscall( 

SETDTA ,  Sregs  ); 

/* 

Initialize  the  regs  structures  ♦  / 
Change  the  Disk  Transfer  Addr  ♦  / 
to  point  at  info  structure  ♦  / 


269,: 

argc 

-  getargs(  argc,  argv, 

270: 

it  ( 

Longfmt  ) 

271 : 

Num_cols  =  1; 

272: 

Argtab,  TSIZE  ); 

/*  -1  is  always  printed  in  1  column  */ 
/*  even  if  -cN  was  given  on  the  cmd  ♦  / 
/♦  line  */ 


273: 

274: 

275: 

276: 

277: 

278: 


if(  argc  >=  2  )  /♦  Put  the  requested  file  name  into  */ 

(  /♦  "name."  The  default  is  *.*  ♦  / 

name  =  ♦(++argv); 

if(  argc  =■  2  88  ! ha swi Id ( name )  ) 

name  m  fixup_name(  name,  Sregs,  &info  )j 


279 

280 
281 
282 

283 

284 


do 

( 

if(  !find_first(name,  Files_only  ?  ALL_FILEs  :  ALL,  Sregs)) 

if(  !add_entry(8info)  ) 
break ; 


285 

286 

287 

288 

289 

290 


if(  haswild( name )  ) 

while (  !find_next(  Sregs  )  ) 

if(  ! add_entry (Sinf o )  ) 
goto  abort; 

name  -  *(++argv); 


291 : 


while(  — argc  >  1  ); 


292:  abort:  printf  ( " \ n " ) ; 


Dr.  Dobb’s  Journal,  July  1985 


29 

517 


C  Chest  (Listing  Continued,  text  begins  on  page  20) 

Listing  One 


Listing  Two 


printdir  (  Dire,  Dirs  ); 
printf  ("\n")j 
if(  Numfiles  ) 

( 

pr in t f ( "Xd  fileXs  (Xld  bytes,  %d  K)", 

Numfiles,  Numfiles  =  -  1  ?  ""  .  »s" 
j  Total,  Total/1024  ); 

if(  Nurodirs  ) 

( 

if(  Numfiles  ) 

printf (" ,  "); 

j  print f ( "Xd  d irec torXs" , Numd ir s  ,  Numdirs  »=1  ?  "y"  :"ies"); 

End  Listing  One 


MYDOS.H 


Various  defines  for  talking  to  dos 


Typdefs  for  using  dos().  Note  that  this  structure  can  be  used 
by  the  various  routines  supplied  by  Lattice  (intdos,  bdos  etc.] 
but  dos()  can't  use  the  structure  defined  in  Lattice's  dos.h. 

Since  "REGS"  and  "byte"  are  both  also  defined  in  dos.h,  you 
shouldn't  linclude  both  of  them  in  the  same  place. 


typedef  short  word; 
typedef  char  byte; 

struct  LREG  (  word  ax,  bx,  cx,  dx, 

struct  SREG  (  byte  al,ah,  bl.bh,  cl,ch,  dl.dhj 


si  ,  di ,  es ,  cs ,  ss , 


typedef  union 


struct  LREG 
struct  SREG 


*  Stuff  needed  to  get  a  directory  from  MSDOS 

* 

*  The  FILE_INF0  type  structure  is  filled  by  a  find  first  or  find 

*  next  command  (dos  system  calls  0x4e  and  0x4f). 

*/ 

typedef  struct 
( 


32: 

char 

f i_resv[ 21  ] ; 

/* 

Bytes 

0-20 

Reserved  by  DOS 

*/ 

33: 

char 

f i_att r ib ; 

/* 

Byte 

21 

File  attribute 

*/ 

34: 

short 

f i_time ; 

1* 

Bytes 

22-23 

Create/update  time 

*/ 

35: 

short 

f i_date ; 

/* 

Bytes 

24-25 

Create/update  date 

*/ 

36: 

long 

f i_f size ; 

/* 

Bytes 

26-27 

File  size  in  bytes 

*/ 

37: 

char 

f i_narae[ 13] ; 

/* 

Bytes 

28-40 

File  name  &  extension 

*/ 

FILE_INF0 ; 


Macros  to  extract  information  from  a  FILE_INF0.  In  all  these  macros 
the  argument  "p"  is  a  poshorter  to  a  FILE_INF0  structure.  Note  that 
the  C_YEAR  and  C_SEC  macros  compensate  for  MSDOS  wierdnessess . 


IS_READONLY(p) 
IS_HI DDEN ( p ) 
IS_SYSTEM( p) 
IS_LABEL(p) 
IS_SUBDIR(p) 
IS_DIRTY ( p) 

C_HR(p) 

C_MIN(p) 

C  SEC(p) 


File  is  read  only. 

File  is  invisible  in  normal  directory  searches 

File  is  a  system  file 

Info  is  a  volume  label,  not  a  file. 

File  is  a  directcry 

True  when  file  is  written  to  and  closed,  set  to 
False  by  the  program  backup. 

Hour  of  last  update  or  create  (0-23) 

Minute  of  last  update  or  create  (0-59) 

Second  of  last  update  or  create  (0-59) 


32 

518 


Dr.  Dobb's  Journal,  July  1985 


55: 

*  C  YEAR(p) 

56: 

•  *  C  MONTH(p) 

57: 

*  C  DAY ( p) 

58: 

*/ 

59: 

Idefine 

READONLY 

60: 

Idefine 

HIDDEN 

61: 

Idefine 

SYSTEM 

62: 

Idefine 

LABEL 

63: 

Idefine 

SUBDIR 

64: 

Idefine 

DIRTY 

65: 

Idefine 

ALL 

66: 

Idefine 

ALL  FILES 

67: 

Idefine 

NORM  FILES 

Year  of  last  update  or  create  (1980-2099) 
Month  of  last  update  or  create  (1-12) 

Day  of  last  update  or  create  (1-31) 


0x01  /*  Attribute  bits  */ 

0x02 

0x04 

0x08 

0x10 

0x20 


(READONLY 

(READONLY 

(READONLY 


DIRTY 

1  SYSTEM 

HIDDEN  |  SUBDIR 

LABEL) 

DIRTY 

|  SYSTEM 

HIDDEN  ) 

DIRTY 

) 

68: 

Idefine 

IS 

READONLY ( p  ) 

69: 

Idefine 

IS" 

"HIDDEN  ( p  ) 

70: 

Idefine 

is" 

"SYSTEM(p) 

71 : 

Idefine 

is" 

'LABEL(p) 

72: 

Idefine 

is" 

'SUBDIR(p) 

73: 

Idefine 

is" 

"dirty  (  p) 

( ( p)->f i_attrib  &  READONLY 
( ( P )— >f i_a t t r i b  &  HIDDEN 
( ( p)->f i_attrib  «  SYSTEM 
( ( p )->f i_a t t ri b  &  LABEL 
( ( p)->f i_attrib  &  SUBDIR 
( ( p) ->f i_attrib  &  DIRTY 


) 

) 

) 

) 

) 

) 


74:  Idefine  C_HR(p) 

75:  (Idefine  C_M  I N  (  p  ) 
76:  #def ine  C_SEC(p) 
77:  Idefine  C_YEAR(p) 
78:  Idefine  C_M0NTH(p) 
79:  Idefine  C_DAY(p) 


(  ( ( p)->f i_time 
(  ( (p)->f i_time 
(  ( ( p ) ->f i_tirae 
( ( ( ( p )->f i_da te 
(  ( ( p)->f i_date 
(  ( (p)->f i_date 


»  11)  R  Ox  1  f 
»  5)  R  0x3f 

<<  1)  R  0x3e 

»  9)  R  0x7f 

»  5)  R  OxOf 

)  R  Oxl f 


) 

) 

) 

) 

) 

) 


+  1980) 


80:  /* - 

81;  *  Directory  related  BDOS  function  numbers 


82 

: 

♦/ 

83 

Idefine 

FINDFIRST 

0x4e 

84 

Idefine 

FINDNEXT 

0x4f 

85 

Idefine 

SETDTA 

Oxla 

86 

Idefine 

GETDTA 

0  x  2  f  End  Listing  Two 

Listing  Three 

i 

TITLE 

DOS  INTERFACE  FUNCTION 

2 

SUBTTL 

Copyright  1985  by  Allen  I.  Holub 

3 

NAME 

DOS 

4 

INCLUDE 

DOS. MAC 

6 

7 

8 

Data  types:  The  REGS  structure  is  defined  as  type  allregs  in  mydos.h 

9 

REGS 

STRUC 

;  Structure  used  to  transfer  register 

10 

AX  REG 

DW 

0 

;  contents.  Note  that  this  structure  is 

11 

BX  REG 

DW 

0 

;  a  supperset  of  that  defined  in  the 

12 

CX  REG 

DW 

0 

;  dos.h  supplied  by  Lattice.  This  particular 

13 

DX  REG 

DW 

0 

;  structure  is  defined  in  \inc 1 ude\mydos . h 

14 

SI  REG 

DW 

0 

15 

DI  REG 

DW 

0 

16 

ES  REG 

DW 

0 

17 

CS  REG 

DW 

0 

18 

SS  REG 

DW 

0 

19 

DS  REG 

DW 

0 

20 

REGS 

ENDS 

21 

OFF 

EQU 

4 

;  Offset  to  arguments 

22 

23 

24 

name  : 

dos  — 

Do  a  dos  function  call 

25 

26 

synopsis : 

(/include 

<mydos . h> 

27 

28 

status  » 

dos(  regsp  )  ; 

29 

int 

status  ;  /*  returned  status  register  */ 

30 

REGS 

*regp  ;  /*  pointer  to  register  struct  */ 

31 

32 

description : 

This  function  is  a  more  useful  version  of  the  bdos() 

33 

function 

provided  with  the  lattice  C  compiler.  It 

34 

saves  the  existing  machine  state,  replaces  the 

35 

contents 

of  all  registers  except  CS  &  SS  from  the 

36 

structure  pointed  to  by  regp,  does  an  int  21,  loads 

37 

the  registers  back  into  the  structure,  restores  the 

38 

machine 

state  and  returns. 

Dr.  Dobb’s  Journal,  July  1985 


33 

519 


C  Chest 


(Listing  Continued,  text  begins  on  page  20) 


Listing  Three 


39 

40 

41 

42 

43 

44 

45 


notes : 


PSEG 


This  function  will  only  work  with  the  SMALL  model,  since 
it  assumes  a  16  bit  pointer.  The  stack  frame  is 
non-standard  too  (ie  the  BP  holds  the  structure  pointer 
arg  rather  than  the  frame  pointer). 


46:  PUBLIC  DOS 

47:  DOS  PROC  NEAR 


48 

Push 

BP  ; 

49 

MOV 

BP.SP  ; 

BP , ( BP+OFF  ]  ; 

50 

5 1 

MOV 

52 

PUSH 

BX  ; 

53 

PUSH 

CX  ; 

54 

PUSH 

DX  ; 

55 

PUSH 

SI  ; 

56 

PUSH 

DI 

57 

PUSH 

ES 

58 

PUSH 

DS 

59 

MOV 

AX , SS : ( BP  ] . AX  REG 

60 

MOV 

BX,SS: [BP]  .BX  REG 

61 

MOV 

CX.SS: [BP] .CX  REG 

62 

MOV 

DX.SS: [BP] .DX  REG 

63 

MOV 

SI, SS: [BP] .SI  REG 

64 

MOV 

DI.SS: [BP] .DI  REG 

65 

MOV 

ES.SS: [BP]  .ES  REG 

66 

MOV 

DS.SS: [BP] .DS  REG 

67 

PUSH 

BP 

68 

INT 

21H 

69 

POP 

BP 

70 

MOV 

SS: [BP] .AX  REG, AX 

71 

72 

MOV 

MOV 

£S:  [BP] .BX  REG , BX 
SS  :  [ BP  ] . CX  REG , CX 

73 

MOV 

SS: [BP] .DX  REG , DX 

74 

MOV 

SS:  [BP] .SI  REG, SI 

75 

MOV 

SS : [ BP ] . DI  REG , DI 

76 

MOV 

SS : [ BP ] . ES  REG , ES 

77 

MOV 

SS : [ BP ] . CS  REG , CS 

78 

MOV 

SS : [ BP ] . DS  REG , DS 

79 

MOV 

SS : [ BP ] . SS  REG , SS 

80 

POP 

DS 

81 

POP 

ES 

82 

POP 

DI  ; 

83 

POP 

SI 

84 

POP 

DX 

85 

POP 

CX 

86 

POP 

BX 

87 

LAHF 

88 

MOV 

AL.AH  • 

89 

XOR 

AH, AH  ; 

Save  the  old  stack  frame. 

BP  =  pointer  to  register  structure. 

Save  the  current  machine  state.  No 
point  in  saving  AX  because  it's 
going  to  be  used  for  the  return 
value . 


;  Set  up  a  new  machine  state 
;  using  the  contents  of  the 
;  REGS  structure.  Don't  modify 
;  the  SS  or  CS  registers. 


;  Make  the  DOS  call.  Save  the  BP 
;  out  of  irrational  paranoia. 

;  Now  update  the  original  structure 
;  to  reflect  the  return  values  from 
;  the  dos  call. 


Restore  the  previous  machine  state 


Return  the  flags: 

Move  flags  to  LSB 
Clear  high  bytes 


90 

91 

92 

93 

94 

95 

96 

97 

98 

99 
100 
101 
102 

103 

104 

105 

106 

107 

108 


POP  BP  ;  Restore  the  previous  stack  frame's 

RET  ;  frame  pointer  and  return. 

DOS  ENDP 


name : 


synopsis : 


description : 
notes : 


gregs  —  Initialize  a  REGS  structure  to  the  current 
register  contents. 

#include  <raydos.h> 

gregs(  regp  ) 

REGS  *regp  j  /*  pointer  to  register  struct  */ 

This  function  is  used  before  before  calling  dos(). 

This  function  will  only  work  with  the  SMALL  model. 


PUBLIC  GREGS 
GREGS  PROC  NEAR 


109: 

PUSH 

BP 

110: 

MOV 

BP.SP 

111: 

MOV 

BP, [BP+OFF] 

112: 

MOV 

SS: [BP] .AX  REG, AX 

113: 

MOV 

SS: [BP] .BX  REG.BX 

;  Save  the  old  stack  frame. 

;  BP  ■  pointer  to  register  structure 

j  Initialize  the  structure  pointed 
;  to  by  bp . 


36 

520 


Dr.  Dobb’s  Journal,  July  1985 


114 

MOV 

115 

MOV 

116 

MOV 

117 

MOV 

118 

MOV 

119 

MOV 

120 

MOV 

121 

MOV 

122 

POP 

123. 

RET 

124 

GREGS  ENDP 

125 

ENDPS 

126 

END 

Listing  Four 

SS:  [BP] 
SS : f  BP ] 
SS:  [BP] 
SS: [BP] 
SS: [BP] 
SS: [BP] 
SS:  [BP] 
SS  :  [ BP ] 

BP 


.  CX_REG , CX 
.  DX_REG , DX 
.  SI_REG , SI 
.  DI_REG , DI 
.ES_REG.ES 
.  CS_REG , CS 
.  DS_REG , DS 
.  SS_REG , SS 


Restore  the  previous  stack 
frame  and  return. 


End  Listing  Three 


^include  <stdio.h> 


2 

3 

4 

5 

6 

7 

8 
9 

10 

11 

12 

13 

14 

15 

16 

17 

18 

19 

20 
21 
22 

23 

24 

25 

26 

27 

28 

29 

30 

31 

32 

33 

34 

35 

36 

37: 

38: 

39: 

40: 

41  : 

42: 

43: 

44: 

45: 

46: 

47: 

48: 

49: 

50: 

51: 

52: 

53: 

54: 

55: 

56: 

57: 

58: 

59: 


/* 

* 

* 

* 

* 

* 

* 

* 

* 

* 

* 

* 

* 

* 

* 

* 

* 

* 

* 

* 

* 

* 

* 

* 

* 

* 

* 

* 

* 

* 

* 

*/ 


PTEXT.C:  Multi-column  print  utility 

Copyright  (c)  1985  Allen  I.  Holub,  all  rights  reserved. 

This  program  may  be  reproduced  for  personal,  non-profit  use  only 

Bugs  and  Features: 

1)  Pr  line  assumes  that  the  output  device  supports  backspace 
('Yb'  ==  “H  ).  This  is  only  a  problem  when  the  source 
has  underlined  text  implemented  as: 

texttexttext\r  _ 

We  can't  Just  ouput  a  '\r'  in  this  case  because  we  may 
not  be  in  the  leftmost  column.  A  \r  should  get.  us  to  the 
left  edge  of  the  current  column. 

2)  When  printing  multi-column  stuff.  If  a  line  is  truncated  it 
will  run  into  the  column  to  its  right.  That  is,  there  is  no 
seperator  between  columns  other  than  the  whitespace  needed 
to  pad  the  column  out  to  a  particular  width.  If  no  padding 
is  necessary  (ie.  the  lines  have  been  truncated),  then  the 
columns  will  run  together. 

3)  When  an  ESC  is  found,  the  escape  and  the  next  three  characters 
take  up  no  space  in  the  output.  This  lets  us  print  out  the 
various  SGR  commands  without  messing  up  the  column  width. 

4)  Strange  things  happen  to  an  IBM  screen  when  the  leftmost 
character  on  a  line  is  printed  with  underline.  To  compensate 
for  this,  a  single  blank  is  printed  as  the  left-most 
character  on  every  line  if  IBM  is  (/defined. 


#de  f ine 
#def ine 
/* - 


ESC 

IBM 


Ox  1  b 
1 


ptext(linec,  linev,  outfile,  numcols,  colwidth,  numrows) 


- */ 


int 

char 

FILE 

int 

( 


linec,  numcols,  colwidth; 
♦♦ 1 i ne  v ; 

♦outfile ; 
numrows ; 


Print  out  the  array  of  strings  "linev"  which  consists  of 
linec  entrys.  Output  is  sent  to  "outfile"  formated  as 
follows : 


"numcols” 

"colwidth1 

"numrows" 


/* 

♦ 

* 

* 

* 

* 

♦ 

* 

* 

* 

* 

*/ 

register  int 
register  char 

lineend  »  &linev [ linec-1 ] ; 

numrows  ;  — j  >*=  0 


number  of  columns 

width  of  a  column  in  characters.  Any  text 
longer  than  colwidth  is  truncated  off. 
columns  are  numrows  long.  The  left-most 
column  is  printed  in  its  entirety 
first  then  the  next  column  in  its 
entirety,  and  so  on. 


j  » 

**lineend,  **line ,  **nextline 


/*  Last  linev  entry  to  print  */ 


60:  iifdef  IBM 
61 : 

62:  tfendif 


for(  j 

( 


p  u  t  c  ( 


outfile  ) ; 


Dr.  Dobb’s  Journal,  July  1985 


37 

521 


C  Chest 


(Listing  Continued,  text  begins  on  page  20) 


Listing  Four 


63 

64 

65 

66 

67 

68 

69 

70 

71 


for(  line  =  linev++;  line  <=  lineend;  line  =  nextline) 
nextline  =  line  +  numrows; 

/♦  Print  the  line.  Don't  pad  the  rightmost  */ 
/♦  column.  */ 

pr_line(  *line,  outfile,  colwidth, 

nextline  <=  lineend); 


72:  fputs("\n"  ,  outfile); 

73:  ) 

74:  ) 

75:  /♦-* - - - ♦  / 


76 

77 

78 

79 

80 
81 
82 

83 

84 

85 

86 

87 

88 

89 

90 

91 

92 

93 

94 

95 

96 


static  pr_line(  str,  stream,  width,  padded  ) 


register  char 
FILE 
int 
{ 


♦str ; 

♦stream ; 
width,  padded; 


Print  out  "str"  into  "stream"  padding  it  to  "width" 
columns  wide.  Non-printing  characters  and  3  printing  characters 
immediatly  following  an  ESC  are  not  counted  as  having  printed. 
'\n'  characters  are  treated  as  line  teminators  but  are  not 
printed.  If  "padded"  is  0  then  no  padding  is  done,  though  the 
line  will  still  be  truncated  if  it's  too  long. 


/♦ 

* 

* 

* 

* 

* 

♦/ 

int  col 

while(  col  < 

{ 

If  ( 

else 

( 


■  0  : 
width 


&&  *str  ) 


♦str  « - 
break 
if(  *str 


'  \n '  1 


1  \r '  ) 


/*  Back  up  to  the  left  edge  of  the  current  column 

*/ 


97: 

98: 

99: 

100: 

101: 


while(  col  >  0  ) 

( 

— col ; 

pu tc ( ' \b '  ,  stream) ; 


102 

103 

104 

105 

106 

107 

108 


str++ ; 

) 

else  if(  *str  ==  '\t'  )  /♦  expand  tabs  */ 

{ 

str++  ; 
col++  ; 

putc(  '  '  ,  stream  ); 


109 

110 
111 
112 

113 

114 

115 

116 

117 

118 

119 

120 
121 


while(  (col  X  8)  &&  col  <  width) 

( 

putc(  '  '  ,  stream  ); 
col++; 

) 

) 

else 

{ 

i f (  *str  —  ESC  ) 

{ 

putc(  *str4-+  ,  stream  ); 
if(  !*str  ) 

break ; 


122: 

123: 

124: 


putc(  ♦str+t  ,  stream  ); 
if(  !*str  ) 

break ; 


125 

126 

127 

128 

129 

130 

131 


) 


putc(  *str++  ,  stream  ); 
if(  !*str  ) 

break ; 


else  if  (♦str  ==  ' \ b  '  ) 
—  col  ; 


38 

522 


Dr.  Dobb's  Journal,  July  1985 


132: 

133: 

134 

135 

136 

137 

138 

139 

140 


else  if(  *8tr  >=  '  '  ) 
++c  o 1  j 

putc(  *str++  ,  stream  ); 

) 

) 

if  (padded) 

while(  col++  <  width  ) 

putc(  '  '  ,  stream  ); 


End  Listing  Four 


Listing  Five 


23 


/*  Example  of  how  to  get  and  restore  the  MSDOS  Disk  Transfer  address 
*  using  the  dos()  and  gregs()  functions  defined  in  listing  3 


3 

*/ 

4 

#include  <mydos.h> 

5 

fdefine 

SETDTA  Ox  la 

6 

#define 

GETDTA  0x2f 

7 

main( ) 

8 

{ 

9 

static  FILE_INF0  info; 

10 

static  REGS  oregs,  nregs 

I 

11 

gr egs (  Soregs  ); 

/* 

12 

oregs. h. ah  =  GETDTA; 

/* 

13 

dos(  Soregs  ) ; 

14 

gregs (  Snregs  ); 

15 

nregs. x.dx  =  (word)  Sinfo; 

/* 

16 

nregs. h. ah  =  SETDTA  ; 

/* 

17 

dos(  Snregs  ); 

18: 

/*  ...  */ 

19 

oregs. x.ds  =  oregs. x.es; 

/* 

20 

oregs. x.dx  =  oregs. x.bx; 

/* 

21 

nregs. h. ah  =  SETDTA  ; 

22 

dos (  Soregs  )  ; 

*/ 


*/ 

*/ 


*/ 

*/ 


End  Listings 


Dr.  Dobb’s  Journal,  July  1985 


40 

523 


Build  a  Custom  PC  or  Clone 


by  Jim  Kronman 


I  have  owned  an  8-bit  S-100  micro¬ 
computer  since  1978.  Old  habits 
are  hard  to  kick,  so  I  set  about  as¬ 
sembling  a  PC-compatible  computer 
the  same  way  I  built  my  S-100  sys¬ 
tem.  I  individually  selected  each 
component  of  the  system  to  suit  my 
specific  needs.  I  now  have  a  PC  XT 
work-alike  that  is  totally  "IBM  com¬ 
patible,”  costs  substantially  less  than 
the  closest  off-the-shelf  equivalent, 
and  fits  my  requirements  exactly. 

My  approach  may  not  be  for  every¬ 
one.  I  am  pleased  with  my  completed 
project,  but  you  should  consider  the 
pros  and  cons  before  taking  the 
plunge  yourself.  Before  going  into  the 
advantages  of  rolling  your  own  PC, 
I’ll  point  out  some  of  the  drawbacks 
you  should  consider. 


with  microcomputer  hardware  or  are 
timid  when  handling  equipment  cost¬ 
ing,  in  the  aggregate,  up  to  several 
thousand  dollars.  When  you  put 
Manufacturer  A’s  board  and  Manu¬ 
facturer  B’s  board  into  your  comput¬ 
er,  you  alone  are  responsible  for  de¬ 
termining  that  the  boards  are  compa¬ 
tible  both  with  each  other  and  with 
the  PC  itself.  The  vendors  and  manu¬ 
facturers  usually  will  try  to  help  if 
you  have  a  problem,  but  they  may 
never  have  encountered  exactly  the 
configuration  you  have  assembled 
and  therefore  may  be  unable  to  deter¬ 
mine  the  cause  of  your  problem. 

Now  that  you  have  received  fair 
warning  of  some  of  the  potential  pit- 
falls,  I’ll  shamelessly  promote  the  ad¬ 
vantages  of  building  your  own  PC. 


Careful  shopping ,  gentle  persuasion  with  vise  grips 
and  a  lot  of  patience  yield  an  XT  work-alike 
at  a  PC  price. 


You  will  need  patience.  If  you 
must  have  a  computer  tomorrow,  go 
buy  something  off  the  shelf  of  your 
local  computer  store.  I  ordered  pieces 
from  five  separate  mailorder  suppli¬ 
ers  (including  IBM),  and  it  was  over 
five  weeks  from  the  time  I  placed  the 
orders  by  telephone  until  I  first  boot¬ 
ed  the  system.  At  that  point,  I  was 
still  missing  a  vital  element,  but  more 
on  that  subject  later. 

Do  not  attempt  to  assemble  your 
own  PC  if  you  are  not  experienced 


Jim  Kronman,  6085  Venice  Blvd., 
No.  16,  Los  Angeles,  CA  90034  (213) 
558-3281 


You  can  save  a  bundle  of  money 
when  you  compare  your  homegrown 
computer  to  one  with  the  same  func¬ 
tionality  in  a  store:  for  the  same 
amount  of  money,  you  can  get  a  lot 
more  capability.  With  my  project 
now  complete,  I  realize,  too,  that 
there  are  other  benefits  I  had  not  an¬ 
ticipated  when  I  began. 

An  obvious  reason  to  undertake  this 
adventure  is  to  assemble  a  system  ex¬ 
actly  suited  to  your  needs.  One  of  my 
prime  requirements  was  to  have  a  Se- 
lectric  style  keyboard,  which  IBM 
does  not  offer  with  the  PC.  I  did  not 
want  to  buy  the  standard  IBM  PC  be¬ 
cause  I  would  end  up  throwing  away  a 
perfectly  good  (if  poorly  laid  out)  key- 


42 

524 


Dr.  Dobb’s  Journal,  July  1985 


board  and  buying  a  replacement.  (As 
far  as  I  know,  IBM  will  not  sell  the  PC 
System  Unit  without  a  keyboard.) 

I  purchased  the  Keytronic  KB5151 
deluxe  keyboard  with  a  Selectric 
style  layout  and  separate  numeric 
and  arrow  keypads;  it  also  has  the 
function  keys  across  the  top  of  the 
keyboard  so  they  will  align  with  the 
function  key  legends  in  row  25  of  the 
display  in  many  application  pro¬ 
grams.  My  one  criticism  of  the  Key¬ 
tronic  design  was  that  the  “touch” 
was  too  soft;  sometimes  the  slightest 
tap  would  cause  a  character  to  ap¬ 
pear  on  the  screen.  This  problem  was 
easy  to  remedy.  I  carefully  took  each 
keytop  off  of  the  keyboard,  stretched 
the  spring  under  the  keytop  to  about 
0.8  inches,  and  then  replaced  the  key- 
top.  Do  one  key  at  a  time  and  you 
should  have  no  problems  with  this 
slight  modification;  it’s  well  worth 
the  time! 

I  had  decided  that  I  wanted  the  ca¬ 
pability  of  a  hard  disk  and  that  I 
wanted  it  internal  to  the  computer.  I 
also  did  not  like  the  crowded  arrange¬ 
ment  in  the  eight-slot  PC  XT  or  the  XT 
clones.  These  considerations  and  oth¬ 
ers  led  me  to  opt  for  a  custom  assem¬ 
bly  with  a  five-slot  PC  motherboard. 
I’m  sure  you  can  think  of  plenty  of 
your  own  reasons  to  customize,  too. 

While  my  project  was  in  progress,  I 
had  occasion  to  help  a  friend  with  an 
IBM  PC  that  had  a  failed  power  sup¬ 
ply.  What  I  have  come  to  call  the 
“$290  Fuse  Syndrome”  is  a  strong 
argument  to  buy  as  few  components 
as  possible  from  IBM.  Big  Blue’s  war¬ 
ranty  policy  (which  I  have  seen  re¬ 
viewers  praise  in  several  magazine 
articles)  is  to  replace  defective  com¬ 
ponents  on  the  spot  and  without  ques¬ 
tion.  That’s  great  while  you  are  with¬ 
in  the  warranty  period  and  the  tab  is 
on  Blue,  but  unfortunately  the  policy 
continues  and  is  mandatory  after  the 
warranty  expires  and  you  are  paying 
the  bill.  If  you  try  to  obtain  a  sche¬ 
matic  or  other  service  information  for 
an  IBM  PC  component  that  is  desig¬ 
nated  a  FRU  (Field  Replaceable 
Unit),  you  will  encounter  as  firm  a 
stonewall  from  IBM  as  you  will  ever 
want  to  see.  Even  if  you  figure  out 
who  made  the  component  for  IBM, 
you  can’t  get  any  help  because  IBM 

Dr.  Dobb’s  Journal,  July  1985 


has  its  vendors  contractually  bound 
to  stony  silence  as  well. 

The  $290  Fuse  Syndrome  is  so 
named  because  inside  of  each  IBM 
power  supply  is  a  standard  slow-blow 
radio-type  fuse — in  a  fuse  holder!  If 
this  fuse  blows  (or  even  simply  fails), 
IBM’s  policy  is  that  the  supply  is  ter¬ 
minally  (if  you’ll  pardon  the  expres¬ 
sion)  ill  and  must  be  replaced.  A  PC 
XT  (130  watts)  power  supply  costs 
$290  when  ordered  from  IBM;  as  I 
write  this  article,  the  going  rate  for  a 
clone  replacement  seems  to  be  around 
$170.  By  purchasing  your  power  sup¬ 
ply,  plug-in  boards,  disk  drives,  and  so 
on  from  other  sources,  you  can  obtain 
servicing  information  in  most  cases.  If 
the  availability  of  servicing  informa¬ 
tion  is  vital  to  you,  check  with  each 
manufacturer  before  you  buy. 

My  original  plan  was  to  purchase  a 
PC  motherboard,  the  case,  and  a  1 30 
watt  power  supply  from  IBM.  This 
would  have  given  the  machine  the  ap¬ 
pearance  of  being  “true  Blue.”  IBM 
was  one  step  ahead  of  me:  I  was  un¬ 
able  to  obtain  the  entire  case  from 
IBM,  even  though  all  the  pieces  are 
listed  in  the  IBM  PC  Service  Man¬ 
ual’s  illustrated  parts  breakdown  and 
the  Greencastle  Parts  Depot  accept¬ 
ed  my  order.  I  did  buy  the  power  sup¬ 
ply,  which  has  given  me  no  problems 
to  date  (although  the  first  supply  sent 
to  me  was  dead  on  arrival),  but  I  re¬ 
gret  not  saving  the  $100  by  buying  a 
supply  from  a  third  party. 

My  inability  to  obtain  the  case 
from  IBM  was  frustrating;  after  the 
IBM  Parts  Center  accepted  my  order 
for  all  of  the  pieces,  it  simply  failed  to 
deliver  all  of  them.  I  never  received 
notification  that  the  entire  order 
would  not  be  filled.  As  the  weeks 
dragged  by,  I  assembled  all  of  the 
components  of  the  by  now  functional 
computer  on  a  24  X  27-inch  piece  of 
particle  board.  When  I  finally  found 
out  what  was  happening  with  my  or¬ 
der  from  IBM,  the  Parts  Depot  at 
least  let  me  return  for  credit  the 
pieces  of  the  case  I  had  obtained  and 
could  not  use. 

I  found  a  clone  chassis  made  in 
Taiwan  for  about  half  the  price  of  the 
IBM  parts,  and  after  a  small  amount 
of  persuasion  with  vise  grip  pliers  to 
straighten  out  a  bracket,  everything 


fit  fine.  The  case  is  identical  in  ap¬ 
pearance  to  the  IBM  from  the  front, 
except  for  the  absence  of  the  famous 
logo,  and  the  back  panel  has  a  variety 
of  cutouts  adaptable  to  various  hard¬ 
ware  configurations. 

One  piece  of  the  system  that  had  to 
be  genuine  IBM  was  the  mother¬ 
board,  which  contains  the  IBM  ROM 
BIOS,  thus  assuring  total  PC  com¬ 
patibility.  Although  some  of  the 
clone  boards  might  be  close  to  100% 
compatible,  the  difference  in  price 
i  was  not  worth  the  risk.  If  you  have 
reliable  information  about  a  specific 
non-IBM  motherboard  that  meets 
your  needs,  then  go  for  it! 

One  other  specific  piece  of  hard¬ 
ware  made  the  choice  of  the  PC  (rath¬ 
er  than  XT)  motherboard  attractive: 
Maynard  Electronics’  “Sandstar  Se¬ 
ries”  WS-2  disk  controller  board  sup¬ 
ports  up  to  four  floppy  disk  drives  and 
two  10  Mb  hard  disks  and  takes  up 
only  a  single  slot.  An  added  advantage 
of  this  configuration  is  that  the  May¬ 
nard  hard  disk  software  driver  is  in  a 
PROM  that  is  installed  on  the  main 
circuit  board,  occupying  address 
space  in  the  FOOOOh  segment,  while 
the  IBM  XT  controller  is  a  separate 
board  and  has  its  driver  in  the  middle 
of  the  COOOOh  segment.  Thus,  by  not 
using  the  XT  controller  or  any  other 
board  with  ROM  code  in  the  COOOOh 
segment,  you  make  available  64K  of 
memory  space  along  with  the  DOOOOh 
and  EOOOOh  segments,  which  allows 
you  to  install  a  192K  RAM  disk  with¬ 
out  using  any  of  the  640K  available  to 
DOS.  Use  caution  in  dealing  with  the 
COOOOh  through  EOOOOh  segments; 
according  to  the  IBM  Technical  Ref¬ 
erence  Manual  (page  2-11),  plug-in 
boards  can  use  the  area  from  C2000h 
to  F4000h  for  software  drivers  that 
the  ROM  BIOS  will  recognize  at 
power-on. 

When  you  do  not  buy  the  System 
Unit  from  IBM,  you  do  not  receive 
the  IBM  PC  Guide  to  Operations 
manual,  the  BASIC  manual,  or  the 
IBM  Diagnostic  Disk.  You  may  pur¬ 
chase  these  items  from  IBM  if  you 
need  them.  If  you  purchase  the  IBM 
Technical  Reference  Manual,  you 
will  not  need  the  Guide  to  Opera¬ 
tions.  (If  you  need  this  Guide  to  Op¬ 
erations,  you  probably  shouldn’t  be 


43 

525 


assembling  your  own  computer!)  I 
highly  recommend  that  you  purchase 
the  Technical  Reference  Manual;  it 
is  the  definitive  source  of  information 
on  the  ROM  BIOS  function  calls,  con¬ 
taining  the  source  code  for  the  entire 
ROM  BIOS. 

After  I  completed  my  project  (and 
after  my  friend  purchased  a  replace¬ 
ment  power  supply),  I  discovered 
that  Howard  W.  Sams  publishes  a 
“Computerfact”  package  for  the 
IBM  PC.  It  costs  $39.95  and  includes 
schematics,  circuit  board  photos  with 
component  identifications  and  trou¬ 
ble-shooting  instructions.  The  pack¬ 
age  covers  the  PC  motherboard, 
power  supply,  keyboard,  display 
adapters,  floppy  disk  controller,  and 
a  few  other  optional  cards. 

You  must  also  purchase  an  operat¬ 
ing  system  separately.  The  various 
choices  are  PCDOS  2.1  or  3.0  from 
IBM  and  Digital  Research’s  CP/M- 
86,  Concurrent  CP/M  v3.1,  or  Con¬ 
current  DOS  v3.2.  By  far  the  most 
software  is  available  for  PCDOS.  Be¬ 
cause  v3.0  seems  to  offer  the  single 
user  little  advantage  over  v2. 1 , 1  selec¬ 
ted  PCDOS  2.1.  The  choice  is  yours. 
You  can  use  more  than  one  operating 
system  (but  not  simultaneously). 

That  is  about  all  there  is  to  tell 
about  my  experiences.  Not  only  would 
I  do  it  again  this  way  if  I  had  to  (less 
the  mistakes,  of  course),  I  would  not 
consider  doing  it  any  other  way!  What 
follows  is  a  summary  of  my  advice  and 
some  useful  tabulations  of  informa¬ 
tion,  components,  and  sources. 

If  you  intend  to  assemble  your  own 
PC: 

( 1 )  Be  confident  of  your  skills  and 
knowledge  in  microcomputers. 

(2)  Be  prepared  to  wait;  this  isn’t  a 
project  for  the  impatient. 

(3)  Select  your  components  careful¬ 
ly;  make  sure  each  item  is  compati¬ 
ble  with  the  others. 

(4)  Understand  each  vendor’s  return 
policy  before  you  order,  just  in  case. 

(5)  Select  your  software  with  as 
much  or  more  care  as  your  hard¬ 
ware. 

(6)  If  you  have  a  specific  software 
application  in  mind,  make  sure  the 
hardware  you  select  is  compatible. 


44 


The  components  you  will  need  (as 
a  minimum)  are: 

( 1 )  Motherboard,  with  CPU,  ROM 
BIOS,  memory,  keyboard  interface, 
and  support  circuits 

(2)  Power  supply,  62  to  1 30  watts 
( 1 30  watts  recommended) 

(3)  Display  adapter  card 

(4)  Monitor  compatible  with  display 
adapter 

(5)  Disk  controller  card  (possibly  a 
second  card  for  hard  disk) 

(6)  Case 

(7)  Keyboard 

(8)  Floppy  disk  drive,  5'/4-inch 
DSDD,  48  tpi,  PC-compatible 

Optional  components  include  (but 
are  not  limited  to): 

(1)  Multifunction  card  (additional 
memory,  battery  run  clock,  serial 
and  parallel  ports,  game  port,  etc.) 

(2)  MODEM  card 

(3)  Math  coprocessor  chip  (8087) 

(4)  Coprocessor  board  (Z80  CP/M- 
80,  Apple,  10286,  etc.) 

The  following  items  are  available 
from  IBM: 

( 1 )  PC  motherboard  with  64K  mem¬ 
ory  installed,  P/N  8654213,  $640.00 

(2)  PC  XT  motherboard  with  128K 
memory  installed,  P/N  8529254, 
$750.00 

(3)  Speaker  with  mounting  bracket, 
P/N  8529143,  $8.15 

(4)  Guide  To  Operations  manual 

(5)  Technical  Reference  Manual,  P/ 
N  1502234 

(6)  BASIC  reference  manual,  P/N 
6025010 

(7)  PCDOS  2.1,  P/N  6024120 

(8)  PC  Diagnostic  Disk,  P/N 
1502212 

All  of  the  above  except  for  the 
motherboards  and  speaker  should  be 
available  through  the  IBM  Product 
Centers  or  other  retail  outlets  (Sears 
Business  Centers  are  usually  a  good 
place  to  check).  The  replacement 
parts  (motherboard,  etc.)  are  avail¬ 
able  from  the  IBM  Parts  Depot  in 
Greencastle,  IN.  It  is  best  to  deal  by 
telephone:  (317)  658-2022.  The  ad¬ 
dress  is  P.O.  Box  505,  Greencastle, 


IN  46135.  The  hardware  prices  may 
change;  they  are  included  here  for 
reference  only. 

For  the  other  pieces  that  you  will 
need  for  your  system,  look  through  the 
ads  in  this  magazine,  The  Computer 
Shopper,  and  Byte.  Many  firms  sell 
PC  clone  cases,  power  supplies,  key¬ 
boards,  and  so  on.  I  have  even  found 
bargains  at  surplus  companies;  a  firm 
in  the  Los  Angeles  area  recently  had 
surplus  Keytronics  keyboards  manu¬ 
factured  with  special  legends  on  the 
keys  for  just  $35! 

Happy  hunting  and  happy  cloning. 
I  hope  this  article  encourages  fellow 
computer  hobbyists  to  explore  the  PC 
bus  in  the  same  spirit  as  the  S-100 
bus.  I  have  found  the  experience  to  be 
well  worth  the  effort. 


DDJ 


Dr.  Dobb's  Journal,  July  1985 


The  Ultimate  Parallel  Print  Spooler 


by  Don  Rindsberg 


Don  Rindsberg  has  come  up  with  a 
printer  spooler  design  incorporating 
low-cost  6665  (64K)  dynamic  RAM 
chips.  The  result — a  60K  print  spool¬ 
er  that  you  can  build  for  about  a  hun¬ 
dred  dollars. 


If  you  are  tired  of  waiting  while  your 
parallel  printer  does  its  thing,  here 
is  a  project  that  will  allow  you  to 
print  and  compute  at  the  same  time.  It 
is  a  print  spooler  that  provides  a  whop¬ 
ping  60K  of  buffering.  The  spooler, 
designed  to  stand  alone,  will  interface 
almost  any  parallel  printer  port. 

The  project  incorporates  “software 
refresh”  of  the  dynamic  RAM  memo¬ 
ry — a  concept  little  publicized,  to  my 
knowledge,  except  in  Motorola’s  appli¬ 
cation  literature.  By  using  software  re¬ 
fresh  of  RAM,  you  can  eliminate  a 
number  of  integrated  circuits  from  the 
design  of  the  spooler.  The  refresh  cycle 


I  built  this  spooler  to  interface  my 
Apple  II  computer  to  an  Epson  paral¬ 
lel  printer.  However,  it  should  be  eas¬ 
ily  adaptable  to  virtually  any  com¬ 
puter  with  a  parallel  printer  port  and 
any  parallel  printer.  The  60K  of  buff¬ 
ering  holds  at  least  12  pages  of  solid, 
single-spaced  text.  In  the  graphics 
mode,  an  entire  screen  is  dumped  to 
the  spooler  in  a  couple  of  seconds. 

The  Hardware 

The  6809  microprocessor,  which  is 
now  available  for  about  $12,  is  ideal 
for  our  software-refreshed  memory 
system:  its  internal  clock  can  provide 
the  time-critical  signals  to  the  dynam¬ 
ic  memory  without  the  use  of  addi¬ 
tional  clocks  or  delay  lines.  The  6809 
(not  the  6809E)  has  its  own  on-chip 
clock  generator  and  requires  only  a 
crystal  in  the  3.5  to  4.0  MHz  range;  a 
low-cost  TV  color-burst  crystal  is  a 


This  design  uses  software  refresh  of  dynamic  ROM  to 
create  a  60K  spooler  for  about  $  100. 


ties  up  the  microprocessor  for  less  than 
10  percent  of  its  time,  a  penalty  easily 
tolerated  when  you  are  controlling  a 
printer,  which  operates  at  a  snail-like 
speed  from  the  microprocessor’s  point 
of  view.  You  implement  software  re¬ 
fresh  by  carefully  writing  the  software 
so  that  the  128  consecutive  row  ad¬ 
dresses  are  read  at  least  every  2  ms:  1 28 
rows  in  a  dynamic  RAM  must  be  re¬ 
freshed,  and  the  maximum  allowable 
time  between  refreshes  is  2  ms. 


Don  Rindsberg,  5958  S.  Shenandoah 
Rd.,  Mobile,  AL  36608. 


simple  and  inexpensive  choice. 

The  6809  produces  two  clock  sig¬ 
nals,  E  (enable)  and  Q  (quadrature), 
which  are  both  50  percent  duty  cycle 
square  waves  at  a  frequency  exactly 
one-fourth  that  of  the  crystal.  As 
shown  on  the  timing  diagram  (Figure 
1,  page  47),  the  Q  signal  leads  the  E 
signal  by  one-fourth  of  a  cycle — the 
E  signal,  by  the  way,  is  the  same  as 
the  phase  two  signal  on  the  6800  and 
6502  microprocessors.  We  use  the 
rising  edge  of  Q  to  trigger  RAS  (row 
address  strobe)  on  the  dynamic 
RAM.  The  rising  edge  of  E  causes  the 
switch  from  row  addresses  to  column 


Dr.  Dobb’s  Journal,  July  1985 


46 

527 


+5~ n 

mfdT 

GND - 1 

8X.05  MFD- 


13 

330 

14 

12 

10 

9 

AAA  13 

11  74LS 

AM/ — 

6  157 

7 

»  A  10 

5 

3 

4 

•w* — 

2 

- v  V  V 

NC-HNC 


13 

A-J  14 

AJ  - 

10 

9  / 

A2  11 

6 

74LS 

_ _ r 

5 

1 57 

1 _ t 

A1  - 1 

3 

2 

i _ f 

AO - 

4164  OR  6665 
MEMORY  ARRAY 


% 

O 

O 

D  Q 

74LS157  POWER:  2  14  2  14  2  14  2  14  2  14  2  14  2  1 

GND  PIN8  - 1 -  - -  - -  - - - — 

+5  PIN  16 

DO  D1  D2  D3  D4  D5  D6 

Figure  3. 

The  eight  dynamic  RAM  chips  are  wired  in  parallel.  Make  identical  connections 
to  each  chip  with  the  exception  of  the  data  lines;  a  different  data  line  connects 
to  each  chip  as  shown.  Be  sure  to  install  a  decoupling  capacitor  near  each  6665. 


Q 


RAS 


CAS 


Figure  1 

i  lie  U1111119  uidyrdm  snows  now  ine  signals  t  ana  u  from  the  b»U9  gate  the 
column  (CAS)  and  row  (RAS)  logic  in  the  6665  dynamic  memory  chips.  This 
eliminates  the  need  for  delay  circuits. 


addresses,  while  the  falling  edge  of  Q 
triggers  CAS  (column  address  strobe) 
to  the  RAM  chips.  RAS  is  exerted  ev¬ 
ery  cycle  to  force  a  refresh,  while 
CAS  is  exerted  only  if  the  address  is 
not  an  “F”  address  (SF000-FFFF). 

Figure  2  (page  48)  shows  that  we 
decode  address  F  in  the  74LS20, 
which  produces  a  low  when  A1 2-A1 5 
are  all  high.  The  2716  ROM  is  at 
$F800—  FFFF,  and  the  P1A  (parallel 
interface  adapter)  is  at  SF400-F403; 
address  1 1  provides  the  distinction 
between  the  ROM  and  the  P1A.  We 
buffered  A 1  1  —A  1 5  with  a  74LS367 
because  the  6809  has  limited  drive 
capabilities.  Figure  3  (page  48) 
shows  how  the  switching  between  the 
row  addresses  A0-A7  and  column 
addresses  A8-A 1 5  was  done  in  a  pair 
of  74LS 1 57  chips.  The  33  ohm  resis¬ 
tors  prevent  ringing  on  the  RAM’s  ad¬ 
dress  lines. 

The  6821  PIA  in  Figure  4  (page 
50)  was  designed  for  applications 
such  as  this,  having  exactly  the  num¬ 
ber  of  functions  we  need.  It  automati¬ 
cally  provides  the  handshaking  sig¬ 
nals  used  by  parallel  printers.  When 
the  A  side  is  used  as  a  parallel  input, 
STR  from  the  host  computer  sets  a 
flag.  When  the  spooler  reads  the 
data,  the  PIA  automatically  provides 
a  one-cycle  negative-going  signal, 
which  is  used  for  ACK.  On  the  B  side 
of  the  PIA,  a  similar  STR  signal  auto¬ 
matically  is  generated  on  a  write  to 
port  B,  and  the  printer’s  ACK  signal 
sets  a  flag  to  signal  that  the  printer  is 
ready  for  another  character. 

Figure  5  (page  50)  shows  the  com¬ 
pleted  spooler.  A  74LS74,  used  in 
conjunction  with  the  pause  switch,  al¬ 
lows  manual  shutdown  of  the  print¬ 
out  to  change  paper  and  so  on.  This 
chip  changes  state  at  the  falling  edge 
of  E  when  you  change  the  switch  po¬ 
sition.  At  this  time,  the  opposite  half 
of  the  ROM  is  bank-switched  into 
memory,  which  contains  a  holding 
routine.  The  two  halves  of  the  ROM 
are  selected  by  its  A10  line.  The 
power  supply  (Figure  6,  page  51) 
provides  regulated  5  volts  at  some¬ 
what  less  than  500  ma. 

The  Software 

The  software  has  two  unusual  re¬ 
quirements: 


(1)  The  software  must  read  every 
possible  combination  of  the  seven 
low-order  addresses  (A0-A6),  at 
least  one  every  2  ms,  to  refresh  the 
dynamic  RAM. 

(2)  The  software  must  bank-switch 
between  the  upper  and  lower  halves 
of  the  ROM  to  provide  the  pause 
function. 

Aside  from  this,  the  software  is 
straightforward.  The  6809  supports 
relocatable  programs  with  its  “long 
branch”  instructions.  Listing  One 
(page  53)  shows  how  these  functions 
are  implemented. 

After  a  short  routine  to  initialize 
the  PIA,  the  program  wakes  up  the 
printer  with  a  null  and  sets  up  the  X, 
Y,  and  U  registers  as  pointers  and 
counters.  It  next  enters  a  loop  that 
starts  with  a  RAM  refresh,  which  con¬ 
sists  of  stepping  through  1 28  consecu¬ 
tive  ADDA  #0  instructions;  this  steps 
through  our  128  consecutive  address¬ 
es.  The  Motorola  literature  recom¬ 
mends  128  NOPs,  which  execute  in 
256/iS,  but  I  have  elected  to  use  64 
consecutive  addresses  in  128/us.  Dur¬ 
ing  this  sequence,  once  each  byte, 
RAS  on  the  dynamic  RAMs  is  activat¬ 
ed.  Notice  that  the  eight  low-order  ad¬ 
dresses  correspond  to  the  1 28  rows  in 
the  dynamic  RAMs  even  though  we 
are  executing  code  reading  the  ROM. 
CAS  remains  high,  and  because  the 
RAMs  require  both  RAS  and  CAS  for 
a  read  or  write,  the  RAMs  do  not  as¬ 
sert  any  data  on  the  data  bus.  Refresh 
of  the  RAMs,  however,  does  take 
place  because  the  RAS  signal  alone 
sets  off  the  refresh  internally. 

After  the  refresh  sequence,  the  ac¬ 
tual  input,  storage,  and  output  of 
characters  take  place.  It  is  critical 


that  this  routine  not  use  any  second¬ 
ary  loops  that  might  prevent  refresh. 
For  example,  if  the  printer  is  not 
ready,  the  routine  continues  on  rather 
than  waiting  in  a  tight  loop  for  it  to 
get  ready. 

To  implement  a  pause  function 
with  minimum  hardware,  we  have 
put  the  same  code  in  both  halves  of 
the  ROM  except  for  one  instruction 
(see  Listing  Two,  page  55):  a  BRA 
(branch  always)  operation  in  the  ac¬ 
tive  half  of  the  ROM  and  a  BRN 
(branch  never)  operation  in  the  pause 
half.  The  change  of  a  single  bit  ($20 
vs.  $21)  causes  the  output  portion  of 
the  routine  to  be  bypassed  when  you 
close  the  pause  switch.  See  the  parts 
list  (page  below)  for  a  source  for  a 
preprogrammed  ROM  if  you  do  not 
care  to  program  your  own  2716. 

Construction 

The  only  critical  aspect  of  the  spooler 
board’s  construction  is  to  provide 
heavy  power  connections  and  good  by¬ 
passing  for  the  dynamic  RAM  chips.  I 
used  the  Vector  3677-2  board,  which 
has  wide  ground  and  5  volt  busses. 
This  4.5  X  6.5-inch  board  will  accom¬ 
modate  the  entire  circuit,  except  for 
the  transformer  and  rectifier,  if  you 
position  the  IC  sockets  carefully.  If 
you  are  not  sure,  use  the  4.5  X  9-inch 
board  (Vector  3677). 

The  RAM  bypass  capacitors  must 
be  close  to  the  power  pins  of  the  RAM 
chips.  I  used  stripped  wire-wrap 
throughout,  rather  than  Just-wrap  or 
equivalent,  because  my  technique 
with  the  shortcut  methods  leaves  an 
occasional  bad  connection,  which  is 
tedious  to  debug.  Because  we  do  not 
use  the  edge-connector,  there  is  space 


Dr.  Dobb  s  Journal.  July  1985  48 


con 


GlttO* 


COMPUTER 

OUTPUT 


Computer 

Ground 


DATA 

IN 


74LS74 


Printer 

Ground 


2200 

PAUSE 

SW 


DATA 

OUT 


TO 

PRINTER 


Figure  4. 

Wiring  of  the  271  6  EPROM  and  the  6821  parallel  interface  adapter 


F'gure 

s  finished  spc 


Although  the  layiou' 


Dr  Dobb  s  Journal,  July  1985 


10 

32 1 

11 

_ ill 

13 

30 

14 

29 

15 

28 

16 

27 

17 

26 

110/12V 
500  MA. 


The  rectifiers,  transformer,  and  switches  are  mounted  in  the  enclosure,  while  the 
rest  of  the  power-supply  components  fit  on  the  circuit  board  (see  Figure  5). 


Parts  List 

Integrated  Circuits 

1  MC6809  microprocessor  (do  not  use  MC6809E) 

1  MC6821  peripheral  interface  adapter 

8  MCM6665A  or  41 64  64K  dynamic  RAM  200  ns 

2  74LS1 57  quad  2-line  to  1-line  data  selector 
1  74LS00  quad  NAND  gate 

1  74LS04  hex  inverter 
1  74LS20  dual  4-input  NAND  gate 
1  74LS367  hex  bus  driver 
1  74LS74  dual  D-type  flip-flop 

1  2716  EPROM,  5-volt  type.  Programmed  version  available  at  the  Bit  Stop, 
5958  Shenandoah  Rd.,  Mobile,  AL  36608  ($30  ppd) 

1  7805  5 -volt  regulator  TO-220  case 
Sockets  and  Plugs 

2  40-pin  wire-wrap  sockets 

1  24-pin  wire-wrap  sockets 
11  1 6-pin  wire-wrap  sockets 
4  1 4-pin  wire-wrap  sockets 

2  20-pin  wire-wrap  header,  JDR  Microdevices  IDH20W  or  equivalent 

2  20-contact  ribbon  header  sockets,  JDR  IDS20  or  equivalent,  plus  1-2  feet 
20-conductor  ribbon  cable 

Miscellaneous 

1  Vector  3677-2  4.5  X  6.5-inch  board  or  Vector  3677  4.5  X  9.0-inch  board 
1  4.0  MHz  crystal  or  TV  color-burst  crystal 
1  Heat  sink  for  TO-220  regulator 
1  1  -amp  50  v.  bridge  rectifier 

1  Power  transformer,  primary  110  v.  secondary  1 2  v.  at  500  ma 
1  Reset  pushbutton,  normally  open 
1  Pause  switch,  SPST 
1  2200  mfd  1 5  v.  electrolytic  cap. 

1  33  mfd  1 5  v.  trantalum  cap. 

2  27  pf.  disc  cap. 

8  .05  mfd  disc  cap. 

3  2  mfd  15  v.  trantalum  cap. 

1  1N4001  diode 

8  33  ohm  %  w.  resistor 
1  56K  Vi  w.  resistor 

1  6  X  8  X  2-inch  aluminum  enclosure  or  larger  (Radio  Shack) 

Most  of  the  required  parts  are  available  from  these  alternate  sources: 

JDR  Microdevices,  1224  S.  Bascom  Ave.,  San  Jose,  CA  95128 
ordering  line  1-800-538-5000;  in  CA  1-800-662-6279 
Priority  One  Electronics,  9161  Peering  Ave.,  Chatsworth,  CA  91311 
ordering  line  1-800-423-5922 

DoKay  Computer  Products,  2100  De  La  Cruz  Blvd.,  Santa  Clara,  CA  95050 
ordering  line  1-800-538-8800;  in  CA  1-800-848-8008 


to  place  the  large  power-supply  ca¬ 
pacitor  on  the  board.  The  only  off- 
board  components  are  the  power  sup¬ 
ply  transformer,  the  rectifier,  and  the 
control  switches. 

Possible  Design  Modification 

You  can  alter  the  design  for  use  with 
a  serial  printer  by  replacing  the  PIA 
with  a  6850  AC1A  and  adding  a  suit¬ 
able  baud-rate  generator.  The  ACIA 
has  some  built-in  handshaking  capa¬ 
bilities  that  can  control  the  interfac¬ 
ing  of  the  host  computer  and  printer. 

As  to  memory  refresh,  you  can  di¬ 
vorce  the  refresh  from  the  mainline 
routine  by  interrupting  the  micropro¬ 
cessor  at  500  Hz  or  faster  (2  ms).  The 
interrupt  routine  would  read  your 
128  consecutive  addresses  and  then 
either  return  or  do  any  other  short 
function  required  before  returning 
from  interrupt. 

By  further  decoding,  you  could  in¬ 
crease  the  buffer  size  from  60K  to 
about  63. 5K,  using  only  0.5K  for  the 
ROM  space  and  only  a  few  bytes  for 
the  PIA.  Note  that  we  do  not  use  the 
upper  4K  of  RAM  in  our  design.  Also 
note  that  all  of  the  low  RAM  space  is 
available  for  character  buffering  be¬ 
cause  the  software  uses  internal  mi¬ 
croprocessor  registers  exclusively  as 
pointers  and  counters. 

Because  the  60K  spooler  is  a 
“smart”  device  with  its  own  micro¬ 
processor,  you  could  use  routines  to 
add  a  variety  of  control  functions, 
such  as  automatic  form  feed.  If  your 
software  gets  complex,  you  may  wish 
to  implement  a  stack  in  RAM  and  call 
a  subroutine  for  refresh,  e.g.: 

LOOP  BSR  REFRESH 
(test  a  flag) 

BPL  LOOP 
(continue) 

Note  that  the  loop  contains  a  call  to 
the  refresh  routine.  Each  loop  where 
a  significant  delay  is  encountered 
should  contain  such  a  call. 

DD| 


Dr.  Dobb's  Journal,  July  1985 


53 


Print  Spooler  (Listing  Continued,  text  begins  on  page  46) 

Listing  One 


An  assembled  listing  of  the  ROM  contents.  The  2K  EPROM  occupies  locations 
$F8000  to  $FFFF. 


1  000 

*  SP00LER64 

-  DUAL 

1010 

.OR 

*F800 

1020 

.  TA 

$0800 

1030 

* 

.TF 

SP00LER64 . 0B-J5 

F3FF- 

1040 

STACK 

.EG 

4F3FF 

F400- 

1 050 

PORTA 

.EG 

4F400 

F40O- 

1 060 

ODRA 

.EG 

4F4U0 

F401- 

1070 

CRA 

.EG 

4F401 

F402- 

1 080 

PORTB 

.EG 

4F402 

F402- 

1  090 

DDRB 

.EG 

4F402 

F403- 

1  100 

CRB 

.EG 

4F403 

0000- 

1  110 

SOM 

.EG 

*0000 

F000- 

1120 

EOM 

.EG 

4F000 

F000- 

1130 

MAX 

.EG 

EOM— SOM 

1  140 

* - 

— 

FSO0- 

1 150 

.BS 

4FBO0-* 

SKIP  4O3O0  BVTES 

FB00-  7F 

F4 

01 

1170 

START 

CLR 

CRA 

ACCESS  DDR'S 

FB03-  7F 

F4 

G3 

1 180 

CLR 

CRB 

FB06-  ob 

00 

1  1 90 

LDA 

#400 

A  -  INPUT 

FB08-  B7 

F4 

00 

1  200 

STA 

DORA 

FB0B-  ©6 

FF 

1210 

LDA 

#4FF 

B  -  OUTPUT 

FBOD-  B7 

F4 

02 

1 228 

STA 

DDRB 

FBI O-  86 

2F 

1230 

LDA 

#42F 

00101 1 1 1 

FBI 2-  B7 

F4 

01 

1 24© 

STh 

CRA 

FBI 5-  ©€• 

2F 

1 250 

LDA 

•  42F 

00101 1 1 1 

FBI 7-  B7 

F4 

03 

1 260 

STA 

CRB 

FBI A-  86 

00 

1270 

LDA 

#400 

FB1C-  B7 

F4 

02 

1 280 

STA 

PORTB 

TOSS  NULL 

FB1F-  B6 

F4 

00 

1 290 

LDA 

PORTA 

STROBE  CA2 

FB22-  8E 

00 

00 

1 300 

LDX 

•SOM 

X  =  INPTR 

FB25-  10 

8E 

00 

FB28—  00 

1310 

LOV 

•SOM 

V  =  OUT FT R 

FB29-  CE 

00 

00 

1320 

LDU 

#0 

U  =  CHRCNT 

FB2C-  10 

CE 

F3 

FB2F-  FF 

1330 

LDS 

#STACK 

1350 

*  REFRSH  IS  HERE.  CONSISTS  OF 

1360 

♦  64  ADDA  #0  CODES 

FOR  REFRESH. 

1370 

*  INSTRUCTION  IS  8B 

OO 

FBB0-  1 1 

83 

FO 

FBB3-  0O 

1490 

CHKIN 

CMPU 

I  #MAX 

CHECK  COUNT 

FBB4-  27 

14 

1  500 

BEG 

PAUSE 

SKIP  IF  MAX. 

FBB6-  F6 

F4 

01 

1510 

LDB 

CRA 

GET  INPUT  FLAG 

FBB9-  2A 

0F 

1520 

BPL 

PAUSE 

SKIP  IF  CLEAR 

FBBB—  B6 

F4 

00 

1 530 

LDA 

PORTA 

GET  INPUT  DATA 

FBBE-  A 7 

80 

1540 

STA 

,  X+ 

STORE  &  I NCR  X 

FBCO-  33 

41 

1550 

LEAL 

i  1,U 

I NCR.  COUNT 

FBC2-  ©C 

F0 

00 

1 560 

CMPX 

:  •EOM 

END  OF  MEM? 

FBC5-  26 

03 

1570 

BNE 

PAUSE 

BRANCH  IF  NOT 

FBC7-  8E 

00 

00 

1580 

LDX 

•SOM 

WRAPAROUND 

FBCA-  21 

03 

1590 

PAUSE 

BRN 

CHKOUT 

NEVER  BRANCH 

FBCC—  16 

FF 

61 

1  600 

LBRA 

i  REFRSH 

FBCF-  11 

83 

00 

FBD2-  00 

1610 

CHKOUT 

CMPU 

1  #0 

COUNT  ZERO? 

FBD3-  27 

19 

1 620 

BEG 

GOREFR 

SKIP  IF  0 

FB05-  F6 

F4 

03 

1630 

LDB 

CRB 

GET  PRTR  READY 

FBC>8-  2A 

14 

1640 

BPL 

GOREFR 

BRANCH  IF  BUSY 

FBDA-  A 6 

A0 

1 650 

LDA 

*  V+ 

GET  DATA.  I NCR  V 

FBC»C-  B7 

F4 

02 

1660 

STA 

PORTB 

PRINT  If 

FBDF-  B6 

F4 

02 

1670 

LDA 

PORTB 

CLEAR  IRG  FLAG 

FBE2-  33 

5F 

1 680 

LEAU 

1  -l.U 

DECR  COUNT 

FBE4-  10 

8C 

F0 

FBE7-  OO 

1 690 

CMPV  #EQM 

AT  MEMORY  END? 

FBE8-  26 

04 

1  700 

BNE 

GOREFR 

BRANCH  IF  NOT 

FBEA-  10 

8E 

00 

FBED-  00 

1710 

LOV 

•SOM 

WRAPAROUND 

FBEE-  16 

FF 

3F 

1 720 

GOREFR 

LBRA  REFRSH 

GO  TO  REFRESH 

FBF1- 

1740 

.BS 

4FBFE-* 

SPACE  TO  VECTORS 

FBFE-  FB 

00 

1750 

RESET 

.DA 

START 

NEED  ONLY  RESET 

1 760 

♦ - 

— 

FC00— 

1780 

.BS 

4FF0O-* 

SKIP  4300  BVTES 

1790 

* - 

— 

FF'00—  7F 

F4 

01 

1800 

STARTX 

CLR 

CRA 

ACCESS  DDR'S 

FF03-  7F 

F  4 

03 

1810 

CLR 

CRB 

FF06-  86 

00 

1 820 

LDh 

•  400 

A  -  INPUT 

FF08-  B'T 

F  4 

00 

1 830 

STA 

DORA 

FF0B-  86 

FF 

1840 

LDA 

•  4FF 

B  -  OUTPUT 

FFOD-  87 

F  4 

02 

1 850 

STA 

DDRB 

FFib-  ee. 

2F 

1 860 

LDA 

•  42F 

00101 1 1 1 

FF12-  87 

F4 

01 

1870 

STA 

CRA 

FF15-  86 

2F 

1880 

LDA 

•  42F 

00101 1 1 1 

FF17-  87 

F4 

03 

1890 

STA 

CRB 

FF 1 A—  86 

00 

1  900 

LDA 

•  400 

FF1C-  87 

F4 

02 

1910 

STA 

PORTB 

TOSS  NULL 

FF1F-  86 

F4 

00 

1920 

LDA 

PORTA 

STROBE  CA2 

Dr.  Dobb's  Journal,  July  1985 


FF22-  6E 

00 

00 

1930 

LDX  #SOM 

X  =  INPTR 

FF25-  10 

8E 

00 

FF28-  00 

1940 

LDV  #S0M 

V  =  OUTPTR 

FF29-  CE 

00 

00 

1930 

LDU  #0 

U  =  CHRCNT 

FF2C-  I© 

CE 

F3 

FF2F-  FF 

I960 

LDS  #ST 3CK 

1980  *  REFR 

SH  IS  HERE. 

CONSISTS  OF 

*  64  ADDA  CODES 

FOR  REFRESH. 

2000  *  INSTRUCTION  IS  8 

B  00 

FFB0-  1 1 

83 

F0 

FFB3-  00 

2120  CHKINX 

CMPU  #MAX 

CHECK  COUNT 

FFB4-  27 

14 

2130 

BEQ  P3USEX 

SKIP  IF  MAX. 

FFB6-  F6 

F4 

01 

2140 

LDB  CR3 

SET  INPUT  FLAG 

FFB9-  23 

OF 

2130 

BPL  P3USEX 

SKIP  IF  CLEAR 

FFBB-  B6 

R4 

00 

2160 

LD3  FORTH 

GET  INPUT  DATA 

FFBE-  37 

80 

2170 

STA  ,X+ 

STORE  &  I NCR  X 

FFC0-  33 

41 

2180 

LE3U  1,U 

I NCR.  COUNT 

FFC2-  80 

F0 

00 

2190 

CMRX  #EOt1 

END  OF  MEM? 

FFC5-  26 

03 

2200 

BNE  P3USEX 

BRANCH  IF  NOT 

FFC7-  8E 

00 

00 

2210 

LDX  #S0M 

WRAPAROUND 

FFC3-  20 

03 

2220  RAUSEX 

BRA  CHKOUX 

FFCC-  16 

FF 

61 

2230 

LBR3  REFRSX 

FFC-F-  11 

83 

00 

FFD2-  00 

2240  CHKQUX 

CMPU  #0 

COUNT  ZERO? 

FFD3-  27 

19 

2230 

BEQ  GOREFX 

SKIP  IF  0 

FFD3-  F  6 

F4 

03 

2260 

LDB  CRB 

GET  PRTR  READV 

FFD8-  23 

14 

2270 

BPL  GOREFX 

BRANCH  IF  BUSV 

FFDA-  36 

30 

2280 

LDA  pY+ 

GET  DATA.  I NCR 

FFDC-  B7 

F4 

02 

2290 

STA  PORTB 

PRINT  IT 

FFC>F-  B6 

F4 

02 

2300 

LDA  PORTB 

CLEAR  IRQ  FLAG 

FFE2-  33 

5F 

2310 

LEAU  -1,U 

DECR  COUNT 

FFE4-  10 

8C 

F0 

FFE:'7-  O0 

2320 

CMF'V  #E0M 

AT  MEMORY  END? 

FFE3-  26 

04 

2330 

BNE  GOREFX 

BRANCH  IF  NOT 

FFE3-  10 

8E 

00 

FFED-  OO 

2340 

LDV  #SOM 

WRAPAROUND 

FFEE-  16 

FF 

3F 

2330  GORE F 5s 

LBRA  REFRSX 

GO  TO  REFRESH 

FFF1- 

2370 

.BS  ♦FFFE—* 

SPACE  TO  VECTOR 

FFFE-  FF 

00 

2380  RESETX 

.DA  STARTX 

NEED  ONLY  RESET 

End  Listing  One 

Listing  Two 

A  hexadecimal  dump  of  the  EPROM.  Base  address  for  this  dump  is  $0800  (corresponds  to  the  first  byte  of  the  EPROM). 


:>#0B00.0BFF  I  >$0F00. 0FFF 


0B0®-  7F 

F4 

01 

7F 

F4 

03 

S6 

00 

0F00- 

7F 

F4 

01 

7F 

F4 

03 

86 

00 

QB08-  B7 

F4 

00 

86 

FF 

B7 

F4 

02 

0F08- 

B7 

F4 

00 

86 

FF 

B7 

F4 

02 

0B10-  86 

2F 

B7 

F4 

01 

86 

2F 

B7 

0F10- 

86 

2F 

B7 

F4 

01 

86 

2F 

B7 

0B18-  F4 

03 

36 

00 

B7 

F4 

02 

B6 

0F18- 

F4 

03 

86 

00 

B7 

F4 

02 

B6 

0B20-  F4 

00 

SE 

00 

00 

10 

SE 

00 

0F20- 

F4 

00 

SE 

00 

00 

10 

SE 

00 

0B28-  00 

CE 

00 

00 

10 

CE 

F3 

FF 

0F28- 

00 

CE 

00 

00 

10 

CE 

F3 

FF 

0B30-  SB 

00 

SB 

00 

SB 

00 

SB 

00 

OF30- 

SB 

00 

SB 

00 

SB 

00 

SB 

00 

0B38-  SB 

00 

SB 

00 

8B 

00 

SB 

00 

0F3S- 

SB 

00 

SB 

00 

SB 

00 

SB 

00 

0B40-  SB 

00 

SB 

00 

SB 

00 

SB 

00 

0F40- 

SB 

00 

SB 

00 

SB 

00 

©Ei 

00 

0B43-  SB 

00 

SB 

00 

SB 

00 

SB 

00 

0F48- 

SB 

00 

SB 

00 

SB 

00 

SB 

00 

0B50-  SB 

00 

SB 

00 

SB 

00 

SB 

00 

0F50- 

SB 

00 

SB 

00 

SB 

00 

SB 

00 

0B58-  SB 

00 

SB 

00 

SB 

00 

SB 

00 

0F58- 

SB 

00 

SB 

00 

SB 

00 

SB 

00 

0B60-  SB 

00 

SB 

00 

SB 

00 

SB 

00 

0F60- 

SB 

00 

SB 

00 

SB 

00 

SB 

00 

0B68-  SB 

00 

SB 

00 

SB 

00 

SB 

00 

0F68- 

SB 

00 

SB 

00 

SB 

00 

SB 

00 

0B7O-  SB 

00 

SB 

00 

SB 

00 

SB 

00 

8F70- 

SB 

00 

SB 

00 

SB 

00 

SB 

00 

0B78-  SB 

00 

SB 

00 

SB 

00 

SB 

00 

0F78- 

SB 

00 

SB 

00 

SB 

00 

SB 

00 

0B80-  SB 

00 

SB 

00 

SB 

00 

SB 

00 

0F80- 

SB 

00 

SB 

00 

SB 

00 

SB 

00 

0B88-  SB 

00 

SB 

00 

8B 

00 

SB 

00 

0F8S- 

SB 

00 

SB 

00 

SB 

00 

SB 

00 

0B90-  SB 

00 

8  B 

00 

SB 

00 

SB 

00 

0F90- 

SB 

00 

SB 

00 

SB 

00 

SB 

00 

0B93-  SB 

00 

SB 

00 

SB 

00 

SB 

00 

0F98- 

SB 

00 

SB 

00 

SB 

00 

SB 

00 

0BA0-  SB 

00 

SB 

00 

SB 

00 

SB 

00 

0FA0- 

SB 

00 

SB 

00 

SB 

00 

SB 

00 

0BA3-  SB 

00 

SB 

00 

SB 

00 

SB 

00 

0FA8- 

SB 

00 

SB 

00 

SB 

00 

SB 

00 

0BBO—  1  1 

C*"T 

F0 

00 

27 

14 

F6 

F4 

0FB0— 

1  1 

y  3 

F0 

00 

■tfl  I-' 

14 

F6 

F4 

0BB8-  01 

2A 

0F 

B6 

F4 

00 

A7 

S0 

0FB3- 

01 

2A 

OF 

B6 

F4 

00 

A7 

80 

0BC0-  33 

41 

SC 

F0 

00 

26 

03 

SE 

0FC0- 

33 

41 

sc 

F0 

00 

26 

03 

SE 

0BC8—  00 

00 

21 

03 

1 6 

FF 

6 1 

1  1 

0FCS- 

00 

00 

20 

03 

16 

FF 

61 

1  1 

0BD0-  03 

00 

00 

2  »■’ 

19 

F6 

F4 

03 

0FD0- 

S3 

00 

00 

■~y~7 

X—  1 

19 

F6 

F4 

03 

0BD8-  2S 

14 

86 

H0 

B7 

F4 

02 

B6 

0FD8- 

2A 

14 

A6 

AS 

B7 

F4 

02 

B6 

0BE0—  F4 

02 

-r  -r 

5F 

10 

sc 

F0 

00 

0FE0— 

F4 

02 

5F 

10 

.SC- 

F8 

00 

0BE8-  26 

04 

10 

8E 

00 

00 

16 

FF 

0FE8- 

26 

04 

10 

SE 

00 

00 

16 

FF 

0BF0—  3F 

00 

00 

00 

00 

00 

00 

00 

0FF0- 

3F 

00 

00 

00 

00 

00 

00 

00 

0BF8-  00 

00 

00 

00 

00 

00 

FB 

00 

0FF8- 

00 

00 

00 

00 

00 

00 

FF 

00 

End  Listing 


Dr.  Dobb's  Journal,  July  1985 


55 

533 


Designing  a 
Real-Time  Clock 
for  the  S-100  Bus 


by  Alan  D.  Wilcox 


The  author ,  with  time  on  his  hands  and 
mind,  builds  a  hardware  dock  for  his 

Com pu Pro  816/Z. 


Adapted  by  permission  of  the  publisher,  Prentice-Hall, 
Inc.,  from  the  forthcoming  book.  Designing  and  Trouble¬ 
shooting  a  68000  Microcomputer  System,  by  Alan  D. 
Wilcox,  available  1986.  No  part  of  this  adaptation  may 
be  reproduced,  in  any  form  or  by  any  means,  without 
written  permission  from  the  publisher. 

After  revising  my  latest  program  and  printing  it  out, 
I  set  it  aside  to  look  over  later.  Naturally,  it  was  in 
a  stack  of  earlier  versions  of  the  program  that 
didn’t  quite  work  right  and  looked  almost  the  same  except 
for  a  few  hard-to-find  bugs.  You  know  the  rest  of  the 
story:  when  I  came  back  in  a  few  weeks,  I  couldn’t  tell 
which  was  the  latest  and  therefore  correct  copy  of  my 
program.  If  I  had  jotted  down  the  time  or  at  least  the  date 
on  my  latest  copy,  I  could  have  identified  it  then.  So  I 
decided  it  was  time  to  design  a  clock  circuit  to  do  the 
writing  for  me  automatically. 

The  result  is  a  clock  circuit  using  the  popular  National 
MM58167A  real-time  clock  IC.  The  circuit  requires  no 
changes  in  the  operating  system  and  has  battery  backup 
so  you  need  not  reset  it  after  turning  off  the  computer. 
Designed  to  meet  S-100/IEEE-696,  it  performs  easily 
with  virtually  any  S-100  microprocessor  system.  Two  as¬ 
sembly  language  programs  set  and  print  the  time  and 
date;  a  third  program  prints  the  time,  date,  and  filename 
as  a  title  header  then  prints  out  the  file  in  60-line  pages. 

Background 

There  are  two  basic  ways  to  put  a  clock  in  a  computer: 
either  modify  the  system  software  to  keep  track  of  the 
time  or  provide  a  piece  of  hardware  that  will  keep  time 
independently  of  the  computer.  The  software  approach  is 
easier  in  that  you  need  not  tamper  with  the  computer 
hardware,  but  it  has  several  drawbacks.  First,  when 
power  is  off,  the  clock  is  off;  this  requires  resetting  the 
clock  every  time  you  turn  the  computer  on.  Second,  you 
must  modify  the  system  software  to  support  a  time-keep¬ 
ing  program;  procedures  for  this  modification  can  be  al¬ 
most  incomprehensible  to  the  novice.  Finally,  the  subse¬ 
quent  time-keeping  isn’t  accurate  anyway;  for  example, 
the  clock  must  be  off  during  disk  read  or  write  operations. 
The  hardware  approach  avoids  these  problems. 

The  concept  of  a  hardware  clock  is  simply  to  have  a 
clock  available  for  the  computer  to  read  when  it  wants  the 
time  and  date.  The  clock  has  its  own  battery  power  supply 
to  keep  it  running  when  the  computer  is  turned  off.  The 
only  programming  required  is  for  setting  the  clock  and 
reading  it;  these  straightforward  programs  do  not  require 
an  intimate  understanding  of  the  system  software.  Final¬ 
ly,  accuracy  is  crystal  controlled  and  is  unaffected  by 
other  computer  operations. 

A  number  of  articles  have  discussed  how  to  implement 
a  clock  in  a  personal  computer.  In  one  design,  Calaway 
and  Hill  presented  what  I  call  a  software  approach  to 

Alan  D.  Wilcox,  60  South  Eighth  St.,  Lewisburg,  PA 
17837. 


56 


Dr.  Dobb's  Journal,  July  1985 


time-keeping.'  Their  basic  idea  is  to  provide  a  small 
amount  of  hardware  to  generate  an  interrupt  each  second; 
their  system  responds  to  this  interrupt  by  updating  the 
total  number  of  pulses  counted  and  thereby  updating  the 
clock  time.  The  operating  system  needs  modification, 
however,  which  can  range  from  straightforward  to  pro¬ 
foundly  complex. 

Hassebrock  described  a  simple  approach  to  keeping  the 
time  and  date  using  the  Hayes  Chronograph.2  The  Hayes 
unit  is  a  piece  of  hardware  connected  to  the  computer 
through  a  serial  data  port.  When  you  want  the  time  or 
date,  you  run  a  program  that  tells  the  clock  to  send  the 
information  to  the  computer  for  printing  or  display.  Be¬ 
cause  the  Chronograph  is  connected  to  a  serial  port,  you 
must  configure  the  port  to  match  the  required  data  rate, 
and  so  on.  Therefore,  you  need  a  modem-type  program  to 
access  the  clock;  the  program  may  be  either  part  of  the 
time-setting/reading  program  or  part  of  the  operating 
system.  Hassebrock  chose  the  latter  and  modified  his  sys¬ 
tem  somewhat.  Overall,  the  concept  of  an  external  serial- 
data  clock  is  appealing  except  that  a  spare  serial  port  may 
not  be  available. 

A  hardware  approach  described  by  Ciarcia  seemed  ini¬ 
tially  to  offer  some  distinct  advantages.3  First,  the  design 
uses  hardware  to  keep  time:  no  difficulty  keeping  time 
when  the  power  is  off,  no  system  software  modifications, 
and  no  accuracy  problems.  Second,  the  clock  does  not  use  a 
serial  port,  making  port  initialization  unnecessary.  The  in¬ 
tended  application  was  for  use  with  the  Z8-BASIC  micro¬ 
computer  as  an  intelligent  clock.  Although  the  approach 
has  merit,  as  it  stands,  it  is  not  a  generally  useful  design. 

Design  Overview 

The  design  requirement  was  for  a  real-time  clock  to  oper¬ 
ate  in  my  CompuPro  System  816/Z,  a  Z80B  S-100  ma¬ 
chine  running  at  6  MHz.  The  clock  should  keep  both  the 
time  and  date,  as  well  as  provide  a  means  of  interrupting 
the  system  either  on  a  regular  basis  or  at  a  predetermined 
time.  It  should  maintain  an  accuracy  within  10  sec/ 
month,  with  or  without  system  power.  Except  for  initially 
setting  the  time  and  date,  the  operator  should  not  have  to 
interact  with  the  clock  in  any  way. 

The  clock  should  meet  S-100/IEEE-696  and  operate  as 
a  slave  without  affecting  normal  system  operation;  like¬ 
wise,  it  should  require  no  changes  in  the  system  software. 
Data  transfer  between  clock  and  system  should  use  I/O- 
mapped  ports.  The  handshaking  should  use  the  S-100 
RDY  line,  and  because  the  processor  operates  at  6  MHz, 
some  wait  states  should  be  available.  Clock  interrupts  to 
the  system  should  be  switchable  to  any  of  the  S-100  vec¬ 
tored  interrupt  lines. 

Although  all  initial  programs  should  poll  the  clock  for 
the  time,  the  clock  should  be  able  to  use  any  of  the  vec¬ 
tored  interrupt  lines  to  implement  a  future  real-time  mul¬ 
titasking  executive;  in  the  long  term,  this  could  involve 
multiprocessing  with  a  new  10  MHz  68000  CPU  board  in 
the  system.  For  the  near  term,  however,  the  software 
should  at  least  set  and  display  the  time,  as  well  as  print 
files  with  a  time/date  header. 


Hardware  Design 

The  National  MM58167A  real-time  clock  IC4  provided  a 
solution  to  the  design  requirements.  As  shown  in  the  block 
diagram  (Figure  1,  page  58),  you  can  partition  hardware 
using  this  clock  IC  into  several  major  modules  that  in¬ 
clude  the  clock,  address  decoder,  data-bus  interface,  in¬ 
terrupt  switches,  and  power  supply.  Once  you  divide  the 
hardware  into  modules,  the  design  can  proceed  in  much 
the  same  way  as  in  software  development:  top  down,  bot¬ 
tom  up,  or  most  critical  first. 

The  appropriate  design  method  in  this  case  is  to  consid¬ 
er  the  most  critical  section  first.  Everything  depends  on 
the  clock  IC,  and  its  requirements  come  before  all  else. 
The  block  diagram  of  the  MM58167A  (Figure  2,  page 
59)  shows  that  the  IC  uses  five  address  lines  (32  different 
addresses)  and  a  chip  select  and  has  a  single  bidirectional 
data  bus  with  separate  read/write  controls,  two  interrupt 
outputs,  and  a  power-down  control.  Designing  to  accom¬ 
modate  each  of  these  requirements  individually  results  in 
the  implementation  shown  in  the  circuit  schematic  (Fig¬ 
ure  3,  page  60). 

I  decided  to  use  I/O  mapping  rather  than  memory 
mapping  for  the  clock  data  transfer.  With  a  maximum  of 
256  ports,  only  the  lower  eight  bits  of  the  address  bus  need 
decoding,  and  five  of  these  are  decoded  internally  by  the 
clock  IC.  For  maximum  flexibility,  I  chose  to  use  DIP 
switches  to  select  the  three  most  significant  bits  of  the 
I/O  address.  The  output  of  the  decoder  is  used  as  a  chip 
select  for  the  clock  and  by  the  read /write  qualifier  circuit. 

The  S-100  data  bus  in  and  out  lines  are  buffered  with  a 
pair  of  74LS244s  connected  directly  to  the  clock  IC.  The 
buffer  for  data-in  (DI  from  the  processor’s  viewpoint)  is 
strobed  using  the  I/O  read  qualifiers  pDBIN  and  sINP 
and  the  chip  select  CS.  The  data-out  buffer  is  strobed 
using  pWR*,  sOUT,  and  the  chip  select  CS.  During  a 
typical  read  bus  cycle,  for  example,  the  system  places  the 
proper  address  on  the  address  bus,  asserts  sINP,  then  as¬ 
serts  pDBIN  to  strobe  the  DI  buffer  and  the  clock  RD* 
input.  In  a  typical  write  bus  cycle,  the  system  puts  the 
address  on  the  bus,  asserts  sOUT  then  asserts  pWR*  to 
strobe  the  DO  buffer  and  the  clock  WR*  input. 

Timing 

A  basic  read  or  write  bus  cycle  has  three  bus  states  (BS1, 
BS2,  and  BS3),  each  of  which  takes  167  ns  in  a  6  MHz 
system;  consequently,  a  bus  read  or  write  normally  com¬ 
pletes  within  500  ns.  However,  for  I/O  devices  that  re¬ 
quire  substantial  access  times,  you  can  extend  the  bus 
cycle  by  adding  a  number  of  wait  states.  To  determine 
whether  wait  states  are  required  during  any  read  or  write 
bus  cycle,  the  bus  master  samples  the  RDY  line  at  the 
rising  edge  of  the  system  clock  in  BS2.  If  the  RDY  line  is 
low,  then  a  wait  state  (BSw)  is  inserted  immediately  after 
BS2;  if  the  RDY  line  is  still  low  one  bus  state  later,  yet 
another  BSw  is  inserted.  The  addition  of  wait  states  con¬ 
tinues  until  RDY  finally  goes  high;  at  that  point,  the  bus 
cycle  concludes  with  BS3. 

The  MM58167A  clock  requires  the  addition  of  wait 
states  because  of  its  slow  access  time.  For  a  clock  read 


Dr.  Dobb's  Journal,  July  1985 


57 

535 


operation,  the  maximum  specified  time  from  valid  ad¬ 
dress  until  valid  data  is  1050  ns,  far  longer  than  a  normal 
read  bus  cycle  with  no  wait  states.  The  read  bus  cycle  on 
my  6  MHz  CompuPro  system  is  shown  in  Figure  4  (page 
62)  with  approximate  times  to  scale.  Asserting  pDBIN  at 
the  beginning  of  BS2  leaves  po  time  for  the  clock  to  bring 
the  RDY  line  low.  However,  all  Z80  microprocessors 
automatically  insert  a  single  wait  state  for  I/O  instruc¬ 
tions  (by  carrying  out  a  microcoded  instruction  in  the 
Z80),  so  the  clock  really  has  until  the  beginning  of  the 
first  wait  state  BSw  (auto)  to  get  RDY  low. 

The  timing  question  becomes:  “Can  clock  RD*  be 
pulled  low  and  can  the  clock  respond  with  RDY  low — all 
within  167  ns?”  The  specs  for  the  clock  indicate  that 
read-strobe  to  ready-strobe  (clock  pin  4)  is  150  ns  maxi¬ 
mum,  but  the  7406  easily  could  add  another  40  ns  in  prop¬ 
agation  delay  to  the  system  RDY  line.  Add  these  up.  In 
the  worst  case,  the  clock  board  cannot  get  the  RDY  line 
down  in  time  even  by  taking  advantage  of  the  Z80  auto¬ 
matic  wait  state.  As  indicated  in  Figure  4,  the  clock  actu¬ 
ally  takes  from  about  100  ns  (middle  of  BS2)  to  about  190 
ns  (middle  of  BSw).  Actual  timing  measurements  and 


read  operations  indicate  that  the  clock  sometimes  is  fast 
enough  but  usually  is  not.  For  reliable  operation  of  RDY, 
we  must  add  an  extra  wait  state. 

In  my  system,  I  used  the  Z80  processor  board’s  option 
switch  to  add  a  single  wait  state  to  I/O  operations  in 
addition  to  the  Z80  automatic  wait.  This  required  no  ad¬ 
ditional  hardware,  although  you  could  enhance  a  more 
general  clock  design  by  including  an  on-board  wait  state 
generator  circuit.  Libes  and  Garetz  present  a  number  of 
suitable  circuits  in  their  book.6 

When  the  clock  data  is  finally  valid,  the  system  allows 
the  RDY  line  to  go  high,  and  the  processor  reads  the  data  in 
BS3.  To  finish  BS3,  pDBIN  is  negated,  which  raises  the 
clock  RD*  input,  and  the  address  is  deselected. 

The  write  bus  cycle  timing  requirements  for  the  clock 
are  similar  to  the  read  timing  requirements.  As  in  the  read, 
you  must  include  a  single  wait  state  to  allow  sufficient  time 
for  RDY  to  be  pulled  low  during  the  write  operation. 

Power-Down  Design 

The  power-down  design  is  critical  in  the  actual  operation 
of  the  clock.  If  POWER  DOWN*  is  not  asserted  to  a  low- 


+8V 

FROM  BUS 


DATA 

BUS 


STATUS  & 
CONTROL 


ADDRESS 

BUS 

VECTORED 

INTERRUPTS 


Figure  1. 

Block  Diagram  of  Real-Time  Clock  Board 


58 


RQC 


Dr.  Dobb's  Journal,  July  1985 


voltage  level  at  least  one  microsecond  before  system 
power  removal,  then  the  contents  of  the  clock  memory 
might  be  anything  (or  nothing)  when  you  restore  normal 
system  power.  Furthermore,  when  the  power  does  come 
back  on,  POWER  DOWN*  must  be  held  low  until  all  bus 
signals  are  valid. 

A  common  way  of  accomplishing  this  is  to  use  a  zener 
diode  that  sets  a  reference  threshold  voltage  to  control  a 
transistor  switch.  Consider  the  circuit  (Figure  3)  when 
the  system  power  is  off:  Q1  and  Q2  are  both  off.  When  the 
system  power  comes  on,  nothing  happens  until  the  system 
bus  voltage  reaches  about  6.6  V.  Then  the  zener  conducts, 
turning  on  Q2,  which  then  turns  on  Ql.  When  Q1  goes  on, 
voltage  is  applied  to  POWER  DOWN*,  which  activates 
the  clock  chip  for  normal  operation.  Because  a  system  bus 
voltage  greater  than  6.6  V  is  enough  to  allow  near-normal 
output  from  all  5  V  regulators,  all  bus  signals  should  be 
valid  by  this  time.  When  the  system  power  goes  off,  the 
system  voltage  drops  gradually  enough  so  that,  when  it 
passes  below  6.6  V,  the  zener  and  Q1/Q2  can  drop  the 
POWER  DOWN*  to  a  low  voltage  before  the  bus  signals 
are  no  longer  valid. 

A  small,  3.75  V,  20  mAh,  NiCad  rechargeable  battery 
maintains  clock  operation  when  system  power  is  off. 
Specified  current  consumption  of  the  clock  is  on  the  order 
of  10  to  20  uA  during  power-down;  my  clock  measured  14 
uA  while  using  the  battery  with  a  series-blocking  diode. 
The  470  ohm  resistor  provides  a  trickle  charge  from  xh 


mA  to  about  2  mA,  depending  on  the  state  of  the  battery 
and  component  tolerances.  For  an  average  use  of  several 
hours  a  day,  this  resistor  keeps  the  battery  charged  be¬ 
tween  3.8  and  4.15  V  with  no  difficulties. 

Logic  Circuits 

IEEE  Std-696  (sec.  3.7)  states  that  a  card  may  not  source 
more  than  0.5  mA  at  0.5  V  nor  sink  more  than  80  uA  at 
2.4  V  on  most  system  signal  lines.5  The  effect  of  this  is 
that  you  can  connect  only  one  LS  TTL  gate  per  card  to 
each  line  of  the  address  bus.  My  decoder  uses  the 
74LS136  exclusive-or,  which  sources  a  worst-case  maxi¬ 
mum  of  0.8  mA  at  0.4  V.  I  expect  this  to  cause  no  opera¬ 
tional  problems  in  any  practical  sense.  I  opted  to  use  the 
74LS136,  rather  than  buffer  the  address  bus  with  extra 
gates,  to  be  strictly  within  the  specification.  I  prefer  to  be 
conscious  of  trade-offs  than  to  not  be  aware  of  them  and 
have  a  problem  occur  later. 

The  pull-up  resistors  for  the  open-collector  7406  are 
selected  to  maintain  proper  logic  levels  and  currents.  The 
design  trade-off  is  to  let  the  various  resistors  be  high  for 
lower  current  consumption  or  to  let  them  be  low  for  more 
speed.  I  designed  for  as  much  speed  as  possible.  Typical 
calculations  are  discussed  in  the  standard  TTL  Data  Book 
(page  6-6).7 

Near  the  end  of  the  project,  I  decided  to  implement  the 
clock  standby  interrupt,  which  required  an  extra  non-in¬ 
verting  gate.  I  used  the  spare  74LS10  and  the  last  spare 


0SC  in 
OSCOUT 

TO 

Mft 

00 

ot 

02 

01 

04 

OS 

00 

01 

Cf 
F9WTA 
53*  N 


A] 

A4 


Figure  2. 

Block  Diagram  of  the  MM581 67A  (material  courtesy  of 
National  Semiconductor  Corporation) 


Dr.  Dobb's  Journal,  July  1985 


59 


‘NOTE  Leave  interrupt  switches  S2  and  S3  all  open  unless  interrupts  are  to  be  implemented 
If  interrupts  are  used,  close  only  1  switch  in  S2  and  1  switch  in  S3  at  most. 


Figure  3. 

Schematic  Wiring  Diagram  of  S-1 00 
Real-Time  Clock 


60 


Dr.  Dobb's  Journal,  July  1985 


7406  gate  to  get  this  output  from  the  clock  board.  This 
particular  output  does  not  have  the  flexibility  of  the  pro¬ 
grammable  output,  but  you  can  use  it  to  indicate  a  match 
between  real  time  and  a  preset  time  for  a  wake-up  alarm. 

Construction  and  Installation 

I  prototyped  the  entire  real-time  clock  on  a  Vector  8800V 
wire-wrap  board,  as  shown  in  the  photo  (page  64).  I  added 
the  interrupt  switches  (S3)  for  the  wake-up  interrupt  af¬ 
ter  taking  the  photo;  they  are  located  beside  the  main 
interrupt  switches  (S2).  All  the  circuits  took  less  than  a 
third  of  a  standard  S-100  board.  I  wired  the  transistor 
power-down  circuit  on  the  24-pin  header  at  the  lower-left 
edge  of  the  board  by  the  battery.  I  used  plastic  “wrap  ID” 
panels  on  the  back  of  the  board  for  each  IC  socket  to 
make  component  and  pin  identification  easier. 

My  system  does  not  have  any  I/O  port  assignments  in 
the  range  AOh  to  BFh,  so  I  selected  AO  as  the  base  address 
for  the  clock.  For  this  base  address,  SI  sections  a  and  c 
are  closed  for  a  “  1”  and  section  b  is  left  open  for  a  “0.”  As 


READ  BUS  CYCLE 


shown  in  Figure  5  (page  63),  the  clock  uses  a  total  of  24 
addresses  rather  than  the  whole  block  of  32  allocated  to  it. 
This  permits  additional  design  flexibility  if  port  addresses 
are  at  a  premium. 

You  may  install  the  board  in  any  convenient  location 
on  the  bus.  Because  of  its  wire-wrap  construction,  howev¬ 
er,  the  board  takes  up  the  space  of  two  normal  boards  and 
cannot  be  crowded.  Depending  on  your  needs,  you  can 
snip  down  the  wire-wrap  pins  considerably  (watch  your 
eyes)  to  make  the  board  thinner. 

Software 

When  writing  the  programs  to  set  the  clock  and  print  the 
time,  I  concentrated  on  writing  clear,  structured,  modular 
code.  Thus,  you  should  have  little  difficulty  making 
changes  to  transport  the  clock  software  to  another  system 
or  to  make  modifications.  Naturally,  the  resulting  code 
perhaps  is  not  as  brief  as  might  have  been  possible  were  I 
writing  only  for  myself. 

I  have  presented  several  programs  here  for  using  the 
clock.  The  first  is  CLOCKSET  (see  Listing  One,  page  68), 
a  program  to  set  either  the  day  and  date,  the  time,  or  both. 
The  second  is  TIME  (Listing  Two,  page  74  ),  a  program  to 
display  the  time  and  date  on  the  console.  This  same  pro¬ 
gram  will  print  the  time  and  date  plus  a  short  text  (such  as 
a  filename)  to  LST:  if  you  include  the  text  with  the  pro¬ 
gram  invocation.  This  second  feature  of  TIME  is  handy 
when  you  are  using  a  text  editor  and  need  a  date  on  a  page 
about  to  be  printed.  The  last  is  TIMELIST  (see  Listing 
Three,  page  79  ),  a  program  to  print  a  time/date  header 
with  filename  then  list  a  text  file  with  60  lines  per  page. 

Recall  that  I  chose  to  use  AOh  as  a  clock  base  address  for 
I/O.  In  all  the  programs,  I  used  CLOCK  as  the  base  ad¬ 
dress  and  set  its  value  equal  to  AOh.  1  identify  other  clock 
port  addresses  by  a  simple  reference  to  CLOCK+1, 
CLOCK  +  2,  and  so  on.  For  example,  from  Figure  5, 
CLOCK +  3  is  the  address  for  “minutes”  and  CLOCK +  7  is 
the  address  for  “months.”  Using  CLOCK  in  this  way  allows 
you  to  make  an  easy  change  to  any  base  address  just  by 
changing  one  statement  at  the  beginning  of  the  program. 

The  first  program,  CLOCKSET,  asks  whether  or  not  to 
set  the  date.  If  you  select  the  default  “No,”  then  it  asks 
whether  to  set  the  time.  If  you  choose  a  second  default 
“No,”  the  program  terminates  without  changing  the  clock. 
If,  however,  you  do  select  the  date-set  option,  then  it  asks 
for  two  digits  for  the  day  of  the  week:  enter  01  for  Sunday, 
02  for  Monday,  and  so  on.  You  enter  the  day  of  the  month 
the  same  way:  0 1  through  3 1 .  You  enter  the  month  itself  as 
month  01  through  12.  Select  the  year  by  entering  just  the 
last  two  digits;  that  is,  85  for  1985.  Each  January,  you 
must  change  the  year  manually;  the  clock  IC  does  not 
automatically  change  to  a  new  year.  When  writing  this 
program,  I  opted  to  keep  it  simple:  no  error  checks  and 
nothing  fancy  like  entering  literal  names  instead  of  num¬ 
bers.  If  I  make  an  error,  I  just  invoke  the  program  over 
again  and  do  it  correctly  the  next  time  through. 

The  second  part  of  CLOCKSET  is  similar  to  the  date¬ 
setting  procedure.  You  enter  the  time  in  two-digit  groups 
for  the  hours  and  minutes,  and  a  carriage  return  sets  the 


Dr.  Dobb's  Journal,  July  1985 


62 


Functional  Description 

TABLE  II.  ADDRESS  CODES  AND  FUNCTIONS 

A4 

A3 

A2 

A1 

AO 

Function 

0 

0 

0 

0 

0 

Counter— Ten  Thousandths  ol  Seconds 

0 

0 

0 

0 

1 

Counter  — Hundredths  and  Tenths  ol  Seconds 

0 

0 

0 

1 

0 

Counter  —  Seconds 

0 

0 

0 

1 

1 

Counter  — Minutes 

0 

0 

1 

0 

0 

Counter  —  Hours 

0 

0 

1 

0 

1 

Counter  — Day  ol  Week 

0 

0 

1 

1 

0 

Counter  —  Day  of  Month 

0 

0 

1 

1 

1 

Counter  —  Month 

0 

1 

0 

0 

0 

RAM— Ten  Thousandths  ol  Seconds 

0 

1 

0 

0 

1 

RAM—  Hundredths  and  Tenths  ol  Seconds 

0 

1 

0 

1 

0 

RAM  — Seconds 

0 

1 

0 

1 

1 

RAM  — Minutes 

0 

1 

1 

0 

0 

RAM  — Hours 

0 

1 

1 

0 

1 

RAM  —  Day  of  Week 

0 

1 

1 

1 

0 

RAM  — Day  ol  Month 

0 

1 

1 

1 

1 

RAM  —  Months 

1 

0 

0 

0 

0 

Interrupt  Status  Register 

1 

0 

0 

0 

1 

Interrupt  Control  Register 

1 

0 

0 

1 

0 

Counters  Reset 

1 

0 

0 

1 

1 

RAM  Reset 

1 

0 

1 

0 

0 

Status  Bit 

1 

0 

1 

0 

1 

GO  Command 

1 

0 

1 

1 

0 

STANDBY  INTERRUPT 

1 

1 

1 

1 

1 

Test  Mode 

Figure  5. 

Clock  Address  Codes  (material  courtesy  of 
National  Semiconductor  Corporation) 


Figure  6 

Structure  Chart  of  the  TIMELIST  Program 


63 


Dr.  Dobb's  Journal,  July  1985 


seconds  to  zero.  The  clock  chip  keeps  its  time  based  on  a 
24-hour  count,  so  if  the  time  is  7: 1 5  pm,  for  example,  then 
the  entry  is  19  hours,  15  minutes.  The  carriage  return  to 
set  seconds  to  zero  does  a  write-data  to  CLOCK  +  21  (the 
GO  command,  see  Figure  5).  This  write  pulse  resets  the 
thousandths,  hundredths,  tenths,  units,  and  tens  of  sec¬ 
onds  counters.  In  the  SETT1M  subroutine,  note  that  I  re¬ 
set  the  seconds  (an  early  GO)  before  setting  the  minutes; 
this  is  because  the  clock  chip  will  increment  the  minute 
counter  upon  GO  if  the  seconds  counter  is  greater  than 
40.  For  example,  if  the  time  is  almost  7:1 5  pm  and  I  enter 
1 9  and  1 5  followed  by  <cr>  to  zero  seconds,  I  might  find 
the  time  erroneously  set  to  19:16;  hence,  the  need  to  reset 
GO. 

In  the  program  TIME,  unlike  the  simpler  CLOCKSET 
program,  I  wanted  more  than  just  bare  numbers  from  the 
clock:  I  wanted  the  literal  names  for  days  and  months.  The 
subroutine  NTOLIT  makes  the  conversion  from  the  clock’s 
packed  BCD  to  a  literal  string.  It  does  this  by  converting 
the  number  (say,  1 1  for  November)  to  binary  then  index¬ 
ing  through  the  list  of  literals  until  it  reaches  the  eleventh 
one.  Then  it  moves  the  word  “November”  in  memory  to 
the  space  reserved  for  the  message  to  be  printed. 

You  can  use  TIME  in  two  ways:  either  as  a  simple  dis¬ 
play  to  the  console  or  as  a  display  plus  an  output  to  the 
printer.  To  get  the  display,  type  TIME.  To  get  the  printer 
output  in  addition  to  the  console  display,  type  TIME 
<text>.  The  additional  letters  will  send  the  time  and 
date  information  plus  the  <text>  to  the  currently  de¬ 
fined  LST:  device  connected  to  the  computer.  This  is  one 
way  of  time/date-stamping  a  page  before  sending  some 
special  output  to  the  printer.  Because  the  print  output  is 
aligned  toward  the  right-hand  side  of  the  page,  you  have 
space  for  only  about  15  characters  before  the  printer 
wraps  around.  I  use  the  space  to  put  in  a  filename  or  some 
other  text  identification.  Another  way  I  use  this  feature  is 
to  print  a  disk  name  as  <text>  then  send  a  disk  directory 
to  the  printer;  I  keep  this  directory  listing  with  the  disk  so 
I  have  a  dated  record  of  its  contents. 

For  convenient  time/date-stamping  of  a  full  program 


listing,  I  chose  the  approach  in  the  structure  chart  shown  in 
Figure  6  (page  63).  This  program  is  invoked  by  TIMELIST 
<filename>.  First,  the  title  line  is  printed  in  the  format: 
time,  date,  program  name.  Then,  after  skipping  several 
lines,  the  printout  of  the  text  of  the  program  begins,  with  a 
total  of  60  lines  on  each  page.  All  of  the  printing  is  done  in 
subroutine  PRINTLOOP,  and  you  can  interrupt  it  anytime 
from  the  keyboard;  that  is,  a  control-S  pauses  and  any 
keypress  continues,  unless  the  printing  is  not  paused,  in 
which  case  any  keypress  aborts  the  program. 

The  clock-related  aspects  of  this  program  are  similar  to 
TIME.  The  obvious  difference  is  that  a  file  must  be  opened, 
read,  and  printed.  At  the  very  beginning,  then,  because  I 
used  the  128  bytes  between  80H  and  100H  for  a  disk  buff¬ 
er  area,  it  is  necessary  to  move  the  stack  to  a  safe  place. 
Then  the  first  subroutine,  OPENFILE,  either  opens  the  file 
successfully  or  calls  QUIT  if  it  encounters  a  problem.  On 
exit,  the  stack  pointer  is  restored  to  its  previous  position. 

PRINTLOOP  sends  one  character  for  each  main  loop 
within  the  subroutine.  Before  getting  each  new  character, 
the  program  checks  to  see  if  a  console  break  is  present,  to 
verify  required  spaces  at  the  beginning  of  a  line,  and  to 
determine  if  the  maximum  number  of  lines  per  page  has 
been  reached.  The  program  as  now  assembled  adds  three 
spaces  at  the  left  margin  for  all  printed  text;  this  is  be¬ 
cause  I  usually  notebook-punch  my  programs  and  my 
Okidata  82A  prints  too  closely  to  the  left-hand  margin. 
To  change  this  margin  setting,  adjust  the  SKIP  equate. 
Likewise,  to  change  from  60  lines  per  page,  modify  the 
LPAGE  equate. 

After  getting  a  new  byte  by  a  call  to  GETNB,  I  check 
for  the  form-feed  character  and  ignore  it.  To  allow  form 
feeds  from  the  text,  change  the  KEEPFF  equate  to  TRUE. 
When  accepting  form  feeds  from  the  text,  I  disabled  the 
line-counting  within  PRINTLOOP  on  the  assumption  that 
page  length  would  have  already  been  determined. 

Before  actually  printing  the  character,  the  program 
checks  for  the  proper  line  length.  This  is  necessary  be¬ 
cause  I  skip  several  spaces  at  the  left  margin.  If  the  text 
runs  to  80  characters,  but  I’ve  already  printed  three 


Dr.  Dobb's  Journal,  July  1985 


64 

R41 


spaces,  then  three  characters  will  have  to  wrap  around  to 
another  line  on  the  printer.  To  maintain  the  left  margin,  I 
need  to  insert  the  usual  spaces  before  beginning  to  print 
the  wrap-around  letters. 

If  the  character  to  be  printed  is  a  tab  character  (09), 
then  TABTOSP  does  a  conversion  to  spaces  so  that  the 
printer  lines  up  to  every  eighth  column.  The  conversion  is 
not  simply  to  replace  every  tab  with  eight  spaces:  the  pro¬ 
gram  checks  to  see  what  the  current  column  position  is  in 
relation  to  every  eighth  column  then  inserts  just  enough 
spaces  to  get  the  next  eighth  position.  Thus,  a  tab  posi¬ 
tions  the  printer  to  columns  9,  17,  25,  33,  and  so  on. 
Change  the  NRSPCS  equate  to  the  desired  tab  conversion 
if  eight  is  not  suitable. 

As  with  all  programs,  a  number  of  modifications  al¬ 
ways  seem  necessary.  The  CLOCKSET,  TIME,  and  TIME- 
LIST  programs  are  certainly  not  the  final  versions.  Im¬ 
provements  are  always  possible;  it  becomes  an  issue  of 
whether  the  programs  are  “good  enough  to  do  the  job”  or 
must  be  revised.  I  chose  to  leave  these  three  programs  as 
they  stand  and  go  on  to  other  more  creative  programs. 

One  program  is  to  display  the  current  time  on  the  con¬ 
sole,  but  1  have  a  wristwatch  that  displays  time  quite  well. 
Consider  a  “reminder”  program  to  sound  the  console  bell 
at  a  certain  time.  Also  consider  how  to  keep  tax  records  of 
business  use  of  the  computer:  a  program  to  LOGON  and 
LOGOFF  that  records  date,  time  on/off,  and  subject  of 
computer  use.  This  program  should  create  a  file  suitable 
for  use  by  a  spreadsheet  program  for  tax  records.  These 
programs  and  others  using  the  interrupt  features  of  the 
clock  are  the  subject  of  a  future  article. 

Operation 

When  first  starting  up  the  clock,  set  the  proper  time  and 
date  using  CLOCKSET.  Then  run  the  TIME  program  to 
see  that  the  time  and  date  are  being  displayed  properly. 
Also  check  the  printing  feature  of  TIME  by  appending  a 
small  text  to  the  TIME  invocation  to  see  that  the  time  and 
text  go  to  the  printer.  Test  run  TIMELIST  with  a  two-  or 
three-page  text  file  to  see  that  it  prints  the  first  page  with 
time  and  date  then  prints  60  lines  on  each  of  the  following 
pages. 

Conclusions 

The  clock  board  has  proven  to  be  a  useful  addition  to  my 
computer  system.  I’ve  found  it  especially  convenient  for 
dating  disk  directory  prints,  as  well  as  for  the  normal 
dating  of  program  printouts.  Keeping  the  time  and  date 
on  my  program  listings  has  certainly  improved  my  pro¬ 
gram  filing  system:  now  when  I  discover  a  long-lost  paper, 

I  know  when  it  disappeared! 

I  would  like  to  thank  our  Bucknell  Technician,  Thomas 
J.  Thul,  Jr.,  for  his  help  in  laying  out  and  constructing  the 
clock  circuit  board.  Program  source  code  is  available 
from  the  author  on  an  8-inch  CP/M  disk  for  $15  postpaid. 

References 

1  Calaway,  J.  L.,  and  B.  Hill,  “CP/M,  Your  Time  Has 
Come,”  BYTE ,  May  1982,  pp.  479-493. 


2  Hassebrock,  Glen  E.,  Jr.,  “The  Hayes  Chronograph,  an 
H-89,  and  CP/M,”  REMark ,  No.  35,  December  1982, 
pp.  9-14. 

3  Ciarcia,  Steve,  “Everyone  Can  Know  the  Real  Time,” 
BYTE,  May  1982,  pp.  34-58. 

4  MM 58 167 A  Microprocessor  Real  Time  Clock,  Nation¬ 
al  Semiconductor  Data  Sheet,  April  1982. 

5  IEEE  Standard  696  Interface  Devices,  New  York,  NY: 
IEEE,  1983. 

6  Libes,  Sol,  and  Mark  Garetz,  Interfacing  to  S-100/ 
IEEE-696  Microcomputers,  Berkeley,  CA:  Osborne/ 
McGraw-Hill,  1981. 

7  The  TTL  Data  Book  for  Design  Engineers,  2nd  ed.,  Dal¬ 
las,  TX:  Texas  Instruments,  Inc.,  1981. 

Trademarks 

Hayes  Chronograph  is  a  trademark  of  Hayes  Microcom¬ 
puter  Products,  and  Z8  is  a  trademark  of  Zilog. 


Hardware:  IEEE  Standard  696-1 983  compatible 

Operation  to  6  MHz  (using  two  wait  states) 
Handshaking  using  RDY  line  (72)  pulled  low 
until  clock  output  data  valid 
DIP-switch  selection  of  base  address  on  any 
32-byte  I/O  port  boundary,  32  ports  required 
DIP-switch  selection  of  vectored  interrupt  0 
through  7  for  clock  interrupts  100  ms 
to  1  month  or  RAM  and  counter  compares 
DIP-switch  selection  of  vectored  interrupt  0 
through  7  for  RAM  and  counter  compares 
Accuracy  0.01  %,  but  generally  better  than  5 
sec/month 

Power  requirement  +8V  <  200  mA  during 
normal  operation 
Rechargeable  battery  backup 
Construction  using  Vector  8800V  and 
wire-wrap 

Software:  24-hour  time:  hour,  minute,  second 

Calendar:  day  of  week  and  month,  month,  year 
File-list  utility  with  time/date  header 
Clock  time-  and  date-setting  program 


Table 

Specifications  of  the  S-100  Real-Time  Clock 


66 


Dr.  Dobb’s  Journal,  July  1985 


Real-Time  Clock 

Listing  One 


(Listing  Continued,  text  begins  on  page  56) 


;  CLOCKSET  Program  to  set  date  &  time  of  real-timdc 


Program  by 

Alan 

D.  Wilcox 

: 

6  January  1985 

0100 

ORG 

100H 

00A0 

= 

CLOCK 

EQU 

OAOH 

;  Clock  base  address 

0000 

= 

FALSE 

EQU 

0 

OOFF 

= 

TRUE 

EQU 

OFFH 

0005 

= 

BDOS 

EQU 

5H 

;  BDOS  entry  point 

0001 

= 

CONSIN 

EQU 

1 

;  Console  input 

0002 

= 

CONSOUT 

EQU 

2 

;  Console  output 

0009 

= 

PRINTST 

EQU 

9 

;  Print  string  function 

000B 

= 

GETSTAT 

EQU 

11 

;  Console  status 

000A 

= 

LF 

EQU 

OAH 

;  Line  Feed 

000D 

= 

CR 

EQU 

ODH 

;  Carriage  Return 

00 1 B 

= 

:sc 

EQU 

1BH 

;  Escape 

*  *  *  *  * 

* 

* 

MAIN 

PROGRAM 

***** 

0100  113202 

START:  LXI 

D,  GREET 

0103  CD1302 

CALL 

PRINTIT 

0106  116502 

LXI 

D , DATESET 

;  Set  the  date? 

0109  CD1302 

CALL 

PRINTIT 

010C  CD1B02 

CALL 

GETANS 

;  Get  answer 

010F  FEFF 

CPI 

TRUE 

0111  CC2901 

CZ 

SETDATE 

0114  117F02 

LXI 

D , TIMESET 

;  Set  the  time? 

0117  CD1302 

CALL 

PRINTIT 

01 1A  CD1B02 

CALL 

GETANS 

;  Get  answer 

01 ID  FEFF 

CPI 

TRUE 

01  IF  CC6401 

CZ 

SETTIME 

0122  119F03 

LXI 

D ,  CRLF 

0125  CD1302 

CALL 

PRINTIT 

0128  C9 

30NE :  RET 

* 

;  Return  to  CP/M 

* 

*****  SUBR 

SETDATE  ***** 

Set  the  current  date 

0129  119902 

SETDATE : LXI 

D ,  DAY 

;  What  day  of  week 

012C  CD1302 

CALL 

PRINTIT 

012F  CDDBOl 

CALL 

GETBCD 

;  Get  it  and  store 

0132  D3A5 

OUT 

CLOCK+5 

;  in  elk  counter 

0134  11C402 

LXI 

D, DATE 

;  What  date  is  it? 

0137  CD1302 

CALL 

PRINTIT 

013A  CDDBOl 

CALL 

GETBCD 

;  Get  it  and  store 

0 1 3D  D3A6 

OUT 

CLOCK+6 

;  in  elk  counter 

013F  11E002 

LXI 

D, MONTH 

;  What  month  is  it 

0142  CD 1 30 2 

CALL 

PRINTIT 

0145  CDDBOl 

CALL 

GETBCD 

;  Get  it  and  store 

0148  D3A7 

OUT 

CLOCK+7 

;  in  elk  counter 

014A  11FC02 

LXI 

D , YEAR 

;  And  the  year? 

014D  CD1302 

CALL 

PRINTIT 

(Continued  on  page  70) 


68 


Dr.  Dobb’s  Journal,  July  1985 


Real-Time  Clock 

Listing  One 


(Listing  Continued,  text  begins  on  page  56) 


0150 

CDDB01 

CALL 

GETBCD 

;  Get  it  and  store 

0153 

D3A9 

OUT 

CLOCK+9 

;  in  clock  RAM 

0155 

118A03 

LXI 

D,  AGAIN 

;  Do  this  over  again? 

0158 

CD1302 

CALL 

PRINTIT 

015B 

CD1B02 

CALL 

GETANS 

;  Get  answer 

015E 

FEFF 

CPI 

TRUE 

0160 

CA2901 

JZ 

SETDATE 

;  Yes ,  do  it. 

0163 

C9 

RET 

»»»*»  SUBR 

SETTIME  »**»* 

Set  the  current  time 

0164 

CD9C01 

SETTIME : CALL 

DISPLAY 

;  Show  the  time  at  start 

0167 

113503 

LXI 

D , SETHR 

;  Set-hrs  message 

016A 

CD1302 

CALL 

PRINTIT 

016D 

CDDB01 

CALL 

GETBCD 

;  Get  2  ASCII,  convert 

to  BCD 

0170 

D3A4 

OUT 

CLOCK+4 

;  Put  hours  into  clock 

enter 

0172 

115203 

LXI 

D , SETMIN 

;  Set-min  message 

0175 

CD1302 

CALL 

PRINTIT 

0178 

D3B5 

OUT 

CLOCK+21 

;  Reset  seconds 

017A 

CDDB01 

CALL 

GETBCD 

;  Get  2  ASCII,  convert 

to  BCD 

017D 

D3A3 

OUT 

CLOCK+3 

;  Put  mins  into  clock 

counter 

017F 

116E03 

LXI 

D , SETSEC 

;  Set-seconds  message 

0182 

CD1302 

CALL 

PRINTIT 

0185 

CD0B02 

CALL 

INCHAR 

i  Get  key-press 

0188 

D3B5 

OUT 

CLOCK+21 

;  "GO"  command  zero  seconds 

018A 

CD9C01 

CALL 

DISPLAY 

;  Show  the  results 

018D 

118A03 

LXI 

D,  AGAIN 

;  Ask  to  do  again 

0190 

CD1302 

CALL 

PRINTIT 

0193 

CD1B02 

CALL 

GETANS 

0196 

FEFF 

CPI 

TRUE 

0198 

CA6401 

JZ 

SETTIME 

;  Yes,  do  it  again 

019B 

C9 

RET 

*****  SUBR  DISPLAY  ***** 


Displays  current  time 


019C 

111803  I 

DISPLAY : LXI 

D.TMSG 

;  Time  is  ... 

019F 

CD1302 

CALL 

PRINTIT 

01A2 

DBA4 

IN 

CLOCK+4 

;  Get  hours  from  clock 

01A4 

CDBC01 

CALL 

PRHEX 

;  Print  hours 

01A7 

3E3A 

MVI 

A,  '  :  1 

01A9 

CD0402 

CALL 

PCHAR 

01  AC 

DBA3 

IN 

CLOCK+3 

;  Get  minutes  from  clock 

01AE 

CDBC01 

CALL 

PRHEX 

;  Print  minutes 

01B1 

3E3A 

MVI 

A,  '  :  ' 

01B3 

CD0402 

CALL 

PCHAR 

01B6 

DBA2 

IN 

CLOCK+2 

;  Get  seconds 

01B8 

CDBC01 

CALL 

PRHEX 

;  Print  seconds 

01BB 

C9 

RET 

*****  SUBR 

PRHEX  ***** 

Converts  binary  value  into  two  ASCII-hex  characters 
and  prints  on  console 

(Continued  on  page  72) 


70 


Dr.  Dobb’s  Journal,  July  1985 


Real-Time  Clock 

Listing  One 


(Listing  Continued,  text  begins  on  page  56) 


01BC 

F5 

PRHEX : 

PUSH 

PSW 

01BD 

OF 

RRC 

;  Put  4  MSB's  into  4  LSB's 

0 1  BE 

OF 

RRC 

0 1BF 

OF 

RRC 

01C0 

OF 

RRC 

01C1 

CDC901 

CALL 

PRNIB 

;  Prnt  hex  equiv  to  4  MSB's 

01C4 

FI 

POP 

PSW 

01C5 

CDC901 

CALL 

PRNIB 

;  Prnt  hex  equiv  to  4  LSB's 

01C8 

C9 

RET 

***** 

SUBR 

PRNIB  ***** 

Prints 

a  nibble  of 

Reg  A 

01C9 

E60F 

PRNIB : 

ANI 

OFH 

;  Mask  out  top  4  bits 

OICB 

FEOA 

CPI 

10 

;  Is  it  number  or  letter? 

01CD 

D2D501 

JNC 

LETR 

;  Must  be  a  letter  if  jump. 

01  DO 

C630 

ADI 

'0' 

;  Add  offset  to  make  ASCII 

01D2 

C3D701 

JMP 

PRNT 

01D5 

C637  LETR: 

ADI 

'  A '  -  10 

;  Add  offset  to  make  binary 

;  into  ASCII  letter 

01D7 

CD0402  PRNT : 

CALL 

PCHAR 

;  Send  ASCII  to  console 

01  DA 

C9 

RET 

*  *  *  *  * 

SUBR 

GETBCD  ***** 

Gets 

2 

ASCII  characters,  converts  them  to  packed  BCD 

EXIT: 

A  =  2  BCD 

characters 

01DB 

CD0B02 

3ETBCD : 

CALL 

INCHAR 

;  Get  character 

0  IDE 

FE30 

CPI 

'O' 

01E0 

DADB01 

JC 

GETBCD 

;  Ignore  char  if  <  ASCII  0 

01E3 

FE3A 

CPI 

'  9  '  +1 

01E5 

D2DB01 

JNC 

GETBCD 

;  Ignore  char  if  >  ACCII  9 

01E8 

D630 

SUI 

■O' 

;  Make  ASCII  into  binary 

01EA 

E60F 

ANI 

OFH 

;  Mask  out  4  MSB ' s 

01  EC 

07 

RLC 

0 1  ED 

07 

RLC 

0 1  EE 

07 

RLC 

01EF 

07 

RLC 

;  Put  into  MSB  position 

01F0 

47 

MOV 

B  ,  A 

;  and  save  in  B 

01F1 

CD0B02  LOBYTE: 

CALL 

INCHAR 

;  Get  2nd  character 

01F4 

FE30 

CPI 

'0' 

01F6 

DAF101 

JC 

LOBYTE 

01F9 

FE3A 

CPI 

'  9  1  +1 

01FB 

D2F101 

JNC 

LOBYTE 

01FE 

D630 

SUI 

'O' 

;  Make  ASCII  into  binary. 

0200 

E60F 

ANI 

OFH 

0202 

80 

ADD 

B 

;  Combine  both  BCD  char  in  A 

0203 

C9 

RET 

*  *  *  *  * 

SUBR 

PCHAR  *♦**« 

Prints 

Reg  A  to  console 

0204 

0E02  F 

’CHAR : 

MV  I 

C , CONSOUT 

0206 

5F 

MOV 

E,  A 

0207 

CD0500 

CALL 

BDOS 

020A 

C9 

RET 

*  *  *  *  * 

SUBR 

INCHAR  ***** 

Reg  A 

gets  ASCII  value  from  keyboard 

020B 

C5 

NCHAR : 

PUSH 

B 

;  Have  char  saved  in  B 

020C 

0E01 

MV  I 

C , CONSIN 

(Continued  on  page  74) 


Dr.  Dobb’s  Journal,  July  1985 


72 

545 


Real-Time  Clock  (Listing  Continued,  text  begins  on  page  56) 
Listing  One 


020E 

CD0500 

CALL 

BDOS 

0211 

Cl 

POP 

B 

0212 

C9 

RET 

***** 

SUBR  PRINTIT  ***** 

ENTRY: 

DE  =  Start  address  of 

0213 

F5 

I 

3RINTIT 

POSH 

PSW 

0214 

0E09 

MVI 

C , PRINTST 

0216 

CD0500 

CALL 

BDOS 

;  Pri 

0219 

FI 

POP 

PSW 

02 1A 

C9 

RET 

***** 

SUBR  GETANS  »*»»* 

Ask  question,  then  call 

this 

Assume 

default  answer  is 

NO. 

EXIT: 

A  =  True  =  FF  if 

YES 

A  =  False=  0  if 

NO 

02  IB 

0E01 

3ETANS : 

MVI 

C , CONSIN 

02  ID 

CD0500 

CALL 

BDOS 

;  Get 

0220 

FE59 

CPI 

’  Y  ' 

0222 

CA2F02 

JZ 

ENDTRU 

0225 

FE79 

CPI 

’  y 1 

0227 

CA2F02 

JZ 

ENDTRU 

022A 

3E00 

MVI 

A, FALSE 

;  Not 

022C 

C33102 

JMP 

GOTANS 

022F 

3EFF 

ENDTRU: 

MVI 

A, TRUE 

0231 

C9 

30TANS : 

RET 

***** 

CONSOLE 

MESSAGES  ***** 

;  Print  string  to  console 


;  Get  console  input 


;  Not  Y  or  y.  Assume  NO. 


0232 

0D0A50726F 

GREET: 

DB 

CR , LF Program  to 

set  DATE  & 

TIME  of 

0262 

726561 6C2D 

DB 

'real-time  clock. 

'  , LF , CR ,  '$' 

0265 

0D0A0A5365 

DATESET: 

DB 

CR , LF , LF , 'Set  the 

date?  <N>  - 

-> 

$' 

027F 

0D0A0A5365 

TIMESET: 

DB 

CR,LF,LF, 'Set  the 

time?  <N>  - 

-> 

$' 

0299 

ODOAOA5375 

DAY: 

DB 

CR , LF , LF , ' Sunday 

=  01 .  ' 

02A8 

0D0A456E74 

DB 

CR,LF, 'Enter  day 

01  to  07. 

-> 

S  ' 

02C4 

0D0A456E74 

DATE: 

DB 

CR, LF , 'Enter  date 

01  to  31  - 

-> 

S  ' 

02E0 

0D0A456E74 

MONTH: 

DB 

CR,LF, 'Enter  month  01  to  12  - 

-> 

$' 

02FC 

0D0A4  56E74 

YEAR: 

DB 

CR , LF , 'Enter  year 

84  to  99  - 

-> 

$' 

0318 

0D0A0A4  87  2 

TMSG  : 

DB 

CR , LF , LF , ' Hrs  :  Min  :  Sec  now 

— >  s 

0335 

0D0A0A496E 

SETHR : 

DB 

CR,LF,LF, 'Input  HRs  00  to  23 

— >  $ 

0352 

0D0A496E70 

SETMIN : 

DB 

CR, LF , ' Input  Min 

00  to  59 

-> 

$ ' 

036E 

0D0A507265 

SETSEC: 

DB 

CR,LF, 'Press  <cr> 

to  zero  sec 

.  $ ' 

038A 

ODOAOA4  4  6F 

AGAIN: 

DB 

CR , LF , LF , ' Do  over?  <N>  ...  $' 

039F 

ODOA24 

CRLF  : 

DB 

CR, LF, ' $ ' 

03A2 

END 

Listing  Two 


TIME 

Program  by 
Invoke  by 


Display  time  and  date  of  real-time  clock 


Alan  D.  Wilcox 
18  December  1984 

A>TIME 

A>TIME  <text> 


Output  to  Console 
Output  to  Console  &  LST : 


End  Listing  One 


74 

546 


Dr.  Dobb’s  Journal,  July  1985 


(List  time  &  text) 


0100 

ORG  100H 

00A0  = 

CLOCK 

EQU  OAOH  ; 

Clock  base  address 

0005  = 

BDOS 

EQU  5H  ; 

BDOS  entry  point 

0080  = 

BUFF 

EQU  80H  ; 

Buffer 

0005  = 

LISTOUT 

EQU  5  ; 

List  output 

0009  = 

PRINTST 

EQU  9  ; 

Print  string  function 

OOOA  = 

LF 

EQU  OAH  ; 

Line  Feed 

OOOD  = 

CR 

EQU  ODH  ; 

Carriage  Return 

00 1 B  = 

2SC 

EQU  1BH  ; 

Escape 

*  *  *  *  * 

* 

MAIN  PROGRAM  »»** 

* 

* 

Read  current  time 

and  save  in  memory 

0100  DBA4 

GETIME:  IN 

CLOCK+4 

;  Get  hours  from  clock  cntr 

0102  11D701 

LX  I 

D, HOURS 

0105  CD6D01 

CALL  BCDTOASC 

;  Convert  to  ASCII  &  save 

0108  DBA3 

IN 

CLOCK+3 

;  Get  minutes  from  elk  cntr 

010A  11DA01 

LXI 

D, MINUTES 

010D  CD6D01 

CALL  BCDTOASC 

;  Convert 

0110  DBA2 

IN 

CLOCK+2 

;  Get  seconds 

0112  11DD01 

LXI 

D, SECONDS 

0115  CD6D01 

CALL  BCDTOASC 

<• 

Read  day  of  week  and 

save  literal  equiv.  in  memory 

0118  DBA5 

GETDAY:  IN 

CLOCK+5 

01 1A  11E201 

LXI 

D ,  DAY 

;  Where  to  save  the  result 

01 ID  211302 

LXI 

H , LITDAY 

;  Location  of  literal  weekday 

0120  CD7F01 

CALL 

NTOLIT 

;  Convert  number  to  literal 

: 

Read 

date  and  save  in 

memory 

0123  DBA6 

GETDATE : IN 

CLOCK+6 

0125  11EE01 

LXI 

D , DATE 

;  Where  to  save  result 

0128  CD6D01 

CALL 

BCDTOASC 

;  Convert  to  ASCII  and  save 

>• 

Read 

month  and  save  literal  equivalent  in  memory 

012B  DBA7 

GETMON:  IN 

CLOCK+7 

01 2D  11F101 

LXI 

D, MONTH 

;  Where  to  save  result 

0130  215902 

LXI 

H , LITMON 

;  Location  of  literal  months 

0133  CD7F01 

CALL 

NTOLIT 

;  Convert  to  literal 

; 

Read 

year  from  clock  RAM  &  save  in  memory 

0136  DBA9 

GETYR: 

IN 

CLOCK+9 

0138  11FD01 

LXI 

D, YEAR 

;  Where  to  save  result 

013B  CD6D01 

CALL 

BCDTOASC 

;  Convert  to  ASCII  and  save 

: 

Send 

time  message  to  console 

013E  11CE01 

CONSL 

LXI 

D , MSGL 

;  DE  gets  adr  of  message 

0141  0E09 

MV  I 

C, PRINTST 

0143  CD0500 

CALL 

BDOS 

;  Print  string  to  console 

■ 

Send 

time  msg  to  printer  if  cmd  string  required  it 

0146  3A8000 

PRINT: 

LDA 

BUFF 

;  Was  cmd  'TIME  <text>' 

0149  FEOO 

CPI 

0 

;  Done  if  nothing  there 

014B  CA6C01 

JZ 

DONE 

014E  4F 

MOV 

C  ,  A 

;  Put  buffer  count  in  C 

014F  218100 

LXI 

H , BUFF+1 

;  HL  is  source  of  text 

0152  110102 

LXI 

D , TEXT 

;  DE  is  destin  for  text 

0155  7E 

MOVETXT : MOV 

A ,  M 

;  Get  byte 

0156  12 

STAX 

D 

;  Save  it 

(Continued  on  next  page ) 


Dr.  Dobb's  Journal,  July  1985 


75 

547 


Real-Time  Clock 

Listing  Two 


(Listing  Continued,  text  begins  on  page  56) 


0157 

23 

I  NX 

H 

0158 

13 

I  NX 

D 

0159 

0D 

DCR 

C 

015A 

C25501 

JNZ 

MOVETXT 

Do  until  C  is  0 

015D 

3E24 

MVI 

A,  1  $  ' 

End-text  marker 

015F 

12 

STAX 

D 

0160 

21BD01 

LXI 

H , MSGR 

Go  ahead  to  printer 

0163 

CDA701 

CALL 

LISTIT 

0166 

21BA01 

LXI 

H.CRLF 

J 

Finish  the  line 

0169 

CDA701 

CALL 

LISTIT 

016C 

C9 

DONE  : 

* 

RET 

>• 

Return  to  CP/M 

* 

*  *  *  *  * 

SUBR 

BCDTOASC  ***** 

Converts  binary 

value  into  two  ASCII-hex  characters 

ENTRY 

:  Reg  A  = 

2  packed- 

BCD  characters 

DE  = 

Adr  of  Where  to  store  ASCII  answers 

EXIT: 

(DE)  = 

MS  char 

( DE+1 )  = 

LS  char 

3CDT0ASC : 

016D 

F5 

PUSH 

PSW 

016E 

OF 

RRC 

’ 

Put  4  MSB's  into  4  LSB ' s 

016F 

OF 

RRC 

0170 

OF 

RRC 

0171 

OF 

RRC 

0172 

E60F 

ANI 

OFH 

Mask  out  top  4  bits 

0174 

C630 

ADI 

'O’ 

Add  offset  to  make  ASCII 

0176 

12 

STAX 

D 

Save  the  result 

0177 

FI 

POP 

PSW 

Get  the  next  character 

0178 

E60F 

ANI 

OFH 

017A 

C630 

ADI 

’O' 

Make  ASCII 

017C 

13 

I  NX 

D 

017D 

12 

STAX 

D 

J 

Save  it  in  next  memory  loc 

017E 

C9 

RET 

***** 

SUBR  NTOLIT  ***** 

Converts  packed-BCD  number  (1  to  12)  to  its  equiv 

literal 

string  and  saves  in  specified  memory. 

ENTRY: 

A  =  2  packed-BCD  numbers  from  clock 

DE  =  Adr  of  where  to  store  literal  results 

HL  =  Pntr  to  10-char  literal  strings 

017F 

47 

*TOLIT: 

MOV 

B , A  ;  Save  data 

0180 

E610 

ANI 

00010000B  ;  See  if  have  tens  digit 

0182 

CA8B01 

JZ 

SMALLNR  ;  Jump  if  not 

0185 

78 

MOV 

A ,  B 

0186 

C60A 

ADI 

10  ;  Add  10  to  low  nibble  so 

0188 

C38C01 

JMP 

BINARY  ;  it's  binary  now. 

018B 

78 

SMALLNR 

MOV 

A,  B 

018C 

E60F 

BINARY: 

ANI 

OFH  ;  Mask  off  top  nibble 

018E 

D601 

SUI 

1  ;  Make  number  0  to  11  max 

0190 

CA9B01 

JZ 

MOVE  ;  At  first  already 

0193 

010A00 

LXI 

B , 1 0  ;  10  literals  in  word 

0196 

09 

NEXT: 

DAD 

B  ;  Add  it  to  HL  for  next  word 

0197 

3D 

DCR 

A  ;  until  get  to  proper  one. 

0198 

C29601 

JNZ 

NEXT 

(Continued  on  page  78) 


76 

548 


Dr.  Dobb's  Journal,  July  1985 


Real-Time  Clock 

Listing  Two 


(Listing  Continued,  text  begins  on  page  56) 


019B 

010A00 

MOVE: 

LX  I 

B,  10 

;  Move  10  letters  only 

019E 

7E 

MOVDAT : 

MOV 

A ,  M 

;  HL  pts  to  desired  word 

019F 

12 

STAX 

D 

;  DE  pts  to  destination  mem 

01  AO 

13 

INX 

D 

01A1 

23 

I  NX 

H 

01A2 

OD 

DCR 

C 

;  Proper  literal  word  now 

0 1  A3 

C29E01 

JNZ 

MOVDAT 

;  moved  to  memory  to  print 

01A6 

C9 

RET 

*****  SUBR 

LISTIT  ***** 

Lists 

to  printer  until 

'$'  encountered. 

ENTRY 

:  HL  =  string 

address 

01A7 

7E 

j I ST IT :  MOV 

A ,  M 

;  Get  the  letter  to 

be  sent 

01A8 

FE24 

CPI 

'S' 

;  End  of  text  yet? 

01AA 

CAB901 

JZ 

LISTEND 

01  AD 

E5 

PUSH 

H 

;  Must  retain  HL ! 

01AE 

5F 

MOV 

E,  A 

01AF 

0E05 

MV  I 

C , LISTOUT 

01B1 

CD0600 

CALL 

BDOS 

;  Print  the  char 

01B4 

El 

POP 

H 

01B5 

23 

INX 

H 

01B6 

C3A701 

JMP 

LISTIT 

;  Keep  going  until 

S' 

01B9 

C9 

DISTEND: RET 

* 

*****  MESSAGE 

AREA  ***** 

01  BA 

ODOA24 

CRLF : 

DB 

CR , LF ,  1  $ 

01BD 

2020202020 

MSGR : 

DB 

1 

01CC 

2020 

DB 

1  1 

01CE 

2020202020 

MSGL: 

DB 

1 

01D7 

HOURS: 

DS 

2 

01D9 

3A 

DB 

i  .  i 

01  DA 

MINUTES: 

DS 

2 

01  DC 

3A 

DB 

i  .  i 

01DD 

SECONDS: 

DS 

2 

01DF 

202020 

DB 

i  i 

01E2 

DAY: 

DS 

10 

01  EC 

2020 

DB 

i  i 

01EE 

DATE: 

DS 

2 

01F0 

20 

DB 

i  i 

01F1 

MONTH: 

DS 

10 

01FB 

3139 

DB 

'  19' 

01FD 

YEAR: 

DS 

2 

01FF 

2020 

DB 

i  i 

0201 

2020202020 

TEXT : 

DB 

i 

0210 

ODOA24 

DB 

CR , LF  ,  1  $ 

*  *  «  *  * 

LITERALS 

***** 

Pack 

with  NULLs  for  even  spacing  on  print  line 

0213 

53756E6461 

LITDAY : 

DB 

'Sunday, 1 ,0,0,0 

021D 

4D6F6E64  6 1 

DB 

1  Monday, ' ,0,0,0 

0227 

5475657364 

DB 

1  Tuesday,  1 ,  0,0 

0231 

5765646E65 

DB 

' Wednesday, ‘ 

023B 

5468757273 

DB 

1  Thursday, 1 ,  0 

0245 

4672696461 

DB 

■Friday, ' ,0,0,0 

024F 

5361747572 

DB 

1  Saturday, 1 ,  0 

0259 

4A616E7561 

LITMON: 

DB 

•January  0,0 

0263 

4665627275 

DB 

'February  ',  0 

026D 

4D6 1 726368 

DB 

'March  ' ,  0,0, 0,0 

Dr.  Dobb’s  Journal,  July  1985 


0277 

417072696C 

DB 

1  April  1 ,  0 , 0 , 0 , 0 

0281 

4D61792000 

DB 

'May  ',0,0, 0,0, 0,0 

028B 

4A756E6520 

DB 

' June  ' ,  0 , 0 , 0 , 0 , 0 

0295 

4A756C7920 

DB 

'July  ' ,  0,0, 0,0,0 

029F 

4175677573 

DB 

' August  1 ,  0,0,0 

02A9 

5365707465 

DB 

'September  ' 

02B3 

4F63746F62 

DB 

' October  1 ,  0,0 

02BD 

4E6F76656D 

DB 

'November  0 

02C7 

446563656D 

DB 

' December  ' ,  0 

02D1  END  End  Listing  Two 


Listing  Three 

;  TIMELIST  Program  to  read  a  file  and  send  it  out 

;  to  printer  with  time/date  and  program  name 

;  Program  by  Alan  D.  Wilcox 
;  29  October  1984 

;  Osage  A>  TIMELIST  filespec.txt 


0100  ORG  100H 


00A0  = 

CLOCK  EQU 

OAOH 

;  Clock  base  address 

0000  = 

FALSE  EQU 

0 

FFFF  = 

TRUE 

EQU 

NOT  FALSE 

FFFF  = 

TOLPTR  EQU 

TRUE 

;  Make  FALSE  to  send  all  output  to 

;  console  rather  than  to  line  prntr 

0000  = 

KEEPFF 

EQU 

FALSE 

;  Make  TRUE  to  use  form  feeds  in 

;  orig  text.  TRUE  also  cancels  line 
;  counting  by  this  program. 

0005  = 

BDOS 

EQU 

0005H 

;  BDOS  entry  point 

0002  = 

CONSOUT  EQU 

2 

;  Console  Output 

0005  = 

LISTOUT  EQU 

5 

;  List  Output 

0009  = 

PRINTST  EQU 

9 

;  Print  String  Function 

000B  = 

GETSTAT  EQU 

11 

;  Console  Status 

000F  = 

OPENF 

EQU 

15 

;  Open  File 

0014  = 

READF 

EQU 

20 

;  Read  Next  Record 

005C  = 

FCB 

EQU 

5CH 

;  File  Control  Block  Address 

007C  = 

FCBCR 

EQU 

FCB+32 

;  Current  (next)  record  for  r/w 

0080  = 

BUFF 

EQU 

80H 

;  Input  Disk  Buffer  Address 

000C  = 

TSIZE 

EQU 

12 

;  Title  block  size 

0003  = 

SKIP 

EQU 

3 

;  Added  number  of  spcs  at  left  margd 

0050  b 

COLS 

EQU 

80 

Max  possible  columns  per  page 

003C  = 

LPAGE 

EQU 

60 

Lines  to  print  per  page 

0008  = 

NRSPCS 

EQU 

8 

Convert  tab  to  NRSPCS  spaces 

0009  = 

TAB 

EQU 

09H 

Horizontal  Tab 

000A  = 

LF 

EQU 

OAH 

Line  Feed 

000B  = 

VT 

EQU 

OBH 

Vertical  Tab  (alt  FF) 

OOOC  = 

FF 

EQU 

OCH 

Form  Feed 

000D  = 

CR 

EQU 

ODH 

Carriage  Return 

001 A  = 

CNTLZ 

EQU 

1  AH 

End  of  File  (cntl-z) 

001B  = 

ESC 

EQU 

1BH 

Escape 

0020  = 

SPC 

EQU 

20H 

Space 

**********  MAIN  PROGRAM  ****»*»»*» 


0100 

210000 

* 

STACK: 

LX  I 

H,  0 

;  Stack  in 

safe  place  while 

0103 

39 

DAD 

SP 

;  using 

disk  buffer  space 

0104 

227904 

SHLD 

OLDSTK 

;  Save  old 

stack  position 

(Continued  on  next  page) 


79 


Dr.  Dobb’s  Journal,  July  1985 


Real-Time  Clock  (Listing  Continued,  text  begins  on  page  56) 

Listing  Three 

0107 

319F04 

LXI 

SP , NEWSTK 

;  Use  new  stack  pointer 

010A 

CD1B01 

CALL 

OPENFILE 

;  Open  program  to  be  read 

010D 

CD3B01 

CALL 

SAVETITLE 

;  Save  pgm  name  as  the  title 

0110 

CD7901 

CALL 

FIRSTLINE 

;  Prnt  time/date/title  line 

0113 

CDD301 

CALL 

PRINTLOOP 

;  Read  &  print  file 

0116 

2A7904 

QUIT: 

LHLD 

OLDSTK 

;  Get  the  old  stack  pntr  in 

0119 

F9 

SPHL 

;  HL  &  put  in  on  the  stack. 

01 1A 

C9 

* 

RET 

;  Return  to  CP/M 

* 

********** 

SUBR  OPENFILE 

********** 

DPENFILE : 

;  Open  the  file 

requested 

01  IB 

AF 

XRA 

A 

;  Zero  accumulator 

one 

327C00 

STA 

FCBCR 

;  Zero  file  record  count 

0 1 1 F 

115C00 

LXI 

D ,  FCB 

0122 

OEOF 

MVI 

C , OPENF 

;  Open  the  file 

0124 

CD0500 

CALL 

BDOS 

;  Get  OFFH  in  Accum  if  error 

0127 

FEFF 

CPI 

OFFH 

0129 

C23501 

JNZ 

OKOPEN 

012C 

113D03 

LXI 

D , OPENERR 

;  Had  error  in  file  opening 

012F 

CD3503 

CALL 

PRINTIT 

;  Print  error  message 

0132 

CD1601 

CALL 

QUIT 

;  Restore  stack  and  get  out 

0135 

3E80 

OKOPEN: 

MVI 

A, BUFF 

;  Set  position  of  input 

0137 

327B04 

STA 

INBUFPT 

;  buffer  pointer 

013A 

C9 

RET 

********** 

SUBR  SAVETITLE 

********** 

C 

SAVETITLE: 

;  Save  name  of 

program  as  title 

013B 

OEOC 

MVI 

C , TSIZE 

;  Clear  TSIZE  space  in  memory 

013D 

21AA03 

LXI 

H, HEADER 

;  starting  at  HEADER. 

0140 

3620 

CLRBLK: 

MVI 

M ,  SPC 

;  Fill  with  spaces 

0142 

23 

I  NX 

H 

0143 

OD 

DCR 

C 

0144 

C24001 

JNZ 

CLRBLK 

0147 

3A8300 

LDA 

BUFF+3 

;  See  if  drive  in  cmd  line 

014A 

FE3A 

CPI 

1  .  1 

;  Z  set  if  true 

014C 

C25A01 

JNZ 

NODRIV 

0 1 4  F 

118400 

LXI 

D , BUFF+4 

;  DE  pts  to  pgm  name  only 

0152 

3A8000 

LDA 

BUFF 

;  Get  count  of  chars 

0155 

D603 

SUI 

3 

;  Sub  ent,  space,  drive  spec 

0157 

C36201 

JMP 

ADJSIZE 

015A 

118200 

NODRIV: 

LXI 

D , BUFF+2 

;  DE  points  to  pgm  name 

015D 

3A8000 

LDA 

BUFF 

;  Get  count  of  chars 

0160 

D60 1 

SUI 

1 

;  Subtr  count  &  space 

0162 

FEOC 

ADJSIZE 

CPI 

TSIZE 

;  Adjust  char  ent  so  it  fits 

0164 

F26B01 

JP 

BIG 

;  Jump  if  count  >  TSIZE 

0167 

4F 

MOV 

C,  A 

;  Acceptable  count  into  C 

0168 

C36D01 

JMP 

OKSIZE 

016B 

OEOC 

BIG: 

MVI 

C, TSIZE 

;  Title  is  max  size 

016D 

21AA03 

OKSIZE : 

LXI 

H, HEADER 

;  Destination  for  title  move 

0170 

1A 

MTITLE: 

LDAX 

D 

;  Get  the  character 

0171 

77 

MOV 

M,  A 

;  and  save  it  as  header 

0172 

23 

I  NX 

H 

0173 

13 

I  NX 

D 

Dr.  Dobb’s  Journal.  July  1985 


0174 

OD 

DCR 

C 

0175 

C27001 

JNZ 

MTITLE 

Title  saved  when  zero 

0178 

C9 

RET 

********** 

SUBR  FIRSTLINE  *«*»«*»*** 

FIRSTLINE: 

;  Read  current  time  and  save  in  memory 

0179 

DBA4 

IN 

CLOCK+4 

Get  hours  from  clock 

017B 

116B03 

LX  I 

D, HOURS 

017E 

CDE102 

CALL 

BCDTOASC 

Convert  to  ASCII  &  save 

0181 

DBA3 

IN 

CL0CK+3 

Get  minutes  from  clock 

0183 

116E03 

LXI 

D, MINUTES 

0186 

CDE102 

CALL 

BCDTOASC 

Convert 

0189 

DBA2 

IN 

CLOCK+2 

Get  seconds 

018B 

117103 

LXI 

D, SECONDS 

018E 

CDE102 

CALL 

BCDTOASC 

; 

Read 

day  of  week  and  save  literal  equiv.  in  memory 

0191 

DBA5 

IN 

CLOCK+5 

0193 

118003 

LXI 

D ,  DAY 

Where  to  save  the  result 

0196 

21BB03 

LXI 

H , LITDAY 

Location  of  literal  weekday 

0199 

CDF302 

CALL 

NTOLIT 

Convert  number  to  literal 

<• 

Read 

date  and  save  in  memory 

019C 

DBA6 

IN 

CLOCK+6 

019E 

118B03 

LXI 

D , DATE 

Where  to  save  result 

01A1 

CDE102 

CALL 

BCDTOASC 

Convert  to  ASCII  and  save 

: 

Read 

month  and  save  literal  equivalent  in  memory 

01A4 

DBA7 

IN 

CLOCK+7 

01A6 

118E03 

LXI 

D, MONTH 

Where  to  save  result 

01A9 

210104 

LXI 

H , LITMON 

Location  of  literal  months 

01  AC 

CDF302 

CALL 

NTOLIT 

Convert  to  literal 

: 

Read 

year  from  clock  RAM 

&  save  in  memory 

01  AF 

DBA9 

IN 

CLOCK+9 

01B1 

119A03 

LXI 

D, YEAR 

01B4 

CDE102 

CALL 

BCDTOASC 

Convert  to  ASCII  and  save 

■ 

Clear 

spaces  at  left  margin  before  printing  time 

01B7 

0E03 

MV  I 

C, SKIP 

01B9 

216803 

LXI 

H, TOPLINE 

01BC 

3620 

CLRSKP 

MVI 

M ,  SPC 

Fill  with  spaces 

01BE 

23 

I  NX 

H 

01BF 

OD 

DCR 

C 

01C0 

C2BC01 

JNZ 

CLRSKP 

IF  TOLPTR 

;  Send  time  message  to  line  printer 

01C3 

216803 

LXI 

H, TOPLINE 

HL  gets  adr  of  message 

01C6 

CD1B03 

ENDIF 

CALL 

LISTIT 

To  printer 

IF  NOT 

TOLPTR 

;  Send  time  message  to  console 

LXI 

D, TOPLINE 

DE  gets  adr  of  message 

CALL 

PRINTIT 

Print  to  console 

ENDIF 

: 

Set  line  cntr  to  3  to  account  for  title  block  lines 

01C9 

3E03 

MVI 

A, 3 

01CB 

327E04 

STA 

LINECNT 

Initialize  column  counter 


01CE  AF 

XRA 

A 

;  Zero  accum 

01CF  327D04 

STA 

C0LCNT 

;  Column  counter  zero 

01D2  C9 

RET 

( Continued  on  next  page) 

81 


CCO 


Dr.  Dobb's  Journal,  July  1985 


Real-Time  Clock 

Listing  Three 


(Listing  Continued,  text  begins  on  page  56) 


********** 


SUBR  PRINTLOOP  ********** 


PRINTLOOP:  ;  Will  stay  in  this  loop  until  either  break 

;  from  console  or  file  is  completely  printed 

;  Check  console  for  break.  Exit  without  losing  stack 

01D3  OEOB  MV I  C , GETSTAT 

01D5  CD0500  CALL  BDOS  ;  Accum  LSB=1  if  brk  request 

0108  0F  RRC  ;  Put  LSB  into  carry 

01D9  DA5702  JC  DONE  ;  Leave  program 


Check  to  see  if  need  spaces  at  start  of  left  margin 


01DC 

3A7D04 

LDA 

COLCNT 

;  Current  column  count 

01DF 

FE03 

CPI 

SKIP 

;  Sign  set  while  COLCNT<  SKIP 

01E1 

F2ED01 

JP 

CKLINE 

;  Jump  when  COLCNT>=SKIP 

01E4 

3C 

INR 

A 

;  Increment  col  counter 

01E5 

327D04 

STA 

COLCNT 

01E8 

3E20 

MV  I 

A ,  SPC 

01EA 

C35102 

JMP 

GOPRNT 

;  Go  print  space  &  continue 

Check 

for  proper 

lines  per  page.  Send  FF  if  needed 

01  ED 

3A7E04 

CKLINE 

LDA 

LINECNT 

;  Current  line  count 

01F0 

FE3C 

CPI 

LPAGE 

;  Max  lines  per  page 

01F2 

C20102 

JNZ 

DATA 

;  Get  data  if  not  equal  yet 

IF  KEEPFF 

JMP 

DATA 

;  Don't  bother  counting. 

ENDIF 

;  Use  FF ' s  in  source  text. 

01F5 

AF 

XRA 

A 

;  Zero  accum 

01F6 

327E04 

STA 

LINECNT 

;  Reset  line  cntr  when  equal 

01F9 

327D04 

STA 

COLCNT 

;  Reset  column  counter  too 

01FC 

3E0C 

MVI 

A ,  FF 

01FE 

C35102 

JMP 

GOPRNT 

;  Print  FF  &  continue 

; 

Get  the  data  and 

filter  out  various  characters 

0201 

CD5B02 

DATA: 

CALL 

GETNB 

;  Get  the  next  byte. 

0204 

DA5702 

JC 

DONE 

;  Carry  set  if  EOF 

0207 

FEOD 

CPI 

CR 

0209 

CA5102 

JZ 

GOPRNT 

;  Print  the  CR 

020C 

FEOA 

CKLF : 

CPI 

LF 

020E 

C22102 

JNZ 

CKFF 

;  Jump  if  not  a  LF 

0211 

3A7E04 

LDA 

LINECNT 

0214 

3C 

INR 

A 

;  Increment  line  counter 

0215 

327E04 

STA 

LINECNT 

0218 

AF 

XRA 

A 

0219 

327D04 

STA 

COLCNT 

;  Set  col  counter  to  zero 

02  1C 

3E0A 

MVI 

A ,  LF 

;  Restore  data 

021E 

C35102 

JMP 

GOPRNT 

;  Print  the  LF 

IF  KEEPFF 

CKFF : 

JMP 

LNWID 

;  Go  and  print  FF  &  VT  chars 

ENDIF 

IF  NOT 

KEEPFF 

0221 

FEOC 

CKFF: 

CPI 

FF 

0223 

CA0102 

JZ 

DATA 

;  If  FF,  ignore.  Get  new  data 

0226 

FEOB 

CPI 

VT 

0228 

CAO 102 

JZ 

DATA 

;  If  VT,  ignore  it  too. 

ENDIF 

; 

Check 

for  proper 

width  of  line 

022B 

F5 

LNWID: 

PUSH 

PSW 

022C 

3A7D04 

LDA 

COLCNT 

;  Get  current  column  count 

022F 

FE50 

CPI 

COLS 

;  Max  allowed  per  line 

0231 

C24202 

JNZ 

NOTMAX 

(Continued  on  page  84) 


Dr.  Dobb’s  Journal,  July  1985 


82 

553 


Real-Time  Clock  (Listing  Continued,  text  begins  on  page  56) 
Listing  Three 


0234 

CDC902 

CALL 

PRCRLF 

i 

If  equal,  do  CR/LF,  reset 

the  col  counter  to  zero, 

• 

increment  line  counter. 

0237 

3A7B04 

LDA 

INBUFPT 

Set  buffer  pointer  back 

023A 

3D 

DCR 

A 

to  ignore  this  char. 

023B 

327B04 

STA 

INBUFPT 

023E 

FI 

POP 

PSW 

Reset  stack  and  go  back 

023F 

C3D301 

JMP 

PRINTLOOP 

thru  read  loop 

0242 

FI 

NOTMAX  : 

POP 

PSW 

0243 

FE09 

CKTAB: 

CPI 

TAB 

0245 

CC8602 

CZ 

TABTOSP 

If  TAB,  expand  it  to  spaces 

0248 

F5 

PUSH 

PSW 

Save  data  byte  now  in  accum 

0249 

3A7D04 

LDA 

COLCNT 

024C 

3C 

INR 

A 

Increment  column  counter 

024D 

327D04 

STA 

COLCNT 

0250 

FI 

POP 

PSW 

Restore  data  for  print 

0251 

CD2E03 

GOPRNT : 

CALL 

PCHAR 

0254 

C3D301 

JMP 

PRINTLOOP 

0257 

CDC902 

DONE  : 

CALL 

PRCRLF 

Print  CR/LF  to  wrapup 

025A 

C9 

RET 

********** 

SUBR  GETNB 

********** 

Gets 

the  next  byte 

from  memory  or  EOF  flag  if  done 

EXIT 

A  =  Next  file 

byte  fetched  from  memory 

F  =  Carry  set 

if 

End-of-File 

025B 

3A7B04 

GETNB : 

LDA 

INBUFPT 

Get  current  buff  rel  ptr 

025E 

FE80 

CPI 

BUFF 

Is  it  to  end  of  rec  yet? 

0260 

C27302 

JNZ 

GET 

Get  next  byte  if  not. 

Read 

a 

128-byte  record  from  disk 

0263 

115C00 

LXI 

D ,  FCB 

DE  gets  FCB  adr.  Reads  128 

0266 

0E14 

MVI 

C , READF 

bytes  into  mem  starting 

0268 

CD0500 

CALL 

BDOS 

at  BUFF  adr 

026B 

B7 

ORA 

A 

A=0  if  record  read  OK,  so 

026C 

CA7302 

JZ 

GET 

go  get  the  info. 

Else  if  A#0,  then  is  EOF. 

026F 

37 

EOF: 

STC 

Set  carry  to  indicate  EOF. 

0270 

C38502 

JMP 

GETEND 

GET: 

;  Read 

the  byte  at 

BUFF  +  Reg  A's  relative  offset 

0273 

5F 

MOV 

E ,  A 

DE  contains  rel  position  of 

0274 

1600 

MVI 

D,0 

pntr  into  disk  buff  space 

0276 

3C 

INR 

A 

0277 

327B04 

STA 

INBUFPT 

Incr  and  save  new  pointer. 

027A 

218000 

LXI 

H , BUFF 

Start  addr  of  buffer  space 

027D 

19 

DAD 

D 

plus  rel  offset  - >  HL 

027E 

7E 

MOV 

A ,  M 

Get  byte  ptd  to  by  HL 

027F 

FE1 A 

CPI 

CNTLZ 

Check  for  EOF  within  record 

0281 

CA6F02 

JZ 

EOF 

&  exit  with  carry  bit  set 

0284 

B7 

ORA 

A 

Reset  carry  bit 

0285 

C9 

3ETEND : 

RET 

********** 

SUBR  TABTOSP 

********** 

rABTOSP 

;  Converts 

a 

tab 

to  every  NRSPCS  columns 

0286 

3A7D04 

LDA 

COLCNT 

Which  column  are  we  in? 

0289 

DE03 

SBI 

SKIP 

Make  relative  offset 

028B 

5F 

MOV 

E,  A 

Put  it  in  E 

028C 

1608 

MVI 

D , NRSPCS 

(Continued  on  page  86) 

84 


>^4 


Dr.  Dobb's  Journal,  July  1985 


Real-Time  Clock 

Listing  Three 


(Listing  Continued,  text  begins  on  page  56) 


028E 

CDB002 

CALL 

MODULO 

;  Do  (COLCNT (mod  NRSPCS 

;  Remainder  is  in  Reg  C 

0291 

3E08 

MV  I 

A , NRSPCS 

0293 

91 

SUB 

C 

;  A  has  total  #  spaces  to  do 

0294 

3D 

DCR 

A 

;  Pickup  space  after  RET 

0295 

4F 

MOV 

C,  A 

;  C  has  #  spaces  to  do  now 

0296 

FEO 1 

CPI 

1 

;  Set  sign  if  need  less  than 

0298 

FAAD02 

JM 

ENDTAB 

;  one  space  printed. 

029B 

3E20 

SPACES 

MV  I 

A ,  SPC 

029D 

C5 

PUSH 

B 

029E 

CD2E03 

CALL 

PCHAR 

02A1 

Cl 

POP 

B 

02A2 

3A7D04 

LDA 

COLCNT 

;  Keep  column  count  current 

02A5 

3C 

INR 

A 

;  while  sending  spaces 

02A6 

327D04 

STA 

COLCNT 

02A9 

OD 

DCR 

C 

02AA 

C29B02 

JNZ 

SPACES 

;  Loop  until  enough  spaces 

0  2  AD 

3E20 

ENDTAB 

MV  I 

A,  SPC 

;  Print  the  last  space  when 

;  get  to  GOPRNT 

02AF 

C9 

RET 

********** 

SUBR  MODULO 

********** 

Does 

division  of  2  8 

-bit  numbers:  (Reg  E)/(Reg  D) 

Result  in  Reg  H 

Remainer  in  Reg  C  = 

(Reg  E)  mod  (Reg  D) 

02B0 

210800 

40DUL0 

'  LX  I 

H, 00001000B 

02B3 

0E00 

MV  I 

C,0 

02B5 

7B 

NEWBIT 

MOV 

A,  E 

02B6 

17 

RAL 

02B7 

5F 

MOV 

E,  A 

02B8 

79 

MOV 

A ,  C 

02B9 

17 

RAL 

02BA 

92 

SUB 

D 

02BB 

D2BF02 

JNC 

NOADD 

02BE 

82 

ADD 

D 

02BF 

4F 

NOADD: 

MOV 

C,  A 

02C0 

3F 

CMC 

02C1 

7C 

MOV 

A,  H 

02C2 

17 

RAL 

02C3 

67 

MOV 

H,  A 

02C4 

2D 

DCR 

L 

02C5 

C2B502 

JNZ 

NEWBIT 

02C8 

C9 

RET 

********** 

SUBR  PRCRLF 

********** 

3RCRLF 

;  Prints  a  CR/LF  pair 

02C9 

F5 

PUSH 

PSW 

02CA 

AF 

XRA 

A 

;  Zero  accum 

02CB 

327D04 

STA 

COLCNT 

;  Set  column  counter  to  zero 

02CE 

3A7E04 

LDA 

LINECNT 

02D1 

3C 

INR 

A 

;  Increment  line  counter 

02D2 

327E04 

STA 

LINECNT 

02D5 

3E0D 

MV  I 

A ,  CR 

02D7 

CD2E03 

CALL 

PCHAR 

02  DA 

3E0A 

MV  I 

A,  LF 

02  DC 

CD2E03 

CALL 

PCHAR 

02DF 

FI 

POP 

PSW 

02E0 

C9 

RET 

********** 

SUBR  BCDTOASC 

********** 

Dr.  Dobb’s  Journal,  July  1985 


86 

555 


Real-Time  Clock  (Listing  Continued,  text  begins  on  page  56) 

Listing  Three 

:  Converts  binary  value  into  two  ASCII-hex  characters 


ENTRY: 

Reg  A  =2  packed-BCD  characters 

DE  =  Adr  of 

where  to  store  ASCII  answers 

EXIT: 

(DE)  =  MS  char 

( DE+1 )  =  LS  char 

E 

JCDTOASC : 

02E1 

F5 

PUSH 

PSW 

02E2 

OF 

RRC 

;  Put  4  MSB's  into  4  LSB 1 S 

02E3 

OF 

RRC 

02E4 

OF 

RRC 

02E5 

OF 

RRC 

02E6 

E60F 

ANI 

OFH 

;  Mask  out  top  4  bits 

02E8 

C630 

ADI 

•O' 

;  Add  offset  to  make  ASCII 

02EA 

12 

STAX 

D 

;  Save  the  result 

02EB 

FI 

POP 

PSW 

;  Get  the  next  character 

02EC 

E60F 

ANI 

OFH 

02EE 

C630 

ADI 

’  0  ' 

;  Make  ASCII 

02F0 

13 

I  NX 

D 

02F1 

12 

STAX 

D 

;  Save  it  in  next  memory  loc 

02F2 

C9 

RET 

********** 

SUBR  NTOLIT 

********** 

Converts  packed-BCD  number  (1  to  12)  to  its  equiv 

1 i teral 

string  and  saves 

in  specified  memory. 

ENTRY : 

A  =  2  packed-BCD  numbers  from  clock 

DE  =  Adr  of  where  to  store  literal  results 

HL  =  Pntr  to  10 

-char  literal  strings 

02F3 

47 

JTOLIT :  MOV 

B,  A 

;  Save  data 

02F4 

E610 

ANI 

00010000B 

;  See  if  have  tens  digit 

02F6 

CAFF02 

JZ 

SMALLNR 

;  Jump  if  not 

02F9 

78 

MOV 

A ,  B 

02  FA 

C60A 

ADI 

10 

;  Add  10  to  low  nibble  so 

02FC 

C30003 

JMP 

BINARY 

;  it's  binary  now. 

02FF 

78 

SMALLNR : MOV 

A,  B 

0300 

E60F 

BINARY:  ANI 

OFH 

;  Mask  off  top  nibble 

0302 

D601 

SUI 

1 

;  Make  number  0  to  11  max 

0304 

CA0F03 

JZ 

MOVE 

;  At  first  already 

0307 

010A00 

LXI 

B,  10 

;  10  literals  in  word 

030A 

09 

NEXT:  DAD 

B 

;  Add  it  to  HL  for  next  word 

030B 

3D 

DCR 

A 

;  until  get  to  proper  one. 

030C 

C20A03 

JNZ 

NEXT 

030F 

010A00 

MOVE:  LX I 

B,  10 

;  Cnt  to  move  10  letters  only 

0312  7E 

MOVDAT:  MOV 

A ,  M 

;  HL  pts  to  desired  wor 

0313 

12 

STAX 

D 

;  DE  pts  to  destination  mem. 

0314 

13 

INX 

D 

0315 

23 

INX 

H 

0316 

OD 

DCR 

C 

;  Proper  literal  word  now 

0317 

C21203 

JNZ 

MOVDAT 

;  moved  to  memory  to  print 

031A 

C9 

RET 

********** 

SUBR  LlSTIT 

********** 

Lists  to  printer  until  1 

$'  encountered. 

ENTRY: 

HL  =  string  address 

031B 

7E 

LlSTIT:  MOV 

A ,  M 

;  Get  the  letter  to  be  sent 

03 1C 

FE2  4 

CPI 

;  End  of  text  yet? 

031E 

CA2D03 

JZ 

LISTEND 

0321 

E5 

PUSH 

H 

;  Must  retain  HL ! 

0322 

5F 

MOV 

E,  A 

0323 

0E05 

MVI 

C, LISTOUT 

0325 

CD0500 

CALL 

BDOS 

;  Print  the  char 

87 


ccc 


Dr.  Dobb's  Journal,  July  1985 


0328 

El 

POP 

H 

0329 

23 

I  NX 

H 

032A 

C31B03 

JMP 

LISTIT 

Keep  going  until  '$' 

032D 

C9 

jISTEND : RET 

********** 

SUBR  PCHAR  ********** 

’CHAR:  ;  Prints  Reg  A  to  console 

or  printer 

IF  TOLPTR 

032E 

0E05 

MV  I 

C, LI STOUT 

Output  to  printer 

ENDIF 

IF  NOT  TOLPTR 

MVI 

C , CONSOUT  ; 

Output  to  console 

ENDIF 

0330 

5F 

MOV 

E,  A 

0331 

CD0500 

CALL 

BDOS 

0334 

C9 

RET 

********** 

SUBR  PRINTIT  **»****».» 

ENTRY: 

DE  =  Start  address 

of  string 

0335 

F5 

I 

’RTNTIT : PUSH 

PSW 

0336 

0E09 

MVI 

C , PRINTST 

0338 

CD0500 

CALL 

BDOS  ; 

Print  string  to  console 

033B 

FI 

POP 

PSW 

033C 

C9 

RET 

********** 

CONSOLE  MESSAGES 

********** 

033D  0DOA496E70  OPENERR :  DB  CR,LF,'Input  file  not  found  or  not 

035B  7370656369  DB  ' speci f ied ! ' , CR , LF , ' $ ' 


**********  TIME /DATE /PGM  HEADING  »«»*«***** 


0368 

TOPLINE: 

DS 

SKIP 

036B 

HOURS : 

DS 

2 

036D 

3A 

DB 

1  .  • 

036E 

MINUTES: 

DS 

2 

0370 

3A 

DB 

i  .  ■ 

0371 

SECONDS : 

DS 

2 

0373 

2020202020 

DB 

t 

0380 

DAY: 

DS 

10 

038A 

20 

DB 

i  i 

038B 

DATE  : 

DS 

2 

038D 

20 

DB 

t  i 

038E 

MONTH : 

DS 

10 

0398 

3139 

DB 

1  19  ' 

039A 

YEAR  : 

DS 

2 

039C 

2020202020 

DB 

i 

03AA 

HEADER: 

DS 

TSIZE 

03B6 

0D0A0A0A24 

DB 

********** 

CR , LF , LF , LF , '$ 

LITERALS  ** 

03BB 

53756E6461 

LITDAY : 

DB 

'Sunday,  ' 

03C5 

4D6F6E646 1 

DB 

1  Monday ,  1 

03CF 

5475657364 

DB 

'Tuesday,  ' 

03D9 

5765646E65 

DB 

' Wednesday , ' 

03E3 

5468757273 

DB 

'Thursday,  ' 

03ED 

4672696461 

DB 

‘Friday,  ' 

(Continued  on  page  90) 


88 

667 


Dr.  Dobb's  Journal,  July  1985 


Real-Time  Clock 

Listing  Three 


(Listing  Continued,  text  begins  on  page  56) 


03F7 

5361747572 

DB 

Saturday, 

1 

0401 

4A616E7561  LITMON: 

DB 

January 

1 

040B 

4665627275 

DB 

February 

1 

0415 

4D61726368 

DB 

March 

1 

04 1 F 

417072696C 

DB 

April 

1 

0429 

4D61792020 

DB 

May 

1 

0433 

4A756E6520 

DB 

June 

1 

043D 

4A756C7920 

DB 

July 

1 

0447 

4175677573 

DB 

August 

1 

0451 

5365707465 

DB 

September 

1 

045B 

4F63746F62 

DB 

October 

1 

0465 

4E6F76656D 

DB 

November 

1 

046F 

446563656D 

DB 

December 

1 

******* 

*  *  * 

VARIABLES 

********** 

0479 

OLDSTK : 

DS 

2 

;  Stack  pointer  to  get  back 

;  to  the  CCP  from  this  pgra . 

047B 

INBUFPT: 

DS 

2 

;  Input  buffer  pointer 

047D 

COLCNT : 

DS 

1 

;  Current  length  of  line 

047E 

LINECNT: 

DS 

1 

;  Keep  count  of  lines  printed 

;  on  the  page 

********** 

STACK 

LOCATION 

********** 

047F 

DS 

32 

;  Reserve  16-level  stack 

NEWSTK : 

049F 

END 

90 


End  Listings 


CCO 


Dr.  Dobb's  Journal,  July  1985 


REALIZABLE  FANTASIES 


by  Michael  Swaine  and  Bob  Albrecht 


rr 

£ 

fry 

rrr'rr 
rrrrr. 
rrrrrr 
rrrrr* 
rrrrrrwwwww 
rrrrrrrrrrr 
rrrrrrrrrrr 

rrrrrrrrrrr _ _ 

rrrrrrrrrrrrrrrrrrr 


We  announced  this  column  of  imag¬ 
ined  and  realizable  projects  in  Febru¬ 
ary  in  a  piece  called  “Tiny  Hackers” 
and  actually  launched  it  in  March 
with  Richard  Stallman’s  “GNU 
Manifesto.”  But  the  point  of  writing 
about  realizable  fantasies  is  to  en¬ 
courage  their  realization,  so  this 
month  we  present  progress  reports  on 
the  fantasies  presented  to  date. 

Liberating  the  Mac 

In  January  we  published,  though  not 
in  this  column,  Tom  Lafleur’s  illus¬ 
trated  guide  to  fattening  your  Macin¬ 
tosh.  Tom  received  over  a  hundred 
calls  and  letters  shortly  after  the  issue 
came  out.  By  the  time  of  the  Mac¬ 
world  show,  several  people  had  set  up 
business  dedicated  to  Mac-fattening, 
at  least  some  of  which  seem  to  have 
been  spin-offs  from  Tom’s  article. 
Despite  our  warnings  that  doing  it 
yourself  voided  your  warranty,  risked 
frying  your  Mac  and  was  a  tedious 
process,  a  lot  of  you  opened  your 
Macs  and  went  at  it.  We  think  that 
one  reason  for  the  article’s  popularity 
is  that  a  lot  of  you  believe  that  Ap¬ 
ple’s  decision  to  build  the  Mac  as  a 
closed  system  was  a  mistake,  and  one 
you’re  willing  to  rectify.  (“Apple  just 
ain’t  the  company  it  once  Woz.” — 
Laran  Stardrake.) 

In  April  we  followed  up  on  this  be¬ 
lief  by  reporting  in  this  column  on 
various  ways  in  which  people  could 
be  said  to  be  liberating  the  Mac,  in¬ 
cluding  Lee  Felsenstein’s  Hacker’s 
Mac  (aka  Hackintosh),  a  project  for 
learning  about  the  Mac  by  redesign¬ 
ing  it;  and  Jack  Tramiel’s  Atari  ST 
(aka  Jackintosh),  which,  running 
DRI’s  GEM  environment,  may  bring 
Maclike  capabilities  to  the  rest  of  us. 
As  this  goes  to  press,  Atari  has  just 
previewed  the  ST  in  Germany,  DRI 
has  announced  the  first  applications 


for  GEM  and  Lee  thinks  that  Tramiel 
may  be  doing  some  of  the  Hackintosh 
team’s  work  for  it,  though  he  hasn’t 
abandoned  the  project.  Contact  Lee 
at  Golemics,  2600  Tenth  Street, 
Berkeley  CA  94710,  (415)  486-8344. 

Also  in  April,  we  described  Steve 
Jasik’s  MacNosy  disassembler  for  the 
Mac,  but  failed  to  give  a  full  address. 
It’s  Free  the  ROM  64,  343  Trenton 
Way,  Menlo  Park  CA  94025,  and  his 
telephone  number  is  (415)  322-1386. 
Steve  has  fixed  some  bugs  in  Nosy 
and  is  now  supplying  a  fast  sort  rou¬ 
tine  with  the  program.  -MacNosy’s 
opening  screen  echos  the  opening  lines 
of  the  cult  classic  television  program 
The  Prisoner: 

Who  are  you?  I  am  number 
two. 

Who  is  number  You  are  number 
one?  six. 

What  do  you  want?  Information. 

GNU  Manifestations 

As  last  month’s  letters  indicated,  re¬ 
action  to  March’s  Realizable  Fanta¬ 
sy,  a  proposal  by  Richard  Stallman  to 
develop  a  free  operating  system  that 
provides  the  capabilities  of  Unix,  has 
been  emotional.  Stallman’s  mail  has 
been  overwhelmingly  supportive;  he’s 
received  many  offers  of  help,  and  he 
called  to  offer  us  quarterly  updates 
on  the  project’s  progress.  We’ll  prob¬ 
ably  print  the  first  of  these  next 
month.  Stallman  is  reachable  at  166 
Prospect  St.,  Cambridge  MA  02139. 

Tiny  Hackers 

Dragonsmoke,  Bob  Albrecht’s  news¬ 
letter,  is  the  place  to  follow  The  Drag¬ 
on’s  master  plan  to  turn  innocent  chil¬ 
dren  into  programmers,  and  it  seems 
that  it  will  soon  be  carrying  excerpts 
from  Ron  Jeffries’  newsletter,  the  Jef¬ 
fries  Report.  Dragonsmoke  costs 


$  12/year  and  you  can  get  a  sample 
issue  by  sending  $1.00  or  $.39  and  an 
SASE  to  Dragonsmoke,  P.O.Box 
7627,  Menlo  Park  CA  94026. 

Check  the  June  issue  of  Rainbow 
for  a  review  of  the  CoCoMac,  a  low- 
budget  way  to  get  Maclike  features. 
The  same  issue  also  contains  informa¬ 
tion  on  CoCoMac  RAM  disk  con¬ 
struction  and  a  plan  to  get  the  Fujitsu 
dual-6809  machine  distributed  in  the 
U.S.  It’s  supposedly  very  powerful, 
even  without  the  68K  you  can  drop  in. 

Subversives 

In  a  guest  essay  in  this  space  in  May, 
Resident  Intern  Dave  Cortesi  gave 
his  vision  of  this  magazine,  a  realiz¬ 
able  fantasy  for  DDJ:  that  DDJ  is  and 
should  always  be  subversive.  He  said, 
“As  a  good  revolutionary,  I  must  be¬ 
lieve  that  computer  use  is  for  every¬ 
body,  and  I  really  do.  But  program¬ 
ming  the  computers  so  they  can  be 
used  is  almost  certainly  a  job  for  spe¬ 
cialists.  Whose  specialists?”  Not  the 
establishment’s,  Cortesi  hopes,  but 
those  who  want  to  promulgate  ideas 
to  all  who  can  grasp  them,  those  who 
do  not  see  information  exchange  as  a 
zero-sum  game,  those  who  see  them¬ 
selves  involved  in  a  revolution,  those 
with  a  mischievous,  subversive  desire 
“to  snatch  the  tools  of  the  establish¬ 
ment  and  apply  them  in  the  public 
domain.”  We  never  ignore  Cortesi’s 
advice,  and  we  hope  you  won’t  either. 
You  have,  through  your  contribu¬ 
tions,  the  power  to  steer  this  maga¬ 
zine  away  from  the  safe  shores  and 
keep  it  pushing  out  recklessly  on  jour¬ 
neys  of  discovery. 

DDJ 

Reader  Ballot 

Vote  for  your  favorite  feature/article. 

Circle  Reader  Service  No.  1 95. 


92 


Dr.  Dobb’s  Journal,  July  1985 


16-BIT  SOFTWARE  TOOLBOX 


by  Ray  Duncan 


MSDOS  Installable  Device  Drivers 

One  of  the  more  novel  features  added  in  Version  2  of 
MSDOS  is  the  concept  of  “installable  device  drivers.” 
This  allows  the  user  to  attach  new  drivers  for  additional 
hardware  devices  or  to  supersede  the  system’s  existing 
built-in  drivers  by  the  simple  expedient  of  putting  the  exe¬ 
cutable  driver  file  on  the  boot  disk  and  editing  a  line  into 
the  file  CONFIG.SYS.  This  extremely  powerful  concept 
has  made  the  lives  of  third-party  mass  storage  device 
manufacturers  much  easier  (previously  they  had  to  disas¬ 
semble  and  patch  the  operating  system  to  get  their  prod¬ 
ucts  to  run). 

But  First ...  an  Overview  of  Unix  Device  Drivers 

Because  the  installable  device  drivers  of  MSDOS  are  pat¬ 
terned  after  Unix  and  much  of  the  terminology  used  in 
the  Microsoft  driver  documentation  derives  from  Unix 
terminology,  it  is  instructive  to  review  the  structure  of  the 
real  McCoy.  Much  of  the  information  presented  here  was 
gleaned  from  the  article  “Writing  Device  Drivers  for 
Xenix  Systems”  by  Jean  McNamara,  et  al.  (UniForum 
Conference  Proceedings,  January  1984). 

Unix  knows  two  types  of  devices:  block  and  character. 
A  block  device  is  typically  a  mass  storage  medium  such  as 
a  fixed  or  removable  disk  or  magnetic  tape  drive.  Such 
devices  usually  transfer  data  in  chunks  of  fixed  size, 
which  are  related  (in  the  case  of  disks)  to  the  characteris¬ 
tics  and  “format”  of  the  physical  media. 

In  contrast,  a  character  device  is  a  sequential  device 
such  as  a  CRT  terminal  or  paper  tape  reader  that  supplies 
or  accepts  a  stream  of  bytes.  Although  ideally  the  data 
stream  of  a  character  device  has  no  inherent  structure,  in 
practice  it  usually  is  delimited  by  special  control  charac¬ 
ters  such  as  carriage  returns,  which  the  operating  system 
also  recognizes. 

Under  Unix,  all  drivers  (whether  controlling  a  block  or 
character  device)  have  the  same  general  structure  and 
contain  two  major  parts:  task  time  routines  and  an  inter¬ 
rupt  handler. 

The  task  time  routines  are  called  at  the  time  of  the 
application  program’s  (the  task’s)  request  for  I/O.  The 
Unix  kernel  transforms  the  request  from  its  high-level, 
logical,  device-independent  character  into  addresses  and 
parameters  that  are  relevant  to  the  physical  device.  When 
the  driver’s  task  time  routines  are  executing,  the  applica¬ 
tion  itself  may  be  thought  of  as  being  in  control  of  the 
system  and  active,  although  it  is  running  in  privileged  or 
kernel  mode. 


The  interrupt  handler  is  entered  asynchronously  when 
the  corresponding  device  generates  a  hardware  interrupt. 
Interrupts  are  issued  when  an  I/O  operation  has  either 
finished  or  been  terminated  due  to  a  hardware  fault.  If 
the  system  or  the  device  does  not  support  hardware  inter¬ 
rupts,  the  interrupt  handler  is  entered  when  the  kernel, 
polling  the  device  periodically,  determines  that  it  is  no 
longer  busy.  Typically,  the  task  requesting  I/O  from  the 
device  is  inactive  when  the  interrupt  handler  receives  con¬ 
trol;  indeed,  the  task  is  usually  in  a  suspended  state  pend¬ 
ing  I/O  completion,  and  some  other  application  is  active 
and  in  control  as  far  as  the  operating  system  is  concerned. 

A  Unix  block  device  driver  comprises  some  tables  and 
five  major  routines: 

•  Init  is  called  once  when  the  system  is  booted  and  the 
driver  is  first  loaded  into  memory.  It  checks  for  the  exis¬ 
tence  of  the  physical  device  and  initializes  it  properly  for 
future  I/O,  if  present. 

•  Open  is  called  by  the  Unix  kernel  when  a  user  tries  to 
access  a  file  on  the  device.  This  routine  should  complete 
any  initialization  not  performed  by  Init  to  prepare  for 
I/O;  it  may  be  part  of  a  mount  sequence  for  removable 
media. 

•  Close  is  called  by  the  Unix  kernel  to  reset  any  flags  or 
variables  set  by  Open  and  to  ensure  that  pending  opera¬ 
tions  are  completed.  A  typical  action  would  be  to  flush 
buffered  data  to  the  physical  device.  This  routine  may  be 
part  of  a  dismount  sequence  for  removable  media. 

•The  Slrat  routine,  an  abbreviation  for  “strategy,”  is 
called  by  the  Unix  kernel  when  the  application  issues  an 
I/O  request  for  the  block  device.  St  rat  is  called  with  the 
address  of  a  buffer  header  or  parameter  list,  which  de¬ 
fines  the  device  unit  number,  request  type  (read,  write,  or 
format),  where  to  find  the  data  (logical  block  number), 
how  much  data  it  should  transfer,  and  the  memory  ad¬ 
dress  of  its  buffer.  Strat' s  responsibility  is  to  validate  the 
request  then  place  it  on  the  request  queue  for  that  device. 
Finally,  it  calls  the  internal  routine  Start  (see  below). 

•  Intr  is  called  by  the  Unix  kernel  when  the  block  device 
issues  an  interrupt.  It  is  responsible  for  determining  that 
the  interrupt  signal  is  valid  (Was  an  operation  on  this 
device  really  in  progress  and  are  the  appropriate  comple¬ 
tion  flags  set  on  the  controller?).  It  determines  success 
or  failure  status  for  the  last  operation  by  interrogating 
the  controller  and  passes  the  appropriate  flags  back  to 
the  kernel.  Finally,  if  additional  requests  are  pending,  it 
calls  Start  to  try  to  initiate  another  I/O  operation. 


94 


Dr.  Dobb’s  Journal,  July  1985 


Start  is  an  internal  driver  routine  called  by  Strat  or 
Intr,  it  removes  the  highest  priority  request  from  the  re¬ 
quest  queue  and  initiates  the  I/O.  If  the  device  is  busy  or 
if  no  requests  are  waiting,  Start  simply  exits. 

The  body  of  a  Unix  character  device  driver  is  similar  to 
that  of  a  block  device  driver.  It  is  made  up  of  seven  major 
routines: 

•  I  nit.  Open,  and  Close  are  similar  in  function  and  intent 
to  those  of  the  same  name  in  a  block  device  driver. 

•  Read  transfers  data  from  the  device  driver’s  input  buffer 
to  the  application’s  buffer. 

•  Write  transfers  data  from  the  application’s  buffers  to  the 
device  driver’s  output  buffers;  it  also  starts  output  to  the 
device  if  it  is  idle.  If  the  output  buffers  are  full,  Write 
suspends  the  requesting  process  until  they  have  emptied. 

•  Ioctl  provides  direct  communication  between  the  appli¬ 
cation  and  the  driver.  It  allows  the  application  to  ask  for 
device-specific  information  or  to  set  the  values  of  the 
driver’s  internal  flags  and  variables  (such  as  baud  rate  or 
number  of  stop  bits  for  a  serial  I/O  port). 

•  Intr,  the  asynchronous  part  of  the  driver,  is  called  by  the 
kernel  when  the  device  issues  a  hardware  interrupt.  This 
procedure  performs  the  actual  data  transfer  between  the 
physical  device’s  hardware  controller  and  the  driver’s 
input/output  buffers. 

MSDOS  Device  Drivers 

MSDOS  installable  device  drivers  bear  a  strong  resem¬ 
blance  to  Unix  device  drivers,  both  structurally  and  func¬ 
tionally.  Like  Unix,  they  fall  into  two  major  classes. 

Character  device  drivers  control  devices  that  perform 
I /O  one  byte  at  a  time,  similar  to  a  traditional  TTY  termi¬ 
nal.  MSDOS  has  built-in  drivers  for  the  console  device, 
serial  port,  and  list  device,  named  CON,  AUX,  and  PRN, 
respectively.  You  can  access  these  drivers  using  the  tradi¬ 
tional  CP/M-like  character  I/O  calls,  or  you  can  open 
them  by  name,  like  a  file  for  input  and  output,  using  the 
new  “handle”  function  calls  of  MSDOS  2.0  and  above.  A 
character  device  driver  can  support  one  hardware  unit. 

Block  device  drivers  control  random  access  storage  de¬ 
vices  such  as  flexible  disk  drives  or  fixed  disks.  A  block 


Offset 

Contents' 

0 

Offset,  pointer  to  next  device  header 

2 

Segment,  pointer  to  next  device  header 

4 

Device  attribute  word  (see  Table  2) 

6 

Pointer  to  device  strategy  code  (offset) 

8 

Pointer  to  device  interrupt  code  (offset) 

10 

Logical  name  (8  bytes)  if  character  device  or 
number  of  units  (1  byte)  if  block  device  fol¬ 
lowed  by  7  bytes  that  may  contain  a  name  or 
nothing  at  all 

Table  1 

Device  driver  header. 

device  driver  can  support  more  than  one  hardware  unit 
and/or  may  map  a  single  physical  unit  onto  two  or  more 
logical  drives.  Each  device  unit  for  a  given  block  driver  is 
assigned  a  drive  designator  (such  as  A:,  B:,  etc.);  the  first 
drive  letter  for  a  given  block  device  driver  is  determined 
by  that  driver’s  position  in  the  overall  chain  of  drivers. 

Once  a  driver  is  written  and  assembled,  you  may  load 
and  link  it  into  the  operating  system  simply  by  placing  an 
entry  of  the  form 

device  =  filename.ext 

in  the  CONFIG.SYS  file  on  the  system  boot  disk.  You  can 
override  the  default  system  driver  for  a  character  device 
with  an  installed  driver  simply  by  giving  it  the  same  logical 
device  name  in  the  device  header.  When  processing  an  I /O 
request,  DOS  always  scans  the  list  of  installed  drivers  be¬ 
fore  the  default  devices  and  takes  the  first  match. 

Structure  of  an  MSDOS  Device  Driver 

MSDOS  device  drivers  always  have  an  ORIGIN  of  zero  but 
are  otherwise  assembled,  linked,  and  converted  into  an  exe¬ 
cutable  module  as  though  they  were  COM  or  EXE  files.  A 
device  driver  consists  of  three  major  parts. 

A  Device  Header  (Table  1,  below)  contains  the  linkage 
to  the  next  driver  in  the  chain,  a  set  of  attribute  flags  (Ta¬ 
ble  2,  below)  for  the  device,  offsets  to  the  executable  strate¬ 
gy  and  interrupt  routines  for  the  device,  and  the  logical 
device  name  (if  it  is  a  character  device  such  as  PRN  or 
COM  1 ).  The  linkage  address  of  the  last  driver  in  a  file  is 


Bit 

Meaning 

15 

=  1  if  character  device 

=  0  if  block  device 

14 

=  1  if  IOCTL  is  supported 

13 

=  1  if  non-IBM  format  (block  only) 

12 

# 

11 

=  1  if  open/close/RM  is  supported’ 

10 

# 

9 

# 

8 

# 

7 

# 

6 

# 

5 

# 

4 

# 

3 

=  1  if  current  clock  device 

2 

=  1  if  current  NUL  device 

1 

=  1  if  current  standard  output  device 

0 

=  1  if  current  standard  input  device 

#  Currently  undefined  and  should  be  0 

’  DOS  3.0  and  above  only;  should  be  0  for  DOS  2.x 

Table  2 

Device  attribute  word,  found  in  device  driver  header;  only 

bits  11,13,  and  1 4  have  significance  on  block  devices. 

Dr.  Dobb's  Journal,  July  1 985 


95 


always  set  to  -1  when  the  driver  is  created;  the  drivers  are 
chained  together,  and  the  linkage  fields  are  updated  by 
MSDOS  at  system  initialization. 

The  Strategy  Routine  for  the  device  is  entered  by  DOS 
via  a  Far  Call  when  the  driver  is  first  loaded  and  installed 
and  whenever  an  application  program  issues  an  I/O  re¬ 
quest  for  the  device.  DOS  uses  ES:BX  to  pass  the  Strategy 
routine  a  double-word  pointer  to  a  data  structure,  which  is 
called  a  Request  Header;  this  structure  contains  informa¬ 
tion  about  the  type  of  operation  to  be  performed.  Accord¬ 
ing  to  MSDOS  conventions,  the  Strategy  code  never  actual¬ 
ly  performs  an  I/O  operation  but  simply  saves  the  pointer 
to  the  Request  Header. 

The  last  and  most  complex  part  of  a  device  driver  is  the 
misnamed  Interrupt  Routine.  This  code  implements  the 
device  driver  proper;  it  performs  the  actual  operation  based 
on  the  function  code  and  other  information  passed  in  the 
Request  Header.  Status  and  completion  information  may 
be  passed  back  to  DOS  in  the  same  Header. 

When  an  I/O  request  is  issued,  the  Interrupt  Routine 


;  MS-DOS  Request  Header  structure  definition 


Request 

struc 

request  header  template 
structure 

Rlength 

db 

? 

0  length  of  request  header 

Unit 

db 

? 

1  unit  number  for  this  request 

Command 

db 

? 

2  request  header's  command 
code 

Status 

dw 

? 

3  driver's  return  status  word 

Reserve 

db 

8  dup  (?) 

5  reserved  area 

Media 

db 

? 

1 3  media  descriptor  byte 

Address 

dd 

? 

1 4  memory  address  for  transfer 

Count 

dw 

? 

1 8  byte/sector  count  value 

Sector 

dw 

? 

20  starting  sector  value 

Request 

ends 

end  of  request  header  template 

Table  3 

Format  of  request  header.  Only  the  first  1 3  bytes  are  com¬ 
mon  to  all  driver  functions;  the  number  and  definition  of  the 
following  bytes  vary  depending  on  the  function  type.  The 
structure  shown  here  is  the  one  used  by  the  read  and  write 
subfunctions  of  the  driver. 


Bit(s) 

Significance 

15 

Error 

10-14 

Reserved 

9 

Busy 

8 

Done 

0-7 

Error  code  if  bit  1 5  = 

Table  4 

Return  status  word  of  request  header. 


entry  point  is  called  by  DOS  immediately  after  the  call  to 
the  Strategy  Routine.  Unlike  in  Unix,  the  Interrupt  Rou¬ 
tine  is  never  entered  asynchronously  (as  is  the  case  on  an  1/ 
O  completion  interrupt).  The  division  of  function  between 
the  Strategy  and  Interrupt  Routines  is  completely  artifi¬ 
cial,  at  least  under  the  currently  available  nonmultitasking 
versions  of  MSDOS. 

The  Request  Header 

The  MSDOS  BDOS  uses  a  data  structure  called  the  Re¬ 
quest  Header  to  give  the  driver  the  necessary  information 
to  perform  an  I/O  operation.  The  address  of  the  Request 
Header  is  passed  to  the  Strategy  Routine  and  saved  in  a 
local  variable;  the  Interrupt  Routine,  which  is  called  imme¬ 
diately  afterward,  uses  this  address  to  access  information 
from  the  Header. 

The  first  13  bytes  of  the  Request  Header  are  the  same 
for  all  device  driver  functions  and  therefore  are  referred  to 
as  the  “static”  portion  of  the  Header.  The  number  and 
contents  of  the  following  bytes  vary  according  to  the  type 
of  function  requested  (Table  3,  at  left  ).  The  Request 
Header’s  primary  components  are  a  Command  Code  that 
selects  a  driver  subfunction  (such  as  Read,  Write,  or  Sta¬ 
tus)  and  a  Return  Status  word  that  informs  the  BDOS 
about  the  driver’s  success  with  the  request  I/O  operation 
(Table  4,  below  left,  and  Table  5,  page  98).  Other  infor¬ 
mation  passed  in  the  Header  to  the  driver  includes  minor 
unit  numbers,  transfer  addresses,  sector  or  byte  counts, 
and  so  on. 

When  an  I/O  function  is  completed,  the  device  driver 
uses  fields  in  the  Request  Header  to  pass  status,  sector  or 
byte  counts,  and  other  information  back  to  the  operating 
system. 

The  Driver  Command  Code  Routines 

The  Command  Code  for  the  requested  driver  subfunction 
is  passed  in  the  third  byte  of  the  Request  Header.  The 
Interrupt  Routine  extracts  it  from  the  Request  Header  us¬ 
ing  the  double-word  pointer  saved  during  the  call  to  the 
Strategy  Routine.  Typically,  the  Command  Code  is  used 
as  an  index  into  a  jump  table  that  points  to  the  proper 
service  code. 

In  the  descriptions  below,  RH  refers  to  the  Request 
Header  whose  address  was  passed  to  the  Strategy  Routine 
in  ES:BX.  DWORD  refers  to  a  long  address,  of  which  the 
first  two  bytes  contain  the  offset  and  the  last  two  bytes 
contain  the  segment. 

Function  0 — Driver  Initialization 

This  initialization  code  for  the  driver  is  called  only  once, 
when  the  driver  is  loaded.  It  is  responsible  for  performing 
any  necessary  hardware  initialization  of  the  device,  setup 
of  interrupt  vectors,  and  so  on.  It  returns  the  address  of 
the  start  of  free  memory  after  the  driver,  so  that  DOS 
knows  where  it  can  load  the  next  installable  driver.  If  it  is 
initializing  a  block  device  driver,  it  must  also  return  the 
number  of  units  and  the  address  of  the  BIOS  parameter 
block  (BPB)  pointer  array;  if  all  units  are  the  same,  all 
pointers  in  the  array  can  point  to  the  same  BPB. 


96 


Dr.  Dobb's  Journal,  July  1985 


If  the  initialization  routine  finds  that  the  device  is  miss¬ 
ing  or  defective  and  wants  to  abort  without  using  memo¬ 
ry,  it  should  set  the  number  of  units  to  zero  and  set  the 
ending  address  to  CS:0000. 

The  operating  system  services  that  the  initialization 
code  can  invoke  at  load  time  are  limited;  the  code  can  call 
only  MSDOS  services  01 -OCH  and  30H.  This  is  adequate 
to  check  the  DOS  version  number  and  display  a  driver 
identification  message,  but  not  much  else. 

Many  programmers  position  the  initialization  code  at 
the  end  of  the  driver  and  return  its  address  as  the  location 
of  the  first  free  memory;  this  allows  the  memory  occupied 
by  the  code  to  be  reclaimed  after  it  is  finished  with  its 
work. 

This  routine  is  called  with: 


RH  +  18 

DWORD  Pointer  to  the  character  after  the  = 
on  the  CONFIG.SYS  line  that 

Code 

Error  Definition 

0 

Write  protect  violation 

1 

Unknown  unit 

2 

Drive  not  ready 

3 

Unknown  command 

4 

CRC  error 

5 

Bad  drive  request  structure  length 

6 

Seek  error 

7 

Unknown  media 

8 

Sector  not  found 

9 

Printer  out  of  paper 

10 

Write  fault 

11 

Read  fault 

12 

General  failure 

13-14 

Reserved 

15 

Invalid  disk  change  (MSDOS  3.x) 

Table  5 

Driver  error  codes  returned  in  bits  0-7  of  return  status  word 

in  request  header. 

Byte(s)  Contents 

0-1  Bytes  per  sector 

2  Sectors  per  allocation  unit  (must  be  power  of  2) 

3-4  Number  of  reserved  sectors  (starting  at 

sector  0) 

5  Number  of  file  allocation  tables  (FATs) 

6-7  Maximum  number  of  root  directory  entries 

8-9  Total  number  of  sectors  in  media 

10  Media  descriptor  byte 

11-12  Number  of  sectors  occupied  by  a  single  FAT 


loaded  driver;  this  information  is 
read  only 

RH  +  22  BYTE  Drive  letter  for  first  unit  of  a  block 
driver:  0  =  A,  1  =B,  etc.  (MSDOS 
3.x  only) 

This  routine  returns: 

RH  +  3  WORD  Return  status 
RH+13  BYTE  Number  of  units  (block  devices  only) 
RH  +  1 4  DWORD  Address  of  first  free  memory  above 
driver 

RH+18  DWORD  BPB  pointer  array  (block  devices 
only) 

Function  1  — Media  Check 

The  media  check  function  is  used  on  block  devices  only 
and  should  be  a  NOP  in  character  device  drivers.  This 
routine  is  called  first  by  BDOS  for  a  block  device  transfer, 
passing  the  current  media  descriptor  byte  (Table  7,  page 
99).  If  feasible,  the  routine  returns  a  code  indicating 
whether  the  media  has  changed  since  the  last  transfer. 
This  feature  requires  a  machine  that  provides  a  door  in¬ 
terlock  hardware  status  flag  or  something  similar.  Deter¬ 
mining  the  media  change  status  improves  performance 
because  MSDOS  does  not  need  to  reread  the  file  allocation 
table  (FAT)  for  each  directory  access. 

This  routine  is  called  with: 

RH+1  BYTE  Unit  code 

RH+13  BYTE  Media  descriptor  byte 

This  routine  returns: 

RH  +  3  WORD  Return  status 

RH+14  BYTE  Media  change  code: 

-1  Media  has  changed 
0  Don’t  know  if  media  changed 
1  Media  has  not  changed 

RH  +  15  DWORD  Pointer  to  previous  volume  ID,  if 
device  attribute  bit  1 1  =  1  and 
media  has  changed  (MSDOS  3.x 
only) 

Function  2 — Build  BIOS  Parameter  Block 

The  build  BPB  function  is  supported  on  block  devices  only 
and  should  be  a  NOP  for  character  devices.  The  BDOS 
uses  it  to  get  a  pointer  to  the  valid  BPB  (Table  6,  at  left) 
for  the  current  media.  This  routine  is  called  when  the 
media  check  routine  returns  a  “Media  has  changed”  code 
or  when  it  returns  a  “Don’t  know  if  media  changed”  code 
and  there  are  no  dirty  buffers  (buffers  with  changed  data 
that  have  not  yet  been  written  to  disk).  Thus  this  call 
indicates  whether  the  media  has  legally  changed.  Under 
MSDOS  3.x,  this  function  should  also  read  the  volume  ID 
off  the  disk  and  save  it. 

The  build  BPB  call  also  receives  a  pointer  to  a  one- 
sector  buffer  in  the  address  field  of  the  request  header.  If 
the  “non-IBM-format”  bit  in  the  device  attribute  word  is 

98 


Table  6 

Format  of  BIOS  parameter  block;  a  copy  of  this  block  is 
always  found  in  the  boot  sector  of  an  initialized  disk. 


Dr.  Dobb’s  Journal.  July  1985 


zero,  the  buffer  contains  the  first  sector  of  the  FAT,  in-  This  function  is  available  on  character  devices  only.  If  an 

eluding  the  media  descriptor  byte,  and  should  not  be  al-  input  status  request  returns  a  busy  bit  =  0  (characters  wait- 

tered  by  the  driver.  If  the  “non-IBM-format”  bit  is  set,  the  ing),  the  next  character  that  would  be  read  is  returned  to 

buffer  may  be  used  as  scratch  space.  DOS  but  stays  in  the  input  buffer.  This  basically  provides 

This  routine  is  called  with:  DOS  with  the  capability  to  look  ahead  by  one  character. 

This  routine  returns: 

RH  +  1  BYTE  Unit  code 

RH  +  13  BYTE  Media  descriptor  byte  RH  +  3  WORD  Return  status 

RH+14  DWORD  Buffer  address  RH+13  BYTE  Character 


This  routine  returns: 

RH  +  3  WORD  Return  status 
RH+18  DWORD  Pointer  to  new  BPB 

Function  3 — I/O  Control  Read 

This  function  allows  control  information  to  be  passed  di¬ 
rectly  from  the  application  program  to  the  device  driver. 
It  is  called  only  if  the  IOCTL  bit  is  set  in  the  device  header 
attribute  word.  No  error  check  is  performed  on  IOCTL 
I/O  calls. 

This  routine  is  called  with: 

RH+1  BYTE  Unit  code  (block  devices  only) 
RH+13  BYTE  Media  descriptor  byte 
RH+14  DWORD  Transfer  address 
RH+18  WORD  Byte/Sector  count 
RH  +  20  WORD  Starting  sector  number  (block 
devices  only) 

This  routine  returns: 

RH  +  3  WORD  Return  status 

RH  +  1 8  WORD  Actual  bytes  or  sectors  transferred 

Function  4 — Read 

This  function  transfers  data  from  the  device  into  the  spec¬ 
ified  memory  buffer.  Under  MSDOS  3.x,  the  routine  can 
use  the  reference  count  of  open  files  maintained  by  the 
open  and  close  routines  (functions  13  and  14)  and  the 
media  descriptor  byte  to  determine  whether  the  media 
has  changed  illegally. 

This  routine  is  called  with: 

RH  +  1  BYTE  Unit  code  (block  devices  only) 
RH+13  BYTE  Media  descriptor  byte 
RH+14  DWORD  Transfer  address 
RH+18  WORD  Byte/Sector  count 
RH  +  20  WORD  Starting  sector  number  (block 
devices  only) 

This  routine  returns: 

RH  +  3  WORD  Return  status 
RH  +  1 8  WORD  Actual  bytes  or  sectors  transferred 
RH  +  22  DWORD  Pointer  to  volume  ID  if  error  OFH  is 
returned  (MSDOS  3.x  only) 

Function  5 — Nondestructive  Read 

99 


Function  6 — Input  Status 

This  function  is  available  on  character  devices  only  and 
returns  the  current  input  status  for  the  device.  MSDOS 


Current  Valid  MSDOS  Descriptor  Bytes  (5%-inch  Disks) 

0F9H  2  sided,  1 5  sector 

OFCH  1  sided,  9  sector 

OFDH  2  sided,  9  sector 

OFEH  1  sided,  8  sector 

OFFH  2  sided,  8  sector 

0F8H  (fixed  disk) 


Table  7 

Media  descriptor  byte  of  request  header,  assuming  "non- 
IBM-format"  bit  in  attribute  word  of  device  header  is  zero. 


Addr  Attr 

Str 

Int 

Type 

Units  Name 

00E3:0111  8004 

0FD5 

OFEO 

C 

NUL 

0070:0148  8013 

008E 

0099 

C 

CON 

0070:01  DD  8000 

008E 

009F 

C 

AUX 

0070:028E  8000 

008E 

OOAE 

C 

PRN 

0070:0300  8008 

008E 

00C3 

C 

CLOCK 

0070:03CC  0000 

008E 

00C9 

B 

02 

0070:01  EF  8000 

008E 

009F 

C 

C0M1 

0070:02A0  8000 

008E 

OOAE 

C 

LPT1 

0070:06F0  8000 

008E 

00B4 

C 

LPT2 

0070:0702  8000 

008E 

OOBA 

c 

LPT3 

0070:0714  8000 
End  of  device  chain. 

008E 

00A5 

c 

COM2 

Table  8 

Example  listing  of  device  chain  under  MSDOS  2.1 :  "plain 

vanilla"  PC  with  no  hard  disks  or  user  device  drivers. 

Dr.  Dobb's  Journal,  July  1985 


Media  Descriptor  Byte 

Bit(s)  Significance 

3-7  Always  set  (=  1) 

2  1=  removable 

O=not  removable 
1  1=8  sector 

O=not  8  sector 
0  1=2  sided 

O=not  2  sided 


assumes  all  character  devices  have  a  type-ahead  buffer.  If 
the  device  does  not  have  a  type-ahead  buffer,  it  should 
always  return  a  busy  bit  =  0  so  MSDOS  will  not  hang. 
This  routine  returns: 

RH  +  3  WORD  Return  status 

Busy  bit  =  1  Read  request  goes  to 
physical  device 

Busy  bit  =  0  Characters  already 
in  device  buffer;  read  request 
returns  quickly 

Function  7 — Flush  Input  Buffers 

This  function  is  available  on  character  devices  only.  It 
terminates  all  pending  requests;  i.e.,  the  input  buffer  is 
emptied. 

This  routine  returns: 

RH  +  3  WORD  Return  status 

Function  8 — Write 

This  routine  transfers  data  from  the  specified  memory 
buffer  to  the  device.  Under  MSDOS  3.x,  the  routine  can 
use  the  reference  count  of  open  files  maintained  by  the 
open  and  close  routines  (functions  13  and  14)  and  the 
media  descriptor  byte  to  determine  whether  the  media 
has  changed  illegally. 

This  routine  is  called  with: 

RH+1  BYTE  Unit  code  (block  devices  only) 
RH+13  BYTE  Media  descriptor  byte 
RH  +  14  DWORD  Transfer  address 
RH+18  WORD  Byte/Sector  count 
RH  +  20  WORD  Starting  sector  number  (block 
devices  only) 

This  routine  returns: 

RH  +  3  WORD  Return  status 
RH+18  WORD  Actual  bytes  or  sectors  transferred 
RH  +  22  DWORD  Pointer  to  volume  ID  if  error  OFH  is 
returned  (MSDOS  3.x  only) 

Function  9 — Write  with  Verify 

This  routine  transfers  data  from  the  specified  memory 
buffer  to  the  device.  If  feasible,  a  read-after-write  verifi¬ 
cation  of  the  data  is  performed  to  confirm  that  the  data 
was  written  correctly.  Otherwise,  it  is  exactly  like  func¬ 
tion  8. 

Function  10 — Output  Status 

This  routine  returns  the  current  output  status  for  the  de¬ 
vice.  This  function  is  available  on  character  devices  only. 
This  routine  returns: 

RH  +  3  WORD  Return  status 

Busy  bit  =  1  Write  request  waits 
for  completion  of  current 
request 


Busy  bit  =  0  Device  idle;  write 
request  starts  immediately 

Function  1 1  — Flush  Output  Buffers 

This  function  is  available  on  character  devices  only  and 
terminates  all  pending  output  requests.  The  output  buff¬ 
er,  if  any,  is  emptied. 

This  routine  returns: 

RH  +  3  WORD  Return  status 

Function  12 — I/O  Control  Write 

This  function  allows  control  information  to  be  passed  di¬ 
rectly  from  the  driver  to  the  application  program.  It  is 
called  only  if  the  lOCTL  bit  is  set  in  the  device  header 
attribute  word.  No  error  check  is  performed  on  IOCTL 
I/O  calls. 

This  routine  is  called  with: 

RH+1  BYTE  Unit  code  (block  devices  only) 

RH  +  13  BYTE  Media  descriptor  byte 
RH+14  DWORD  Transfer  address 
RH  +  18  WORD  Byte/Sector  count 
RH  +  20  WORD  Starting  sector  number  (block 
devices  only) 

This  routine  returns: 

RH  +  3  WORD  Return  status 

RH+18  WORD  Actual  bytes  or  sectors  transferred 

Function  13 — Open 

This  function  is  available  on  MSDOS  version  3.0  and 
above  only  and  is  called  only  if  the  open/close/RM  bit  is 
set  in  the  device  attribute  word. 

Block  devices  may  use  the  open  function  to  manage 
local  buffering  and  to  increment  a  reference  count  of  the 
number  of  open  files  on  a  device. 

Character  devices  can  use  this  function  to  send  a  device 
initialization  string,  which  in  turn  can  be  set  by  IOCTL 
Write.  Note  that  the  predefined  CON,  AUX,  and  PRN 
devices  are  always  open. 

This  routine  is  called  with: 

RH  +  1  BYTE  Unit  code  (block  devices  only) 

This  routine  returns: 

RH  +  3  WORD  Return  status 

Function  14 — Device  Close 

This  function  is  available  on  MSDOS  version  3.0  and 
above  only  and  is  called  only  if  the  open/close/RM  bit  is 
set  in  the  device  attribute  word. 

On  block  devices,  this  function  can  manage  local  buff¬ 
ering  and  decrement  a  reference  count  keeping  track  of 
the  number  of  open  files  on  the  device;  when  the  count 
reaches  zero,  all  files  have  been  closed,  and  the  driver 
should  flush  buffers  because  the  user  may  change  disks. 


Dr.  Dobb’s  Journal,  July  1985 


100 

565 


On  character  devices,  this  function  can  send  a  device¬ 

the  device  attribute  word  and  the  device  is  a  block  type. 

dependent  post-I/O  string  such  as  a  form  feed,  which  in 

This  routine  is  called  with: 

turn  can  be  set  by  an  IOCTL  Write.  Note  that  the  CON, 
AUX,  and  PRN  devices  are  never  closed. 

RH+1 

BYTE 

1 

Unit  code  (block  devices  only) 

This  routine  is  called  with: 

This  routine  returns: 

RH+1  BYTE  Unit  code  (block  devices  only) 

RH  +  3 

WORD 

Return  status 

This  routine  returns: 

Busy  bit  =  1  Media  is  non¬ 
removable 

RH  +  3  WORD  Return  status 

Busy  bit  =  0  Media  is  removable 

Function  1 5 — Removable  Media 

This  function  is  available  on  MSDOS  version  3.0  and  above 
only  and  is  called  only  if  the  open/close/RM  bit  is  set  in 

DD| 

16-Bit  Toolbox  Listing  (Text  begins  on  page  94) 


1 

name 

driver 

2 

page 

55,132 

3 

4 

title 

‘DRIVER  —  installable  driver  template1 

j 

6 

1 

;  This  is  a  "template"  for  a  MS 

DOS  installable  device  driver. 

7 

;  The  actual  driver  subroutines 

are  stubs  only  and  have 

8 

9 

;  no  effect  but 

to  return  a  non 

error  "done"  status. 

10 

l 

;  Ray  Duncan,  April  1985 

11 

12 

;  Laboratory  Microsystems  Inc. 

13 

0000 

code  segment 

public  'CODE 1 

14 

15 

0000 

driver  proc 

far 

16 

17 

assume 

cs : code, ds: code 

es:code 

18 

19 

0000 

org 

0 

20 

21 

=  000F 

Max  Cmd  equ 

15 

;  driver  command  code  maximum 

22 

;  12  for  MS-DOS  2.x, 

23 

;  15  for  MS-DOS  3.x 

24 

25 

=  0000 

cr  equ 

Odh 

;  ASCII  carriage  return 

26 

=  000A 

If  equ 

Oah 

;  ASCII  line  feed 

27 

28 

=  0024 

eom  equ 

'$' 

;  end  of  message  signal 

29 

page 

30 

31 

;  Device  Driver 

Header 

32 

33 

0000 

FF  FF  FF  FF 

Header  dd 

-1 

;link  to  next  device, -1=  end  of 

34 

35 

0004 

8000 

dw 

8000h 

,-attribute  word 

36 

;bit  15=1  for  character  devices 

37 

38 

0006 

0056  R 

dw 

Strat 

, -device  "Strategy"  entry  point 

39 

40 

0008 

0061  R 

dw 

Intr 

;device  "Intrrupt"  entry  point 

41 

42 

000A 

44  52  49  56  45  52 

db 

'DRIVER  ' 

;char  device  name,  8  char,  or 

43 

20  20 

44 

;if  block  device,  no.  of  units 

45 

;in  first  byte  followed  by 

46 

;7  don't  care  bytes 

47 

48 

- 

49 

;  local  variables  for  use  by  driver 

50 

; 

51 

0012 

???????? 

RH  Ptr  dd 

? 

;  pointer  to  request  header 

52 

;  passed  to  Strat  by  BDOS 

53 

54 

0016 

0D  0A  0A 

I dent  db 

cr, If , If 

102 


ccc 


Dr.  Dobb’s  Journal,  July  I9S5 


db 


Example  Device  Driver  1.0' 


55  0019 

56 

57 

58 

59 

60  0032 

61 
62 

63 

64 

65 

66 

67 

68 

69 

70 


71 

0036 

72 

73 

0036 

00BA 

R 

74 

0038 

009C 

R 

75 

003A 

009E 

R 

76 

003C 

OOAO 

R 

77 

003E 

00A2 

R 

78 

0040 

00A4 

R 

79 

0042 

00A6 

R 

80 

0044 

00A8 

R 

81 

0046 

00AA 

R 

82 

0048 

00AC 

R 

83 

004A 

00AE 

R 

84 

004  C 

00B0 

R 

85 

004E 

00B2 

R 

86 

0050 

00B4 

R 

87 

0052 

0086 

R 

88 

0054 

00B8 

R 

89 

90 

91 

92 

93 

94 

95 

96 

97 

98 

99 

100 
101 
102 
103 


104 

0000 

?? 

105 

0001 

?? 

106 

0002 

?? 

107 

0003 

???? 

108 

0005 

08  [ 

109 

110 

111 

112 

0000 

?? 

113 

000E 

???????? 

114 

0012 

???? 

115 

0014 

???? 

116 

117 

0016 

118 

119 

120 
121 
122 

123 

124 

125 

126 

127 

128 

129 

130 

131  0056 

132 


45  78  61  60  70  6C 
65  20  44  65  76  69 
63  65  20  44  72  69 
76  65  72  20  31  2E 
30 

00  OA  OA  24 


db  cr, If , If ,eom 

page 


;  Driver  Command  Codes  dispatch  table 

;  The  "Intr"  routine  uses  this  table  and  the  Command  Code 
;  supplied  in  the  Request  Header  to  transfer  to  the 
;  appropriate  driver  subroutine. 

Dispatch: 


dw  I  nit  ;  0 
dw  Media_Chk  ;  1 
dw  Bui ld_Bpb  ;  2 
dw  Ioctl_Inp  ;  3 
dw  I nput  ;  4 
dw  Nd_ Input  ;  5 
dw  Inp_Stat  ;  6 
dw  Inp_Flush  ;  7 
dw  Output  ;  8 
dw  Outp_Vfy  ;  9 
dw  Outp_Stat  ;  10 
dw  Out p_F lush  ;  11 
dw  Ioctl_Outp  ;  12 
dw  Dev_Open  ;  13 
dw  Dev_Close  ;  14 
dw  Rem_Medi,a  ;  15 


init  driver  into  system 
media  check  on  blk  dev 
build  BIOS  param  block 
I/O  Ctrl  read  from  dev 
normal  destructive  read 
non- destructive  read, no  wait 
return  current  input  status 
flush  device  input  buffers 
normal  output  to  device 
output  with  verify 
return  current  output  status 
flush  output  buffers 
I/O  control  output 
device  open  (MS-DOS  3.x) 
device  close  (MS-DOS  3.x) 
removeable  media  (MS-DOS  3.x) 


page 

MS-DOS  Request  Header  structure  definition 

The  first  13  bytes  of  the  Request  Header  are  the  same 
for  all  Command  codes  and  are  termed  the  "Static11  part  of 
the  Header.  The  number  and  meaning  of  the  following  bytes 
vary  depending  on  the  Command  code. 

The  Request  Header  shown  here  applies  to  Read  S  Write  functions. 


Request  struc 


request  header  template  structure 


R length  db  ? 

Uni t  db  ? 

Command  db  ? 

Status  dw  ? 

Reserve  db  8  dup  (?) 


length  of  request  header 
unit  nunber  for  this  request 
request  header's  command  code 
driver's  return  status  word 
reserved  area 


Media  db 
Address  dd 
Count  dw 
Sector  dw 

Request  ends 


media  descriptor  byte 
memory  address  for  transfer 
byte/sector  count  value 
starting  sector  value 

end  of  request  header  template 


page 

;  Device  Driver  "Strategy  Routine" 

;  Each  time  a  request  is  made  for  this  device,  the  BDOS 
;  first  call*  "Strategy  routine",  then  immediately  calls 
;  the  "Interrupt  routine". 

;  The  Strategy  routine  is  passed  the  address  of  the 
;  Request  Header  in  ES:BX,  which  it  saves  in  a  local 
;  variable  and  then  returns  to  BDOS. 


far 


Dr.  Dobb’s  Journal,  July  1985 


Strat  proc 


:  save  address  of  Request  Header 


104 

567 


16-Bit  Toolbox  Listing  (Listing  Continued,  text  begins  on  page  94) 


0056  2E:  89  IE  0012  R 
0058  2E:  8C  06  0014  R 

0060  CB 


mov  word  ptr  cs:  [RH_Ptr]  ,bx 

mov  word  ptr  cs: [RH_£tr+2] ,es 


;  back  to  BOOS 


strat  endp 


;  Device  Driver  "Interrupt  Routine" 

;  This  entry  point  is  called  by  the  BOOS  immediately  after 
;  the  call  to  the  "Strategy  Routine",  which  saved  the  long 
;  address  of  the  Request  Header  in  the  local  variable  'RH_Ptr". 

;  The  "Interrupt  Routine"  uses  the  Command  Code  passed  in 
;  the  Request  Header  to  transfer  to  the  appropriate  device 
;  handling  routine.  Each  command  code  routine  is  responsible 
;  for  any  necessary  return  information  into  the  Request  Header, 

;  then  transfers  to  Error  or  Exit  to  set  the  Return  Status  code. 


0061 

Intr  proc 

far 

0061 

50 

push 

ax 

;  save  general  registers 

0062 

53 

push 

bx 

0063 

51 

push 

cx 

0064 

52 

push 

dx 

0065 

IE 

push 

ds 

0066 

06 

push 

es 

0067 

57 

push 

di 

0068 

56 

push 

si 

0069 

55 

push 

bp 

006A 

0E 

push 

cs 

;  make  local  data  addressable 

006B 

IF 

pop 

ds 

006C 

C4 

3E  0012 

R 

les 

di, [RH_Ptr] 

;  ES:DI  =  Request  Header 

•  get  BX  =  Command  Code 

0070 

26: 

8A  5D 

32 

mov 

bl ,es: [di .Command] 

0074 

32 

FF 

xor 

bh,bh 

0076 

83 

FB  OF 

cmp 

bx,Max_Cmd 

;  make  sure  its  legal 

0079 

7F 

06 

jg 

Unk_Command 

;  too  big,  exit  with  error  code 

007B 

D1 

E3 

shl 

bx,1 

;  form  index  to  Dispatch  table 

;  and  branch  to  driver  routine 

007D 

FF 

A7  0036 

R 

jmp 

word  ptr  [bx+Dispatch] 

page 

;  General  collection  of  exit 

points  for  the  driver  routines. 

0081 

Unk_Command: 

;  Come  here  if  Command  Code  too 

0081 

B0 

03 

mov 

al,3 

;  Sets  "Unknown  Command"  error 

;  code  and  "Done"  bit. 

0083 

Error: 

;  Transfer  here  with  AL  =  error 

0083 

B4 

81 

mov 

ah,81h 

;  Sets  "Error"  and  "Done"  bits. 

0085 

EB 

03  90 

jmp 

Exit 

0088  B4  01 


Done:  mov 


;  Come  here  if  I/O  complete  and 
;  no  error,  sets  "Done"  bit  only. 


008A  2E:  C5  IE  0012  R 
008F  89  47  03 

0092  5D 


;  General  purpose  exit  point. 
;  Transfer  here  with  AX  = 

;  Return  Status  word  to  be 
;  placed  into  Request  Header. 


bx,cs:  [RH_Ptr] 
ds: [bx. Status] ,ax 


set  status 


; restore  general  registers 


Dr.  Dobb's  Journal,  July  1985 


;  back  to  BDOS 


208 

0093 

5E 

pop 

209 

0094 

5F 

pop 

210 

0095 

07 

pop 

211 

0096 

IF 

pop 

212 

0097 

5A 

pop 

213 

0098 

59 

pop 

2U 

0099 

5B 

pop 

215 

009A 

58 

pop 

216 

009B 

CB 

ret 

217 

218 

page 

219 

220 

/ 

;  Function  1 

221 

222 

223 

009C 

Media_Chk: 

224 

009C 

EB  EA 

jmp 

225 

226 

1 

227 

;  Function  2 

228 

! 

Bui ld_Bpb: 

229 

009E 

230 

009E 

EB  E8 

jmp 

231 

232 

1 

233 

;  Function  3 

234 

1 

235 

OOAO 

Ioctl_Inp: 

236 

00A0 

EB  E6 

jmp 

237 

238 

1 

239 

;  Function  4 

240 

1 

241 

00A2 

Input: 

242 

OOA2 

EB  E4 

jmp 

243 

244 

1 

245 

;  Function  5 

246 

/ 

247 

00A4 

Nd_Input: 

248 

00A4 

EB  E2 

jmp 

249 

250 

1 

251 

;  Function  6 

252 

1 

253 

00A6 

Inp_Stat: 

254 

00A6 

EB  EO 

jmp 

255 

256 

1 

257 

;  Function  7 

258 

1 

259 

00A8 

InpFlush: 

260 

00A8 

EB  DE 

jmp 

261 

262 

1 

263 

;  Function  8 

264 

1 

265 

00AA 

Output : 

266 

00AA 

EB  DC 

jmp 

267 

268 

1 

269 

;  Function  9 

270 

1 

271 

00AC 

Outp_Vfy: 

272 

00AC 

EB  DA 

jmp 

273 

274 

1 

275 

;  Function  10 

276 

1 

277 

00AE 

Outp_Stat: 

278 

OOAE 

EB  D8 

jmp 

279 

280 

t 

281 

;  Function  11 

282 

! 

283 

00B0 

Outp  Flush: 

284 

00B0 

EB  D6 

jmp 

285 

286 

/ 

Dr.  Dobb’s  Journal,  July  1985 


si 
di 
es 
ds 
dx 
cx 
bx 
ax 

Media  Check 

Done 

Build  BIOS  Parameter  Block 

Done 

I/O  Control  Read 

Done 

Read  from  Device 

Done 

Non-destructive  Read 

done 

Return  Input  Status 

Done 

Flush  Input  Buffers 

Done 

Write  to  Device 

Done 

Write  with  Verify 

Done 

Return  Output  Status 

Done 

Flush  Output  Buffers 

Done 

106 

569 


16-Bit  Toolbox  Listing 


00B2 

00B2  EB  D4 


(Listing  continued,  text  begins  on  page  94) 
;  Function  12  I/O  Control  Write 

I 

Ioctl_Outp: 

jmp  Done 


00B4 

00B4  EB  D2 


;  Function  13  Device  Open  (MS-DOS  3.x) 

I 

Dev_Open: 

jmp  Done 


00B6 

00B6  EB  DO 


;  Function  14  Device  Close  (MS-DOS  3.x) 

I 

Dev_Close: 

jmp  Done 


00B8 

00B8  EB  CE 


;  Function  15  Removeable  Media  (MS-DOS  3.x) 

Rem_Media: 

jmp  Done 


The  Initialization  code  for  the  driver  is  called  only  once 
when  the  driver  is  loaded.  In  this  example,  it  returns  its 
own  address  to  the  DOS  as  the  start  of  free  memory  after  the 
driver,  so  that  the  memory  occupied  by  1NIT  will  be  reclaimed 
after  it  is  finishes  with  its  work.  Only  MS-DOS  services  01-0CH 
and  30H  can  be  called  by  the  I N I T  code. 

Block  device  drivers  must  also  return  the  number  of  units  and 
the  address  of  the  BIOS  Parameter  Block  pointer  array;  if  all 
units  are  the  same,  all  pointers  can  point  to  the  same  BPB. 


OOBA  06 
00BB  57 
00BC  B4  09 
00BE  BA  0016  R 
00C1  CD  21 
00C3  5F 
00C4  07 

00C5  26:  C7  45  0E  OOBA  R 

00CB  26:  8C  4D  10 
00CF  EB  B7 


es 

di 

ah, 9 

dx, offset  Ident 
21  h 
di 


Function  0 

initialize  device  driver 
push  Request  Header  addr 

print  sign- on  message 


restore  Request  Header  addr 


;  set  first  usable  memory  addr. 
word  ptr  es: [di .Address] .offset  Init 
word  ptr  es:  [di .Address+2]  ,cs 
Done 


Intr  endp 
Driver  endp 


code  ends 


Structures  and  records: 


Width  #  fields 
Shift  Width  Mask 


Initial 


REQUEST.  . 
RLENGTH. 
UNIT  .  . 
COMMAND . 
STATUS  . 
RESERVE. 
MEDIA.  . 
ADDRESS. 
COUNT.  . 
SECTOR  . 


K7n 


Dr.  Dobb’s  Journal,  July  1985 


Segments  and  Groups: 

Name  Size  Align  Combine  Class 

CODE .  00D1  PARA  PUBLIC  1 COOE 1 

Symbols: 

Name  Type  Value  Attr 

BUI LD_BPB .  L  NEAR  009E  COOE 

CR .  Number  0000 

DEV*  CLOSE .  L  NEAR  00B6  COOE 

DEVOPEN .  L  NEAR  00B4  COOE 

DISPATCH .  L  NEAR  0036  COOE 

DONE .  L  NEAR  0088  COOE 

DRIVER .  F  PROC  0000  COOE  Length  =00D1 

EOM .  Number  0024 

ERROR .  L  NEAR  0083  COOE 

EXIT .  L  NEAR  008A  COOE 

HEADER .  L  DWORD  0000  COOE 

IDENT .  L  BYTE  0016  CODE 

INIT .  L  NEAR  OOBA  COOE 

INPUT .  L  NEAR  00A2  COOE 

INP  FLUSH .  L  NEAR  00A8  COOE 

INP_STAT .  L  NEAR  OOA6  COOE 

INTR .  F  PROC  0061  COOE  Length  =0070 

IOCTL  INP .  L  NEAR  00A0  COOE 

IOCTLJOUTP .  L  NEAR  00B2  CODE 

LF .  Number  000A 

MAXCMD .  Number  000F 

MEDIA  CHX .  L  NEAR  009C  COOE 

ND  INPUT .  L  NEAR  00A4  COOE 

OUTPUT .  L  NEAR  OOAA  COOE 

OUTP  FLUSH .  L  NEAR  00B0  COOE 

OUTP_STAT . •- .  L  NEAR  OOAE  COOE 

OUTP  VFY .  L  NEAR  OOAC  COOE 

REM  MEDIA .  L  NEAR  OOB8  COOE 

RH  PTR .  L  DWORD  0012  COOE 

STRAT .  F  PROC  0056  COOE  Length  =000B 

UNX  COMMAND .  L  NEAR  0081  COOE 

49190  Bytes  free 

Warning  Severe 
Errors  Errors 

0  0  End  Listing 


Dr.  Dobb's  Journal,  July  1985 


109 

<:7i 


COMPUTER  CALISTHENICS 


by  Michael  Wiesenberg 

I’ve  received  a  lot  of  response  to  the 
first  two  “Computer  Calisthenics” 
columns. 

The  first  puzzle  in  the  November 
1984  issue  was: 

There  are  two  world-famed  math¬ 
ematicians,  Mr.  P  and  Mr.  S.  A 
friend  of  theirs  who  likes  puzzles 
tells  both  of  them  that  he  has  two 
numbers  in  mind.  The  only  thing  he 
will  tell  both  of  them  about  these  two 
numbers  is  that  they  are  both  inte¬ 
gers,  greater  than  1,  and  they  are  not 
the  same. 

He  then  whispers  to  Mr.  P  the 
product  of  the  two  numbers. 

He  whispers  to  Mr.  S  the  sum  of 
the  two  numbers. 

Mr.  P  knows  that  Mr.  S  knows  the 
sum,  but  he  doesn’t  know  what  that 
sum  is.  Similarly,  Mr.  S  knows  that 
Mr.  P  knows  the  product,  but  he 
doesn’t  know  what  that  product  is. 

The  following  conversation  then 
takes  place: 

Mr.  P:  "I  don’t  know  the 
numbers." 

Mr.  S:  "/  know  that;  I  also  don’t 
know  the  numbers." 

Mr.  P:  "Oh,  then  /  know  the 
numbers." 

Mr.  S:  "Really,  then  I  do  also.” 

Your  task,  of  course,  is  to  discover 
the  two  numbers.  It  would  take  you  a 
long  time  to  figure  them  out  by  hand, 
so  write  a  program  to  do  it.  A  short 
program. 

Many  offered  programmatic  solu¬ 
tions  that  produced  the  correct  an¬ 
swer,  but,  unfortunately,  they  also 
yielded  other  sets  of  answers  that 
were  wrong. 

Let’s  see  what’s  happening  at  each 
statement.  I’m  going  to  give  Alan 
Tracht,  of  Cleveland  Heights,  OH, 
an  honorable  mention  for  coming  up 
with  the  correct  reasoning  required  to 
solve  the  puzzle  programmatically, 


but  he  doesn’t  get  the  t-shirt  because 
his  program  is  one  of  those  that  yields 
too  many  answers: 

(1)  Mr.  P:  “I  don’t  know  the  num¬ 
bers.”  They  are  not  both  prime. 

(2)  Mr.  S:  “I  know  that  .  . . .”  The 
numbers  cannot  be  the  sum  of  two 
primes. 

(3)  “. .  .  I  also  don’t  know  the  num¬ 
bers.”  The  sum  is  at  least  7,  so  that  it 
can  be  the  sum  of  at  least  two  differ¬ 
ent  pairs  of  numbers. 

(4)  Mr.  P:  “Oh,  then  I  know  the  num¬ 
bers.”  Of  all  the  factor-pairs  of  the 
product,  only  one  sums  to  a  number 
that  cannot  be  the  sum  of  two  primes. 

(5)  Mr.  S:  “Really,  then  I  do  also.” 
Of  all  the  addend-pairs,  only  one 
multiplies  to  a  product  that  gives  Mr. 
P  the  answer  (under  the  conditions  of 
the  previous  statement). 

Let’s  see  why  certain  sets  of  num¬ 
bers  fail  one  or  more  of  the  tests. 

(5,  7):  P  =  35,  S=  12 

Mr.  P  would  not  have  made  state¬ 
ment  1. 

(5,6):  P  =  30,  S  =  1 1 

Knowing  the  product,  Mr.  P  would 
have  made  his  first  statement.  Know¬ 
ing  the  sum,  Mr.  S  would  have  made 
statements  2  and  3.  At  this  point, 
however,  Mr.  P  still  would  not  know 
whether  his  product  of  30  was  formed 
from  the  pair  (5,  6)  or  (2,  15),  and  so 
would  not  have  made  statement  4. 

(4,  61):  P  =  244,  S  =  65 

This  was  the  most  common  incorrect 
answer.  These  two  numbers  do  not 
satisfy  statement  5,  because  (4,  61) 
and  (8,  57)  each  satisfy  statement  4. 

Mike  Meyer,  of  Norman,  OK,  pro¬ 
vided  an  interesting  extension  of  this 


problem,  showing  how  this  puzzle  is 
the  first  of  an  infinite  sequence  of  puz¬ 
zles;  that  is,  the  conversation  becomes: 

Mr.  P:  “I  don’t  know  the 
numbers.” 

Mr.  S:  “I  know  that;  I  also  don’t 
know  the  numbers.” 

Puzzle  1: 

Mr.  P:  “I  still  don’t  know  the 
numbers.” 

Mr.  S:  “I  still  don’t  know  the  num¬ 
bers,  either.” 

Puzzle  2: 

and  so  on.  At  each  point,  which  we 
can  label  n,  insert  the  sequence: 

Mr.  P:  “Oh,  then  I  know  the 
numbers.” 

Mr.  S:  “Really,  then  I  do  also.” 

to  create  puzzle  number  n. 

Interesting.  The  question  is:  who 
can  solve  one  of  those  now? 

The  award  goes  to  Charles  Wells 
of  the  Department  of  Mathematics 
and  Statistics,  Case  Western  Univer¬ 
sity,  Cleveland,  OH.  The  answer  spit 
out  by  his  program  (Listing  One, 
page  1  14)after  running  all  night  is(4, 
13).  He  ran  the  program  up  to  num¬ 
ber  pairs  including  40,  at  which  point 
he  says  it  ran  very  slowly.  He  also  im¬ 
plies  that  there  might  be  larger  an¬ 
swers,  but  “I  refuse  to  believe  any¬ 
one,  mathematician  or  otherwise, 
could  work  this  out  for  numbers 
above  20  in  his  head.”  Agreed.  Per¬ 
haps  I  should  have  added  to  the  puz¬ 
zle  the  proviso  that  neither  number 
was  greater  than  100,  but  nowhere 
did  I  say  that  the  two  world-famed 
mathematicians  were  idiot  savants. 
Each  would  know  that  the  other 
could  not  come  up  with  an  answer  in 
his  head  for  numbers  greater  than 
100. 


110 


Dr.  Dobb's  Journal,  July  1985 


Although  scores  of  entrants  tried 
their  hand  at  the  preceding  problem 
in  logic,  only  two  essayed  the  much 
easier  task  of  programmatically  de¬ 
scribing  a  horse.  J.  C.  Williams,  of 
Cincinnati,  OH,  wins  the  prize  for  his 
program,  presented  in  its  entirety  in 
these  two  lines: 

FUNCTION  HORSE(  )  RETURN 
BOOLEAN 

RETURN  FALSE 

He  includes  this  “proof”:  “The 
function  (program)  is  quite  obviously 
a  neigh-sayer,”  and  appends  the  com¬ 
ment  “(Sorry).”  I  presume  he  is  apol¬ 
ogizing  for  the  pun,  but  that  sort  of 
originality  was  precisely  what  I  was 
hoping  for. 

December’s  problem  inspired  more 
than  double  the  entries  of  Novem¬ 
ber’s: 

Assign  a  numerical  value  to  each 
letter  of  the  alphabet,  starting  with  1 
for  A  and  going  up  to-26  for  Z.  Any 
word  in  the  English  language  has  a 
value  obtained  by  multiplying  the 
values  for  each  of  its  letters.  For  ex¬ 
ample,  the  word  hello  is  worth 
86,400,  obtained  by  multiplying  8  X 
5X12X12X15.  Which  English 
word  is  equal  to  exactly  1,000,000? 
If  there  is  none,  which  is  the  closest? 
Only  words  found  in  The  Random 
House  Dictionary  of  the  English  Lan¬ 
guage  (unabridged  edition)  can  be 
used.  No  capitalized  words,  none 
with  hyphens  or  other  embedded 
punctuation,  nor  those  designated  as 
foreign. 

Well,  how  about  it,  folks?  Can  you 
devise  a  program  that  finds  the  right 
word?  And,  having  done  that,  can 
you  tell  us  what  that  word  is?  Your 
program  must  be  short  and  elegant. 
The  algorithms  can  be  demonstrated 
in  a  good  pseudolanguage  if  you 
wish  or  perhaps  in  flowcharts. 

Several  entrants  sent  me  words 
that  multiplied  out  to  1,000,000,  but 
I  did  not  award  any  of  them  a  t-shirt 
for  several  reasons.  Many  discovered 
that  the  word  typey  has  a  value  of 
1,000,000  and  told  me  what  dictio¬ 
naries  they  had  found  it  in.  Others 
offered  tetty ,  defined  in  the  Oxford 
English  Dictionary  ( OED )  as  a  form 
of  “testy.”  (It’s  also  in  my  Webster’s 


New  International  Dictionary ,  sec¬ 
ond  edition.)  The  OED  also  yields 
tytte,  an  old  form  of  “teat.” 

Those  who  sent  me  only  one  of 
these  words  did  not  qualify  for  the 
prize,  because  I  asked  for  a  program , 
not  just  an  answer.  Those  who  includ¬ 
ed  a  program  that  yielded  only  one  of 
these  words  also  did  not  win,  because 
they  did  not  follow  the  rules.  I  speci¬ 
fied  the  Random  House  Dictionary 
for  a  number  of  reasons,  and  it  does 
not  contain  typey,  tetty,  or  tytte. 

Why  this  particular  dictionary?  It 
is  available  as  a  dictionary  program 
and  thus  does  not  necessitate  typing 
in  a  whole  dictionary  full  of  words. 
Also,  it  is  free  of  what  I  call  “cross¬ 
word  puzzle  dictionary  words” — ob¬ 
solete  words,  archaic  variants,  and 
other  words  that  are  seldom  found  in 
use  today.  Furthermore,  not  all  the 
words  in  it  are  in  the  electronic  dic¬ 
tionary.  No  currently  available  elec¬ 
tronic  dictionary  even  comes  close  to 
having  as  many  words  as  its  “hard 
copy”  counterpart  does.  It  is  an  un¬ 
usual  spelling  checker  that  has  even 
50,000  words,  and  yet  the  tome  of 
over  2000  pages  that  is  my  main  ref¬ 
erence  work  contains  in  excess  of 
260,000  entries,  and  there  are  many 
more  words  than  entries.  For  exam¬ 
ple,  tablet  is  a  separate  entry,  but 
tableted  is  not.  Kaleidoscopically  is 
not  a  separate  entry;  it  is  found  at  the 
end  of  the  definition  for  kaleido¬ 
scope.  Spelling  checkers  are  even 
more  limited:  they  usually  have  only 
one  form  of  each  word.  The  plurals  of 
nouns,  the  progressive  forms  of  verbs, 
and  so  on,  are  often  not  there.  The 
point  I  wished  to  make,  which  most 
people  did  not  get,  was  that  perhaps 
brute  force,  in  the  form  of  examining 
every  word  in  a  given  dictionary,  is 
not  the  way  to  solve  this  puzzle. 

I  commend  all  those  who  wrote  pro¬ 
grams  generating  all  possible  candi¬ 
dates  and  embedded  in  their  programs 
the  logic  that  the  letters  forming  the 
hypothetical  word  must  be  multiples 
of  2  or  5  or  both,  those  being  the  only 
prime  factors  of  1,000,000;  that  is,  if  a 
word  exists  in  the  specified  dictionary 
that  multiplies  out  exactly  to 
1,000,000,  it  must  consist  of  some 
combination  of  B,  D,  E,  H,  J,  P,  T, 
and  Y.  Any  qualifying  word  can  have, 


Dr.  Dobb’s  Journal,  July  1985 


111 

573 


in  addition,  any  number  of  As,  that 
being  a  “free”  letter  because  its  addi¬ 
tion  to  a  word  merely  multiplies  the 
product  by  1.  In  fact,  Ulrich  Sonder- 
mann,  of  Concord,  CA,  chastised  me 
for  specifying  the  values  of  the  letters 
to  be  1  through  26,  rather  than  2 
through  27 — which,  he  said,  was  the 
way  the  problem  was  originally  stated 
in  the  puzzle  magazine.  (The  best 
word  in  that  contest  was  ixodid,  a 
kind  of  tick;  Ulrich  submitted  the  cor¬ 
rect  answer  in  that  contest.)  Ah,  but  I 
did  not  wish  to  repeat  a  previously 
published  puzzle,  and  I  did  want  a 
“free”  letter  because  I  wanted  to  pro¬ 
vide  a  different  kind  of  puzzle. 

Ulrich  deserves  mention  for  anoth¬ 
er  reason.  He  wrote  his  program  in 
BASIC  on  a  Timex/Sinclair:  quite  a 
feat!  It  generated  1 23  candidates  (us¬ 
ing  the  values  I  specified)  in  four 
minutes.  He  also  provided  a  good  al¬ 
gorithm  for  finding  out  if  a  given 
electronic  dictionary  contains  any 
words  consisting  of  these  letter  com¬ 
binations.  He  said,  “The  problem 
then  consists  of  creating  a  dictionary 
with  words  that  have  letters  sorted  in 
descending  order.  A  match  can  then 
be  made  with  each  of  the  123  candi¬ 
dates.”  (But  what  about  the  124th 
candidate,  Ulrich?) 

I  commend  those  of  you  who  wrote 
such  programs,  but  I  did  not  award 
you  prizes  because  I  am  not  yet  con¬ 
vinced  that  the  Random  House  Dic¬ 
tionary  has  a  word  multiplying  out  to 
exactly  1,000,000  and  because,  with 
one  notable  exception,  none  of  the 
programs  accounted  for  the  fact  that 
adding  any  number  of  As  to  a  word 
does  not  change  its  value. 

Among  the  preceding,  I  give  hon¬ 
orable  mention  to  both  Ken  Waldron, 
of  Vancouver,  BC,  and  Ray  Gardner, 
of  Englewood,  CO,  because  each  de¬ 
vised  a  program  that  produced  the 
same  124  candidates  (all  sans  As).  I 
also  include  Ray’s  program  (Listing 
Two,  page  1 16),  because  he  accom¬ 
plished  in  24  lines  of  C  code  (seven 
lines  of  which,  in  good  C  coding  prac¬ 
tice,  consist  merely  of  one  brace) 
what  took  Ken  a  page  and  a  half  of 
Pascal.  Ray  claims  his  program,  writ¬ 
ten  in  Lattice  C,  runs  in  under  four 
seconds  on  his  IBM  PC.  Ray  also  says 
that  his  wife  found  the  word  typey  in 


15  minutes,  without  the  benefit  of  a 
computer.  (So  much  for  computers.) 

I  also  mention  Thomas  Shores,  of 
Lincoln,  NB,  because,  while  his  C 
program  does  not  specifically  gener¬ 
ate  words  with  As,  he  did  acknowl¬ 
edge  in  his  accompanying  letter  that 
they  should  be  inserted  “appropriate¬ 
ly”  within  generated  candidates. 
Also,  his  program  generates  all  possi¬ 
ble  candidates,  not  just  those  multi¬ 
plying  out  to  1,000,000.  The  best 
word  he  found  was  moors ,  value 
1,000,350,  but  he  thought  others 
might  come  up  with  better  possibili¬ 
ties.  He  stated  that  if  any  better 
words  were  found,  they  would  have 
one  of  these  values:  999,702; 
999,810;  999,856;  1,000,000; 

1,000,188.  Good  figuring,  particular¬ 
ly  since  the  word  1  think  is  the  best 
has  a  value  of  1 ,000, 1 88.  ( How  come 
you  missed  rooms,  Thomas,  an  ana¬ 
gram  of  moors,  and  comers,  which  is 
also  value  1,000,350?) 

Trent  Garverick,  of  Columbus, 
OH,  gets  an  honorable  mention  for 
his  short  and  sweet  self-documenting 
pseudocode  algorithm,  the  imple¬ 
mentation  of  which  in  UCSD  Pascal 
on  an  Apple  II  yielded  what  I  believe 
to  be  the  word  closest  to  1,000,000 
found  in  the  specified  dictionary. 
Curing  multiplies  out  to  1 ,000, 1 88. 

My  own  program  had  come  up 
with  that  very  word.  My  program 
first  generated  candidates  within  plus 
or  minus  1000  of  1,000,000.  I  then 
“eyeballed”  the  list  for  real  words. 
When  I  found  several  within  350,  I 
narrowed  the  variance.  Each  time  I 
found  a  better  word,  I  started  again 
with  the  smaller  offset.  My  program, 
however,  was  inefficient,  it  took  for¬ 
ever  to  run,  and  it  generated  literally 
thousands  of  possibilities,  mostly  be¬ 
cause  it  spit  out  all  permutations  of 
each  “word,”  not  just  words  sorted  in 
alphabetic  descending  order. 

Trent  based  his  program  on  exami¬ 
nation  of  a  real  dictionary,  rather 
than  on  generation  of  candidates.  I 
still  don’t  think  that  is  the  best  meth¬ 
od,  but  his  program  at  least  does  not 
assume  that  a  word  multiplying  out 
to  exactly  1,000,000  must  exist  nor 
does  it  refuse  to  account  for  words 
with  As  in  them. 

I  also  mention  Tom  Balon,  of  Apa- 


lachin,  NY,  and  Bob  Smith,  of  Wind¬ 
sor,  NY,  who  jointly  produced  a  Pas¬ 
cal  program  on  a  VAX  11-785  to 
examine  a  dictionary  of  33,595 
words,  coming  up  with  curing.  They 
also  found,  tied  at  1 ,000, 1 88,  Nicara¬ 
gua,  but  that  word  is  disqualified  as 
being  a  proper  noun.  Their  program, 
while  relatively  short,  was  not  self- 
documenting.  Joe  Celko,  of  Los  An¬ 
geles,  also  deserves  mention  for  a 
short,  relatively  easy-to-follow  C  pro¬ 
gram  that  produces,  in  combination 
with  a  spelling  checker,  lots  of  possi¬ 
bilities,  the  best  of  which  he  con¬ 
cludes  is  curing. 

And  now  for  the  winner.  The  enve¬ 
lope,  Randy,  please.  Ah,  yes,  Fred 
Smith,  of  Stoneham,  MA,  wrote  a 
short  C  program  into  which  you  input 
a  list  of  words;  the  program  then  de¬ 
termines  the  value  of  each  word.  The 


goatees 

997500 

soybean 

997500 

stogy 

997500 

suety 

997500 

alkaline 

997920 

budlike 

997920 

chuckle 

997920 

clinked 

997920 

driven 

997920 

flanker 

997920 

invader 

997920 

lurked 

997920 

pucker 

997920 

variant 

997920 

viaduct 

997920 

village 

997920 

mopped 

990400 

palmated 

998400 

banquet 

999600 

curing 

1000188 

nicaragua 

1000188 

comers 

1000350 

cozies 

1000350 

moors 

1000350 

rooms 

1000350 

seamier 

1000350 

arkansas 

1000692 

sulks 

1000692 

packets 

1003200 

shakeable 

1003200 

shelve 

1003200 

shoved 

1003200 

specked 

1003200 

stalked 

1003200 

Figure 

Partial  Listing  of  Words 
Produced  by  Fred  Smith's  Million. C 


112 


Dr.  Dobb' s  Journal.  July  1985 


default  is  to  print  only  those  words 
whose  values  lie  within  10,000  of 
1,000,000,  but  it  has  a  command  line 
switch  (great  for  debugging)  that 
prints  all  values.  The  program  also 
ignores  words  of  less  than  five  letters. 
And  here  is  the  clever  part,  the  spark 
of  originality  for  which  Fred  really 
deserves  the  t-shirt:  he  then  used  the 
powerful  capabilities  of  Unix  to  ex¬ 
amine  an  entire  dictionary,  one  of 
over  90,000  words. 

He  used  two  Unix  tools,  each  per¬ 
forming  one  task  well.  (One  was  his 
own  word  value-determination  pro¬ 
gram.)  His  program,  million ,  reads 
all  the  words  in  a  90,000-word  dictio¬ 
nary  called  words  and  pipes  its  output 
to  the  sort  utility,  which  performs  a 
numeric  sort  on  the  second  field  of 
each  line  and  writes  the  final  sorted 
file  to  an  output  file  called  m.w.sort- 
ed.  Fred’s  program  is  Listing  Three 


(pagel  18).  Here  is  the  Unix  command: 

million  <  words  sort  —  n  +  1 

>  m.w.sorted 

So  why,  if  Fm  not  convinced  that 
reading  all  the  words  of  a  dictio¬ 
nary — furthermore,  one  that  does  not 
contain  all  the  possible  words  of  the 
dictionary  I  specified — is  the  best 
method,  do  1  award  Fred  the  t-shirt? 
Two  reasons.  One,  his  program  was 
the  cleverest.  Two,  nobody  did  it  the 
way  I  thought  it  should  be  done. 

I  was  hoping  that  someone  would 
devise  a  program  that  generates  all 
candidates  equal  to  and  close  to 
1,000,000  and  retains  only  those  ob¬ 
viously  or  likely  to  be  English  words, 
coming  up  with  a  small  list  that  a  hu¬ 
man  being  then  could  scan  for  “real” 
words.  What  I  wanted  to  show  was 
the  necessary  cooperation  between 


machines  and  humans  on  problems  of 
this  sort. 

Russ  Nelson,  of  Potsdam,  NY, 
also  deserves  mention  because  he  was 
clever  enough  to  figure  out  precisely 
at  which  sprawling  Silicon  Valley 
corporate  octopus  my  curious  collec¬ 
tion  of  1-Q  Industries  eccentrics  ac¬ 
tually  works,  proving  to  me  that  his 
knowledge  was  no  mere  guess  by  rep¬ 
licating  the  convoluted  programmat¬ 
ic  word  game  with  which  I  produced 
the  name.  I  would  have  awarded  him 
an  honorary  t-shirt  if  he  had  also  fig¬ 
ured  out  what  classical  bit  of  enter¬ 
tainment  inspired  the  transposition. 

Thanks  for  all  the  kind  letters! 

DD| 

Reader  Ballot 

Vote  for  your  favorite  feature/article. 

Circle  Reader  Service  No.  1 97. 


Computer  Calisthenics  (Text  begins  on  page  1 10) 
Listing  One 


PROGRAM  DAVIS ( I NPUT , OUTPUT ) ; 

CONST  LARGE=50 ; 

VAR  M , N r INTEGER; 

(♦AT  EACH  STAGE  BELOW,  N  REFERS  TO  THE 
NUMBER  THAT  MATHEMATICIAN  WAS  TOLD.#) 


FUNCTION  STAGE AIN:  INTEGER) : BOOLEAN; 

(♦AT  STAGE  A,  MR.  P  DOESN'T  KNOW. 
THAT  MEANS  N  HAS  MORE  THAN  ONE 
ACCEPTABLE  FACTORIZATION.#) 

VAR  COUNT, F,-G:  INTEGER; 

BEGIN 

F: =2;  G : =N  DIV  2;  C 0 U N T : =  0 ; 
WHILE  F<G  DO 
BEGIN 

IF  N  MOD  F  =  0  THEN 
COUNT: =C0UNT  +  1 ; 

F : =F  + 1 ;  G:=N  DIV  F 
END; 

STAGEA:  =  (COUNT  >1  ) 

END; 


FUNCTION  STAGEB (N: INTEGER) : BOOLEAN; 

(♦AT  STAGE  B,  MR.  S  KNOWS  MR  P.  DOESN'T 
KNOW.  THAT  MEANS  EACH  ACCEPTABLE  PARTITION 
S+T=N  YIELDS  A  NUMBER  S#T  WHICH  SATISFIES 
STAGE  A.  HE  ALSO  DOES  NOT  KNOW  HIMSELF, 


WHICH  MEANS  N  HAS  MORE  THAN  ONE  ACCEPTABLE 
PARTITION,  WHICH  MERELY  MEANS  N>6.#) 

VAR  S , T , COUNT : INTEGER; 

TEMP: BOOLEAN; 

BEGIN 

S: =2;  T : =  N - S ;  COUNT: =0;  TEMP :  =  ( N >6 ) ; 
WHILE  S< T  DO 
BEGIN 

COUNT : =C0UNT+ 1 ; 

TEMP: =TEMP  AND  STAGEA ( S ♦ T )  ; 

S  .■  =  S  + 1 ;  T :  =N-S 
END; 

STAGEB:  =  (COUNT > 1 )  AND  TEMP 
END; 


FUNCTION  STAGECIN: INTEGER) : BOOLEAN; 

VAR  F , G , COUNT : INTEGER; 

(♦AT  STAGE  C,  P  KNOWS.  THIS  MEANS  THERE 
IS  EXACTLY  ONE  ACCEPTABLE  FACTORIZATION 
OF  M#N  FOR  WHICH  STAGE  B  IS  CORRECT#) 

BEGIN 

F : =2 ;  G:=N  DIV  F;  C0UNT:=0; 

WHILE  F<G  DO 
BEGIN 

IF  (N  MOD  F  =  0)  AND  STAGEB ( F+G ) 
THEN  COUNT : =C0UNT  +  1 ; 

F : =F  + 1 ;  G:=N  DIV  F 
END; 

STAGEC : = (COUNT1 1 ) 

END; 

(Continued  on  page  116) 


Dr.  Dobb’s  Journal,  July  1985 


114 

575 


Computer  Calisthenics  (Listing  Continued,  text  begins  on  page  110) 
Listing  One 


FUNCTION  STAGED (N: I NTEGER ) : BOOLEAN; 

VAR  S , T , COUNT! INTEGER; 

(*AT  STAGE  D,  S  KNOWS  TOO.  THIS  MEANS 
THAT  THERE  IS  EXACTLY  ONE  ACCEPTABLE 
PARTITION  S+T  OF  N  FOR  WHICH  STAGE 
C  IS  CORRECT  FOR  S*T.*> 

BEGIN 

S s  =2 ;  T : =N-S ;  COUNT s  =0? 

WHILE  S<  T  DO 
BEGIN 

IF  STAGEC(S«T)  THEN  COUNT s  =COUNT  + 1 ; 
S ; =  S  + 1 ;  T : =N-S 
END; 

STAGED :  =  (COUNT  = 1 ) 

END; 


BEGIN  ( * M A I N * ) 

FOR  Ms  =3  TO  LARGE  DO 
BEGIN 

WRITE (Ms  3) ; 

IF  (M  MOD  12  =  0)  THEN  WRITELN; 

FOR  N; ■  2  TO  M-l  DO 
IF  STAGEA (M*N)  THEN 
IF  STAGEB (M+N)  THEN 
IF  STAGEC (M*N)  THEN 
IF  STAGEDIM+N)  THEN 
BEGIN 
WRITELN; 

WRI TELN (  THIS  PAIR  WORKS:  '  , Ns  3 , Ms  3) 
END; 

END; 

END. 

End  Listing  One 


Listing  Two 

/*  solve  —  Computer  Calisthenics  solution 

*/ 

char  letter  CD  =  "bdehipty"; 
char  solut ionC20] : 

main  ( ) 

■C 

solve (O,  0,  1 OOOOOOL )  ; 

> 

solve  (length,  next  letter,  poal) 
int  length,  next  letter: 
long  goal ; 

-C 

int  i,  valLie; 

if  <  goal  ==  1  )  -C  /*  found  a  solution,  write  it  ■*/ 

sol  Lit  ion  C  length]  ’  \0’  ; 
puts ( so 1 ut i on ) ; 

>  else  C  /*  partial  solution,  extend  it  */ 

for  (  i  =  next  letter;  let  ter  til:  i++  )  -C 
value  =  letter CiD  —  ’a’  +  1; 

if  <  goal  %  value  ==  0  )  -C 

sol ut ion C length]  =  letterCi]; 
solve  < lengt h+1 ,  i, goal/value) ; 

> 

> 

> 

End  Listing  Two 

(Continued  on  page  118) 


116 


Dr.  Dobb’s  Journal,  July  1985 


Computer  Calisthenics  (Listing  Continued,  text  begins  on  page  110) 
Listing  Three 


1  c  /* 

2  c  - -  mi  I  I  i on. c  < - 

3  c 

4  c  Usage:  million  [-nil  <  infile  >  outfile 

5  c 

6  c  I/O  is  from  stdio.  By  default,  only  words  with  products  >-  930000  and 

7  c  <-  1010000  are  output.  If  -nl  switch  is  used  (‘no  limits’),  the 

8  c  limit  test  is  suppressed,  and  all  words  >-  5  letters  in  length  are 

9  c  printed. 

10  c 


12 

13 

14 

15 


IB  //include  <stdio.h> 

17  //include  <ctype.h> 

18 

19  //define  OPT  1000000  /#  optimum  value  »/ 

20  //define  MIN  OPT  -  10000  /*  minimum  acceptable  */ 

21  //define  MAX  OPT  +  10008  /*  maximum  acceptable  */ 

22  //define  BOOL  unsigned  short 

23  //define  MAXLINE  80  /#  longest  permissible  input  string  */ 

24  //define  TRUE  1 

25  //define  FALSE  0 

2B 

27  main (argc, argv) 

28  int  argc; 

29  char  *argv  [] ; 

30  0 -  ( 

31  |  char  word  IMAXLINEI ;  /*  input  word  to  be  examined  */ 

32  j  register  char  *pw;  /*  register  pointer  thereto  */ 

33  j  register  int  wordlen;  /*  length  of  string  stored  in  word  */ 

34  j  register  unsigned  long  prod;  /»  working  word  value  */ 

35  j  register  BOOL  print  »  TRUE;  /*  ok  to  print  word  if  TRUE  »/ 

3G  j  register  BOOL  limit  -  TRUE;  /*  assume  MIN-MAX  ranqe  limits  */ 

37  | 

38  j  if  ((argc  >  1)  &&  ! strcmp (argv  Ill ,  “-nl")) 

39  |1 -  (  /*  suppress  limit  test  for  printing  */ 

40  |  |  limit-  FALSE; 

41  |1— . —  1 

42  j  while  (  gets(word))  /*  until  end  of  input,  get  next  word  */ 

43  |1 -  I 

44  j |  if  ((wordlen  -  str I en (word) )  <  5)  /*  filter  short  words  */ 

45  i  j  cont i nue; 

46  j  j  for  (print  -  TRUE,  prod  -  1,  pw  -  word  ;  *pw  ;  pw++) 

47  j  |2 - c  1  /*  examine  for  non-alpha  characters, 

48  j j  |  c  calculate  product  */ 

49  j | j  it  (  !  i salpha(*pw) ) 

50  1113 -  ( 

51  | | | |  print  -  FALSE; 

52  j  |  j  j  break; 

53  |||3 -  I 

54  III  prod  *-  ( to  I  oner (*pu)  -  ’a’  +  1); 

55  |  |2 -  I 

56  |  |  if  (((prod  <  MIN)  ||  (prod  >  MAX))  &S.  limit) 

57  j  1 2 -  (  /*  check  limit  conditions  */ 

58  Ml  print  -  FALSE; 

59  ||2 - '  ) 

60  j  |  if  (pr  int) 

61  j  j 2 -  1  /*  is  it  ok  to  print?  */ 

62  ill  pr  int f  ("Xs“ , word) ;  /*  print  word  */ 

63  |||  for  (  ;  wordlen  <  15  ;  wordlen++) 

64  j  I  j 3 -  (  /*  pad  with  blanks  */ 

65  |  |  j  |  putchar  (’  ’ ) ; 

66  |  |  1 3 -  ) 

67  III  pr i nt f ("Xu\n“ , prod) ;  /*  print  word  value  */ 

68  |  { 2 -  ) 

69  |1 -  ) 

70  0 -  ) 

71 

End  Listings 


Dr.  Dobb’s  Journal,  July  1985 


118 

577 


OF  INTEREST 


by  Michael  Swaine 


The  marketing  of  microcomputer 
technology  is  not  an  exact  science.  Lee 
Felsenstein,  designer  of  the  Osborne  1 
and  a  founder  of  the  Community 
Memory  Project  that  wrote  the  Sequi- 
tur  relational  database  management 
system,  is  now  selling  Sequitur 
through  his  company,  Golemics,  at  a 
third  of  its  original  price.  Sequitur 
was  generally  regarded  as  hot  stuff 
when  introduced  some  three  years 
ago,  but  CM’s  faulty  marketing  kept 
it  off  the  dealers’  shelves,  an  article  of 
furniture  to  which  Sequitur  will  re¬ 
main  a  stranger,  since  Sequitur  will 
now  be  sold  strictly  by  mail  order  at 
$250.  “I  kept  my  mouth  shut  [earlier] 
because  I  thought  I  didn’t  know 
enough  to  criticize.  My  Osborne  expe¬ 
rience  made  me  realize  that  nobody 
else  knows  any  better  either,”  Lee 
says.  He’s  wiser  now;  he  turned  forty 
this  spring  . . .  Micro  Craft,  manufac¬ 
turer  of  the  Dimension  computer,  a 
pricey  multiprocessor  machine  curi¬ 
ously  positioned  on  release  as  a  do-all 
end-user  product,  has  also  recently 
risen  from  the  rubble  of  bad  market¬ 
ing.  The  company  is  now  selling  dual- 
68000  Unix  machines  and  enjoying 
post-Chapter  1 1  prosperity  . . .  New 
Functions,  the  distributor  of  Judy,  a 
concurrent  personal  assistant  (aka 
gal/man  Friday,  sidekick,  right-hand 
man,  desktop  accessory  or  pop-up), 
has  one  of  the  more  novel  distribution 
schemes  extant.  Judy  owners  can  gen¬ 
erate  and  give  away  Judy  “clones” — 
limited-time  free  samples — and  when 
the  sampler  buys  his  own  Judy,  the 
clonemaker  gets  a  commission.  New 
Functions  recently  raised  that  com¬ 
mission  from  10%  to  20%. 

The  largest  computer  show  in  the 
world  is  not  held  in  the  U.S.  Atari 
introduced  its  68000-based  ST  com¬ 
puter  at  the  mammoth  Hannover 
Fair  in  Germany  in  April.  Despite 


the  fact  that  the  ST  had  a  tendency  to 
crash  when  asked  to  do  anything 
much  beyond  the  prepared  demo,  our 
correspondent  on  the  scene  found 
both  the  machine  and  its  reception 
impressive,  and  advised  us  to  look 
into  software  development  for  the  ST 
under  the  GEM  environment .  . .  Jack 
Tramiel’s  former  company.  Commo¬ 
dore,  which  has  long  stood  strong  in 
Europe,  lost  money  this  spring  for  the 
first  time  ever  as  dealers  held  out  for 
the  new  128  machine  ...  A  new  Eu¬ 
ropean  Forth  chip  could  provide  some 
competition  for  the  Novix  NC4000 
Forth  chip,  although  the  Novix  chip 
should  actually  be  available  by  the 
time  you  read  this.  The  NC4000  was 
designed  by  Charles  Moore,  who  cre¬ 
ated  Forth,  and  Robert  Murphy;  it 
can  address  four  megabytes  of  mem¬ 
ory  and  is  intended  to  execute  Forth 
code  on  the  order  of  ten  times  the 
speed  of  any  software  implementa¬ 
tion  of  Forth.  Its  designers  expect  it 
to  find  application  in  speed-critical 
areas  like  animation. 


Boards 

RAM  price  wars  are  behind  price  re¬ 
ductions  on  some  hardware  enhance¬ 
ments.  The  price  of  a  do-it-yourself 
Macintosh  upgrade  is  now  below 
$200  .  .  .  DFE  has  cut  prices  on  its 
NS32032  coprocessor  board  for  IBM 
PC/XT/AT  and  PC-compatible  com¬ 
puters.  The  basic  Tiger-32  was  intro¬ 
duced  last  summer  with  a  6MHz 
clock,  32082  MMU,  512K  onboard 
RAM  and  Xenix  3.0  for  $2495,  but 
RAM  price-cutting  has  brought  it 
down  to  $2095  .  .  .  Univation’s  Tur¬ 
bocharger  is  a  9.54  MHz  8086  board 
for  the  IBM  PC  that  sells  for  $1295 
with  640K  on-board  RAM  .  .  .  Real 
Time  Devices  is  selling  a  GPIB  instru¬ 
mentation  bus  interface  board  for 


PCs  for  $299,  a  per-channel  cost  of 
about  $20. 

Some  new  boards  refine  existing 
capabilities:  Interstate  Voice  Prod¬ 
ucts  announced  a  speaker-dependent 
IBM  PC  board  that  has  some  limited 
facility  for  recognizing  words  in  con¬ 
nected  speech  .  . .  We’re  examining 
STB’s  video  board,  which  runs  all 
IBM-compatible  software,  whether  it 
was  designed  for  a  color  or  a  mono¬ 
chrome  monitor,  by  converting 
graphics  displays  into  full-screen  16- 
level  grey-scale  images  . . .  ANEX 
Technology  is  selling  a  banked  2-me¬ 
gabyte  RAM  board  for  PCs  that  are 
running  multi-user  software. 

For  $61  (quantity  one  price),  you 
can  pick  up  a  VLSI  chip  that  lets  you 
directly  replace  an  8088  micropro¬ 
cessor  with  an  80286.  Edsun  Labs’ 
EL286-88  Processor  Converter 
stands  between  the  286  and  the  pe¬ 
ripherals,  playing  an  8088  to  the  pe¬ 
ripherals,  while  accepting  and  con¬ 
verting  all  16-bit  transfer  requests 
from  the  286.  The  idea  is  to  combine 
16-bit  main  memory  operations  with 
low-cost  8-bit  peripherals. 


MSDOS 

Alternatives  or  extensions  to 
MSDOS/PCDOS  are  popping  up  like 
pop-ups.  DRI  has  announced  the  first 
software  to  run  under  its  Graphics 
Environment  Manager  (GEM):  GEM 
Desktop,  Draw,  Write,  Paint,  Word- 
chart  and  Graph.  Desktop  lets  you 
run  up  to  six  utility  programs  on  top 
of  applications,  like  the  Mac’s  desk¬ 
top  accessories;  all  are  supposed  to  be 
shipping  as  you  read  this  .  . .  Digi- 
trol’s  pcShare  substitutes  a  multi¬ 
user  system  with  file  protection  for 
PCDOS  .  .  .  RTCS’s  PC/RTX  substi¬ 
tutes  a  real-time  O/S  (an  implemen¬ 
tation  of  Intel’s  iRMX-86)  .  .  .  Soft- 


120 

578 


Dr.  Dobb’s  Journal.  July  1985 


Logic’s  DoubleDOS  makes  PCDOS 
multitask  for  $99  (“multi”  here 
means  “two”). 

For  software  development  under 
MSDOS/PCDOS,  Phoenix  software 
has  a  program  development  manager 
called  Pmaker  that  they  say  is  similar 
to  the  Unix  “make”  command,  and  a 
program  performance  analyzer  caller 
Pfinish  .  .  .  Advance  Trace86  from 
Morgan  Computing  is  a  debugger/ 
assembler  that  lets  you  back  up  exe¬ 
cution  20  instructions  . . .  Genesis’s 
GeneScope  is  a  new  full-screen  sym¬ 
bolic  debugger  for  the  PC. 

Whether  developing  expert  systems 
on  a  micro  is  the  Next  Big  Thing  or 
just  the  latest  fad,  it’s  getting  cheaper 
and  easier.  Insight  2  from  Level  Five 
Research  lets  you  incorporate  Pascal 
l  routines  and  dBase  II  files  into  a 
knowledge  base  developed  on  a  PC  or 
compatible  ...  AI  pioneer  Donald 
Michie’s  Expert  Ease  package  has  un¬ 
dergone  some  changes  of  ownership 
and  distribution  (it’s  now  distributed 
by  Human  Edge),  the  most  visible 
consequence  of  which  is  a  price  cut 
from  $2000  to  $695. 


C 

“To  boldly  go  where  no  C  software 
distributor  has  gone  before  .  .  . 
Phoenix  calls  Pre-C,  its  lint  superset, 
the  industry’s  first  program  analyzer 
for  the  C  language  designed  to  sup¬ 
port  MSDOS  and  PCDOS  . . .  Micro 
Software  Developers  says  its  C  De¬ 
bugger  is  the  first  true  source  debug¬ 
ger  for  C  .  .  .  Catalytics  has  released 
what  it  calls  the  first  complete  C  in¬ 
terpreter,  its  Safe  C  .  . .  For  Lattice  C 
programmers  who  don’t  necessarily 
hanker  to  be  first,  or  at  least  would 
like  some  company  out  there  on  the 
giddy  edge,  Bill  Hunt,  author  of  The 
C  Toolbox,  has  started  the  Lattice  C 
User’s  Group. 

Whitesmiths  has  upgraded  its 
8086-family  C  compiler  to  support  all 
8086  memory  models  and  for  closer 
adherence  to  their  best  guess  at  the 
emerging  ANSI  standard  . . .  Tool¬ 
works  has  moved  its  C/80  compiler  to 
MSDOS  and  is  selling  it  for  $49.95  . .  . 
Smith  &  Smith  Associates  has  re¬ 
leased  CrossRefC,  a  C  cross-refer- 
ence/listing  generator,  for  $39.95  . . . 


Vance  Info  Systems  announced  C  Lib, 
a  C  function  library  of  over  200  rou¬ 
tines,  for  the  MSDOS  environment. 


CP/M 

CP/M  users  needn’t  feel  left  out  of  the 
pop-up  fad:  Poor  Person  Software’s 
Write-Hand-Man  (that’s  something 
like  a  sidekick,  we  suppose)  includes  a 
notebook,  phonepad,  desk  calendar, 
file  and  directory  viewing  programs 
and  a  communications  program;  new 
functions  can  be  added  fairly  easily.  It 
sells  for  $49.95  and  runs  under  CP/M 
2.2  on  all  CP/M  machines. 

Also  for  CP/M,  MB+  Tools  is 
Minnow  Bear  Software’s  $175  set  of 
programmer  productivity  tools  for 
Pascal  MT  +  . . .  Soft  Advances  says 
that  its  DSD80  full-screen  symbolic 
debugger  is  fully  DDT-compatible 
and  fully  supports  Z80  instructions 
using  either  extended  Intel  or  Zilog 
mnemonics  .  .  .  Selfware’s  Convert 
3.1  lets  you  read,  write  and  format 
disks,  copy  files  and  list  directories  in 
105  CP/M  formats  on  an  MSDOS 
machine,  and  costs  $99. 


Mac 

Just  now,  despite  such  attractions  as 
the  novelty  of  a  Smalltalklike  Forth 
extension  (Kriya’s  Neon),  some  of 
the  most  interesting  products  for  the 
Mac  are  to  be  found  on  user  group 
disks.  Not  surprisingly,  a  high  pro¬ 
portion  of  these  public-domain  or 
user-supported  programs  consists  of 
communications  software,  like  Den¬ 
nis  Brothers’  MacTEP  and  Scott 
Watson’s  Red  Ryder.  You  can  pick 
more  fruit  if  you  have  a  ladder  . .  . 
There  are  also  a  lot  of  fonts,  desk  ac¬ 
cessories  and  graphics;  the  IM  Un¬ 
derground’s  graphics  consist  of  sche¬ 
matics  for  the  machine  .  .  .  Equally 
unsurprising,  given  the  features  of 
the  Mac,  is  the  number  of  attractive¬ 
ly-designed  Mac  newsletters,  includ¬ 
ing  MacTutor,  The  Mac  Street  Jour¬ 
nal  and  MacBriefs. 


Apple  ][ 

Contrary  to  the  impression  given  at  its 
stockholders’  meeting  early  this  year, 
Apple  makes  another  computer,  the 


Apple  bracket-bracket.  Programmers 
are  even  continuing  to  write  software 
for  the  bracket-bracket  . .  .  Micro- 
Sparc  has  released  the  ProDOS  ver¬ 
sion  of  its  Ampersoft  utility  package 
at  $49.95  for  Applesoft  programmers 
. . .  There  is,  of  course,  much  existing 
Apple  ][  software;  InCider  is  selling 
disks  of  software  from  the  magazine 
at  $21.47  ...  Pandora  Software  has 
collected  a  library  of  4000+  public- 
domain  programs  for  the  Apple  ][  and 
is  selling  them  at  $5  per  disk  (at  twen¬ 
ty  programs  per  disk,  that’s  $1000  for 
the  library,  but  still). 


A  Visit  to  the  Fab  Lab 

A  blip  of  deja  vu  struck  as  I  read  in 
the  March  IEEE  Software  about 
Ivan  Guzman  de  Rojas  and  his  plan 
for  doing  natural-language  transla¬ 
tion  via  the  peculiarly  algebraic  An¬ 
dean  Indian  language  Aymara.  Yes, 
trivial  recall  was  working  perfectly: 
halfway  down  the  Andean  peak  of 
press  releases  on  my  desk  I  found  the 
announcement  of  the  opening  of  the 
Aymara  Fab  Lab  in  Sunnyvale.  A  co¬ 
incidence  worthy  of  a  drive  down  the 
peninsula. 

Dean  Norman,  Director  of  the  Ay¬ 
mara  Fab  Lab,  greeted  me  at  the 
door  and  began  talking  immediately 


Dr.  Dobb’s  Journal,  July  1985 


121 

579 


about  Guzman.  Norman  explained 
that  Guzman,  discovering  that  Ay- 
mara,  lacking  irregular  verbs  and 
gender,  was  an  unprecedentedly  logi¬ 
cal  language  (although  its  logic  was 
not  standard  two-valued  logic),  had 
succeeded  in  codifying  the  algorith¬ 
mic  structure  of  its  syntax.  For  the 
first  time,  someone  had  expressed  a 
natural  language  in  software.  Wasn’t 
Guzman,  I  asked,  considering  the  ap¬ 
plication  of  his  achievement  in  the 
design  of  translating  machines,  the 
notion  being  that  computerized  Ay- 
mara  could  serve  as  the  bridge  in  a 
multilanguage  translation  system? 

Right,  Norman  answered,  although 
at  AFL  they  were  taking  the  process  in 
a  somewhat  different  direction.  Did  I 
recall  the  Sapir-Whorf  hypothesis 
from  linguistics?  I  did,  more  or  less: 
that  language  delimited  the  thinkable 
thoughts,  and  thus  our  culture  and 
perceptions.  Under  normal  conditions, 
we  see  only  those  distinctions  for 
which  we  have  words.  In  cultures  in 
which  green  is  not  a  major  linguistic 
division  of  the  spectrum,  it  is  also  not 
a  primary  perceptual  division. 

Norman  nodded.  The  principle  can 
be  applied  to  any  language-proces¬ 
sing  system,  natural  or  artificial.  Cu¬ 
rious  as  it  might  sound,  the  Aymara- 
speaking  software  would,  to  a  certain 
extent,  think  like  an  Aymaran.  With 
its  multi-valued  logic,  it  would  make 
distinctions  that  would  never  occur  to 
a  New  York  stockbroker;  with  its 
lack  of  grammatical  gender,  it  would 
fail  to  make  distinctions  the  stockbro¬ 
ker  would  unconsciously  make.  And 
Aymaran  is  only  the  easiest  lan¬ 
guage,  not  the  only  one,  to  which  the 
principle  can  be  applied.  Employing 
Guzman’s  translation  techniques,  it 
would  be  possible  to  develop  front- 
end  packages  that,  with  the  proper 
filtering  out  of  Aymaran  values  and 
perceptions,  would  embody  pure  up¬ 
per-class  British  perceptions  or  an¬ 
cient  Greek  thought  processes.  We 
could  examine  the  way  judges  in  an¬ 
cient  Sumeria  examined  evidence. 

That,  Norman  explained,  was  what 
they  were  up  to  at  AFL:  just  as  the 
developers  of  expert  systems  were  try¬ 
ing  to  capture  the  knowledge  of  select¬ 
ed  individuals  in  software,  AFL  was 
trying  to  capture  the  style  of  thinking, 

124 


the  intellectual  spirit  of  whole  cul¬ 
tures.  I  mulled  that  over.  Wouldn’t 
there  be  a  great  advantage,  I  asked,  in 
combining  the  two  approaches,  devel¬ 
oping  a  system  with  a  specifiable 
knowledge  base  and  a  specifiable  style 
of  thinking?  Couldn’t  one  develop, 
say,  a  machine  with  the  knowledge  of 
a  high-energy  physicist  and  the  spirit 
of  a  12th-century  Mandarin?  Or  the 
knowledge  of  a  modern  statesman  and 
the  intellectual  style  of  the  first  Conti¬ 
nental  Congress?  But  Norman  sud¬ 
denly  looked  uncomfortable  and  said 
that  he  couldn’t  discuss  details  of  on¬ 
going  projects. 


Reference  Map 

Golemics,  Inc. 

2600  10th  St. 
Berkeley,  CA  94710 
(415)  486-8347 

Reader  Service  No.  101. 


Micro  Craft  Corp. 

4747  Irving  Blvd.,  Ste  241 
Dallas,  TX  75247 
(214)  630-2562 

Reader  Service  No.  103. 


New  Functions 
P.O.  Box  BB 

Staten  Island,  NY  10302 
(718)  273-2668 

Reader  Service  No.  105. 


Atari 

1 196  Borregas 
Sunnyvale,  CA  94086 
(408)745-2000 

Reader  Service  No.  107. 

Dr.  Alan  Winfield 

Metaforth  Computer  Systems  Ltd. 

Unit  2  B  The  Newlands  Center 

Inglemire  Lane 

Hull  HU6  7TQ 

England 

Reader  Service  No.  109. 


Novix 

10590  North  Tantau  Ave. 
Cupertino,  CA  95014 
(408)996-9363 

Reader  Service  No.  111. 

DFE  Electronic  Data  Systems 
USA:  5820  Stoneridge  Mall  Rd. 
Suite  1 1 5 


Pleasanton,  CA  94566 
(415)  847-2024 
Europe:  Singerstrasse  3, 

7513  Stutensee 
West  Germany 
07244-901 1 

Reader  Service  No.  113. 

Real  Time  Devices,  Inc. 

1930  Park  Forest  Ave. 

P.O.  Box  906 

State  College,  PA  16804 

(814) 234-8087 

Reader  Service  No.  115. 

Interstate  Voice  Products 
1849  W.  Sequoia  Ave. 

Orange,  CA  92668 
(714)937-9010 

Reader  Service  No.  117. 

STB  Systems,  Inc. 

601  N.  Glenville,  Ste  125 
Richardson,  TX  7508 1 
(214) 234-8750 

Reader  Service  No.  119. 

Univation 

1037  North  Fair  Oaks  Ave. 
Sunnyvale,  CA  94089 
(408)  745-0180 

Reader  Service  No.  121. 

AN  EX  Technology,  Inc. 

151  North  Route  9W 
Congers,  NY  10920 
(914)  268-2400 

Reader  Service  No.  123. 

Edsun  Laboratories,  Inc. 

7  Sears  Rd. 

Wayland,  MA  01778 
(617) 358-5667 

Reader  Service  No.  125. 

Digital  Research  Inc. 

Box  DR1 

Monterey,  CA  93942 
(800)  443-4200 

Reader  Service  No.  127. 

Digitrol  Computers  Inc. 

440  Phillip  St. 

Waterloo,  Ontario 
Canada  N2L  5R9 
(519) 884-4541 

Reader  Service  No.  129. 

RTCS  Corp. 

Dr.  Dobb’s  Journal,  July  1985 


CQA 


1390  Flynn  Rd.,  Unit  E 
Camarillo,  CA  93010 
(805)  987-9781 

Reader  Service  No.  1 31 . 

SoftLogic  Solutions 
530  Chestnut  St. 

Manchester,  NH  03101 
(800)  272-9900 

Reader  Service  No.  133. 

Phoenix  Computer  Products  Corp. 
1416  Providence  Highway,  Ste  220 
Norwood,  MA  02062 
(617)  762-5030 

Reader  Service  No.  135. 

Morgan  Computing  Co.,  Inc. 

P.O.  Box  1  12730 
Dallas,  TX  75011 
(214) 245-4763 

Reader  Service  No.  137. 

Genesis  Microsystems  Corp. 

196  Castro  St. 

Mountain  View,  CA  94041 
(415)964-9001 

Reader  Service  No.  139. 

Level  Five  Research 
4980  South  A-l-A 
Melbourne  Beach,  FL  32951 
(305) 729-9046 

Reader  Service  No.  141. 

Human  Edge  Software  Corp. 

2445  Faber  Place 
Palo  Alto,  CA  94303 
(415)  493-1593 

Reader  Service  No.  143. 

Poor  Person  Software 
3721  Starr  King  Circle 
Palo  Alto,  CA  94306 
(415)  493-3735 

Reader  Service  No.  145. 

Minnow  Bear  Computers 
P.O.  Box  2233  Sta.  A 
Champaign,  IL  61820-8233 
(217) 398-6883 

Reader  Service  No.  147. 

Soft  Advances 
P.O.  Box  49473 
Austin,  TX  78765 
(512)  478-4763 

Reader  Service  No.  149. 


Dr.  Dobb’s  Journal,  July  1985 


Selfware 

3545  Chain  Bridge  Rd.,  Ste  3 
Fairfax,  VA  22030 
(703)  352-2977 

Reader  Service  No.  151. 


Micro-Software  Developers,  Inc. 
214'/2  West  Main  St. 

St.  Charles,  IL  60174 
(312)377-5151 

Reader  Service  No.  1 53. 

Catalytix  Corp. 

55  Wheeler  St. 

Cambridge,  MA  02138 
(617)497-2160 

Reader  Service  No.  155. 

Lattice  C  User’s  Group 
P.O.  Box  3072 
Glen  Ellyn,  IL  601 38 
(312) 858-7950 

Reader  Service  No.  157. 


Whitesmiths 
97  Lowell  Rd. 
Concord,  MA  01742 
(617)  369-8499 

Reader  Service  No.  159. 


The  Software  Toolworks 
15233  Ventura  Blvd.,  Ste  1118 
Sherman  Oaks,  CA  91403 
(818)986-4885 

Reader  Service  No.  161. 


Smith  &  Smith  Associates 
P.O.  Box  160 
Hunt  Valley,  MD  21030 
(301)  666-8129 

Reader  Service  No.  163. 


Vance  Info  Systems 
2818  Clay  St. 

San  Francisco,  CA  94115 
(415)  922-6539 

Reader  Service  No.  165. 


Kriya 

505  N.  Lake  Shore  Dr.  Suite  5510 
Chicago,  IL  6061 1 
(312) 822-0624 

Reader  Service  No.  167. 


Dennis  Brothers’  MacTEP 
Berkeley  Mac  Users  Group 
1 442A  Walnut  St.,  Ste.  153 
Berkeley,  CA  94709 

Reader  Service  No.  169. 


Red  Ryder 
The  FreeSoft  Co. 

10828  Lacklink 

St.  Louis,  MO  63114 

(314)428-8057 

Reader  Service  No.  171. 

The  IM  Underground 
715  Church  St.  #16 
Ann  Arbor,  MI  48104 

Reader  Service  No.  1 73. 

MacTutor 
P.O.  Box  846 
Placentia,  CA  92670 
(714)993-9939 

Reader  Service  No.  175. 

The  Mac  Street  Journal 
P.O.  Box  6686 
Yorkville  Station 
New  York,  NY  10128 

Reader  Service  No.  1 77. 

MacBriefs 
P.O.  Box  2178 

Huntington  Beach,  CA  92647 
(714)  842-0518 

Reader  Service  No.  1 79. 

MicroSparc 
45  Winthrop  St. 

Concord,  MA  01742 
(617) 371-1660 

Reader  Service  No.  181. 

InCider 

CW  Communications/Peterborough 
Peterborough,  NH  03458 

Reader  Service  No.  183. 

Pandora  Software 
P.O.  Box  55 
Clearfield,  UT  84015 

Reader  Service  No.  185. 

IEEE 

345  East  47th  St. 

New  York,  NY  10017 

Reader  Service  No.  187. 

Aymara  Fab  Lab 
1974  Gardner  St. 

Sunnyvale,  CA  94086 

Reader  Service  No.  189. 

DDJ 

Reader  Ballot 

Vote  for  your  favorite  feature/article. 

Circle  Reader  Service  No.  1 98. 

126 

581 


#106  AUGUST  198S 


limizer 

ode 


MSDOS 


CP/M  Exd 
On  Line 


Dr.  Dobb’s  Journal 


Editorial 

Editor-In-Chief  Michael  Swaine 
Managing  Editor  Frank  DeRose 
Technical  Editor  Alex  Ragen 
Editorial  Assistant  Sara  Noah 
Contributing  Editors  Robert  Blum, 

Dave  Cortesi, 

Ray  Duncan, 

Allen  Holub 
Copy  Editor  Polly  Koch 
Typesetter  Jean  Aring 
Production 
Design/Production 

Director  Delta  Penna 
Art  Director  Shelley  Rae  Doeden 
Production  Assistant  Allda  Hinton 
Cover  Artist  William  Cone 
Circulation 

Sub.  Fulfillment  Mgr.  Stephanie  Barber 
Subscription  Mgr.  Maureen  Snee 
Book  Marketing  Mgr.  Jane  Sharninghouse 
Single  Copy  Sales  Mgr.  Kathleen  Boyd 
Administration 
Finance  Manager  Sandra  Dunie 
Business  Manager  Betty  Trickett 
Accounts  Payable  Sup*.  Mayda  Lopez-Quintana 
Accounts  Payable  Asst.  Denise  Giannini 
Billing  Coordinator  Laura  Di  Lazzaro 
Administrative  Coordinator  Kobi  Morgan 
Advertising 
Advertising  Director 
Shawn  Horst  (415)424-0600 
Advertising  Sales 

Walter  Andrzejewski  (617)  567-8361 
Lisa  Boudreau  (415)  424-0600 
BethDudas  (714)643-9439 
Michele  Beaty  (317)  875-0557 
Advertising  Systems  Manager 
Ron  Copeland  (415)424-0600 
DDJ  Classifieds 
Tim  Ortiz  (415)424-0600 
Advertising  Administrative  Manager 
Alice  Abrams  (415)424-0600 

- - - - - - - - - 

M&T  Publishing,  Inc. 

Chairman  of  the  Board  Otmar  Weber 
Director  C.F.  von  Quadt 
President  Laird  Foshay 


Entire  contents  copyright e  1985  by  M&T  Publishing, 
Inc.  unless  otherwise  noted  on  specific  articles.  All 
rights  reserved. 

Dr.  Dobb’s  Journal  (USPS  307690)  is  published 
monthly  by  M&T  Publishing,  inc.,  2464  Embarcadero 
Way,  Palo  Alto,  CA  94303,  (415)  424-0600.  Second 
class  postage  paid  at  Palo  Alto  and  at  additional  entry 
I  points. 

Address  correction  requested.  Postmaster:  Send  Form 
3579  to  Dr.  Dobb's  Journal,  P.O.  Box  27809,  San 
Diego,  CA  921 28.  ISSN  0278-6508 

Subscription  Rates:  $25  per  year  within  the  United 
States,  $46  for  airmail  to  Canada,  $62  for  airmail  to 
other  countries.  Foreign  subscriptions  must  be  pre-paid 
in  US.  Dollars,  drawn  on  a  US.  Bank.  For  subscription 
problems,  call:  outside  of  CA  800-321-3333;  within  CA 
619-485-9623  or  566-6947. 

Foreign  Distributor:  Worldwide  Media  Service,  Inc., 
386  Park  Ave.  South,  New  York,  NY  10016,  (212) 
686- 1 520  TELEX:  620430  (WUI) 


People's  Computer  Company 

Dr.  Dobb’s  Journal  is  published  by  M&T  Publishing, 
:  ,  Inc.  under  license  from  People's  Computer  Company, 

§2682  Bishop  Dr.,  Suite  107,  San  Ramon,  CA  94583,  a 
non-profit,  educational  corporation. 


August  1985 
Volume  10,  Issue  8 


CONTENTS 


This  Month 

This  month  DDJ  focuses  its  attention  on  C.  The  issue  should  contain  a  little 
something  for  everybody.  One  “little  something”  for  all  our  readers  is  Jim 
Hendrix’  “Small-C  Update.”  After  reading  this  latest  chapter  in  the  story  of 
the  compiler  first  published  in  the  pages  of  Dr.  Dobb's,  MSDOS  C  program¬ 
mers  can  go  straight  to  the  review  of  MSDOS  C  compilers,  while  CP/M  C 
programmers  can  flip  to  David  Fox’  article  on  optimizing  assembly  language 
output  from  the  Software  Toolworks’  C/80.  Unix  gurus,  on  the  other  hand, 
will  probably  want  to  turn  to  this  month’s  C  Chest,  where  they  can  find  an 
MSDOS  version  of  one  of  their  favorite  Unix  utilities,  make.  We  hope  you  will 
find  something  you  like. 


Writers'  Guidelines 

What  are  the  topics  on  which  DDJ  would  like  to  receive  articles?  What  style  of 
writing  is  appropriate  to  DDJ1  How  should  I  format  my  text?  How  should  I 
format  listings  and  how  extensively  should  I  comment  them?  Should  I  submit 
hard  copy  of  my  manuscript,  or  a  diskette,  or  both?  How  much  will  I  get  paid 
for  my  contribution?  The  answers  to  these  and  other  frequently-asked  ques¬ 
tions  can  be  found  in  the  latest  revision  of  the  Writers’  Guidelines  for  Dr. 
Dobb’s  Journal.  To  obtain  a  copy  along  with  the  current  editorial  calendar, 
drop  us  a  note. 


This  Month's  Referees 


Richard  Relph,  ELXSI 

Richard  G.  Larson,  University  of  Illinois  at  Chicago 
James  Hendrix,  University  of  Mississippi 


4 


Dr.  Dobb's  Journal,  August  1985 


Dr.  Dobb’s  Journal 


ARTICLES 

C  Compilers  for  MSDOS 

by  the  PicoNet  C-SIG  and  the  SVCS 

30 

The  C-SIG  and  SVCS  have  assembled  a  suite  of  30 
benchmarks,  run  them  through  the  most  popular  MSDOS 
compilers  and  interpreters  and  tabulated  the  results. 

(Reader  Ballot  No.  191). 

A  Peephole  Optimizer  for  Assembly 
Language  Source  Code 

by  David  L.  Fox 

56 

Using  regular  expressions  you  can  optimize  the  output  of 
any  compiler  producing  assembly  language  code.  (Reader 
Ballot  No.  192). 

Small-C  Update 

by  James  Hendrix 

84 

An  update  on  the  compiler  first  published  in  the  pages  of 
DDJ:  bug  fixes,  improvements  and  MAC,  a  new  Small-C 
macro  assembler.  (Reader  Ballot  No.  193). 

Asynchronous  Protocols 

by  David  W.  Carroll 

92 

A  description  of  the  features,  strengths  and  weaknesses  of 
the  most  popular  asynchronous  protocols  in  use  today. 

(Reader  Ballot  No.  194) 

COLUMNS 

Dr.  Dobb's  Clinic 

by  D.  E.  Cortesi 

20 

DRI  Generic  Sales;  the  Z80;  nesting  MSDOS;  quasi 
random  number  generators.  (Reader  Ballot  No.  1 90). 

C  Chest 

by  Allen  Holub 

96 

Allen  Holub  reviews  three  MSDOS  implementations  of  the 
Unix  utility  “make”  and  offers  one  of  his  own.  (Reader 
Ballot  No.  195). 

1 6-Bit  Software  Toolbox 

by  Ray  Duncan 

116 

A  bug  in  TEE;  musings  on  DRI’s  GEM-Paint  and 
Concurrent  PCDOS;  square  roots;  more  Mac  Feedback. 

(Reader  Ballot  No.  196). 

CP/M  Exchange 

by  Robert  Blum 

124 

The  CP/M  Exchange  RCP/M;  a  catalog  of  free  CP/M 
software;  exiting  properly  to  CP/M.  Reader  Service  No. 

197). 

DEPARTMENTS 

Editorial 

6 

Letters 

10 

DDJ  Classified 

89 

Advertiser  Index 

128 

Dr.  Dobb’s  Journal,  August  1985 


5 

585 


EDITORIAL 


We’d  like  to  congratulate  Microsoft,  Lifeboat,  and  PC  World  for 
their  jointly-sponsored  C-85  conference  in  San  Francisco  in  May. 
All  the  DDJ  editorial  staff  attended  (Allen  Holub,  our  C  columnist, 
and  Richard  Relph,  coordinator  of  the  C  compiler  review  in  this  issue,  were 
among  the  speakers),  and  we  found  it  educational  and  well-planned. 

One  session  was  of  particular  interest  to  us.  At  a  discussion  of  compiler  and 
library  evaluation  and  benchmarking,  a  speaker  chastised  computer  publica¬ 
tions  for  the  inadequacy  of  their  technical  reviews.  We  sat  in  the  audience 
nodding  in  agreement. 

It’s  true:  today’s  computer  magazines  typically  do  not  provide  adequate 
information  to  permit  software  developers  to  choose  one  compiler  or  program¬ 
ming  tool  over  another.  A  relatively  conscientious  reviewer  may  (1)  run  three 
benchmark  tests,  usually  a  floating-point  test,  the  Fibonacci  and  the  Sieve  of 
Eratosthenes;  (2)  list  the  product’s  features;  and  (3)  give  a  clear  picture  of  his 
own  experiences  in  using  the  product.  Most  do  less. 

The  C-85  conference  session  pointed  out  the  need  for  a  higher  level  of 
criticism,  at  least  in  the  evaluation  of  technical  products.  This  is  an  area  in 
which  the  vendors  and  customers  are  both  highly  knowledgable.  Programmers 
and  system  designers  read  reviews  for  solid  information  on  which  to  base 
decisions  affecting  their  livelihoods.  Vendors  deserve  an  accurate  appraisal  of 
their  products  in  terms  that  matter  to  their  customers. 

A  useful  compiler  review  should,  we  think,  assess  completeness,  correctness, 
adherence  to  standards,  and  use  a  widely-available  suite  of  benchmarks  ap¬ 
plied  in  a  repeatable  manner  to  measure  all  the  major  aspects  of  compile-  and 
run-time  performance.  Publication  of  a  review  should  constitute  a  commit¬ 
ment  to  continue  to  evaluate  the  product,  and  to  publish  updates  and  correc¬ 
tions  when  appropriate.  And  no  review  should  be  the  work  of  a  single  individ¬ 
ual,  no  matter  how  knowledgable  or  above  reproach. 

We’ve  tried  to  provide  such  a  useful  review  in  this  month’s  evaluation  of  13 
C  compilers,  done  by  over  a  dozen  programmers  using  several  dozen  bench¬ 
marks,  with  several  pairs  of  eyes  reading  the  manuscript.  But  we  don’t  claim 
we  brought  it  off  flawlessly,  and  we’ll  be  following  up  on  this  review  with 
updates,  corrections  (if  necessary),  and  comparable  benchmark  results  for 
other  compilers. 

For  us,  and  we  think  for  computer  publications  generally,  this  is  new  ground, 
we  don’t  believe  that  any  computer  magazine  has  been  doing  technical  reviews 
the  way  they  should  be  done,  and  that  includes  Dr.  Dobb’s  Journal.  We  hope  to 
change  that,  and  this  issue’s  C  compiler  review  is  one  step  in  that  direction. 

We  plan  to  take  some  more  steps  in  the  direction  of  useful  reviews  in  the 
coming  months.  One  desirable  feature  that  this  month’s  C  compiler  review 
lacks  is  a  single  benchmark  test  providing  a  real-world  mix  of  functions;  we’re 
looking  into  some  tests  that  purport  to  do  this.  We  welcome  your  feedback  and 
suggestions. 

^cLJ 

Michael  Swaine 


6 


G.QC 


Dr.  Dobb’s  Journal,  August  1985 


Tiny  BASIC 

Dear  Dr.  Dobb’s : 

1  read  with  great  interest  the  article 
“Tiny  BASIC  for  the  68000,”  which 
ran  in  the  February  1985  issue  of 
your  magazine.  I  have  been  a  long¬ 
time  68000  programmer  and  have 
written  a  full  BASIC  interpreter  for 
the  machine.  But,  dear  Doctor,  a 
68000  BASIC  only  about  10%  faster 
than  a  Z80?  I  quickly  got  a  copy  of 
the  source  from  the  bulletin  board 
and  started  probing.  Mr.  Brandly  did 
a  nice  job  of  coding,  but  he  missed  a 
few  tricks.  It  is  relatively  easy  to  do  a 
substantial  speed-up  without  increas¬ 
ing  size  significantly  or  changing  any 
outward  functionality. 

The  main  problem  in  this  BASIC 
implementation  is  the  TSTC  subrou¬ 
tine.  TSTC  compares  the  current 
character  against  a  test  value  and 
branches  to  a  specified  location  if 
they  do  not  match.  TSTC  gets  used  a 
lot  in  even  simple  statements;  10 
X=  1  (crlf)  calls  it  7  times.  I  count  98 
clocks  for  TSTC  if  the  characters 
match,  and  102  clocks  if  they  do  not 
match.  Inline  code  to  replace  TSTC 
could  be  made  a  macro  as  follows: 

TSTC  MACRO 

BSR  IGNBLK 
CMPI.B  #'&1',(A0) 
BNE  &2 
ADDQ.L  #1,A0 
ENDM 

which  executes  in  46  clocks  (for 
match)  and  40  clocks  (no  match). 
This  gives  a  savings  of  52  to  62  clocks 
per  use  of  TSTC  ...  or  almost  400 
clocks  in  a  simple  assignment  state¬ 
ment.  Any  statement  involving  an  ex¬ 
pression  will  see  a  similar  (or  greater) 
improvement. 

The  second  improvement  also  oc¬ 
curs  in  the  expression  evaluator  routine 
EXPR.  (This  is  where  most  improve¬ 
ments  are  to  be  found,  since  expression 


evaluation  is  key  to  the  interpreter’s 
performance.)  EXPR  uses  the  EXEC 
subroutine  to  check  for  relational  oper¬ 
ators  (=,<,>,<=,>  =  ).  EXEC  is  a 
table-searching  routine  normally  used 
to  check  for  keywords.  It  includes  a  lot 
of  overhead  to  check  for  shorthanded 
keywords  that  can  never  occur  in  rela- 
tionals.  Also,  the  most  usual  case — no 
relational-  requires  searching  the  six 
possible  relationals  first  before  discov¬ 
ering  the  default  condition.  My  sug¬ 
gested  improvement  treats  relational 
operators  as  a  special  case,  removing 
them  from  the  EXEC  tables  and  substi¬ 
tuting  the  code  in  Listing  One  (page 
16)  for  the  EXEC  call  at  the  top  of 
EXPR.  Note  that  the  default  case  (no 
relational)  now  takes  only  three  com¬ 
pares.  This  is  significant  because  it  is 
used  every  time  an  expression  (num¬ 
ber,  variable,  etc. )  is  encountered. 

The  third  improvement  is  found  in 
the  find-lines  routines:  (FNDLN, 
ENDLNP,  FNDNXT,  and  FNDSKP.) 
These  routines  find  heavy  usage  in 
GOTOs,  GOSUBs,  and  IFs.  The  point 
here  is  that  the  original  coding  resets 
register  A2  to  the  end  of  the  program 
text  for  each  line.  This  24  clock  over¬ 
head  per  line  can  be  saved  by  a  little 
re-ordering  and  a  few  extra  bytes 
Listing  Two,  page  16.) 

My  final  improvements  do  not  af¬ 
fect  the  execution  speed  of  the  inter¬ 
preter,  but  rather  speed  up  program 
editing.  The  block-move  subroutines 
MVUP  and  MVDOWN  can  be  speed¬ 
ed  up  quite  a  bit  at  the  cost  of  some 
additional  code.  I’ll  illustrate  with 
MVUP.  It  is  currently  coded: 

MVUP  CMP.L  A1,A3 
BEQ  MVRET 
MOVE.B 

(A1)+,(A2)  + 

BRA  MVUP 

* 

MVRET  RTS 


This  requires  36  clocks  per  byte 
moved.  If  one  calculates  the  number 
of  bytes  to  be  moved  outside  the  loop, 
a  much  tighter  inner  loop  (22  clocks 
per  byte)  is  possible  (see  Listing 
Three,  page  17).  The  set-up  overhead 
is  48  clocks,  so  it  takes  only  4  cycles 
through  the  inner  loop  to  outweigh  it. 
This  version  of  MVUP  can  handle  a 
maximum  of  65,536  bytes  (the  origi¬ 
nal  had  no  restriction),  but  this 
should  not  prove  much  of  a  problem 
in  Tiny  BASIC  programs. 

A  final  note  to  the  Doctor:  please 
keep  articles  like  this  coming!  There 
is  nothing  that  improves  the  “breed” 
of  programmers  like  quality  exam¬ 
ples.  Thank  you. 

Sincerely  yours, 

Robert  D.  Grappel 
28  Buckmaster  Drive 
Concord,  MA  01742 

Ellipses 

Dear  DDJ, 

Many  thanks  to  Tom  Hogan  for  his 
excellent  derivation  of  the  DV  (deci¬ 
sion  variable)  method  for  plotting  el¬ 
lipses.  His  explanation  of  this  poten¬ 
tially  confusing  subject  is  very  clear. 
This  general  approach,  sometimes 
called  ‘displacement  comparison’  ap¬ 
pears  elsewhere  in  the  literature,  but 
this  is  the  first  time  I  have  seen  it  ap¬ 
plied  to  ellipses. 

I  would  like  to  point  out  a  minor 
error  in  the  derivation,  and  then  show 
how  the  full  potential  of  the  method 
can  be  achieved  by  one  more  refine¬ 
ment  that  eliminates  all  multiplica¬ 
tion  within  the  loops. 

The  error  is  in  Equation  10.  If  the 
x-axis  is  the  major  axis,  the  initial 
point  is  at 

(0,  RJ,  not  (0,|-  Rm) 

Equation  10  is  correct  if  the  first 
term  is  changed  to  reflect  this.  The 


Dr.  Dobb's  Journal,  August  1985 


10 

587 


situation  is  clarified  if  one  considers  a 
different  form  of  the  equation  of  the 
ellipse: 

x2  +  y2  =  1 

a2  b2 

where  a  and  b  are  the  radii  along  the 
x  the  y  axes,  respectively. 

If  we  let  a  =  a2  and  /3  =  b2,  then 
Mr.  Hogan’s  Equation  1  is  equiva¬ 
lent.  It  is  clear  that  the  aspect  ratio  is 
b/a,  and  Mr.  Hogan’s  use  of  the  as¬ 
pect  ratio  is  consistent,  except  for 
equation  10.  If  the  x-axis  is  the  major 
axis,  then  Rm  =  a,  and  the  initial 
point  is  at  b,  which  is  given  by  Rm  X 
aspect  ratio,  or  Rm  X  (b/a).  (We  are 
assuming  that  the  center  of  the  el¬ 
lipse  is  at  (0,  0)  ).  This  is  a  fairly  ob¬ 
vious  error,  but  it  could  confuse  the 
unwary  (me,  for  example).  The  state¬ 
ment  in  the  program  which  initializes 
d  is  correct,  so  I  assume  Mr.  Hogan 
found  the  error  and  simply  over¬ 
looked  this  one  instance. 

Much  more  interesting  is  an  ex¬ 
amination  of  the  operations  that  are 
performed  within  the  loops  for  com¬ 
puting  successive  dot  locations.  An 
example  is  the  computation  of  ‘d’  at 
the  end  of  the  first  loop 

(d  +  =  two_beta  * 

(3  +  (rel_x  <<  1)  )  ) 

Note  that  every  term  in  the  computa¬ 
tion  is  a  constant  except  for  rel_x, 
and  further,  that  rel_x  is  increment¬ 
ed  by  exactly  one  every  time  the  loop 
is  executed.  So  let’s  rewrite  that  line 
for  clarity: 

(d+=  two_beta*3  + 
two_beta*rel_x*2) 

or  in  human  rather  than  computer 
math 

(d  +  =  6)3  +  4/3xrel) 

Now  we  will  cleverly  initialize  our 
added  variable  d_norm  to  2/3  before 
entering  the  loop,  so  every  pass  we  add 
4/3  to  d_norm  before  adding  it,  in 
turn,  to  the  former  value  of  d. 

All  of  the  multiplication  operations 
within  the  loops  can  be  replaced  by 
additions  in  a  similar  way,  as  demon¬ 


strated  in  Listing  Four  (page  18)  (we 
use  x,  y  in  place  of  rel _ x,  rel_y  to  de¬ 

note  the  local  coordinates  relative  to 
the  center  of  the  ellipse,  and  we  don’t 
bother  to  maintain  the  global  coordi¬ 
nates  row  and  col,  but  the  result  is  the 
same). 

I  will  be  happy  to  discuss  this  with 
anyone  who  wants  to  call  me  at  (206) 
226-3916,  or  via  IEEE  COMPMAIL 
user  name  eric.therkelsen. 

Again,  many  thanks  to  Mr.  Hogan 
for  a  derivation  that  is  a  significant 
contribution  to  the  literature. 
Sincerely, 

Eric  E.  Therkelsen 
ETTS,  Incorporated 
19224  Southeast  164th 
Renton,  WA  98058 

Prolog 

Sirs: 

I  enjoyed  your  March,  1985,  issue  on 
Prolog,  but  thought  I  should  write  to 
warn  your  other  readers  of  possible 


Listing  One 


BSR  IGN6LK 

MOVE . B  ( A0 )+ , D2 
CMPI .B  #"=' ,D2 

6EQ  XP1 5 

* 

CMPI .6  #' >' ,D2 

BNE  XXP1 

★ 

CMPI .B  #'=' , (A0) 
BNE  XP13 

★ 

ADDQ.L  #1 , A0 

BRA  XP11 

* 

XXP1 

CMPI .B  #' <' ,02 

BEQ  XXP2 

★ 

SUBQ.L  #1 , A0 

BRA  XP1 7 

Vr 

XXP2 

CMPI .B  , (A0) 

BNE  XXP3 

★ 

ADDQ.L  #1 , A0 

BRA  XP14 

★ 

XXP3 

CMPI .6  #' >' , (A0) 
BNE  XP16 

★ 

ADDQ.L  #1 , A0 

BRA  XP1 2 

Listing  Two 

FNDLN 

CMPI .L  #*FFFF,D1 
6CC  QHOW 

MOVE . L  TXTBGN , A1 
MOVE . L  TXTUNF ,A2 
SUBQ.L  #1 ,A2 

* 

FNDLNP 

CMPA.L  A1 ,A2 

BCS  FNDRET 

support  problems  with  Programming 
Logic  Systems,  Inc.,  the  U.S.  mar¬ 
keters  of  MicroPROLOG.  Over  the 
past  5  months  I  have  attempted,  with 
no  success  whatsoever,  to  obtain  the 
simple  information  whether  there 
have  been  any  changes  or  upgrades  to 
the  software  in  the  year  or  so  since  I 
purchased  it.  Neither  the  U.S.  or 
British  offices  will  respond  to  repeat¬ 
ed  mail  inquires  (the  British  office 
simply  sent  a  brochure,  in  no  way  an¬ 
swering  my  very  simple  question).  If 
these  people  can’t  respond  to  a  ques¬ 
tion  as  simple  as  that,  it  does  not  bode 
well  for  their  willingness  to  respond 
to  substantive  inquiries,  and  suggests 
to  me  that  one  of  the  other  fine  imple¬ 
mentations  of  the  language  would  be 
a  better  choice. 

Sincerely, 

George  Carey 

2048  Winding  Creek  Ln. 

Marietta,  GA  30064 


skip  blanks 
get  first  char, 
equals? 

yes ,  process  i t 

>  ,  or  >=  ? 
no 

>=? 

no ,  process  > 

skip  past  = 
process  >= 

<,  <=,  or  <>? 
yes 

no,  default  case 
handle  default 

<  =  ? 

no 

yes,  skip  past  = 
process  <= 

<>? 

no ,  process  < 
skip  past  > 

process  <>  End  Listing  One 


note  change 


14 


Dr.  Dobb's  Journal,  August  1985 


* 


MOVE . B  (Al) ,D2 
LSL.L  #8,D2 

MOVE . B  1 ( Al ) ,B2 
CMP  D1,D2 

BCC  FNDRET 

★ 

FI 

ADOCj.L  #2 ,  Al 

F2 

CMP  1  .B  #CR,(A1)  + 
BNE  F2 

★ 

BRA  FNDLNP 

* 

FNDNXT 

MOVE . L  TXTUNF , A2 
SUBQ.L  #1 ,A2 

BRA  FI 

* 

FNDSKP 

MOVE . L  TXTUNF, A2 
SUBQ.L  #1 ,A2 

BRA  F2 

Listing  Three 

MVUP 

MOVE . L  DO , SP) 
MOVE . L  A3, DO 

SUB.L  Al , DO 

BLE  MVRET 

* 

SUBQ.L  #1 , DO 

* 

MV2 

MOVE . B  (A1)+,(A2)+ 
DBRA  DO ,MV2 

* 

MV  RET 

MOVE . L  ( SP)+ , DO 

RTS 

Listing  Four 

note  change 

note  change 

note  change 

note  change 

End  Listing  Two 

preserve  DO 

compute  no.  of  bytes 
no  bytes? 

adjust  for  DBRA 

restore  DO 

End  Listing  Three 


el  1  i  pse_wri  te  (  xc,  yc,  a,  aspect  ) 
f  nt  xc,  yc,  a; 

/•  xc,  yc  are  coords  of  center;  a  is  radius  along  x-axis  •/ 

/•  we  chose  to  input  a  rather  than  Rm  »/ 

float  aspect; 

{ 

long  alpha,  beta; 

long  four_alpha,  four_beta,  alpha_y,  beta_x,  d,  d_exc,  d_norm; 
int  b,  Rm;  / »  b  is  radius  along  y-axis  *  / 

/*  since  a  is  not  necessarily  the  major  radius,  we  will  compute  Rm  »/ 

int  x,  y;  /»  local  coords  relative  to  center  of  ellipse  »/ 


[  initialization  similar  to  Mr.  Hogan's  ] 


alpha_y  =  alpha  *  y;  /*  new  vbls  to  avoid  multiplication  in  loop...*/ 
beta_x  =  OL; 

d_exc  -  -  four_alpha  *  y; 
d_norm  =  beta  <<  1; 

d  =  2»a 1 pha* ( y • ( y-1 ) )  +  alpha  +  2  *  (beta  -  beta*Rm  -  alpha»beta); 

/»  This  is  Mr.  Hogan's  original  statement  -  it  is  correct 
even  though  equation  10  is  not.  »/ 

while  (  a!pha_y  >  beta  x  ) 

{ 

put  dots  (  xc,  yc,  x,  y  ); 
if  (  d  >=  0  ) 

{  /•  exception  condition  -  decrement  y  occasionally  */ 

d  +=  (  d_exc  ♦  =  four_alpha  ); 
a  1 pha _y  -  =  a  1 pha ; 

y--; 

> 

d  *=  (  dnorm  *-  four_beta  )  ;  /*  always  increment  x  */ 

beta_x  *=  beta; 

X  +  +  ; 

} 

/*  slope  is  now  <  -1,  so  proceed  with  rest  of  ellipse  •/ 


[  ' nuf f  said?  ] 


Dr.  Dobb's  Journal.  August  1985 


End  Listings 


17 

589 


DR.  DOBB'S  CLINIC 


DRI  Generic  Sales  Again 

Barry  Watzman  writes  to  contradict 
us  regarding  Digital  Research’s  poli¬ 
cy  on  selling  generic  operating  sys¬ 
tems.  He  claims  that  DRI  is  “not  only 
not  selling  generic  systems  any  long¬ 
er,  they  are  no  longer  licensing  value- 
added  vendors  either.  The  new  rule  is, 
if  you're  not  the  manufacturer  of  the 
computer  on  which  the  implementa¬ 
tion  will  run,  forget  it.” 

By  way  of  proof,  he  sent  along  a 
photostat  of  a  letter  to  him  from  Al¬ 
ice  Clark,  Contract  Administrator  at 
DRI.  It  includes  the  sentence:  “We 
are  not  currently  licensing  companies 
to  modify  and  redistribute  our  soft¬ 
ware  unaccompanied  by  hardware  or 
an  application  program.” 

The  dispute  seems  to  be  over  how 
much  and  what  kind  of  value  a  “val¬ 
ue-added”  vendor  must  add  to  be  ac¬ 
ceptable.  Watzman  wants  to  make  a 
business  of  configuring  DRI  systems 
to  Zenith  hardware  (he  does  it  very 
well  from  all  we  hear).  DRI’s  posi¬ 
tion,  he  says,  prevents  it. 

What  Z800? 

Roger  Smith  was  turning  over  back 
issues  of  DDJ  when  his  eye  was 
caught  by  the  confident  words  of  L. 
Barker  in  this  column  for  November 
1981.  “When  the  Z800  comes  out 
next  year,”  Barker  averred,  “with  the 
Z80  instruction  set  and  multiply  and 
divide,  this  [worrying  about  the  speed 
of  arithmetic]  will  look  like  a  waste  of 
time.” 

That  pin-compatible  Z80  super¬ 
chip  never  appeared,  so  far  as  we 
know.  “Does  anybody  know  what 
happened?”  Smith  wonders,  and  so 
do  we. 

Nesting  MSDOS 

Remember  how  we  finally  got  CP/M 
batch  command  processing  pretty 
well  fixed  up?  If  you  don’t,  you  might 


like  to  refer  to  these  old  Clinics: 

June  1982  for  PAUSE  and  BEEP 
commands 

August  1982  for  null-line,  lower¬ 
case,  and  control  character  fixes 
September  1982  for  the  QUITIE 
program 

October  1982  for  nested  submits 
January  1 984  for  a  PAUSE  that 
works  in  CP/M  Plus 

You  might  think  that  batch  execu¬ 
tion  in  MSDOS  Version  2  wouldn’t 
need  our  help,  but  you’d  be  wrong: 
MSDOS  won't  nest  batch  files.  When 
you  invoke  a  batch  file  in  a  batch  file, 
the  called  file  replaces  its  caller  in¬ 
stead  of  being  its  subroutine. 

We  just  discovered  how  to  do  it. 
You  nest  batch  files  by  nesting  com¬ 
mand  shells.  Command.com,  the 
shell,  can  be  named  as  a  command.  If 
given  a  /c  switch,  it  executes  the  sin¬ 
gle  command  that  follows  on  the  com¬ 
mand  line  and  ends.  If  that  command 
is  a  batch  file,  it  will  be  executed  in 
full,  and  control  will  return  to  the  call¬ 
ing  batch  file.  Type  in  our  demo  file, 
recurse.bat  (Listing  One,  page  24), 
and  try  it  out  with  a  line  like 

recurse  0  1  2  3  4  5 

What’s  nice  is  that  each  level  of 
command.com  has  its  own  environ¬ 
ment.  The  nested  batch  file  can  do  all 
the  sets  and  paths  it  likes;  on  exit,  the 
prior  environment  is  restored.  Add  a 
chkdsk  command  to  the  end  of  the 
file  to  see  how  much  space  each  nest¬ 
ing  level  takes  up. 

Random  Error 

In  February,  we  published  a  quasi¬ 
random  number  generator  (QRNG) 
credited  to  persons  named  Talley  and 
Metropolis  (about  whom  we  know 
nothing  more).  Ever  since  then,  we’ve 


been  backing  away  from  it,  but  we 
have  the  subject  more  or  less  under 
control  now,  mostly  thanks  to  David 
Ross  and  Michael  Barr. 

We  showed  the  QRNG  in  C.  It  had 
two  main  parts.  The  first,  randO(  ), 
purported  to  produce  quasi-random 
numbers  uniformly  distributed  be¬ 
tween  zero  and  MAX.  Take  that  at 
face  value;  we’ll  come  back  to  it. 

The  second  part,  randi(u),  used  the 
first  to  produce  what  purported  to  be 
quasi-random  numbers  uniformly 
distributed  between  1  and  u.  It  did 
not.  Here’s  the  essence  of  how  we 
coded  it: 

randi(u) 
unsigned  u; 

{ 

return(l  +  randO(  )%u); 


The  percent  sign  is  the  C  sign  for 
modulus.  Can  you  see  what’s  wrong 
with  this? 

Michael  Barr  and  Peter  K.  Pearson 
did.  Barr  put  it  best:  “Imagine  you 
adopted  the  following  method  to  find 
random  digits  from  0  to  9:  throw  a 
dart  at  the  face  of  a  clock  and  take  the 
low-order  digit  of  the  number  nearest 
to  where  the  dart  hits.  It  is  evident 
that  you  will  get  the  digits  1  and  2  one 
time  in  six  and  the  other  digits  once  in 
twelve.  Thus  a  random  event  is  trans¬ 
lated  into  one  that  is  still  random,  but 
not  uniformly  distributed.  [Your  pro¬ 
cedure  randi(w)]  does  that.  It  shows 
up  most  clearly  when  u  is  a  sizable 
fraction,  say  2/3,  of  MAX.  In  that 
case,  you  will  get  approximately  twice 
as  many  values  between  0  and  u  /2  as 
between  u  /2  and  n.” 

We  devised  randi(  )  with  the  vague 
mental  image  of  how  the  mod  func¬ 
tion  folds  the  real  number  line,  like 
a  carpenter’s  rule,  into  a  heap  of  u  - 
sized  segments.  And  so  it  does,  but 


20 

ncin 


Dr.  Dobb  s  Journal  August  1985 


the  short  last  piece  improperly  en¬ 
riches  the  low  end  of  the  range.  What 
randi(  )  really  wants  to  do  is  to  re¬ 
scale  the  range  1  .  .  .  MAX  to  fit  in 
the  interval  \  ...  u.  Rescaling  calls 
for,  not  mod,  but  division,  like  this: 

randi(u) 

unsigned  u; 

{ 

return!  1+  randO(  )*u/MAX); 

} 

But  there  are  problems  implementing 
that.  The  main  charm  of  these  rou¬ 
tines  was  that  they  worked  with  16- 
bit  integers.  The  expression  u  /MAX 
is  a  fraction;  if  we  do  randO(  )*u  first, 
its  result  will  exceed  16  bits. 

Now,  randO(  )  makes  use  of  two 
constants:  MAXO  and  MAX.  MAXO  is 
usually  2W',  while  MAX  =  MAXO— I . 
In  our  original  version,  w  was  1 5,  but 
clearly  it  should  be  the  word  size  of 
the  computer,  usually  16.  Supposing 
that  to  be  the  case,  the  expression 

randO(  )*u/MAX0 

can  be  implemented  cheaply  enough 
by  doing  a  16-bit  unsigned  multiply 
and  taking  the  most  significant  16  bits 
of  the  32-bit  result.  Picture  it  as  using 
multiplication  by  u  to  squeeze  out 
some  of  the  high-order  bits  of 
randO(  ). 

The  right  scaling  factor  is  w/MAX 
not  w/MAXO.  But  David  Ross  says 
that  “to  adjust  xw/MAXO  to  make 
it  equal  am/MAX,  just  add  one  when¬ 
ever  xw/(  MAXO*  MAX)  is  greater 
than  0.5,  i.e.  when  2 xu  exceeds 
MAX0*MAX.” 

All  these  binary  manipulations 
make  it  impractical  to  present  the  re¬ 
vised  randOf  )  and  randi(  )  in  any 
language  but  assembly  language. 
Versions  for  the  8086  and  Z80  fol¬ 
low.  If  we  had  versions  for  the  6xxxx 
machines,  we’d  print  ’em  and  will  if 
you  send  us  some. 

Considering  QRlMGs 

The  strongest  impression  we’ve 
gained  from  this  exercise  in  QRNGs 
is  of  our  own  ignorance.  A  lot  is 
known  about  the  design  of  QRNGs, 
and  most  of  it  is  strongly  mathemati¬ 
cal.  The  best  source  we  know  is  Don¬ 


ald  Knuth’s  Seminumerical  Algo¬ 
rithms ,  pages  1-178.  There  oughta 
be  a  law  that  nobody  can  write  about 
QRNGs  until  they  pass  a  midterm  on 
this  material  in  which  case,  this  col¬ 
umn  would  be  on  another  topic  even 
though  we  spent  several  hours  recent¬ 
ly  poring  over  that  chapter. 

We  learned  quite  a  bit  about  linear 
congruential  QRNGs,  but  the  Talley- 
Metropolis  routine  is  not  one  of  those. 
Mathematically,  it  can  be  stated  as: 

Xn  —  ( 

((  Xn.\  +  Xn.2)  mod  MAXO) 

*  2)  mod  MAX 

which  is  not  identical  to  any  of  the 
QRNG  formulae  Knuth  treats  (al¬ 
though  it  bears  an  uncomfortable  re¬ 
semblance  to  the  Fibonacci  sequence, 
whose  output  Knuth  says  “is  definite¬ 
ly  not  random”). 

We  also  learned  that  you  should 
never  base  an  application  on  a  QRNG 
that  hasn’t  been  formally  justified 
and  thoroughly  tested.  Therefore  you 
shouldn’t  rely  on  this  one.  We  publish 
it  because  readers  might  enjoy  work¬ 
ing  on  it,  as  opposed  to  with  it. 

We  also  learned  a  lot  about  testing 
QRNGs;  Knuth  is  particularly  good 
on  that  subject.  In  particular,  we 
know  that  you  can  spend  about  as 
much  time  as  you  like  coding,  run¬ 
ning,  and  analyzing  tests.  The  time 
we  could  spend  on  this  project  was 
severely  limited.  But  here  are  what 
we  and  our  correspondents  have 
learned. 

Period  Length 

A  major  point  of  interest  is  the  length 
of  the  sequence  of  numbers  the 
QRNG  will  produce.  Michael  Barr’s 
approach  to  this  was  interesting  and 
could  be  the  basis  of  more  extensive 
theoretical  and  practical  work.  “You 
can  think  of  it  as  tracing  an  orbit 
among  the  sets  of  pairs  of  numbers 
(a, b)  with  a  and  b  in  the  range 
0  .  .  .  MAX.  And  you  can  ask  the 
question,  what  is  the  length  and  na¬ 
ture  of  that  orbit? 

“Clearly,  (0,0)  is  an  orbit  of  its 
own.  In  particular,  the  generator  can 
never  produce  two  zeroes  in  a  row 
[really?  DEC  ].  This  is,  by  itself,  a 
failure  of  randomness,  perhaps  not  a 
serious  one,  but  nonetheless  a  failure. 


“When  1  tried  it  with  MAXO  =  8,  j 
1  discovered  that  the  other  63  pairs  ' 
were  all  in  one  orbit!  This  was  sur¬ 
prising,  but  when  I  tried  out  other 
values  of  MAXO  (all  even)  I  found 
that  this  is  a  fluke.  For  no  other  small 
value  of  MAXO  do  all  the  nonzero 
pairs  make  up  a  single  orbit.  For  ex¬ 
ample,  for  MAXO  =  6,  there  is  an  or¬ 
bit  of  length  1 4,  one  of  1 6,  and  two  of 
length  1 — (0,0)  as  always  and  (4,4). 
As  a  matter  of  fact,  if  MAXO  is  divisi¬ 
ble  by  6,  there  is  always  that  second 
fixed  point.” 

We’d  been  operating  under  the  as¬ 
sumption  that  a  generator  that  pro¬ 
duced  16-bit  numbers  would  have  a 
maximum  period  of  65,536,  but  that 
proved  not  to  be  the  case.  We  hacked 
together  a  version  of  what  Knuth 
calls  the  collision  test.  This  program 
cleared  64K  bits  (8K  bytes)  then 
called  RandO  256  times  and  set  the 
selected  bits  to  one.  Then  it  began 
getting  and  counting  RandO  values 
and  testing  the  bits  they  indexed.  It 
counted  successive  hits  of  1-bits. 

We  had  presumed  that,  if  the 
QRNG  were  working  right,  it  would 
generate  64K  numbers  and  then  start 
repeating,  hitting  all  256  1-bits  in  a 
row;  or  it  might  hit  them  all  again 
sooner,  indicating  a  short  sequence 
from  the  original  seeds;  or  it  might 
get  into  a  loop  hitting  0-bits  and  nev¬ 
er  return  to  the  original  256  numbers. 

Nothing  of  the  sort  happened.  It 
behaved  instead  like  a  well-distribut¬ 
ed  generator  with  a  much,  much 
longer  period.  It  made  single  hits  at 
intervals  of  roughly  256  probes;  it 
made  double  hits  at  intervals  of 
roughly  64K;  and  when  we  stopped 
after  1.5  million  probes,  it  had  yet  to 
hit  three  1-bits  in  a  row  (intuitively, 
that  should  happen  around  24  mil¬ 
lion),  nor  had  it  looped. 

This  is  an  intriguing  result.  A 
QRNG  with  such  a  long  period  that 
codes  so  tightly  in  a  1 6-bit  machine  is 
a  rare  bird.  We  only  wish  that  some¬ 
body  would  analyze  its  period  from  a 
theoretical  standpoint.  It  would  be  al¬ 
most  as  nice  if  somebody  would  carry 
out  a  longer  collision  test. 

A  question  related  to  period  length 
is:  What  is  a  good  initial  seed?  The 
QRNG  is  initialized  with  two  num¬ 
bers;  what  should  they  be  for  a  maxi- 


Dr.  Dobb  s  Journal,  August  1985 


21 

591 


mal  period?  It's  all  very  well  to  stick 
in  two  16-bit  prime  numbers;  if  we 
had  any  theoretical  understanding, 
we  might  be  able  to  specify  rules  for 
the  selection  of  good  seeds. 

Distribution 

Of  equal  concern  is  the  distribution. 
A  few  short  tests  have  been  made. 
Everett  Carter  generated  1000  num¬ 
bers  using  randi(  1 00)  with  the  flawed 
randi(  )  function.  He  subjected  them 
to  a  Chi-squared  test  with  99  degrees 
of  freedom;  the  result  was  a  probabil¬ 
ity  of  0.82  that  the  sample  was  drawn 
from  a  uniform  distribution.  Says 
Carter,  “if  I  needed  a  uniform  distri¬ 
bution,  I’d  try  for  better  than  0.82." 

Barr  notes  that,  “provided  MAX0 
is  even,  it  is  possible  to  run  the  gener¬ 
ator  backwards.  That  is,  if  you  think 
of  it  as  producing  a  sequence  X|,  X2, 

.  .  .  Xn,  you  can  calculate  Xn.2  from 
X/7  and  Xfj.\. 

Peter  Pearson  made  the  remarkable 
assertion  that  “sets  of  numbers  drawn 
three  at  a  time  from  randO  are  strong¬ 
ly  interdependent.  Specifically,  if  p ,  q, 
and  r  are  the  results  of  three  succes¬ 
sive  calls  on  randO,  the  quantity 
2 (p  +  q  )  —  r  can  have  one  of  only  four 
values:  0,  32767,  65536,  and  98303.” 

But  on  the  other  hand.  Carter  said, 
“On  the  correlation  test,  this  genera¬ 
tor  does  very  well  over  short  spans  (I 
never  tried  long  spans).  This  test 
measures  the  independence  of  the 
numbers  by  calculating  the  correla¬ 
tion  between  one  number  and  a  num¬ 
ber  later  in  the  series.  In  the  tests  that 
I  made  with  a  lag  distance  of  up  to  20 
intervals,  the  correlations  are  below 
0.1  and  most  are  well  under  0.05. 
While  one  might  hope  for  more  inde¬ 
pendence,  these  values  are  very  good 
relatively." 

Clearly  many  more  tests  of  this 
QRNG  should  be  done.  Knuth  recom¬ 
mends  the  runs  test  and  the  collision 
test  as  particularly  powerful  and  de¬ 
scribes  algorithms  for  them  and  sev¬ 
eral  others. 

The  Mitchell-Moore  QRNG 

There  is  another  simple  QRNG,  one 
that  has  guaranteed  good  character¬ 
istics  and  a  ridiculously  long  period. 
Its  only  drawback  is  that  it  needs  55 
words  of  auxiliary  space  and  a  second 
|  QRNG  to  seed  it.  Knuth  credits  the 

28 


method  to  J.  G.  Mitchell  and  D.  P. 
Moore;  its  formula  is: 

Xn  —  (A/;_24  T  A/j.55)  mod  MAX0 

It  is  easily  expressed  in  C;  see  Listing 
Four  (page  27).  It  uses  a  55-integer 
array  initialized  with  numbers  not  all 
of  which  are  even;  usually  these  55 
seeds  are  drawn  from  a  simpler 
QRNG  (Talley-Metropolis  will  do). 
The  constants  24  and  55  are  not  arbi¬ 
trary;  they  were  chosen  because  they 
produce  a  period  of  length  255.  Nor 
are  they  unique;  Knuth  gives  a  table 
of  other  good  pairs.  Use  27  and  98  if 
you  think  you  need  a  period  of  298. 

Random  8086 

The  routines  in  8086  assembly  lan¬ 


guage  may  be  found  in  Listing  Two 
(page  24).  The  seeding  routine  is 
omitted,  as  are  details  of  segment  def¬ 
initions.  Pay  close  attention  to  the 
consumer  warning  in  the  comments. 
It  was  Michael  Barr  who  spotted  the 
elegant  equivalence  between  the  C 
statements 

c  *  =  2:  if  (c>MAX)  c-=  MAX; 
and  the  single  8086  instruction 
rol  ax,l 

Boy,  if  you  ever  see  a  C  optimizer  that 
can  find  that  transformation,  buy  it. 

Random  Z80 

The  QRNG  in  Z80  assembly  lan¬ 
guage  is  in  Listing  Three  (page  24). 
The  most  complicated  part  of  the 
Z80  (or  any  other  8-bit)  version  is  the 
simulation  of  a  16-bit  unsigned  mul- 


Dr.  Dobb's  Clinic  (Text  begins  on  page  20) 

Listing  One 

rem  recurse.bat  called  with  %2  %3  %5  ?6  %8  %9 

if  (J1)==()  exit 

prompt  command  level  %  1 $g 

command  /c  recurse  %2  %3  %5  $6  J7  %8  %9  End  Listing  One 


Listing  Two 


Talley /Metropolis  routines  for  8086.  Consumer  Warning:  This 
generator  is  experimental  and  lacks  a  sound  mathematical 
basis  or  statistical  validation  —  USE  AT  YOUR  OWN  RISK. 


;  RandO:  return  a  16-bit  quasi-random  number  in  AX. 
;  Preserves  all  registers  except  AX,  flags. 

RandO: 


;  Rand  I: 
5 

Rand  I: 


Rand  lx : 


push 

dx 

mov 

dx,Xminus2 

mov 

ax,Xminus1 

mov 

Xminus2 ,ax 

add 

ax,dx 

rol 

ax,  1 

mov 

Xminusl ,ax 

pop 

dx 

ret 

return 

in  AX  a  number 

in  0..CX-1. 

Preserves  all  except  AX 

,  flags. 

push 

dx 

call 

RandO  ;  ax  = 

rand0( ) 

mul 

cx  ;  dxax 

=  u*rand0() 

mov 

ax,dx  ;  ax  = 

u»rand0()/MAX0 

pop 

dx 

test 

ah , 80h 

jz 

Rand  lx 

inc 

ax  ;  ax  = 

u*rand0( )  /MAX 

ret 

End  Listing  Two 


COO 


tiply.  The  one  in  Listing  Three  is  a 
standard  routine  straight  out  of  that 
classic  work,  Z80  Programming  for 
Logic  Design  by  Osborne,  Kane,  Rec¬ 
tor,  and  Jacobson,  published  in  the 
distant  year  of  1978  by  Osborne  and 
Associates  and  still  one  of  our  most 
useful  references. 

The  only  change  we’ve  made  in  Os¬ 
borne’s  multiply  is  to  use  long  jumps 
(JP  opcodes)  instead  of  short  relative 
ones.  A  JP  takes  10  clock  cycles  to  do 
regardless,  while  a  JR  takes  either  7 
ticks  to  fall  through  or  12  to  jump. 
Ordinarily,  a  JR  is  the  opcode  of 
choice  because  it  is  a  byte  shorter 
and,  under  the  assumption  that  it  will 
fall  through  and  be  taken  with  equal 
frequency,  should  consume  half  a 
clock  cycle  less  on  average. 

However,  if  we  can  predict  any¬ 
thing  about  the  frequency  with  which 
a  branch  will  be  taken,  we  should 
make  a  choice  between  jump  types. 
The  last  JP  in  Listing  Three,  for  in¬ 
stance,  will  be  taken  1 5  times  and  fall 
through  but  once.  Clearly  it  ought  to 
be  a  JP,  since  a  JR  would  cost  30 
more  cycles. 

The  first  of  the  three  jumps  should 
be  a  JR.  It  tests  successive  bits  of  the 
multiplier.  In  this  application,  those 
are  bits  of  a  quasi-random  number, 
and  the  50-50  assumption  certainly 
ought  to  hold,  indicating  the  use  of 
JR.  In  a  general-purpose  multiply 
routine,  we  might  expect  that  both 
multiplier  and  multiplicand  will  be 
positive  binary  numbers,  but  even  if 
we  expect  only  positive  numbers  less 
than  215 — so  that  we  expect  the  jump 
to  be  taken  9  of  16  times  (i.e.,  always 
taken  for  the  first  two  bits  and  taken 
for  half  of  the  other  14  bits)  the  JR 
still  squeaks  out  two-tenths  of  a  clock 
cycle  faster  than  a  JP. 

The  middle  jump  is  not  so  obvious, 
but  because  it  will  be  executed  in  any 
manner  on  half  or  fewer  of  the  itera¬ 
tions,  it  is  also  less  important.  In  ef¬ 
fect,  we  choose  here  between  a  5- 
cycle  JP  or  a  3.5/6  cycle  JR.  The 
jump  will  be  taken  when  no  carry  re¬ 
sults  from  adding  the  multiplier  into 
the  low  word  of  the  partial  product. 
At  first  we  thought  that  would  be  3 
out  of  4  times,  a  clear  win  for  JP  over 
JR;  on  reflection,  we  ain’t  so  sure. 
What  do  you  think? 

DDJ 


Listing  Three 

;  Talley /Metropolis  routines  for  Z80.  Consumer  Warning:  This 
;  generator  is  experimental  and  lacks  a  sound  mathematical 
;  basis  or  statistical  validation  —  USE  AT  YOUR  OWN  RISK. 


;  RandO:  return  a  16-bit  quasi-random  number 
;  Preserves  all  except  HL,  flags. 

RandO: 


push 

Id 

Id 

Id 

add 

add 

jr 

inc 

RandOq:  Id 
pop 
ret 


de 

de , (Xminus2) 

hi, (Xminusl ) 

(Xminus2) ,hl 

hl,de 

hi, hi 

nc  , RandOq 

hi 

(Xminusl ) ,hl 
de 


in 


HL. 


Randl:  return  in  HL  a  number  in  0..BC-1. 
Preserves  all  except  HL,  flags. 


J  Requires  Mull6  to  do  DEHL  =  BC*HL 

Rand  I: 


call 

RandO 

;  HL  =  rand0() 

push 

de 

call 

Mull  6 

;  DEHL  =  BC *rand0( ) 

ex 

de  ,hl 

;  HL  =  BC*randO( )/MAX0 

pop 

de 

bit 

0,h 

ret 

z 

inc 

hi 

;  adjust  to  .../MAX 

ret 

;  a  usable  16- 
;  DEHL  as  the 


Mu  1 1 6 : 


Mull6A: 


to-32-bit  multiply  subroutine,  producing 
32-bit  result  of  HL  *  BC,  preserves  BC,  AF. 


loop  count 
multiplier  to  DE 
clear  partial  product 

next  multiplier  bit 

..shifted  to  carry 

..and  0-bit  appended  to  hi  prod 

(save  m'ier  bit) 

next  product  bit  to  carry 

adjust  bit  shifted  earlier 
if  m  'ier  bit  was  1 , 


push 

af 

Id 

a ,  1 6  ; 

ex 

de,hl  ; 

Id 

hl,0  ; 

ex 

de,hl  ; 

add 

hi, hi  ; 

ex 

de,hl  ; 

push 

af  ; 

add 

hi, hi  ; 

jr 

nc ,Mull6B 

inc 

de  ; 

pop 

af  ; 

Jr 

nc ,Mull6C 

add 

hi, be  ; 

JP 

nc  ,  Mu  1 1 6C 

inc 

de  ; 

dec 

a 

JP 

nz,Mull6A 

pop 

ret 

af 

add  multiplicand  to  product 
.  .propogating  carry 


End  Listing  Three 

(Continued  on  page  83) 


29 

593 


C  Compilers  for 

MSDOS 


by  the  C  Special  Interest  Group 
of  PicoNet  and  the  Silicon  Valley 
Computer  Society  under  the 
direction  of  Richard  Relph 

Our  reviewers  found  that  some  of  the 
fastest  compilers  overall  were  relative¬ 
ly  slow  in  doing  screen  I/O,  and  un¬ 
covered  order-of-magnitude  differ¬ 
ences  in  execution  time  and 
generated-code  size. 


What  would  you  consider  the  ideal  credentials  for 
a  reviewer  of  computer  language  compilers? 
Would  you  want  a  software  designer,  or  per¬ 
haps  even  a  compiler  designer?  Should  the  reviewer  be  a 
professional  who  earns  his  or  her  living  at  the  keyboard? 
Or  should  the  review  be  conducted  by  the  weekend  pro¬ 
grammer  who  really  understands  the  value  of  user-friendly 
software  as  well  as  the  frustration  of  user-hostile  documen¬ 
tation? 

The  reviewer  for  this  project  is  all  of  the  above.  This 
report  on  13  C  compilers  for  MSDOS  was  conducted  by 
the  joint  C  Special  Interest  Group  (C-SIG)  of  PicoNet  (a 
non-profit,  general  microcomputer  users’  group)  and  the 
Silicon  Valley  Computer  Society  (SVCS),  an  IBM  PC  us¬ 
ers’  group. 

This  combined  C-SIG  is  composed  of  a  broad  cross- 
section  of  microcomputer  users.  Some  are  language-com¬ 
piler  designers,  some  design  and  maintain  systems  soft¬ 
ware,  still  others  make  their  living  developing  commercial 
software  such  as  spreadsheet  or  data  base  packages. 
There  are  several  novice  programmers  in  the  group,  but 
most  are  seasoned  programmers,  if  not  expert  software 
designers.  The  “reviewer,”  therefore,  is  well  qualified  for 
the  job. 

Background 

The  first  C-SIG  was  organized  in  1983  by  a  group  of  Pi¬ 
coNet  members.  Their  purpose  was  to  provide  a  forum  for 
discussion  and  study  for  those  interested  in  the  C  program¬ 
ming  language.  In  the  beginning,  classes  were  organized, 
and  after  10  or  12  sessions  the  language  was  covered  fairly 
thoroughly.  The  classes  brought  all  members  of  the  group 
to  a  common  basic  level  of  understanding  of  C. 

The  group  soon  graduated  to  developing  software  utili¬ 
ties:  those  tools  that  are  commonly  found  on  the  larger 
systems  for  which  C  was  originally  developed,  but  that 
are  not  commonly  available  for  the  microcomputer  envi¬ 
ronment.  The  C-SIG  also  organized  group  purchases  of 
compilers,  other  software  packages,  and  hardware  at  dis¬ 
counts,  so  that  all  members  could  own  a  quality  C  compil¬ 
er,  participate  in  the  various  group  activities,  and  contrib¬ 
ute  to  group  projects. 

Invariably,  at  the  monthly  meetings  someone  would 
ask  what  was  the  best  current  compiler  (or  version  there¬ 
of),  opening  a  debate  over  which  compilers  were  the  fast¬ 
est,  which  produced  the  optimum  code,  which  conformed 
most  to  “standard”  C.  We  soon  recognized  from  these 
discussions  that  the  group  was  somewhat  inbred;  that  is, 
among  the  20  or  30  members,  only  two  or  three  C  compil¬ 
ers  were  represented  (partly  due  to  group  purchases). 
Then,  near  the  beginning  of  1985,  the  PicoNet  and  SVCS 
C-SIGs  merged. 

SVCS  was  made  up  of  IBM  PC  users.  PicoNet  was  tra¬ 
ditionally  a  CP/M  group,  but  a  large  number  of  the  origi¬ 
nal  C-SIG  members  had  recently  acquired  MSDOS-based 
PCs  through  a  group  purchase.  Out  of  the  group’s  new 
interest  in  MSDOS  came  increased  interest  in  C  compilers 
that  ran  under  MSDOS.  We  soon  discovered  that  the 
number  of  compilers  available  for  the  PC  environment 


30 

594 


Dr.  Dobb’s  Journal,  August  1985 


was  large  indeed,  and  we  quickly  identified  some  24  im¬ 
plementations.  The  questions  of  which  was  fastest,  which 
produced  the  tightest  code,  and  which  was  closest  to  the 
standard  continued  to  come  up,  but  we  learned  that  no 
one  had  the  answers.  We  decided  to  find  them. 

Given  the  magnitude  of  the  task,  the  breadth  of  the 
group,  and  the  number  of  individuals  involved  in  the  pro¬ 
ject,  responsibilities  had  to  be  divided  among  the  partici¬ 
pating  members.  Each  joined  a  group  that  was  assigned  a 
specific  task  critical  to  the  overall  project.  The  groups  had 
responsibilities  in  the  following  areas: 

•  Standards:  responsible  for  the  evaluation  of  compilers’ 
and  libraries’  adherence  to  the  standard  C  language  as 
defined  by  Kernighan  &  Ritchie  (K&R)  in  The  C  Pro¬ 
gramming  Language.  Also  responsible  for  qualifying  all 
benchmarks  written  or  acquired  to  measure  compiler  per¬ 
formance  for  universal  compatibility  and  for  assessing 
each  compiler’s  ease  of  use  (Richard  Relph,  Don  Law¬ 
rence,  Andi  Pisiali,  Stan  Peters). 

•  Benchmarks:  responsible  for  writing  and/or  acquiring  C 
compiler  benchmark  programs  and  for  conducting  bench¬ 
mark  tests  (Fred  Viles,  Steve  Mahn,  Scott  Thomas, 
George  Giomousis,  Bill  Overstreet,  Fletcher  Johnson). 

•  Documentation:  responsible  for  reviewing  documenta¬ 
tion  for  clarity,  completeness,  style,  and  presentation. 
Also  responsible  for  evaluating  customer  service  for  re¬ 
sponsiveness  and  technical  proficiency  (Chuck  Rulofson, 
Don  Hockler,  Bob  Stephan,  Peter  Vutz). 

•  Writers:  to  put  it  all  together  (Irwin  Diehl,  Don  Dodge). 

That’s  the  background — who  we  are,  what  we  did,  why 
we  did  it,  and  how.  Oh  yes,  we  should  mention  the 
“where.”  We’ve  been  most  fortunate  to  have  been  provid¬ 
ed  a  regular  meeting  place  by  ZeroOne  Systems  (former¬ 
ly  Technology  Development  of  California).  ZeroOne  has 
been  a  most  gracious  host  for  the  regular  monthly  meet¬ 
ings  and  the  special  weekend  benchmark  sessions.  Their 
continuing  support  is  appreciated. 

Benchmarks 

The  benchmark  group  was  charged  with  measuring  the 
performance  of  the  code  produced  by  the  various  compil¬ 
ers.  In  the  course  of  several  meetings,  we  decided  on  our 
general  approach  and  on  the  specific  benchmark  func¬ 
tions  to  be  produced.  We  first  discussed  the  possibility  of 
producing  a  real-world  benchmark  program,  one  that 
would  perform  some  useful  function  using  an  appropriate 
mix  of  language  and  library  features.  This  plan,  although 
attractive,  was  problematic  since  we  could  not  agree  on  an 
appropriate  mix.  We  settled  on  a  “surgical”  approach: 
many  small  functions,  each  intended  to  test  a  single  fea¬ 
ture  of  the  compiler  or  its  library. 

We  started  by  drawing  up  a  list  of  every  aspect  of  com¬ 
piler  and  library  performance  that  any  one  of  us  had  ever 
wanted  to  see  in  other  compiler  reviews;  not  surprisingly, 
the  list  was  enormous  and  impossible.  In  the  interest  of 
completing  this  project  at  all,  we  pared  the  list  down, 
ending  with  30  separate  benchmarks  to  test  performance 


in  the  areas  of  code  size;  time  to  compile  and  link;  storage 
class  support;  math;  array  and  pointer  referencing;  func¬ 
tion  calling;  optimization;  screen  and  disk  I/O;  and  gen¬ 
eral  performance.  We  ran  some  of  these  functions  for  dif¬ 
ferent  memory  models  and  some  with  the  8087  options 
available. 

To  get  accurate  and  consistent  results,  we  used  a  com¬ 
mon  format  for  all  the  execution-time  benchmarks.  Each 
benchmark  was  written  as  a  function  that  accepted  a  loop 
count  and  returned  the  measured  time  in  tenths  of  seconds. 
We  wrote  a  pair  of  functions  to  measure  an  interval  of  time 
using  the  system  clock,  avoiding  the  problems  inherent  in 
the  stopwatch  approach.  All  compilers  except  Digital  Re¬ 
search’s  provided  a  library  function  giving  access  to  DOS 
interrupts,  so  the  timer  functions  could  be  written  easily  in 
C  for  all  but  DRI.  (So  we  wrote  an  assembly  routine.)  We 
then  verified  that  the  overhead  in  the  timer  functions  was 
negligible  for  all  compilers.  The  skeleton  for  all  benchmark 
functions  is  in  the  Figure  (below). 

We  adjusted  each  loop  count  to  force  the  benchmark 
into  a  reasonable  time  frame  (about  1  minute),  so  as  to 
minimize  the  percent  error  in  the  timer  functions.  Al¬ 
though  the  system-clock  interrupt  returns  a  measurement 
of  the  time  down  to  hundredths  of  a  second,  the  clock  is 
actually  updated  only  18  times  per  second,  so  the  time 
reported  for  a  given  function  can  vary  by  as  much  as  0.2 
seconds. 

Except  for  the  8087  benchmarks,  all  the  tests  were  per¬ 
formed  on  a  Leading  Edge  PC.  running  MSDOS  2.1 1  and 
equipped  with  640K  RAM  and  a  10  Mb  hard  disk.  These 
machines  are  close  PC-compatibles  running  at  a  clock  rate 
of  7.16  MHz;  consequently,  CPU-bound  benchmarks  will 
take  50  percent  longer  on  an  IBM  PC.  The  Norton  Utilities 
(V3)  confirm  this  performance  factor. 

We  selected  the  Leading  Edge  PC  because  we  had 
made  a  group  buy  of  several  identically  configured  sys¬ 
tems.  We  ran  the  compile-time  and  link-time  benchmarks 
on  the  same  machine  in  two  different  ways:  using  the  hard 
disk,  with  care  taken  to  ensure  that  files  were  read  and 
written  to  the  same  location  for  each  compiler,  and  on  a 
51 2K  JDrive  RAM  disk.  We  ran  the  disk  I/O  benchmarks 
on  the  hard  disk,  again  ensuring  that  the  same  disk  loca- 

benchfn(loop) 

int  loop; 

( 

/*  Initialization  */ 

time_0( );  /*  Start  clock  */ 

for  ( ;  loop  ;  loop - ) 

{ 

/*  Code  to  be  timed  */ 

} 

return(time_n( ));  /*  As  sec/10  */ 

} 

Figure 


Dr.  Dobb’s  Journal,  August  1985 


31 

595 


tions  were  used.  Before  you  moan  “but  I  only  have  a  plain 
IBM  PC  with  floppies,”  remember  that  the  goal  here  was 
to  compare  the  compilers,  not  the  machines.  The  ratio  of 
the  timings  should  be  the  same  anywhere. 

Benchmark  Design 

So  much  for  background.  Now  we  will  discuss  the  bench¬ 
mark  functions,  grouped  by  aspect  to  be  tested.  We  have 
listings  for  all  but  the  compile-  and  link-time  programs, 
which  were  not  actually  executed.  For  a  diskette  (5 '/4-inch 
MSDOS)  with  the  listings  send  a  check  for  $6.00  payable 
to  M&T  Publishing  to:  DDJ  Compiler  Review,  2464  Em- 
barcadero  Way,  Palo  Alto,  CA  94303. 

Code  Size 

We  wanted  to  get  an  idea  of  the  granularity  of  the  library 
and  the  minimum  .EXE  file  size  that  could  be  produced. 
Granularity,  for  those  not  familiar  with  the  term,  refers  to 
how  finely  the  library  is  structured.  A  highly  granular 
library  allows  the  bare  minimum  of  object  code  to  be 
linked  for  each  function.  Four  programs  were  written  for 
these  tests: 

•  minmain.c  Main  program  with  no  executable  code. 

•  minputs.c — Main  program  with  one  puts(  )  call. 

•  minprtf.c—  Main  program  with  one  printff  )  call. 

•  minfio.c-  Main  program  with  calls  on  fopen(  ),  fgetsf  ), 

fputs(  ),  and  fclose(  ). 

We  compiled  and  linked,  but  did  not  execute,  each  pro¬ 
gram.  We  were  especially  interested  in  minprtf.c,  since 
using  printff  )  usually  means  dragging  in  the  whole  float¬ 
ing-point  package.  Some  compilers  provided  an  option  to 
link  with  an  integer-only  version  of  printff  ).  In  those 
cases  we  linked  minprtf.c  twice,  once  with  the  integer- 
only  and  once  for  the  full  floating-point  versions  of 
printf(  ).  See  Table  1  (page  34)  for  the  results. 

Clocking  Compile  and  Link  Time 

Since  most  of  the  performance  benchmarks  are  very  short 
and  not  representative  of  typical  programs,  we  used  sepa¬ 
rate  programs  ranging  from  1  to  1000  lines  for  this  test. 
They  were: 

•docl.c — A  one-liner,  to  show  the  minimum  overhead. 
•doclO.c — A  10-liner,  about  the  smallest  module  you’d 
ever  compile. 

•doclOO.c  This  100-liner  is  the  most  significant  since  it 
represents  a  typical  size  of  source  file. 

•doclOOO.c — A  1000-line  program.  Basic  overhead 
should  be  minimal  here. 

DoclOO.c  was  produced  from  a  preexisting  program  be¬ 
longing  to  one  of  the  group.  DoclOOO.c  is  a  stripped-down 
version  of  the  DIFF  program  available  from  DECUS  (Dig¬ 
ital  Equipment  Corp.  Users’  Society). 

Since  modular  compilation  tends  to  be  the  rule  with  C 
programming,  we  timed  the  compile  and  link  steps  sepa¬ 
rately.  The  compile  step  included  all  phases  of  the  compila¬ 


tion,  including  assembly,  if  any.  The  compile  and  link  pro¬ 
cess  was  controlled  by  a  batch  file  since  we  weren’t 
interested  in  benchmarking  the  testers’  typing  speeds. 
Some  compilers  provided  a  control  program  that  allowed 
the  linker  to  be  run  automatically  after  the  compile  with  a 
single  command.  In  these  compilers,  we  defeated  that  op¬ 
tion  and  issued  a  second  command  to  do  the  link  so  we 
could  derive  separate  compile  and  link  times.  To  produce 
the  time  estimate,  we  used  the  Norton  Utilities  timemark 
program,  invoking  it  from  the  batch  file  at  the  appropriate 
points.  See  Tables  2a  and  2b  (page  34)  for  these  results. 

Testing  Screen  and  Disk  I/O 

We  wanted  to  test  how  fast  the  generated  code  could 
write  to  the  console  using  puts(  )  and  printf(  ),  as  well  as 
how  fast  it  could  read  and  write  to  the  disk.  We  wrote  six 
functions  to  find  out. 

•The  fillscr.c  function  used  puts(  )  to  write  a  string  of 
1 248  characters  to  the  screen  without  scrolling.  Scrolling 
was  prevented  by  using  a  bare  carriage  return  every  78th 
character  of  the  string.  The  screen  gradually  fills  because 
puts(  )  adds  its  own  newline  at  the  end  of  the  string,  and 
the  puts(  )  call  is  repeated  <loop>  times.  The  screen  was 
cleared  by  the  DOS  CLS  command  prior  to  running  this 
test. 

•  The  scroll.c  function  was  almost  the  same  as  fillscr(  ), 
except  the  string  had  a  newline  everv  78th  character  so 
each  puts(  )  produced  16  lines  of  characters  plus  a  blank 
line  on  the  screen.  So  the  time  difference  between  scroll!  ) 
and  fillscr(  )  measured  the  scrolling  overhead. 

•  The  prtf.c  function  produced  the  same  number  of  char¬ 
acters  and  lines  as  scroll(  ),  except  it  used  printff  )  instead 
of  puts(  )  and  executed  each  basic  format  conversion  sev¬ 
eral  times.  Comparing  the  time  for  prtf(  )  to  that  for 
scroll!  )  shows  how  efficiently  the  compiler’s  printff  ) 
function  performs  its  conversion  duties. 

•The  cpychr.c  function  copied  a  10,000-byte  file  using 
fgetc(  )  and  fputc(  ). 

•The  cpyblk.c  function  copied  a  10,000-byte  file  using 
fread(  )  and  fwrite(  )  to  copy  in  1024-byte  blocks.  The 
difference  between  the  times  for  these  functions  reveals 
the  advantage  blocked  I/O  has  in  a  particular  compiler’s 
library. 

•  The  diskio.c  function  was  designed  to  test  random  char¬ 
acter  I/O  to  a  reasonably  large  data  file.  It  used  fseek(  ), 
fgetc(  ),  and  fputc(  )  to  randomly  read  and  write  individ¬ 
ual  characters  in  a  previously  created  data  file  240,000 
bytes  long. 

We  argued  about  whether  to  use  fopen(  ),  fread(  ), 
fwrite(  ),  and  the  like,  rather  than  open(  ),  read(  ), 
write(  ),  and  other  corresponding  functions.  The  latter 
functions  were  provided  with  many  of  the  compilers  and 
might  have  produced  faster  times,  but  ultimately  we  de¬ 
cided  to  use  the  f  functions,  primarily  on  portability  con¬ 
siderations.  The  February  1985  preliminary  draft  of  the 
proposed  ANSI  standard  does  not  include  open(  ),  et  al., 
on  the  grounds  that  they  are  too  closely  tied  to  the  Unix 


32 

59R 


Dr.  Dobb's  Journal,  August  1985 


Vendor 

minmain 

minouts 

minor  if 

minfio 

DOCI 

POCIQ 

DOC100 

DOC100 

Aziec  (Manx) 

185b 

3136 

4336 

3952 

1600 

4416 

6784 

10928 

Control  C 

6698 

6714 

6714 

7846 

6698 

6794 

12198 

14352 

C  Systems 

12032 

12032 

19456 

12544 

11934 

19440 

25018 

28464 

Computer  Innovations 

4352 

4480 

9344 

5632 

9408 

9408 

12789 

16956 

Dalahght 

3386 

3603 

5644 

3927 

2020 

7732 

12148 

NA 

DeSmet 

1536 

1536 

6656 

8192 

1536 

6656 

14848 

19456 

Digital  Research 

12800 

13440 

18816 

15616 

12800 

18944 

24704 

28416 

EcoSoft 

692 

2452 

7616 

4026 

708 

770 

11528 

NA 

Lattice 

10278 

10306 

11702 

10812 

10294 

11786 

14654 

19688 

Mark  Williams 

6698 

6714 

6714 

7846 

6698 

6794 

12198 

14352 

Microsoft 

1912 

4296 

5750 

5896 

1928 

5802 

9090 

14684 

Software  Toolworks 

3918 

5286 

6850 

7422 

8556 

10964 

14180 

22192 

Wizard 

716 

2440 

8168 

4278 

732 

8236 

12234 

16336 

Table  1 
EXE  File  Size 


Vendor _  DOLL  OOCIO.  QQU.QC.  QfO!2fXL 


compile 

link 

compile 

link 

compile 

l>nk 

compile 

jink 

Aztec  (Manx) 

2 

2 

3  5 

3 

165 

4 

103 

5.5 

Control  C 

2 

12 

3 

12 

12 

19 

69 

21 

C  Systems 

6 

1  1 

9 

14 

395 

175 

233 

19 

Computer  Innovations 

6 

10 

t 

14 

19 

16 

105 

20 

Datalight 

25 

5 

3 

55 

12 

9 

SS»A 

NA 

DeSmet 

3 

35 

3 

.  4 

6 

4  5 

28 

5 

Digital  Research 

3 

16 

4 

20 

17 

25 

120 

29 

EcoSoft 

3 

3 

4 

13 

13 

18  5 

NA 

NA 

Lattice 

25 

95 

4 

10 

18.5 

11.5 

126 

15  5 

Mark  Williams 

2 

12 

3 

12 

12 

19 

69 

21 

Microsoft 

4 

3 

6 

5.5 

20 

7 

1315 

95 

Software  Toolworks 

3 

11 

6 

13 

28 

15 

243.5 

20 

Wizard 

3 

2 

3 

7 

10 

9 

54 

12.5 

Table  2a 

Compile  and  Link  Times:  RAM  Compilation 


Vendor _ 

DOCI 

DOC  10 

DQC1QQ: 

DOCI OOP 

compile 

link 

compile 

link 

compile 

link 

Q2ffiJ2ll£ 

link 

Aztec  (Manx) 

8 

56 

9.5 

8 

245 

10 

1  16 

12 

Control  C 

13 

20 

14 

20 

265 

27.5 

93 

30 

C  Systems 

17 

18 

20 

22 

52 

25 

255 

27 

Computer  Innovations 

21.5 

15 

22 

20 

37 

23 

128 

28 

Daiaiight 

8.5 

7 

9 

115 

20  5 

15 

NA 

NA 

DeSmet 

10 

7 

10 

85 

14.5 

10 

43 

1  1 

Digital  Research 

13 

24 

14 

29 

28 

36 

135 

40 

EcoSoft 

14 

7 

15 

205 

27 

26 

NA 

NA 

Lattice 

9 

155 

10 

16 

27.5 

18 

149 

225 

Mark  Williams 

13 

20 

14 

20 

265 

27.5 

93 

30 

Microsoft 

19 

8 

21.5 

13 

38 

15 

159.5 

20 

Software  Toolworks 

9 

18 

11.5 

21 

35 

24 

254 

30 

Wizard 

15 

5.5 

17 

13 

27 

16 

78 

20 

Table  2b 

Compiler  and  Link  Times:  Hard-disk  Compilation 


Vendor - - - - -  Ovenll  performincc  ind  nmc: 


looptst  optiia 

fibtesi 

...Jim 

Jlitvfi 

psieve  . 

-L5VC1 

Aztec  (Manx) 

37  8 

35  7 

562 

99.0 

580 

53  3 

42  0 

Control  C 

622 

409 

630 

1034 

59  1 

54  8 

60  0 

C  Systems 

1008 

73.1 

697 

1738 

1326 

1907 

51  5 

Computer  Innovations 

66  1 

39  8 

540 

117  5 

1174 

1193 

490 

Datahght 

63  0 

353 

49  8 

99  9 

100  0 

101  3 

42  6 

DeSmet 

630 

46  4 

529 

108  2 

108  4 

103  4 

538 

Digital  Research 

58  8 

669 

517 

1048 

58  8 

57  0 

49  9 

EcuSof  I 

63.0 

507 

520 

115  3 

115  4 

136  1 

857 

Lattice 

630 

25  0 

700 

100  3 

1002 

96  9 

77  2 

Mark  Williams 

618 

40  8 

630 

1033 

590 

547 

59  7 

Microsoft 

588 

59.0 

590 

99.4 

54  9 

50  0 

443 

Software  Toolworks 

74  2 

318 

564 

1336 

1 10  3 

176  8 

1236 

Wizard 

588 

68  8 

526 

101  0 

55  7 

51.1 

60  0 

Table  3a 
Execution  Times 


Dr.  Dobb’s  Journal,  August  1985 


37 

597 


operating  system  and  that  they  are  redundant.  We 
dodged  the  potentially  sticky  problem  of  conversion  be¬ 
tween  newline  and  CR-LF  by  making  sure  that  none  of  the 
data  files  contained  these  characters. 

Testing  String  Library  Functions 

We  wanted  to  test  some  library  functions  beyond  the  usual 
I/O  functions.  We  were  concerned,  though,  that  many  of 
the  functions  we  could  test  would  not  be  provided  with  all 
of  the  compilers,  so  we  wound  up  with  a  single  benchmark 
function,  isort.c,  to  test  strcmpf  )  and  strcpyf  ).  This  func¬ 
tion  used  strcmpf  )  and  strcpyf  )  to  perform  a  simple  sort 
of  an  array  of  up  to  1024  eight-character  strings.  (In  this 
case,  the  <loop>  parameter  was  used  to  control  the  num¬ 
ber  of  elements  to  sort;  it  does  not  represent  the  number  of 
sorts  performed.)  The  isort.c  test  tells  little  about  compiler 
performance  per  se,  but  it  does  give  a  clue  as  to  how  well 
the  runtime  library  was  written. 

Math  Tests 

We  wrote  four  functions  to  test  basic  arithmetic  functions 
for  various  data  types. 

•The  tint.c  function  includes  1500  add,  1600  subtract, 
200  multiply,  and  200  divide  operations,  <loop>  times. 
Inner  loop  overhead  amounts  to  <loop>  *  100  test-for- 
zero  and  decrement  operations.  Since  there  are  not  many 
ways  to  perform  16-bit  integer  arithmetic  on  an  8088,  we 
expected  most  of  the  compilers  to  perform  about  equally 
on  this  one. 


•  The  tlong.c  function  was  exactly  the  same  as  tint(  ),  ex¬ 
cept  that  the  variables  involved  in  the  adds,  subtracts, 
multiplies,  and  divides  were  all  declared  long  instead  of 
int.  Significant  differences  between  compilers  were  more 
likely  here. 

•The  tfloat.c  function  tested  floating-point  arithmetic 
with  40  each  adds,  subtracts,  and  multiplies  and  20  di¬ 
vides,  all  done  <loop>  times. 

•The  tdouble.c  function  was  the  same,  except  that  the 
variables  were  declared  double  instead  of  float.  For  most 
compilers,  tdoublef  )  should  be  somewhat  faster  than 
tfloat(  )  since  K&R  calls  for  all  floats  to  be  converted  to 
double  before  being  used  in  an  expression.  Tfloat(  )  and 
tdouble(  )  were  run  with  whatever  flavors  of  floating¬ 
point  support  (including  8087  options)  were  available  for 
each  compiler.  The  results  of  these  tests  appear  in  Table 
3b  (page  38)  and  Table  4  (page  43). 

Array  and  Pointer  Tests 

We  wrote  two  functions  to  explore  how  efficiently  the 
compilers  handled  array-  and  pointer-referencing  tasks. 
The  function  array.c  simply  initialized  a  three-dimension¬ 
al  array  of  1 000  ints,  and  pointer.c  did  the  same,  but  using 
a  pointer  to  a  pointer  to  a  pointer  to  an  int.  The  unneces¬ 
sary  extra  levels  of  indirection  were  added  to  increase  the 
amount  of  pointer  dereferencing  relative  to  the  overhead; 
i.e.,  to  increase  the  ratio  of  data  to  noise. 

Function  Call  Tests 

To  test  the  overhead  due  to  function  call  and  return,  we 


Vendor 

Math  functions _ 

_  Affi 

ay/pLr 

tint 

llong  array 

mr 

Aztec  (Manx) 

34  0 

125  4  30  7 

50  4 

Control  C 

364 

176  1  96  4 

725 

C  Systems 

39.3 

247.9  900 

85  0 

Computer  Innovations 

36.9 

215  8  95  1 

719 

Datalight 

354 

87  7  614 

652 

DeSmet 

360 

178.9  936 

68  0 

Digital  Research 

34  1 

808  4  96  8 

68  0 

EcoSofl 

36,0 

298  8  917 

68  0 

Lattice 

36  3 

192.8  602 

66  8 

Mark  Williams 

367 

175.4  96  4 

72  5 

Microsoft 

33  5 

102.9  76  2 

50  4 

Software  Toolworks 

590 

N.A  131.5 

1  168 

Wizard 

34  0 

I7j  0  78  4 

50  4 

Vendor 

Screen  I/O: _ 

Disk  I/O: 

Storage  class  support: 

f'llscr 

scroll 

-E£tf  cgyblk  cpychr 

OistfiQ 

aulost 

statist 

resist 

Aztec  (Manx) 

505 

57.8 

61.7  98.2 

39.7 

62.8 

70.6 

79.3 

54.2 

Control  C 

22  3 

29  0 

37  6  104  2 

595 

47.6 

72.3 

81.1 

54.1 

C  Systems 

310 

355 

42.1  182  6 

82.2 

26.7 

126  9 

136.0 

108.8 

Computer  Innovations 

278 

326 

58  1  1470 

42.0 

50.9 

79.3 

86.6 

79.3 

Dalalighl 

499 

54.7 

639  1236 

533 

N.A. 

79.4 

86,5 

79.4 

DeSmet 

27.8 

31.5 

48  1  128  8 

94.6 

48  1 

79  4 

86  5 

79  4 

Digital  Research 

37.5 

419 

35  9  1760 

83.3 

36.2 

70.5 

79.3 

52.2 

EcoSoft 

31.3 

378 

40.8  N.A. 

N.A 

N.A 

79,4 

86.5 

79.4 

Lattice 

62.4 

67.5 

710  129.0 

635 

66.1 

79.3 

86  5 

79  4 

Mark  Williams 

22.3 

29.0 

37.5  156.1 

85.0 

42.4 

72.2 

81.1 

54.0 

Microsoft 

47.7 

550 

59  1  73  0 

530 

57.0 

70.5 

79.3 

52.2 

Software  Toolworks 

625 

67.6 

67.9  7260  . 

334  2 

55.3 

112  6 

1360 

112.5 

Wizard 

49.7 

54.7 

65  5  1397 

60.4 

555 

70.6 

79  3 

52.3 

Table  3b 

Execution  Times  continued 


38 


ttan 


Dr.  Dobb’s  Journal,  August  1985 


created  a  version  (fibtest.c)  of  the  fib  benchmark  from 
the  August  1983  issue  of  Byte  magazine.  (This  is  a  good 
test  of  function-call  overhead  because  it’s  composed  en¬ 
tirely  of  negligible  arithmetic  and  recursive  calls.)  As 
with  tint(  ),  the  optimum  code  for  function  calls  and  re¬ 
turns  is  well  known,  so  we  did  not  expect  to  see  much 
variation  between  compilers  on  this  test,  although  signifi¬ 
cant  differences  would  be  interesting. 

Storage  Class  Tests 

We  were  curious  about  the  efficiency  of  the  code  generat¬ 
ed  for  variables  of  various  storage  classes  and  in  particu¬ 
lar  about  how  well  register  variables  were  supported.  To 
find  out,  we  wrote  three  almost-identical  functions:  au- 
totst.c,  stattst.c,  and  regtest.c.  Each  contained  four  empty 
for  loops.  The  only  difference  among  the  functions  is  that 
the  four  loop  variables  were  declared  with  automatic, 
static,  and  register  storage  classes,  respectively. 

Looking  For  Optimization 

We  thought  it  would  be  a  nice  idea  to  try  to  determine  just 
how  extensive  each  compiler’s  optimization  algorithms 
were.  To  that  end  we  wrote  optimize.c,  a  mishmash  of 
statements  thai  a  compiler  could  reasonably  be  expected 
to  optimize.  The  idea  was  that  the  more  cases  a  compiler 
noticed  and  optimized,  the  better  its  performance  on  the 
test  would  be. 

Assessing  General  Performance 

The  looptst.c  function  did  nothing  at  all;  it  was  just  an 
empty  loop.  We  included  it  so  that  we  could  get  an  idea  of 
how  much  overhead  was  being  introduced  by  our  outer 
loop  in  each  of  the  benchmarks.  Looptst(  )’s  inner  loop  did 
nothing  10,000  times,  so  there  were  5,000,000  total  loop 
iterations. 

We  knew  we  could  not  get  away  with  not  running  the 
infamous  Byte  sieve  benchmark,  so  we  decided  to  go  over¬ 
board  and  produce  three,  starting  with  sieve.c,  our  version 
of  the  standard  sieve  benchmark  as  published  in  the  Janu¬ 
ary  1983  Byte.  We  added  rsieve.c,  identical  to  sieve(  ) 
except  that  the  two  most  frequently  referenced  variables 
were  declared  register.  This  is  the  way  some  vendors  run 
the  sieve  when  producing  numbers  for  their  advertising 
copy.  And  we  wrote  psieve.c,  a  pointerized  version  of  the 
sieve.  This  function  may  not  prove  very  useful  in  compar¬ 
ing  compilers,  but  writing  it  was  an  interesting  exercise.  It 
showed  us  that  it  is  not  true  that  using  pointers  is  always 
better  than  using  array  references.  Psieve(  )  is  considera¬ 
bly  less  readable  and  performs  only  marginally  better 
than  rsievef  ) — on  some  compilers  it  actually  ran  at  a 
slower  rate! 

Testing  Memory  Models 

All  the  benchmarks  mentioned  were  compiled  and  run 
under  the  small-memory  model.  To  see  what  kind  of  per¬ 
formance  penalty  you  pay  to  use  other  models,  we  com¬ 
piled  and  ran  array(  ),  pointer(  ),  and  psieve(  )  with  every 
other  memory  model  option  available  with  each  compiler. 
In  retrospect,  we  probably  should  have  included  fibtest(  ) 
and  maybe  sieve(  )  in  the  set. 

Dr.  Dobb’s  Journal,  August  1985 


Benchmark  Results 

You  can  see  the  results  for  all  these  benchmarks  in  Table 
3a  (page  34)  and  Table  3b  (page  38).  Since  there  are 
some  surprises  and  discoveries  that  may  not  jump  out  at 
you  from  all  those  numbers,  we  want  to  discuss  some  of 
the  more  interesting  (in  some  cases,  bizarre)  results. 
We’ll  discuss  them  by  functional  grouping. 

In  the  area  of  screen  I/O,  none  of  the  compilers  was  so 
fast  as  to  indicate  that  the  library  routines  were  writing 
directly  to  screen  memory  (which  could  be  a  portability 
problem),  but  there  was  a  wide  range  in  performance. 
The  compilers  seemed  to  separate  into  two  groups.  Mark 
Williams’  compiler  was  the  fastest  in  a  group  clustered 
around  30  seconds,  with  Microsoft’s  the  fastest  in  a  group 
clustered  around  55  seconds.  This  suggests  the  use  of  two 
divergent  methods  of  handling  screen  I /O  under  MSDOS. 

Also,  we  were  surprised  to  find  that  for  most  compilers, 
prtf(  )  was  not  much  slower  than  scroll(  ).  We  had  ex¬ 
pected  the  conversion  overhead  to  be  significant,  but  this 
was  true  only  with  Computer  Innovations’  compiler  and, 
to  a  lesser  extent,  with  DeSmet’s.  A  real  anomaly  was 
Digital  Research’s  compiler,  whose  prtf(  )  time  was  faster 
than  scroll)  )  and  even  faster  then  fillscr)  )!  That  means 
that  in  DRI’s  compiler  there  is  some  overhead  associated 
with  using  puts(  )  that  is  avoided  in  using  printf(  );  this 
made  no  sense  to  us. 

The  storage  class  tests  basically  tested  the  efficiency  of 
loading  data  from  memory.  Since  there  are  only  a  limited 
number  of  ways  to  load  a  two-byte  quantity  from  memory, 
it  is  not  surprising  that  there  were  many-way  ties  in  this 
category.  In  fact,  except  for  the  two  slowest  compilers  (c- 
systems’  and  Software  Toolworks’),  all  the  compilers  were 
closely  ranked  for  all  three  tests.  There  were  four  compilers 
tied  for  first,  followed  by  two  more  just  a  couple  of  seconds 
slower  and  five  more  tied  a  bit  further  off.  Mark  Williams’ 
and  Control  C’s  compilers  were  the  two  tied  in  the  second 
group,  but  that’s  no  surprise;  they  tied  in  every  test.  They 
are  really  the  same  compiler. 

If  we  compare  the  regtest)  )  and  autotst)  )  results,  it 
becomes  clear  that  all  the  faster  compilers  implemented 
register  variables,  while  generally  the  slower  compilers 
did  not.  The  c-systems  compiler,  the  slowest  for  these 
tests,  did  implement  register  variables,  but  apparently  to 
no  great  advantage.  Out  of  curiosity,  we  also  ran  a  version 
of  regtestf  )  that  declared  only  two  of  the  four  loop  vari¬ 
ables  to  be  of  register  class.  For  all  compilers,  the  results 
were  the  same  as  for  regtest(  ).  This  was  educational;  it 
means  that,  at  most,  only  the  first  two  variables  declared 
were  actually  kept  in  registers.  This  has  implications  for 
programming  practice:  use  register  declarations,  but  take 
care  that  variables  be  declared  with  the  most  frequently 
used  first. 

For  all  compilers,  the  automatic  storage  class  is  more 
efficient  than  the  static.  This  may  come  as  a  surprise  to 
those  accustomed  to  C  on  8-bit  machines  with  no  stack- 
relative  addressing  mode. 

Most  compilers  performed  about  the  same  on  the 
looptstf  )benchmark — about  60  seconds.  The  Aztec  com¬ 
piler,  however,  was  significantly  faster  at  38  seconds.  It  is 

41 

599 


unusual  that  no  one  else  came  close  to  this  time,  since  most 
benchmarks  had  several  compilers  within  a  few  seconds  of 
the  fastest  time.  It  turned  out  that  the  Aztec  compiler  gen¬ 
erated  only  two  instructions  for  the  empty  inner  loop, 
whereas  the  best  the  others  could  do  was  three  instructions. 

The  Aztec  compiler  repeated  this  performance  on  the 
arrayf  )  test,  coming  in  twice  as  fast  as  the  nearest  compet¬ 
itor.  Here  Aztec’s  performance  was  due  to  avoidance  of  the 
imul  instruction  in  doing  subscript  arithmetic.  In  general, 
the  Aztec  compiler  avoided  imul  in  favor  of  shifts  and  adds 
wherever  possible.  The  Aztec  compiler  also  shared  the  lead 
in  the  pointer(  )  test  with  Microsoft’s  and  Wizard’s. 

There  were  no  surprises  in  the  tint(  )  test.  Except  for 
Software  Toolworks’  C,  all  the  compilers  were  in  a  very 
narrow  range,  clustered  around  35  seconds.  There  are  not 
many  ways  to  add  or  subtract  two  1 6-bit  integers  On  a  16- 
bit  machine,  and  we  did  not  use  register  declarations  in 
tint(  ),  which  would  have  given  a  slight  edge  to  the  com¬ 
pilers  that  support  them. 

Tlong(  )  is  another  story  altogether.  The  Datalight 
compiler  had  its  only  win  here  (by  a  significant  margin) 
at  88  seconds,  followed  by  Microsoft’s  and  then  the  Aztec 
compiler.  The  real  standout  in  this  benchmark  was  Digi¬ 
tal  Research’s  compiler,  which  came  in  last  by  an  as¬ 
tounding  margin  with  a  benchmark  of  808  seconds!  It  is 
hard  to  imagine  how  it  could  execute  this  slowly,  over  nine 
times  as  long  as  the  fastest  time.  It  is  especially  puzzling 
because  the  DRI  compiler  was  a  respectable  performer 
otherwise,  in  fact  fastest  or  essentially  tied  for  fastest  on  a 
number  of  the  other  tests.  If  you  use  longs  very  much, 
DRI’s  is  clearly  not  the  compiler  of  choice! 

The  results  of  the  optimize(  )  benchmark  are  difficult 
to  interpret.  Some  of  the  “lightweight”  compilers  ran  this 
test  quite  well,  while  Microsoft’s  did  rather  poorly,  even 
though  we  had  observed  that  its  optimization  was  gener¬ 
ally  very  good.  Also,  the  Lattice  compiler  was  fastest  by  a 
large  margin  on  this  test  but  was  not  an  outstanding  per¬ 
former  on  any  of  the  other  benchmarks.  We  looked  at  the 
code  generated  by  some  of  the  compilers  for  a  clue  and 
discovered  a  partial  answer.  Optimizef  )  was  especially 
unkind  to  those  compilers  that  do  not  recognize  that  mul¬ 
tiply  operations  can  be  replaced  by  left  shift  instructions. 
One  saved  imul  instruction  can  make  up  for  a  lot  of  re¬ 
dundant  load  and  store  operations!  Actually,  this  kind  of 
test  should  always  be  approached  with  caution.  One  of 
these  days  a  compiler  may  come  along  that  is  so  good  at 
global  optimizations  that  it  notices  when  our  benchmark 
functions  produce  no  side  effects  visible  outside  the  func¬ 
tion.  It  might  then  be  free  to  replace  an  entire  function 
with  a  single  RET — very  fast  indeed! 

The  disk  I/O  benchmarks  were  really  testing  part  of 
the  library,  not  the  compiler,  so  we  expected  a  wide  range 
of  numbers.  What  we  did  not  expect  was  that  for  many  of 
the  compilers,  cpychrf  )  would  be  faster  than  cpyblk(  ). 
When  you  look  at  Table  3b,  don’t  overlook  the  loop  count: 
cpyblkf  )  moved  twice  as  many  bytes  as  cpychff  ).  The 
Microsoft  compiler  had  the  fastest  time  for  cpyblk(  ), 
with  no  other  compiler  coming  close.  The  Aztec  compiler 
was  fastest  for  cpychr(  ),  with  Computer  Innovations’  the 


only  close  second.  The  Software  Toolworks  compiler  was 
abysmally  slow  on  both  of  these,  about  four  times  slower 
than  the  generally  poor-performing  compiler  from  c-sys- 
tems.  On  the  other  hand,  the  Toolworks  compiler  gave  a 
reasonable  time  for  diskio(  ),  while  the  fastest  time,  by  a 
wide  margin,  was  turned  in  by  c-systems’  compiler.  In 
fact,  the  compilers  that  generally  did  the  best  on  the  vari¬ 
ous  compile-time  benchmarks  (those  from  Aztec,  Micro- 
Soft,  and  Wizard)  were  among  the  slowest  on  the  ran¬ 
dom-access  diskio(  )  test.  You  figure  it  out! 

The  min-(  )  benchmarks  were  intended  to  test  the 
granularity  of  the  library:  we  wanted  to  see  how  much 
unwanted  “stuff”  would  be  dragged  into  the  .EXE  file 
along  with  the  library  functions  actually  being  called. 
Minmain(  )  contains  no  function  calls  at  all,  so  the  .EXE 
file  size  for  this  test  is  an  indication  of  the  best  you  can  do. 
The  DRI,  c-systems,  and  Lattice  compilers  were  the  big 
losers  here,  all  requiring  more  than  10,000  bytes  of  li¬ 
brary  code  to  do  nothing  at  all.  Not  surprisingly,  these 
three  were  also  the  worst  on  the  minputs(  ),  minprtf(  ), 
and  minfio(  )  tests,  although  the  Lattice  compiler’s  .EXE 
size  remained  about  the  same.  The  EcoSoft  and  Wizard 
compilers  both  produced  amazingly  small  .EXE  files  for 
minmain(  ),  since  the  startup  code  should  at  least  initial¬ 
ize  the  stdin,  stdout,  and  stderr  files.  DeSmet’s,  in  third 
place  at  1536  bytes,  already  includes  all  the  library  code 
needed  to  support  the  puts(  )  function.  That  made  De¬ 
Smet’s  the  smallest  .EXE  file  for  minputs(  )  by  a  fair  mar¬ 
gin,  although  Wizard’s  and  EcoSoft’s  were  also  very 
small. 

Minprtff  )  was  the  acid  test,  because  the  formatting  sup¬ 
ported  by  printff  )  usually  requires  the  floating-point  pack¬ 
age  as  well  as  I/O  support.  Some  of  the  compilers  provide 
an  integer-only  version  of  printf(  ),  however,  and  we  used 
that  option  whenever  possible  since  we  believe  the  majority 
of  C  applications  do  not  require  floating-point  support. 
The  compilers  that  provide  this  option  are  giving  you  more 
flexibility  with  an  effectively  more  granular  library.  They 
are  the  compilers  from  Aztec,  Mark  Williams  (and  Con¬ 
trol  C),  Lattice,  Microsoft,  and  Datalight. 

The  Microsoft  compiler  deserves  special  mention 
among  these.  The  other  compilers  made  you  specify 
whether  floating-point  support  was  desired,  either  with  a 
command-line  option  or  via  library-search  order.  Micro- 
Soft’s  handled  this  automatically  by  detecting  whether 
any  float  or  double  variables  or  pointers  had  been  de¬ 
clared  in  your  program,  and  then  linking  the  math  library 
only  if  needed.  We  found  this  sort  of  feature  to  be  typical 
of  the  Microsoft  product. 

Using  the  floating-point  version  of  printff  )  typically 
added  from  2000  to  3000  bytes  to  the  .EXE  file  size. 
Choosing  the  compile  option  to  leave  the  float  library  out, 
Aztec  C  produced  the  smallest  .EXE  file  by  a  large  mar¬ 
gin,  even  over  the  integer-only  competitors.  Datalight’s 
and  Microsoft’s  came  next  and  then  DeSmet’s.  The  De- 
Smet  compiler  produced  the  smallest  .EXE  file  with  the 
floating-point  library  included,  although  the  Aztec  file 
was  only  a  few  hundred  bytes  larger.  Also,  it  should  be 
noted  that  the  Software  Toolworks  compiler  is  a  subset 


42 


Rnn 


Dr.  Dobb’s  Journal,  August  1985 


Floating-point  and  8087  Results 


tdbi  EXE  size  8087  inline  Notes 


Vendor 
8087  series: 

Aztec  (Mam) 

Aztec  (Manx) 

c-systems 

c-systems 

Computer  Innovations 
DeSmet 

Digital  Research 

Ecosoft 

Lattice 

Mark  Williams 

Microsoft 

Microsoft 

Microsoft 

Microsoft 

Wizard 

Wizard 

Floating-point  series: 
Aztec  (Manx) 

Aztec  (Manx) 
c-aystems 

Computer  Innovations 

Datahght 

DeSmet 

Digital  Research 

EcoSoft 

Lattice 

Mark  Williams 

Microsoft 

Microsoft 

Microsoft 

Wizard 


im 

119.1  141.0  7584 

149.1  168.5  10000 

145.0  153.7  22424 

59.9  68  3  20956 

59  2  68.3  10027 

119  0  128  3  12288 

67.3  76.5  22528 

290.5  161.7  10828 

1675  8  12560  17452 

53.7  62.9  9632 

81.0  89.7  21172 

810  89.6  14148 

55.7  64.2  20932 

55.6  64.2  13908 

350.0  209.0  11788 

368.7  72.0  11916 

788  81.0  9136 

80.6  83.2  10000 

95.1  78.6  22424 

173.1  166.0  11453 

121.8  71.1  10068 

97.8  79.2  14848 

83.6  61.7  22016 

129.6  105.8  10878 

112.0  84.2  17452 

98.8  94  3  13137 

91.7  101.9  21172 

256  45.6  16580 

92.6  102.6  20932 

23  1  71.4  11788 


Y  N  m87.1ib 

N  N  m87s.lib 

Y  N 

N  Y 

n  y 

N  N  stdio7  s 

N  Y 

Y  N 

Y  N  didn't  use  8087 

N  Y?  -rndp  -vsmall 

Y  N  /fpc 

N  N  /fpc87 

Y  Y  /fpi 

N  Y  /fpi87 

Y  N 

N  Y  -f 

N  m87.1ib 

Y  m87slib 

Y 
N 
N 
N 
N 

Y 
Y? 

N 

Y  /fpc 

N  /fpa 

Y  /fpi 

Y 


The  floating-point  series  are  with  loop  counts  of  600,  run  at  7.16MHz. 

The  8087  series  are  with  loop  counts  of  6000,  run  at  4.77MHz. 

The  rightmost  column  indicates  whether  in-line  code  is  supported.  The 
column  headed  "8087''  indicates,  for  floating-point  results,  whether  the  lest 
runs  faster  with  an  8087  present,  and,  for  the  8087  results,  whether  it  works 
if  an  8087  is  not  present. 

Table  4 

Floating-point  and  8087  Results 


Documentation  .and.5unoorL 


Vendor 

Usability  Readability 

Sv-Boart 

Aztec  (Manx) 

283 

372 

1.88 

Control  C 

3.17 

287 

1.56 

c-systems 

2.50 

2.55 

2.19 

Computer  Innovations  3.17 

283 

2.50 

Dalalight 

1  00 

1  03 

.63 

DeSmet 

233 

2.17 

344 

Digital  Research 

333 

2.58 

4.06 

EcoSoft 

2.17 

2.05 

1.56 

Lattice 

3.67 

2.97 

2.81 

Mark  Williams 

3.17 

287 

1  88 

Microsoft 

4  67 

4.02 

3  13 

Software  Toolworks 

1.83 

133 

1  56 

Wizard 

333 

2.85 

4.38 

Interpreters: 

C-terp 

2.50 

1.83 

.63 

Instant  C 

3.00 

1.25 

1.56 

Run/C 

2  50 

3  *7 

94 

Table  5 

Documentation  and  Support 


Dr.  Dobb’s  Journal,  August  1985 


43 

601 


compiler  with  no  floating-point  support  at  all. 

For  the  minfiof  )  benchmark,  the  Datalight,  Aztec,  and 
EcoSoft  compilers  shone  the  brightest,  with  Wizard’s  not 
far  behind.  Here  DeSmet’s  .EXE  file  was  a  lot  larger  than 
the  one  generated  for  minprtff  ),  even  though  the  float 
library  was  not  required.  This  was  viewed  by  all  as  very 
strange. 

The  min-(  )  benchmarks,  except  for  minmainf  ),  are  all 
significant  because  they  give  you  an  idea  of  how  small  a 
small  program  might  be.  Since  there  are  very  few  useful 
functions  to  be  performed  that  do  not  require  some  console 
and  disk  I/O,  minmainf  )  was  included  primarily  to  show 
the  losers  in  this  category  in  the  unkindest  light  possible. 
The  c-systems  and  DR  I  compilers  did  not  fare  well  at  all, 
requiring  19K  and  18K,  respectively  to  say  “hello  world” 
via  printff  ).  Lattice’s  compiler,  a  very  well  thought  of 
product  generally,  also  looked  none  too  good  here.  The 
Aztec  compiler  was  the  overall  winner  in  this  category.  The 
Aztec  compiler  also  had  the  smallest  execution-time 
benchmark  files,  requiring  only  14K  for  benchall.exe  ver¬ 
sus  an  average  of  55K-65K  for  the  others. 

Memory  Models 

The  8086’s  segmented  architecture  leads  to  some  trade¬ 
off's  between  execution  speed  and  addressing  range.  The 
way  a  compiler  views  the  8086’s  address  space  is  called 
the  memory  model.  All  the  compilers  reviewed  here  sup¬ 
port  the  so-called  “small”  memory  model  as  the  default, 
and  many  of  them  allow  no  other.  The  small  model  allows 
up  to  64K  for  instructions  and  64K  total  for  all  kinds  of 
data:  heap,  stack,  statics,  and  globals.  Automatic  vari¬ 
ables  are  allocated  on  the  stack,  with  space  dynamically 
allocated  via  mallocf  ),  etc.,  coming  from  the  heap. 

Other  possible  models  are  called  different  things  by 
different  manufacturers.  Lattice,  Aztec,  c-systems,  Wiz¬ 
ard,  and  Microsoft  all  provide  a  model  in  which  code  (in¬ 
structions)  can  run  up  to  a  megabyte  (the  maximum  al¬ 
lowed  by  the  8086),  but  data  is  limited  as  in  the  small 
model.  Lattice,  Aztec,  c-systems,  and  Wizard  provide  a 
model  that  limits  code  as  in  the  small  model,  but  the  heap 
may  extend  to  a  megabyte,  the  stack  can  grow  to  64K, 
and  statics  and  globals  together  can  take  up  64K.  DRI  and 
Computer  Innovations,  in  addition  to  the  just-mentioned 
four,  provide  a  model  allowing  code  up  to  a  megabyte  and 
data  as  in  the  preceding  model.  Wizard  and  Microsoft 
have  a  model  that  extends  this  last  model  by  allowing 
more  than  64K  of  combined  statics  and  globals  (although 
no  single  item  may  be  greater  than  64K),  in  addition  to 
the  one-megabyte  code  and  heap  spaces;  the  stack  is  still 
limited  to  64K. 

The  Wizard  and  Lattice  compilers  each  have  an  option 
that  permits  the  compiler  to  generate  much  better  code 
for  the  large  models,  if  the  programmer  is  willing  to  live 
with  certain  restrictions  regarding  pointer  arithmetic. 
These  two  compilers  also  allow  very  small  programs  to  be 
changed  to  .COM  files.  Such  programs  must  fit  all  code 
and  data  into  64K. 

Microsoft  is  currently  the  only  one  of  the  13  reviewed 
manufacturers  supporting  “mixed-model”  programming, 


although  others  indicate  that  it  is  high  on  the  to-do  list. 
Mixed-model  programming  allows  certain  pointers  to  be 
declared  “near”  or  “far,”  allowing  you  to  use  a  pointer 
from  a  model  different  from  the  model  of  the  rest  of  the 
program.  You  can  write  a  program  that  uses  the  small 
model  with  its  speed  benefits  and  make  only  certain  point¬ 
ers  of  the  time-consuming  “far”  variety  to  gain  access  to 
very  large  arrays. 

Floating-Point  and  8087  Support 

Although  C  is  presently  most  often  applied  to  problems 
that  don’t  require  any  floating-point  arithmetic,  the  lan¬ 
guage  does  support  and  define  the  use  of  “real”  numbers. 
Of  the  13  compilers  reviewed  (see  Table  4,  page  43),  all 
but  one,  Software  Toolworks  C,  support  floating-point 
math,  and  Toolworks  expects  to  have  it  available  by  the 
time  you  read  this. 

The  remaining  12  all  support  both  float  and  double 
data  types  as  32-  and  64-bit  quantities,  respectively. 
Nearly  all  use  the  IEEE  floating-point  standard  to  repre¬ 
sent  float  and  double.  This  makes  it  relatively  easy  to  take 
advantage  of  an  8087  math  chip  (which  also  uses  the 
IEEE  standard).  Datalight’s  is  the  only  compiler  we  re¬ 
viewed  that  did  not  offer  any  way  to  take  advantage  of  the 
8087.  Lattice’s  compiler  documentation  indicated  that  its 
library  was  “sensing,"  but  our  results  indicated  that  it  did 
not  use  the  available  8087. 

A  sensing  library  is  a  library  of  floating-point  functions 
that  use  the  8087,  if  one  is  present,  and  do  the  required 
operations  in  software  if  not.  This  means  that  you,  the 
programmer,  don’t  have  to  know  in  advance  if  your  pro¬ 
gram  is  going  to  run  on  a  machine  with  an  8087. 

Another  way  of  supporting  an  8087  is  to  use  a  library 
that  requires  the  presence  of  an  8087.  Compared  to  the 
sensing  library,  this  buys  you  a  little  time  (the  time  the 
sensing  library  takes  to  decide  whether  there’s  an  8087  for 
it  to  use)  and  some  2K-7K  of  code  (the  software  floating¬ 
point  routines).  Or  you  can  generate  8087  instructions  in¬ 
line  like  integer  operations.  This  saves  the  time  needed  to 
make  the  function  call. 

It  is  possible  to  execute  an  8087  instruction  in  a  machine 
not  having  an  8087.  What  happens  is  that  the  8086  traps  to 
a  user-provided  interrupt  routine.  This  routine  may  emu¬ 
late  the  8087  and  return.  This  allows  the  compiler  to  gener¬ 
ate  in-line  instructions  and  still  not  require  an  8087,  but  to 
use  one  if  it’s  available.  Only  Microsoft’s  compiler  imple¬ 
ments  this  type  of  an  emulation  scheme. 

Documentation 

We  reviewed  all  documentation  and  packaging  for  each 
compiler.  To  be  consistent,  we  developed  “benchmarks” 
for  documentation,  with  a  grading  system  consisting  of  two 
main  categories:  usability  and  readability. 

Usability 

The  usability  category  covered  the  overall  packaging  of 
the  documentation:  the  organization  and  quality  of  pre¬ 
sentation  that  make  the  documentation  easy  or  difficult 
to  use.  The  items  that  contributed  to  the  usability  score 


44 

nn? 


Dr.  Dobb’s  Journal,  August  1985 


were  the  following: 

•  Packing  list  (P/L):  Was  a  list  of  all  manuals,  disks,  etc., 
included  so  that  you  could  confirm  that  you  had  received 
everything? 

•  Disk  contents  (DISK):  Was  a  list  of  all  disks  and  their 
contents  included?  Often  one  was  included,  but  buried  in 
the  manual  and  hard  to  find. 

•  Binding  (BIND):  How  was  the  documentation  received 
from  the  vendor?  We  saw  everything  from  loose  pages 
(not  even  clipped  or  stapled)  to  multiple  3-ring  binders  in 
attractive  cases. 

•  Table  of  contents  (TOC):  Was  there  one?  How  complete 
was  it? 

•  Index  (INDEX):  Was  an  index  included,  and  how  com¬ 
plete  was  it?  Believe  it  or  not,  some  vendors  don’t  want 
you  to  locate  anything  in  the  documentation,  as  proven  by 
the  fact  that  they  don’t  index  their  manuals! 

•  Error  codes  (ERR):  Was  a  list  of  error  codes  generated 
by  the  compiler  present,  and  how  useful  was  it? 

•Startup  (START):  Was  a  getting-started  section  sup¬ 
plied  for  those  who  need  this  level  of  support?  Also  con¬ 
sidered  here:  the  quality  of  instructions  on  batch  files  for 
installation,  sample  “test”  programs  to  try  out  the  com¬ 
piler,  and  sample  batch  files  for  operation. 

•Operation  (OPER):  Was  a  compiler-operation  section 
included,  how  complete  was  it,  and  could  you  find  each 
compiler  option  without  rereading  the  entire  section? 

•  Libraries  (LIB):  Was  a  section  describing  the  libraries 
supplied,  and  how  easy  was  it  to  find  a  particular  func¬ 
tion?  This  is  where  a  good  index  helps,  but  at  least  the 
functions  ought  to  be  in  some  order,  not  at  random!  The 
CI-86  compiler  included  a  brief  summary  of  the  functions 
in  its  table  of  contents — bravo! 

Readability 

Readability  pertained  to  clarity  of  presentation  and  to  the 
ease  with  which  the  material  could  be  understood.  To  es¬ 
tablish  some  method  of  consistent  grading,  we  picked 
three  library  functions  and  subjectively  graded  the  pre¬ 
sentation  on  each  function  across  all  compilers.  We 
picked  fopen(  ),  fread(  )  (where  supported),  and  printf(  ). 
We  looked  for  such  things  as  good  examples,  clear  defini¬ 
tion  of  all  parameters,  clear  definition  of  return  values, 
error  returns,  notice  of  deviation  from  standards,  and  a 
good  explanation  of  the  function.  We  also  gave  credit  if 
only  one  function  was  printed  on  a  page,  making  it  easier 
to  find  and  read.  Cop-outs  such  as  “see  K&R  for  descrip¬ 
tion  of  this  function”  sat  very  poorly  with  us. 

Table  5  (page  43)  shows  the  numerical  scores  we  ar¬ 
rived  at  for  documentation.  The  Microsoft  manuals  were 
the  best  on  virtually  every  point.  The  Wizard,  Lattice, 
and  DRI  manuals  should  be  usable  over  the  long  run,  and 
the  Aztec  book  was  very  readable. 

User  Support 

The  best  software  product  is  of  marginal  value  if  it  is  not 
well  supported.  We  attempted  to  review  the  kind  and  quali- 

Dr.  Dobb's  Journal,  August  1985 


ty  of  user  support  available  for  the  compilers  reviewed. 
This  included  contacting  support  groups  for  most  of  the 
compiler  publishers  to  evaluate  their  responsiveness. 

Although  you  should  not  expect  the  same  level  of  sup¬ 
port  for  an  MSDOS  compiler  as  you  might  for  a  compiler 
running  on  a  large  mainframe,  some  of  the  same  support 
features  should  be  available.  Ideally,  support  should  re¬ 
flect  some  modularity,  so  those  who  need  full  support  can 
obtain  it,  while  those  who  need  only  occasional  assistance 
don’t  have  to  pay  for  support  they  won’t  use.  We  attempt¬ 
ed  to  evaluate  user  support  based  upon  several  criteria 
that  we  determined  to  be  important.  The  numerical  re¬ 
sults  are  shown  in  Table  5.  Here  is  a  discussion  of  the 
criteria. 

Bug  List 

Most  of  the  compilers  did  not  have  available  a  current  list 
of  known  bugs.  Some  did  indicate  problems  known  but 
not  yet  fixed  as  of  a  particular  release,  either  in  a  read. me 
file  or  in  the  documentation.  Ideally,  what  we  wanted  to 
see  was  a  regular  update  describing  current  bugs,  the  ex¬ 
pected  fix  date,  and  any  work-arounds.  This  would  be  a 
big  help  at  two  in  the  morning  when  you  are  under  pres¬ 
sure  to  complete  a  big  project,  but  are  stopped  dead  by  a 
compiler  bug.  It  also  ought  to  help  the  customer  service 
group  by  reducing  the  number  of  times  the  same  bug  is 
reported. 

Registration 

Most  vendors  supplied  a  means  to  register  the  compiler  so 
that  the  buyer  could  receive  notices  of  future  releases. 
Some  were  even  postpaid.  We  had  no  means  of  verifying 
that  new  release  notices  would  appear  in  the  mail,  but 
past  experience  indicates  that  sometimes  such  notices  are 
sent  and  sometimes  not,  depending  on  the  vendor. 

Update  Subscription 

Only  one  vendor  (Wizard)  offers  a  fixed-price  annual 
subscription  service  for  all  bug  fixes  and  updates.  Many 
others  indicated  that  updates  were  available  but  were 
charged  on  a  per-update  basis.  The  Wizard  approach  has 
appeal:  it’s  simple,  and  you  should  receive  updates  faster, 
with  less  paperwork  at  both  ends. 

Phone  Consultation 

Phone-in  consulting  service  was  available  from  all  ven¬ 
dors,  but  some  vendors  required  a  serial  number  to  gain 
access  and  others  required  money  before  giving  the  buyer 
i  a  phone  number  to  call.  Although  it  is  certainly  not  un- 
I  reasonable  to  charge  for  support,  many  vendors  bundle 
phone  support  into  the  basic  product.  We  found  that  some 
vendors  restricted  calling  hours. 

Support  Quality 

All  the  reviewers  made  notes  when  they  called  vendors  for 
support,  and  the  documentation  group  also  attempted  to 
contact  the  support  personnel  for  each  vendor.  We  found  a 
wide  range  in  quality  of  support.  Sometimes  we  were  re¬ 
ferred  to  the  author  of  a  compiler.  That  should  get  you 
J  qualified  technical  support,  but  we  felt  it  to  be  a  negative;  it 

45 

603 


meant  the  technical  support  was  dependent  on  one  person, 
and  if  that  person  is  unavailable,  you’re  stuck.  On  the  other 
hand,  talking  to  a  “support  person”  who  doesn’t  know  C 
makes  one  very  uneasy.  We  found  that  the  support  team  at 
Wizard  offered  by  far  the  best  technical  assistance. 

In  Table  5  you  see  an  attempt  to  assign  numbers  to  our 
experiences  with  the  vendors  in  these  areas  of  technical 
support.  The  clear  winners  here  were  Wizard’s,  DRI’s, 
DeSmet’s,  and  Microsoft’s  compiler  support. 

Compiler-specific  Comments 

The  following  comments,  arranged  by  compiler,  elabo¬ 
rate  on  the  major  items  not  already  discussed  or  easily 
discernible  from  the  tables:  the  quality  of  the  documenta¬ 
tion,  standards  issues,  user  support,  overall  benchmark 
performance,  and  summary  remarks. 

Aztec  C86 

Aztec  C86  was  easily  one  of  the  fastest  compilers  overall 
and  handled  array  referencing  more  efficiently  than  any 
other  compiler.  Our  results  indicated  that  its  library  pro¬ 
vides  a  lot  of  flexibility,  allowing  you  to  generate  small 
.EXE  files.  It  was  the  best  in  this  area.  The  Aztec  compiler 
provides  an  integer-only  version  of  printf(  ).  There  are 
several  large-memory  models  to  choose  from.  A  make 
utility  is  included. 

There  was  a  general  problem  with  the  benchmarks:  the 
main(  )  routines  used  a  cast  to  produce  a  null  pointer  to 
function  returning  an  int  —  (int  (*)(  ))  0.  Some  compilers 
could  handle  it;  many  others  couldn’t.  Aztec  C86  couldn’t 
handle  this  function  cast  in  main(  ),  but  it  gave  an  excel¬ 
lent  error  message  when  it  tried. 

The  Aztec  manual  had  one  maior  nroblem:  it  has  no 
index.  Otherwise  it  rated  above  average,  though  we 
thought  it  needed  more  white  space  for  readability.  Aztec 
did  include  a  section  on  making  your  code  ROMable,  very 
useful  to  some,  and  it  had  good  examples  in  the  library 
descriptions,  although  these  tended  to  describe  more  than 
one  function  at  a  time.  This  does  save  repeating  the  same 
text  over  again,  but  it  makes  it  a  little  difficult  to  extract 
specifics  about  a  particular  function.  A  new  manual  is 
available  (received  too  late  for  review);  among  the  utilities 
included  are  editor,  debugger,  make,  Aztec  to  .OBJ  format 
converter,  CRC  utility,  archiver,  squeezer,  grep,  and  hex 
generator. 

Control  C  CC-86 

Control  C’s  compiler  is  a  copy  of  Mark  Williams  C 
(MWC),  documentation  and  all,  so  we’ll  refer  you  to  our 
comments  on  MWC.  Apparently,  Control  C  is  doing  the 
port  of  the  Mark  Williams  compiler  to  CP/M-86  and,  as 
part  of  the  arrangement,  is  able  to  provide  the  MSDOS 
version  to  the  outside  world.  The  only  comments  we’ll 
make  specific  to  the  Control  C  compiler  are  that  one  of 
the  six  disks  in  the  original  package  we  received  had  a 
defect  and  that  the  Control  C  package  did  not  include  the 
function-key  pad  overlay  for  the  debugger,  as  the  Mark 
Williams  package  did. 


c-systems  8088  Software  Development  Package 

Although  generally  slow,  the  c-systems  compiler  turned 
in  the  fastest  time  in  the  disk  I /O  test.  It  did  not  do  well  in 
tests  of  generated-code  size,  c-systems  provides  several 
large-memory  models.  The  c-systems  compiler  does  not 
support  fgetc(  )  in  stdio.h,  even  though  fputc(  )  is  sup¬ 
ported.  Consequently,  fgetc(  )  was  coded  as  a  module, 
rather  than  as  a  macro,  using  getc(  ). 

The  c-systems  manual  contained  a  neat  trick  for  compil¬ 
ing  multiple  modules  using  wildcards.  Other  compilers 
support  wildcards  in  the  command  line;  c-systems  uses  the 
for  batch  command  from  the  command  line.  For  example: 

A>for  %f  in  (min*.c)  do  cc  %f 

This  technique  can  be  used  with  any  compiler,  c-systems’ 
compiler  was  about  average  in  the  area  of  documentation. 
It  didn’t  include  a  binder,  and  the  print  quality  was  only 
fair.  The  installation  section  was  too  brief,  but  the  opera¬ 
tion  section  was  well  done.  It  did  include  a  detailed  list  of 
differences  from  the  standard,  something  many  did  not. 
The  library  descriptions  lacked  examples  and  could  use 
more  information  on  return  and  error  values.  There  were 
problems  using  drivers;  occasionally  they  would  not  run  for 
no  apparent  reason  and  they  only  used  the  first  1 28  bytes  of 
the  environment  string  so  the  INCLUDE  variable  was  not 
always  findable;  the  compiler  has  PL/M  compatibility. 

Computer  Innovations  Optimizing  CI-86 

The  CI-86  compiler  performed  in  the  middle  range  on 
nearly  all  the  benchmarks,  walking  away  with  the  co- 
pychr  disk  I/O  test.  One  nice  feature  of  the  CI-86  compil¬ 
er  is  that  numerous  paths  are  provided  for  interfacing  to 
the  operating  system.  For  instance,  you  can  initialize  and 
release  interrupt  routines  such  as  might  be  required  for 
writing  a  communications  program. 

Computer  Innovations  provides  a  large-memory  model, 
although  when  we  used  it,  it  would  not  allow  pointer 
arithmetic  on  a  multiply-dimensioned  array  across  di¬ 
mensions.  The  documentation  explains  this  strange  be¬ 
havior,  stating  that  without  the  functions  that  convert 
pointer  to  long  (ptrtoabs(  ))  and  long  to  pointer  (ab- 
stoptrf  )),  “big-model  pointer  arithmetic  assumes  the 
data  segments  are  the  same  for  the  pointers.”  The  compil¬ 
er  did  give  a  reasonable  error  message  when  an  input  data 
file  was  not  found.  The  CI-86  compiler  doesn’t  allow  +  + 
or - on  float  or  double  variables.  For  example: 

double  d;  d+  +; 
did  not  work,  while 

double  d;  d  +  =  1; 
did  work. 

We  judged  the  Computer  Innovations  manuals  above 
average.  They  included  a  good  approach  in  the  table  of 
contents:  a  one-line  description  of  each  library  function, 
serving  as  a  quick-reference  guide.  Their  library  descrip- 

I 


48 

604 


Dr.  Dobb's  Journal,  August  1985 


tions  were  well  done,  with  examples  and  notes  on  DOS 
applicability.  The  compiler-operation  section  was  too  brief, 
and  no  listing  of  compiler  error  messages  was  included. 

Datalight  C 

The  old  MSDOS  Small-C  compiler  distributed  by 
Datalight  was  a  port  of  the  public  domain  Small-C  com¬ 
piler  for  CP/M.  The  new  C  Compiler,  which  is  a  full 
implementation  of  the  C  language,  stood  out  in  a  number 
of  areas.  It  came  in  first  or  tied  for  first  on  several  tests, 
standing  in  a  class  by  itself  on  the  long-arithmetic  bench¬ 
mark.  Datalight  provides  an  integer-only  version  of 
printff  ). 

The  Datalight  compiler  appears  to  implement  real 
block  scope  for  declarations.  Function  xmalloc(  )  was  de¬ 
clared  as  extern  char  *xmalloc(  )  inside  main,  and  this 
declaration  was  not  in  force  in  later  functions,  leading  to  a 
“definition  inconsistent  with  prior  use”  error  when  the 
function  was  called.  We  view  this  as  appropriate,  but  it  is 
not  clear  what  behavior  K&R  calls  for,  and  most  of  the 
other  compilers  did  not  catch  this  error. 

The  Datalight  compiler  could  not  compile  the  global 
definition 

char  null _ strg[  ]  =  {“”}; 

It  had  no  problem  when  it  was  changed  to 

char  null_strg[  1  ]  =  {“”}; 

The  documentation  that  came  with  the  new  Datalight 
compiler  was  a  significant  improvement  over  the  previous 
version,  as  was  the  compiler  itself.  The  packaging  was  still 
low-budget,  like  the  compiler — 56  8 l/2-by- 1 1  spiral-bound 
pages  with  small,  difficult-to-read  print.  All  the  essentials 
were  there,  though.  There  was  a  usable  table  of  contents 
and  an  index,  the  few  supported  error  messages  were  all 
described,  there  was  a  setup  section  with  a  sample  pro¬ 
gram,  and  some  of  the  function  definitions  included  ex¬ 
amples.  Overall,  not  bad  for  a  $50  compiler. 

DeSmet  C 

The  DeSmet  compiler  was  generally  mid-range  in  speed, 
coming  in  above  average  in  the  screen  I/O  tests  and  losing 
the  floating-point  math  test.  It  produced  the  smallest 
.EXE  files  in  certain  of  the  code-size  tests.  The  compiler  is 
supported  by  a  good  user  group,  and  there  was  a  bug  list. 

No  error  message  was  produced  when  an  input  data  file 
was  not  found  -the  compiler  just  went  to  sleep!  DeSmet’s 
compiler  does  not  like  really  long  comments;  an  error 
message  was  generated  at  line  28  after  the  opening  /*. 

DeSmet’s  manual  is  a  low-budget  product.  Its  pages 
were  not  bound  nor  even  stapled;  they  lacked  an  installa¬ 
tion  section,  and  although  the  DeSmet  package  supplied  a 
lot  of  software,  nowhere  is  there  a  list  of  what  you  should 
have  received  in  the  package.  The  manual  also  omitted  an 
index  and  didn’t  organize  the  functions  as  we  would  have 
liked,  with  one  function  on  a  page.  The  descriptions  were 
brief,  but  they  did  include  examples. 


Digital  Research  DRI C 

Although  the  DRI  compiler  was  among  the  faster  overall, 
it  dawdled  through  long  arithmetic.  The  compiler  per¬ 
formed  poorly  on  the  tests  of  library  granularity.  DRI 
provides  a  large-memory  model,  and  DRI’s  support  got 
high  marks. 

DRI’s  compiler  had  no  problem  with  the  function  cast 
in  main(  ).  No  timer  function  and  no  access  to  DOS  inter¬ 
rupts  were  provided;  an  assembler  is  required,  but  rasm86 
was  included,  and  the  manual  documented  the  assembly 
and  linkage  process  very  well,  including  an  example.  Cap¬ 
ital  letters  on  the  command  line  were  converted  to  lower 
case  before  being  passed  to  main(  ). 

DRI’s  compiler  was  the  only  one  to  report  an  error  for 
two  statements  in  doclOOO.c  that  defined  and  initialized 
global  arrays.  Both  initialization  lists  included  a  trailing 
comma,  as  in: 

int  arrayf  ]  =  {  1, 2,  3,  4, 

5,6,  7,8, 

}; 

The  output  file  produced  by  cpyblk(  )  was  too  small  be¬ 
cause  fread(  )  returned  zero  instead  of  the  actual  number 
of  bytes  read  on  the  final  block.  The  block  size  was  1024 
bytes,  so  the  last  block  would  have  been  a  short  one. 

Digital  Research  put  a  good  documentation  package  to¬ 
gether.  They  provided  a  reprint,  in  their  own  binding,  of 
K&R.  The  index  was  good,  and  a  nice  error  list  appeared 
in  an  appendix.  The  getting-started  and  operation  sections 
were  well  done,  with  good  tables  and  examples.  The  library 
function  descriptions  could  have  been  better  organized, 
though,  and  should  have  included  examples  instead  of  di¬ 
recting  the  reader  to  K&R  for  descriptions  of  functions. 

EcoSoft-C 

The  EcoSoft  compiler  was  not  dazzlingly  fast  nor  dis¬ 
tressingly  slow  on  the  benchmarks.  It  did  well  in  the  code¬ 
size  tests,  producing  a  remarkably  small  .EXE  file  in  the 
minmainf  )  test.  The  library  had  no  fgetc(  )  nor  fputc(  ). 
These  should  have  at  least  been  defined  as  macros  in 
stdio.h.  We  appreciated  the  fact  that  EcoSoft  included 
the  source  for  CC,  its  driver  program.  Given  that  they  had 
written  a  fairly  good  book  on  C,  we  expected  good  docu¬ 
mentation  from  EcoSoft.  We  were  disappointed  to  find  a 
very  short  table  of  contents  and  no  index.  We  found  the 
documentation  difficult  to  use.  The  getting-started  and 
compiler-operation  sections  were  too  brief.  The  library 
functions  did  not  include  examples  and  were  not  terribly 
well  organized  for  easy  reference.  The  compiler  was  hard 
to  use  relative  to  others  in  our  environment  without  re¬ 
compilation  of  the  CC  driver. 

Lattice  C 

The  Lattice  compiler  had  some  strengths  and  weaknesses 
(e.g.,  screen  I/O)  in  execution  speed.  Although  one 
benchmark  is  inadequate  to  assess  a  compiler’s  optimiza¬ 
tion,  it  should  be  noted  that  Lattice  won  the  optimization 
test  hands  down.  Lattice  didn’t  do  particularly  well  on  the 


Dr.  Dobb’s  Journal,  August  1985 


49 


code-size  tests,  but  it  is  one  of  the  few  vendors  providing 
the  flexibility  of  an  integer-only  version  of  printf(  ).  Lat¬ 
tice  also  allows  small  programs  to  be  changed  to  .COM 
files  and  provides  several  large-memory  models  and  an 
option  that  allows  the  programmer  to  trade  some  pointer- 
arithmetic  facility  for  better  generated  code. 

The  LC  program  could  not  handle  directory  path 
names  in  its  file  list,  even  though  LC1  and  LC2  allowed 
this.  The  math  library  was  required  to  do  any  floating¬ 
point  stuff,  not  just  transcendentals,  etc.  Also,  the  math 


Compilers 

Wizard  C  V.  2.1 
Wizard  Systems  Software 
11  Willows  Ct. 

Arlington,  MA  02174 
(617)  641-2379 

DeSmetC  V.  2.4 
C  Ware  Corp. 

P.O.  Box  C 

Sunnyvale,  CA  94087 
(408)720-9696 

Lattice  C  V.  2.14 
Lattice 

P.O.  Box  3072 
Glen  Ellyn,  IL  60138 
(312)858-7950 

Datalight  C  V.  1.0 
Datalight 

11557  8th  Ave  NE 
Seattle,  WA  98 125 
(206)  367-1803 

8088  Software  Dev  Pkg 
V.  2.08a 
c-systems 
P.O.  Box  3253 
Fullerton,  CA  92634 
(714)  637-5362 

Optimizing  CI-86  V.  2.20k 
Computer  Innovations 
980  Shrewsbury  Ave.  #310 
Tinton  Falls,  NJ  07724 
(201)542-5920 

C  Programming  System  V. 
2.0 

Mark  Williams  Co. 

1430  W.  Wrightwood 
Chicago,  IL  60614 
(312)  472-6659 

Microsoft  C  V.  3.0 
Microsoft 

10700  Northup  Way 
Bellevue,  WA  98004 
(206)  828-8088 


Aztec  C86  V.  3.20c 
Manx  Software  Systems 
Box  55 

Shrewsbury,  NJ  07701 
(201)780-4004 

DRIC  V.  1.1 

Digital  Research 

P.O.  Box  579 

Pacific  Grove,  CA  93950 

(408)  649-3896 

CC-86  V.  2.0 
Control  C  Software 
6441  SW  Canyon  Ct. 
Portland,  OR  97221 
(503)297-7153 

Ecosoft-C  V.  2.0 
6413  N.  College  Ave. 
Indianapolis,  IN  46220 
(317)255-6476 

Software  Toolworks  C 
V.  3.2 

Software  Toolworks 
15233  Ventura  Blvd.,  #18 
Sherman  Oaks,  CA  91403 
(818)  986-4885 


Interpreters 

Instant  C  V.  1 .00 
Rational  Systems 
P.O.  Box 

Run/C  V.  1.11 
Age  of  Reason 
318  E.  6th  #  1 23 
New  York,  NY  10003 

C-terp  V.  1 .04 
Gimpel  Software 
3207  Hogarth  Lane 
Collegeville,  PA  1 9426 
(215)  584-4261 


Table  6 

Addresses  of  Compiler  and  Interpreter  Vendors 


library  had  to  precede  the  std  library  in  the  search  order. 
The  manual  documented  neither  of  these  facts.  Having 
long  supported  C  for  MSDOS,  Lattice  has  produced  above 
average  documentation  for  its  compiler  and  included  a 
good  index  into  the  functions.  While  the  installation  and 
getting-started  section  was  brief,  it  was  adequate.  The 
compiler-operation  section  was  good,  but  finding  the  utili¬ 
ties  operations  was  a  little  difficult;  these  should  have  had 
their  own  sections.  The  function  descriptions  were  fairly 
well  done  but  lacked  examples.  The  function  descriptions 
did  include  a  caution  paragraph,  which  is  good.  This  was 
the  only  vendor  to  supply  a  packing  list  with  the  package. 

Mark  Williams  C  Programming  System 

The  Mark  Williams  compiler  was  the  fastest  compiler  in 
the  screen  I/O  tests.  MWC  provides  an  integer-only  ver¬ 
sion  of  printff  ).  We  couldn’t  determine  from  the  manual 
what  the  memory  model  options  were  when  using  MWC 
object  format  (—  vsmall  and  —  vlarge  also  force  Microsoft 
object  format).  There  appeared  to  be  no  way  to  specify  the 
desired  memory  model  with  MWC  object  format,  implying 
that  the  small  model  was  the  only  one  available.  An  at¬ 
tempt  to  cast  a  long  to  a  char  led  to  the  following  compiler 
message: 

1 1 3:  At  59 1 :  Fatal  error:  no  match,  op  =  62 

This  was  not  too  helpful,  and  the  C  syntax  was  legal.  And 
there  was  no  list  of  errors  in  the  manual,  with  or  without 
probable  cause. 

This  compiler  had  a  fairly  well  done  manual,  with  a 
sample  program  to  compile  and  run  during  installation. 
The  function  descriptions  were  reasonably  well  done  and 
readable.  There  could  have  been  more  examples,  and 
many  functions  were  packed  into  one  description,  making 
it  hard  to  extract  pertinent  information. 

Microsoft  C 

The  Microsoft  compiler  was  fast  in  nearly  all  categories, 
although  it  didn’t  do  well  on  the  random-access  diskio(  ) 
test.  We  found  it  to  be  a  carefully  designed  compiler,  very 
well  documented,  and  with  some  well-thought-out  fea¬ 
tures.  It  provides  an  integer-only  version  of  printf(  )  and  a 
variety  of  large-memory  models,  plus  mixed-model  pro¬ 
gramming.  This  last  feature  and  8087  emulation  are  two 
features  only  Microsoft  offers. 

Microsoft’s  compiler  set  a  very  high  standard  for  soft¬ 
ware  documentation.  Two  full  binders  were  included:  a 
complete  language  reference  manual  plus  the  compiler 
and  library  reference  manual.  The  accompanying  slip 
case  folded  down  to  form  a  book  stand.  We  found  it  diffi¬ 
cult  to  fault  any  aspect  of  Microsoft’s  documentation 
package,  except  for  the  slight  jitter  in  their  laser  printer. 
One  experienced  C  programmer  in  the  group  noted  that 
he  had  never  spent  more  than  a  couple  of  minutes  with  the 
documentation,  because  he  was  able  to  find  what  he  need¬ 
ed  very  quickly. 

The  Microsoft  compiler  includes  the  Bessel  functions; 
has  excellent  Unix/Xenix  portability;  and  is  very  easy  to 
use. 


50 


Dr.  Dobb' s  Journal.  August  1985 


Software  Toolworks  C 

The  Software  Toolworks  compiler  for  MSDOS  is  a  port  of 
their  C80  compiler  for  CP/M.  The  original  compiler  was  a 
descendant  of  Small  C,  adding  such  features  as  multi¬ 
dimensional  arrays  and  structures.  The  MSDOS  version 
does  not  currently  support  the  long,  float,  or  double  data 
types.  Longs  and  floats  will  be  available  in  an  optional 
“mathpack,”  currently  under  development.  The  lack  of 
these  data  types  meant  that  we  could  not  run  some  bench¬ 
marks,  and  the  prtf(  )  benchmark  had  to  be  modified  so 
that  the  same  number  of  lines  were  produced,  but  all 
longs,  floats,  and  doubles  were  changed  to  ints. 

Although  the  compiler  does  not  support  longs,  it  does 
support  fseek  for  files  in  excess  of  64K.  The  file  position 
has  to  be  passed  as  an  array  of  two  ints.  As  a  result,  a 
special  version  of  the  diskio  benchmark  was  written;  the 


principal  changes  were  to  make  all  longs  arrays  of  int  and 
to  use  a  file  created  with  the  Lattice  compiler  to  obtain 
the  random  position  to  seek  to.  The  seek  addresses  were 
identical  to  those  used  in  other  benchmarks.  The  compiler 
also  did  not  support  initialization  of  data  of  class  auto. 
Several  of  the  benchmarks  had  to  be  ch  aged  to  account 
for  this  feature. 

Other  unimplemented  features  included;  typedef,  bit 
fields,  and  line  control  (#line).  Major  restrictions  includ¬ 
ed:  function  calls  had  to  have  the  same  number  of  argu¬ 
ments  as  the  called  function  definition,  which  resulted  in 
a  kludge  in  printf,  fprintf,  sprintf,  scanf,  fscanf,  and  scanf; 
declarations  were  allowed  only  at  the  start  of  a  function; 
#define  could  not  have  arguments.  The  printf  kludge  re¬ 
quired  that  stdio.h  be  included  for  minprtf.  The  compiler 
supported  the  small  model  only  (64K  of  code  and  64K  of 


C  Interpreters 

In  addition  to  the  compilers,  we  evaluated  three  C  inter¬ 
preters:  C-Terp,  Instant  C,  and  Run/C.  We  were  fairly 
demanding  of  the  interpreters,  and  our  comments  should 
probably  be  taken  as  an  experienced  programmer’s  view. 
A  beginner  might  be  happier  than  we  were  with  these 
products.  They  were  generally  faster  than  the  compilers 
when  it  came  to  making  a  change  and  seeing  what  the 
results  were,  and  that’s  important  when  you’re  learning  a 
language.  We  did,  however,  have  some  complaints. 

All  three  forced  some  rewriting  of  our  benchmark  pro¬ 
grams.  All  three  lacked  some  run-time  support  modules, 
in  particular  fread  and  fwrite.  (C-Terp  supplied  them,  but 
not  in  the  standard  release  versions.)  All  three  were  mem¬ 
ory-hungry,  requiring  some  300K  to  execute.  We  found  at 
least  one  bug  in  each  of  the  interpreters  in  the  process  of 
running  our  benchmarks,  once  we  got  the  benchmark 
code  in  a  form  each  compiler  would  accept.  Of  the  three, 
Run/C  was  the  least  expensive,  but  also  the  least  flexible. 
Block  comments  had  to  start  in  column  one  with  only  the 
“/*”  on  the  line;  the  terminating  “*/”  had  to  meet  similar 
constraints.  Run/C  was  also  slow  in  loading  and  execut¬ 
ing.  When  we  specified  a  program  from  the  command 
line,  Run/C  loaded  it,  then  checked  for  errors;  when  one 
was  found,  Run/C  aborted  interpretation,  and  we  lost  the 
(significant)  load  time.  Run/C  could  not  handle  pointers 
to  functions. 

Instant  C  was  fastest  and  had  most  of  the  features  we 
felt  we  couldn’t  get  along  without,but  we  found  that  its 
editor  got  in  the  way.  We  were  frustrated  by  its  limitation 


to  two  array  indices  and  by  its  small  work  area.  It  had 
nice,  APL-like  immediate-mode  variable  output  that  was 
very  handy  during  the  debug  phase. 

C-Terp  was  the  most  flexible  and  came  closest  to  imple¬ 
menting  the  complete  language.  It  had  the  ability  to  inter¬ 
face  to  functions  written  in  assembly  language,  although 
requiring  a  compatible  compiler  to  implement  the  interface. 
Assembly-language  interface  is  promised  for  the  near  future 
for  the  other  two  interpreters.  Unfortunately,  C-Terp’s  doc¬ 
umentation  was  sparse,  a  problem  for  beginners. 

These  tools  are  useful  for  checking  out  a  short  algo¬ 
rithm  quickly,  but  not  as  the  sole  language  implementa¬ 
tion  for  developing  complex  pieces  of  software.  You  can 
learn  C  from  them,  although  a  purist  might  argue  that 
you  don’t  know  C  until  you  have  worked  with  the  full 
language.  Of  these  interpreters  only  C-terp  offered  one  of 
C’s  greatest  strengths,  the  flexibility  of  using  pointers  as 
data. 

There  are  three  questions  to  ask  yourself  in  considering 
buying  one  of  these  interpreters  or  recommending  it  to  oth¬ 
ers:  Can  your  programming  needs  be  met  by  an  interpreter 
alone?  If  you’re  planning  to  develop  significant  software  in 
C,  they  probably  can’t.  Is  it  worthwhile  to  you  to  purchase 
both  an  interpreter  and  a  compiler?  It  may  well  be;  they 
address  different  needs.  And  if  you  intend  to  buy  an  inter¬ 
preter,  which  one?  We’ve  provided  some  material  here  for 
the  beginning  of  your  evaluation  process,  but  note  that  we 
only  looked  at  three  C  interpreters.  And  this  is  an  experi¬ 
enced  programmer’s  view  of  these  products.  For  the  bench¬ 
mark  results  see  Table  8  (below). 


Interpreter: 

tint 

tlone 

array 

pointer 

looptst 

Opt 

fib  test 

sieve 

C-terp 

.2 

.2 

22.1 

28.0 

41.7 

126.4 

54.5 

329.2 

Instant  C 

<0.1 

<0.1 

<0.1 

<0.1 

.8 

1.6 

19.2 

4.4 

Run/C 

2  .4 

2.5 

1 15.2 

1 13.1 

205  3 

815.5 

252.5 

407.3 

Table  8 

Addresses  of  Compiler  and  Interpreter  Vendors 


Dr.  Dobb's  Journal,  August  1985 


52 

fin  7 


data).  The  compiler  included  a  function  profiler  that  re¬ 
corded  the  total  number  of  calls  to  a  function  and  the  time 
spent  in  each  function.  In  contrast  to  the  C80  compiler, 
this  compiler  did  not  support  in-line  assembly  code,  al¬ 
though  details  of  the  assembly  language  interface  were 
provided  in  the  manual.  This  was  clearly  a  subset  compil¬ 
er,  with  poor  documentation.  There  was  an  index,  though 
there  were  many  functions  to  a  page,  making  it  difficult  to 
find  and  read  the  descriptions.  The  function  descriptions 
were  terse  and  devoid  of  examples,  again  making  it  diffi¬ 
cult  for  a  new  user. 

Wizard  C 

Wizard’s  was  generally  a  fast  compiler.  It  supports  a  vari¬ 
ety  of  large-memory  models,  lets  the  programmer  trade 
some  pointer-arithmetic  capabilities  for  better  generated 
code,  and  allows  very  small  programs  to  be  changed  to 
.COM  files.  Wizard’s  got  the  highest  marks  for  support. 

Field  width  specification  did  not  work  for  character 
conversions  in  printf(  );  e.g.,  printf(%17c,c)  produced  one 
character,  not  17.  Careful  reading  of  K&R  leaves  us  with 
small  doubt  that  Wizard  is  in  the  wrong.  It  is  certainly 
safe  to  say  that  if  Wizard  maintains  that  field  width  is  not 
supposed  to  work  with  %c,  then  they  stand  alone. 

The  Wizard  compiler  was  an  obvious  port  down  from 
Unix,  as  the  documentation  reflected  Unix  throughout. 
While  the  getting-started  section  was  small  (one  page) 
and  no  installation  section  was  included,  we  appreciated 
the  22  pages  of  error  messages.  The  function  descriptions 
were  readable,  but  like  functions  were  grouped  together 
and  there  were  no  examples. 

Excellent  diagnostics  were  provided  for  portable  code;  to 
increase  portability  from  Unix,  the  library  included  Unix 
functions  that  have  little  meaning  but  are  a  pain  to  remove 
(e.g.,  getpid).  The  compiler  supports  anachronisms  (=  + 

. . .);  supports  the  use  of  libraries  for  other  compilers  by 
changing  call  and  return  specifics;  and  includes  lint.  It  has 
PL/M  compatibility  and  is  very  easy  to  use. 


Aztec  $  1 99  -  Pro  Package  $  500 

Microsoft  $279 


Computer  Innovations  $395 


DRI  $350 

Mark  Williams  $495 

Control-C  $500 

c-Systems  $199 

Wizard  $450 


DeSmet 


$109  +  $50  (debugger) 
+  $35  (DOS  LINK) 


Datalight 

Ecosoft 

Lattice 

Software  Toolworks 

C-terp 

Instant  C 

Run/C 


$60 

$49.95 

$425 

$49.95  +  ($29.95  float) 

$300 

$495 

$149.95 


Table  7 

Compiler  and  Interpreter  Prices 


Miscellaneous  Comments 

Microsoft,  Wizard,  Mark  Williams,  Control  C,  Ecosoft 

These  all  had  drivers  that  allowed  the  compilation  of  sev¬ 
eral  files  (assembly  of  assembly  language  files)  and  op¬ 
tional  linkage  of  the  files.  This  proved  to  be  a  great  boon. 

Mark  Williams,  Control  C 

Excellent  source  level  debuggers  and  very  nice  examples 
of  usage  in  the  manual  and  on  disk;  does  not  interfere  with 
screen  I /O. 

DeSmet,  Aztec,  c-systems 

Included  source  or  symbolic  level  debuggers,  but  we  did 
not  test  them. 

Microsoft,  Lattice,  Wizard 

Support  use  of  third  party  or  optional  source  level  debug¬ 
gers,  such  as  SYMDEB. 

Aztec,  Wizard,  Lattice,  DeSmet 
Provide  for  generation  of  ROMable  code. 

Microsoft,  Aztec,  Mark  Williams,  Control  C, 
c-systems,  DeSmet,  Wizard 

The  first  six  used  environment  to  pass  “usual”  options  to 
compiler;  Wizard  used  a  file  and  this  approach  was  slight¬ 
ly  preferred.  DeSmet  and  c-systems  had  bugs  in  usage. 
Microsoft,  Aztec,  Wizard,  Contol  C,  Mark  Williams 
Support  new  structures  (passing,  assigning,  and  return¬ 
ing)  and  enum  and  void. 

Aztec,  DRI 

Could  not  locate  math  functions  in  table  of  contents;  in¬ 
cludes  overlay  support. 

Mark  Williams,  Control  C 

Unable  to  determine  from  the  documentation  what  the 
specifics  of  the  large  model  were. 

Aztec,  CI-86,  Wizard,  Software  Toolworks 
Library  source  included  or  available. 

Summary 

Were  we  objective?  We  all  had  our  preferences  and  un¬ 
equal  experience  with  the  compilers  going  into  the  review; 
we  can’t  expect  that  we  entirely  overcame  them.  But  large 
amounts  of  data  can  swamp  out  fairly  extreme  priors,  and 
it  was  hard  to  deny  the  numbers  we  were  getting. 

So,  who  won?  If  you  pressed  us  to  declare  a  winner, 
we’d  probably  say  the  Microsoft  or  Aztec  compiler.  But 
the  Wizard  compiler  had  excellent  diagnostics;  it  would 
be  easier  writing  portable  code  with  it  than  with  any  other 
compiler  we  tested.  And  the  Mark  Williams  compiler  had 
the  best  debugger.  Our  data  would  support  different  re¬ 
sults,  depending  on  how  you  chose  to  weight  the  features 
we  assessed.  No  two  programmers  will  weight  them  the 
same  way.  And  we  took  almost  no  account  of  price.  For 
addresses  of  compiler  vendors  see  Table  6  (page  54);  for 
compiler  prices  see  Table  7  (page  50). 

DD| 


54 


cno 


Dr.  Dobb's  Journal.  August  1985 


A  Peephole  Optimizer  for 
Assembly  Language  Source  Code 


by  David  L.  Fox 


The  need  for  small  fast  programs 
is  a  constant  of  computer  pro¬ 
gramming.  Unfortunately, 
small  fast  programs  tend  to  be  diffi¬ 
cult  to  write  and  maintain.  As  a  re¬ 
sult  a  programmer  often  has  to  make 
a  choice  between  assembly  language 
and  a  high-level  language. 

D.  E.  Cortesi,  who  has  discussed 
this  problem  in  several  “Dr.  Dobb’s 
Clinic”  columns,1  has  pointed  out 
that  an  optimizing  compiler  is  a  pow¬ 
erful  incentive  to  choose  a  high-level 
language.  The  compilers  currently 
available  for  microcomputers,  how¬ 
ever,  fall  far  short  of  the  state  of  the 
art  in  terms  of  the  size  and  speed  of 
the  code  they  produce.  In  fact,  many 
of  these  compilers  produce  code  that 
could  be  improved  by  the  simple 
technique  of  peephole  optimization. 

In  this  article,  I  describe  a  stand¬ 
alone  peephole  optimizer  that  can 
post-process  the  output  of  any  compil¬ 
er  producing  assembly  language 


Program  Optimization 

The  term  optimization,  as  it  is  usual¬ 
ly  applied  to  programs,  is  actually 
something  of  a  misnomer:  it  derives 
from  the  word  optimum,  meaning  the 
best  possible,  and  no  program  opti¬ 
mizer  or  optimizing  compiler  can  se¬ 
riously  claim  to  produce  the  best  pos¬ 
sible  code.  A  more  realistic  goal  is 
merely  to  improve  the  code. 

The  greatest  improvement  in  a 
program  usually  comes  from  using  a 
better  algorithm.  If  you  are  not  con¬ 
vinced  of  the  importance  of  choosing 
an  efficient  algorithm,  I  suggest  that 
you  write  two  routines  to  sort  an  ar¬ 
ray  of  numbers,  one  using  a  bubble 
sort  and  another  using  a  Shell  sort. 
Use  them  to  sort  an  array  of  1000 
numbers.  The  bubble  sort,  which  re¬ 
quires  a  time  proportional  to  the 
square  of  the  number  of  items  sorted, 
will  run  tens  or  even  hundreds  of 
times  longer  than  the  Shell  sort, 
which  requires  time  proportional  to 


Algorithms  for  finding  and  replacing  regular  ex¬ 
pressions  are  well  known.  Common  inefficiencies 
in  compiler  output  can  be  specified  as  regular 
expressions.  So .. . 


source  code.  The  program  is  written  in 
C  and  has  been  compiled  with  Soft¬ 
ware  Toolworks’  C/80  compiler.  Ac¬ 
cording  to  a  published  benchmark 
comparison,2  in  terms  of  both  code 
size  and  execution  speed  this  is  one  of 
the  better  C  compilers  available  for 
CP/M.  Nevertheless,  peephole  optimi¬ 
zation  can  improve  its  output,  and  I 
will  use  it  for  the  examples  in  this 
article. 


David  L.  Fox,  2140  Braun  Dr.,  Gold¬ 
en,  CO  80401 


5  to  sort  n  items.  Unfortunately, 
the  choice  of  algorithm  is  not  some¬ 
thing  that  we  can  easily  mechanize. 
If  you  want  a  substantial  improve¬ 
ment  in  the  performance  of  a  pro¬ 
gram,  rewriting  it  using  a  better  algo¬ 
rithm  may  be  the  only  choice 
available. 

The  more  modest  improvements 
obtained  by  the  means  discussed  be¬ 
low  should  not  be  dismissed,  however, 
because  they  frequently  lend  them¬ 
selves  to  implementation  by  a  pro¬ 
gram  (an  optimizing  compiler  or  a 
separate  optimizer)  and  require  little 


Dr.  Dobb's  Journal,  August  1985 


56 

609 


effort  on  the  part  of  the  programmer. 
Many  of  these  improvements  involve 
recognizing  specific  constructions  in 
the  input  and  replacing  them  with 
more  efficient  code  in  the  output.  This 
process  is  easily  mechanized. 

One  rich  source  of  potential  im¬ 
provement  in  compiler  output  is  re¬ 
moving  the  recalculation  of  values 
that  are  already  available.  For  exarr^ 
pie,  a  straightforward  compilation  of 
a  statement  such  as 

a[i]  =  b[i]  +  c[i] 

will  calculate  the  byte  offset  associat¬ 
ed  with  the  subscript  i  three  times.  (If 
a,  b,  and  c  are  arrays  of  16-bit  inte¬ 
gers,  this  offset  is  2*i.)  Substantial 
savings  would  be  possible  if  this  type 
of  situation  were  recognized  and  the 
offset  calculated  only  once. 

An  even  simpler  example  of  this  is 
illustrated  in  Listing  One  (page  68) 
where  the  compiler  produces  the  pair 
of  instructions: 

SHLDi 

LHLD  i 

After  execution  of  SHLD  i,  the  con¬ 
tents  of  i  are  still  in  the  HL  register 
pair  and  the  LHLD  i  may  be  omitted. 
Note,  however,  if  the  LHLD  instruc¬ 
tion  is  labeled,  it  may  not  be  omitted. 

Another  example  in  Listing  One  is 
the  following: 

MOV  A,H 

ORA  L 

JNZ  LI 

LXI  H,0 

The  zero  flag  is  set  if  HL  contains 
zero,  in  which  case  the  jump  to  LI  is 
not  taken  and  the  LXI  H,0  may  be 
removed. 

Simple  compilation  of  conditionals 
and  loops  often  results  in  multiple 
jump  instructions  that  can  be  easily 
improved.  A  jump  to  a  jump  can  be 
replaced  by  a  single  jump  instruction, 
as  shown  in  Listing  One.  Another 
commonly  found  construction  is  the 
jump  over  a  jump,  such  as 

JZ  LI 
JMP  L2 

LI:  .  .  . 


which  may  be  replaced  by 

JNZ  L2 
LI:... 

Any  unlabeled  instruction  follow¬ 
ing  an  unconditional  jump  can  never 
be  executed  and  may  be  removed. 
Also  candidates  for  removal  are  un¬ 
reachable  instructions  created  by  the 
jump  optimization  described  above. 
For  example: 

JMP  LI 


JMP  L2 
LI:  JMP  L3 

After  we  replace  JMP  LI  with  JMP 
L3  and  remove  the  label  LI,  JMP  L3 
becomes  unreachable  and  may  also 
be  removed.  Thus,  one  improvement 
can  create  an  opportunity  for  further 
improvement,  making  it  worthwhile 
to  pass  the  program  through  the  opti¬ 
mizer  more  than  once. 

The  examples  so  far  have  not  in¬ 
volved  any  trade-offs  between  code 
size  and  execution  speed.  In  the  fol¬ 
lowing  situation,  speed  or  size  can  be 
optimized,  but  not  both.  Pointer  de¬ 
referencing  (obtaining  the  value 
stored  at  a  given  address)  may  in¬ 
volve  the  following  subroutine: 

deref:  MOV  A,M 
INX  H 
MOV  H,M 
MOV  L,A 
RET 

Excluding  the  RET  instruction,  the 
above  subroutine  is  only  four  bytes 
long.  Because  any  program  that  uses 
pointers  or  arrays  does  quite  a  bit  of 
dereferencing,  use  of  the  subroutine 
deref  will  produce  a  smaller  program. 
If  speed  is  more  important,  however, 
replacing  the  three-byte  instruction 
CALL  deref  with  four  bytes  of  in-line 
code  will  result  in  faster  execution. 
Some  compilers  allow  the  user  to 
choose  whether  smaller  or  faster  code 
is  more  important,  but  most  compil¬ 
ers  for  microcomputers  do  not  pro¬ 
vide  this  option. 

The  details  of  the  compiler  and  the 
machine  instruction  set  may  offer 


other  opportunities  for  improvement. 
One  common  possibility  arises  from 
the  fact  that  many  compilers  for 
CP/M  produce  8080  code.  If  you 
have  a  Z80  processor,  you  may  find 
ways  to  replace  combinations  of  8080 
instructions  with  Z80  instructions.  A 
good  example  is  a  16-bit  subtraction, 
which  requires  six  instructions  on  an 
8080  but  only  a  single  Z80  instruc¬ 
tion.  Identifying  the  possible  im¬ 
provements  for  your  compiler  will  re¬ 
quire  careful  study  of  its  output  and 
runtime  library. 

These  examples  illustrate  the  types 
of  improvements  that  the  peephole  op¬ 
timizer  can  make.  Many  other  im¬ 
provements,  which  require  some 
knowledge  of  the  organization  and 
structure  of  the  program  such  as  rec¬ 
ognizing  constant  expressions  and  re¬ 
moving  them  from  loops,  are  beyond 
the  scope  of  the  optimizer  presented 
here. 

If  the  optimizer  is  specific  to  one 
compiler,  it  can  be  quite  simple;  see 
the  optimization  section  of  Small-C 
version  2. 3  On  the  other  hand,  if  you 
want  to  use  the  program  with  differ¬ 
ent  compilers  or  have  some  flexibility 
in  what  changes  to  make,  the  opti¬ 
mizer  will  require  most  of  the  capa¬ 
bilities  of  a  general  text  editor.  For 
that  reason,  I  have  borrowed  heavily 
from  Chapters  Five  and  Six  of  Ker- 
nighan  and  Plauger’s  book  Software 
Tools  in  Pascal ,4  which  give  Pascal 
listings  for  a  fairly  complete  editor. 
Listing  Five  (page  76)  contains  the 
needed  routines  (translated  into  C 
and  slightly  extended)  from  Ker- 
nighan  and  Plauger’s  editor. 

Regular  Expressions 

Once  you  have  identified  output  from 
your  compiler  that  may  be  improved, 
you  must  communicate  descriptions 
of  the  improvable  constructions  to 
the  optimizer  program.  The  notation 
must  be  precise  and  unambiguous — 
capable  of  specifying  constructions 
such  as: 

first  line:  SHLD  (any  label) 

second  line:  LHLD  (same  label 
as  above) 

A  notation  sufficiently  powerful  to 
describe  such  strings  is  used  widely 


57 


Dr.  Dobb's  Journal,  August  1985 


within  the  Unix  environment.  This 
notation,  adopted  here,  is  that  of  reg¬ 
ular  expressions. 

A  regular  expression  is  a  descrip¬ 
tion  of  a  set  of  strings.  The  simplest 
case  is  when  the  regular  expression 
describes  a  single  string:  the  regular 
expression  is  just  the  string  itself.  For 
example,  the  regular  expression  for 
the  8080  store  HL  instruction  mne¬ 
monic  is  SHLD.  Nonprinting  charac¬ 
ters  are  represented  by  the  standard 
C  escapes: 

\b  backspace 

\n  newline 

\r  carriage  return 

\t  tab 

\\  backslash 

\nnn  byte  with  octal  value  nnn 

An  alternative  way  of  looking  at  a 
regular  expression  is  as  a  pattern.  A 
string  matches  the  pattern  if,  starting 
at  the  left,  each  character  of  the 
string  matches  the  corresponding 
character  of  the  pattern.  Because  the 
optimizer  is  concerned  with  identify¬ 
ing  lines  of  assembly  language  code, 
we  will  say  that  any  line  containing  a 
string  described  by  a  regular  expres¬ 
sion  matches  that  regular  expression. 

To  specify  more  complex  patterns, 
we  need  an  element  that  can  match 
more  than  one  character  in  the  input 
string.  One  such  useful  element  is  the 
wild  card  character  ( .  ),  which  will 
match  any  single  character.  We  can 
also  specify  one  of  a  group  of  charac¬ 
ters  by  using  a  character  class,  which 
consists  of  a  [,  followed  by  a  list  of 
characters,  and  ends  with  a  ],  to 
match  a  single  occurrence  of  any  of 
the  characters  enclosed  in  the  square 
brackets.  For  example,  [SLJHLD  will 
match  SHLD  or  LHLD. 

We  may  use  a  shorthand  notation 
for  all  consecutive  letters  or  digits:  [a- 
z]  matches  any  lower-case  letter,  and 
[A-ZO-9]  matches  any  upper-case  let¬ 
ter  or  any  digit.  If  the  first  character 
of  the  character  class  is  ~,  it  negates 
the  character  class:  it  will  match  any 
character  except  those  in  the  list  fol¬ 
lowing  the  For  example,  [~aA] 
matches  any  character  except  a  or  A. 

Character  classes  are  essential  to 
matching  constructions  such  as  “any 
label.”  The  standard  C  identifier  con¬ 


sists  of  any  string  of  letters,  digits,  or 
underscore  characters  that  does  not 
begin  with  a  digit;  thus,  the  character 
class  that  matches  the  first  character 
of  an  identifier  is  [a-zA-Z„],  and  the 
remainder  of  the  characters  match 
the  class  [a-zA-Z0-9„]. 

To  fully  describe  a  label  with  a  reg¬ 
ular  expression,  we  need  a  construc¬ 
tion  for  “the  remainder  of  the  charac¬ 
ters”  that  does  not  limit  us  to  a  I 
specific  number  of  characters.  We 
achieve  this  by  following  any  charac¬ 
ter  (or  character  class)  by  an  aster¬ 
isk,  *.  The  result,  called  a  closure, 
will  match  zero  or  more  occurrences 
of  the  single  character  preceding  the 
*.  For  example,  aa*  matches  any 
string  consisting  of  one  or  more  oc¬ 
currences  of  the  letter  a.  The  regular 
expression  describing  a  C  identifier 
can  now  be  given  as: 

[a-zA-Z^][a-zA-Z0-9  ]* 

This  actually  matches  an  identifier  of 
unlimited  length,  but  it  serves  our 
needs. 

We  now  need  some  way  of  specify¬ 
ing  “same  label  as  above.”  For  this, 
we  draw  on  the  ability  to  group  por¬ 
tions  of  a  pattern  and  refer  to  the 
group(s)  later.  A  portion  of  a  pattern 
that  appears  between  the  pairs  of 
characters  \(  and  \)  constitutes  a 
group.  A  group  of  characters 
matched  by  a  parenthetical  pattern 
may  be  identified  by  the  notation  \1 
for  the  first  group  in  the  pattern,  \2 
for  the  second,  up  to  \9  for  the  ninth. 
For  example, 

\(mat\).*\l 

matches  mathematician  while 

\(.  .A).*\l 

matches  barbarian,  mathematician, 
and  any  string  that  repeats  a  group  of 
three  letters.  The  notation  developed 
so  far  allows  us  to  describe  the  exam¬ 
ple  at  the  beginning  of  this  section 
with  two  regular  expressions: 

SHLD\t\([a-zA-Z  ] 

[a-zA-Z0-9  ]*\) 

LHLD\t\l 

Finally,  it  is  sometimes  useful  to 


require  that  the  string  matching  a 
regular  expression  occur  at  the  begin¬ 
ning  or  end  of  a  line.  The  characters 
to  do  this  are  “  for  the  beginning  of  a 
line  and  $  for  the  end  of  a  line.  Thus, 
*A  matches  any  line  that  begins  with 
A,  and  "$  matches  only  empty  lines. 
Any  characters  that  have  special 
meanings  (e.g.,  [  ]*.V$~-)  may  have 
the  special  meaning  turned  off  by 
preceding  the  special  character  with 
\.  The  pattern  \.$  matches  any  line 
that  ends  with  a  period. 

This  flexible  and  powerful  notation 
suffices  to  describe  most  construc¬ 
tions  appropriate  for  improvement 
with  a  peephole  optimizer.  There  are 
a  few  traps  for  the  unwary,  especially 
when  using  closures.  Remember  that 
x*  matches  zero  or  more  occurrences 
of  x,  so  that  the  regular  expression  x* 
matches  any  line,  even  those  with  no 
x’s  at  all.  When  you  want  to  match 
one  or  more  x’s,  use  xx*.  Another 
point  is  that  any  closure  matches  the 
longest  possible  string:  e.*e  matches 
the  entire  word  emergence,  not  just 
eme  or  erge.  Table  1  (below)  gives 
some  more  examples  of  regular  ex¬ 
pressions  and  the  strings  they  de¬ 
scribe.  For  a  more  extensive  discus¬ 
sion  of  regular  expressions,  see 
Chapter  3  of  Aho  and  Ullman’s  Prin¬ 
ciples  of  Compiler  Design ,5 

Using  the  Optimizer 

The  description  of  each  improvement 
in  the  compiler  output  has  two  parts:  a 
list  of  regular  expressions  describing 
the  code  to  be  replaced  and  a  list  of 
lines  of  improved  assembly  language 
code.  A  group  of  lines  is  replaced  only 
if  each  line  in  turn  matches  the  corre¬ 
sponding  regular  expression.  The  con¬ 
structions  \1,  \2,  and  so  on  in  a  re¬ 
placement  cause  the  replacement  to 
contain  the  same  string  matched  by 
the  corresponding  “\(  \)”  group  in  the 
list  of  regular  expressions. 

The  list  of  potential  improvements 
is  placed  in  a  file  that  is  read  by  the 
optimizer.  Listing  Six  (page  82) 
shows  such  a  file  for  use  with  C/80 
compiler  output.  Each  improvement 
consists  of  the  following:  one  or  more 
lines  containing  regular  expressions 
identifying  the  construction  that  may 
be  improved,  a  separator  line  that 
contains  a  %  as  its  first  character. 


Dr.  Dobb  s  Journal,  August  1985 


64 


zero  or  more  lines  that  are  to  replace 
the  text  matched  by  the  regular  ex¬ 
pressions,  and  a  terminator  line  start¬ 
ing  with  %%.  The  file  may  contain 
any  number  of  potential  improve¬ 
ments.  In  Listing  Six,  commepts  de¬ 
scribe  each  improvement;  they  make 
the  output  somewhat  larger  than  the 
input  and  may  be  removed  if  desired. 

The  program  command  line  syntax 
is 

opt  options  infile  outfile 

where  infile  is  the  name  of  the  file 
containing  the  assembly  language 
source  code  to  be  optimized,  outfile  is 
the  name  of  the  file  to  contain  the  op¬ 
timized  output  (must  be  different 
from  infile),  and  options  represents 


zero  or  more  of  the  following  optional 
parameters: 

-bn  Store  n  lines  in  the  internal 
FIFO  buffer.  Default  is  500. 

-f file  Read  the  list  of  improvements 
from  file.  Default  is  opt.dat. 

-j re  A  jump  instruction  described 
by  the  regular  expression  re. 
Default  is  \tJ[CEMNOPZ]*\t. 
-1  re  A  label  described  by  the  regu¬ 
lar  expression  re.  Default  is  [a- 
zA-Z_\.][a-zA-Z_0-9]*. 

-o  Omit  jump  optimization.  With 
this  option  selected,  the  opti¬ 
mizer  can  be  used  as  a  general 
purpose  stream  editor. 

-u re  An  unconditional  jump  instruc¬ 
tion  described  by  the  regular 
expression  re.  Default  is 


\tJMP\t. 

-v  Turn  off  verbose  output.  Nor¬ 
mally  opt  displays  the  last  line 
of  every  improvable  construc¬ 
tion  on  the  console;  selecting 
this  option  omits  that  output. 

The  internal  organization  of  the 
program  is  quite  simple.  Each  line  is 
read  into  a  FIFO  buffer  in  memory. 
The  size  of  this  buffer  determines  the 
range  of  jump  instructions  that  may 
be  optimized.  The  input  is  compared 
with  the  regular  expressions  of  the 
improvable  constructions,  which  are 
contained  in  a  linked  list.  If  a  match 
is  found,  the  appropriate  replacement 
is  made.  A  symbol  table  containing 
all  the  labels  in  the  input  is  main¬ 
tained  for  jump  optimization. 

Because  this  article  is  on  optimiza¬ 
tion,  it  is  appropriate  to  discuss  the  op¬ 
timization  of  the  optimizer.  As  given 
in  Listings  Two  to  Six  (pages  68-83), 
the  program  processes  approximately 
200  lines  per  minute.  Using  the  opti¬ 
mizer  on  its  own  assembly  language 
code  saved  199  bytes  and  increased 
speed  by  about  2%.  Adding  to  the 
data  file  some  constructions  specific 
to  the  optimizer  and  reoptimizing 
saved  an  additional  86  bytes  and  in¬ 
creased  speed  by  another  1  %,  for  a  to¬ 
tal  improvement  of  285  bytes  and 
about  3%.  A  third  run,  made  with  a 
data  file  that  replaced  calls  to  the 
pointer  dereferencing  routine  with  in¬ 
line  code,  increased  program  size  by 
473  bytes  and  speed  by  10%. 

We  might  wish  to  improve  two  sep¬ 
arate  aspects  of  the  peephole  optimiz¬ 
er’s  performance.  First,  consider  the 
amount  of  improvement  the  optimizer 
makes  in  the  programs  it  processes. 
We  could  increase  performance  in  this 
area  by  finding  more  improvable  con¬ 
structions  in  the  compiler  output  and 
adding  them  to  the  data  file  opt.dat. 
Unless  we  have  missed  some  extreme¬ 
ly  poor  and  very  common  compiler 
output,  however,  this  approach  will 
likely  produce  only  a  small  increase  in 
performance.  As  is  usually  the  case, 
substantial  improvement  requires  the 
use  of  an  improved  algorithm. 

Second,  consider  the  speed  of  the 
optimizer  in  accomplishing  its  task — 
presently  about  200  lines  per  minute. 
Once  again,  small  improvements  may 


Matches  any  line  with  exactly  three  characters 
Matches  any  string  enclosed  in  parentheses 
Matches  any  C  comment  containing  only  blanks  and 
letters 

Matches  any  string  of  nonblank  characters 
Matches  "loop  JMP  loop"  but 

not  "loopl  JMP  loop" 

or  "abc  JMP  xyz" 

Matches  any  string  containing  two  adjacent  repeat¬ 
ed  substrings  such  as  beriberi,  vacuum,  and 
tintinnabulation 

Matches  affair,  accommodate,  and  this  is  the  end 
(\1="th",\2-"is  ") 


Table  1 

Examples  of  Regular  Expressions  and  Matching  Strings 


ROUTINE 

CALLS 

TICKS 

%  TIME  (Approx.) 

amatch 

81309 

9370 

27 

omatch 

92501 

6548 

19 

fgets 

3149 

3707 

11 

outline 

3103 

3541 

10 

match 

32504 

3011 

9 

locate 

36934 

2504 

7 

patsize 

42331 

2331 

7 

findsym 

1316 

1363 

4 

strcmp 

34942 

812 

2 

free 

3374 

654 

2 

optimiz 

3103 

376 

1 

malloc 

3692 

287 

1 

main 

1 

112 

0 

Table  2 

Profiler  Output  for  Optimizing  a  Test  File 


\  .  .$ 

(•*) 

/  \*[  a-zA-Z]*\*/ 

(~  r 

\([a-z][a-z]*\)\t  JMP\t\  1 

\(..*\>\1 

\(..*\)\(..*\)\2\1 


66 


Dr.  Dobb's  Journal,  August  1985 


be  made  by  tweaking  the  present 
code,  but  major  improvements  will 
require  a  change  to  better  algo¬ 
rithms.  By  using  the  profiler  included 
in  the  Software  Toolworks’  C/80 
package,  we  can  identify  those  sub¬ 
routines  of  the  program  that  are  exe¬ 
cuted  most  frequently.  The  profiler 
output  for  a  run  of  the  optimizer  is 
shown  in  Table  2  (page  66). 

Table  2  shows  that  the  optimizer 
spends  over  two-thirds  of  its  time 
matching  regular  expressions  to 
strings  (match(  )  and  subsidiary  rou¬ 
tines).  Clearly,  any  significant  in¬ 
crease  in  speed  will  require  an  im¬ 
provement  in  the  pattern-matching 
code.  I  have  made  a  small  effort 
along  these  lines  by  replacing  the  C 
version  of  the  routine  locate)  )  with 
an  assembly  language  version.  Be¬ 
cause  better  algorithms  for  regular 
expression  matching  are  known,  this 
seems  like  the  most  profitable  course 
for  increasing  the  speed  of  the  pro¬ 
gram.  In  contrast,  replacing  the  lin¬ 
early  searched  linked  list  used  for  the 
symbol  table  with  a  more  efficient 
hash  table  cannot  increase  the  speed 
of  the  program  by  more  than  4%  be¬ 
cause  only  4%  of  the  execution  time  is 
spent  in  the  routine  findsym(  )  look¬ 
ing  up  symbols  in  the  table. 

Installation 

Although  I  have  endeavored  to  make 
the  program  as  flexible  as  possible  so 
it  will  be  useful  with  a  variety  of  com¬ 
pilers,  I  have  tested  it  only  with  the  C/ 
80  compiler.  Users  of  other  compilers 
will  have  to  construct  a  data  file  like 
that  in  Listing  Six  for  the  construc¬ 
tions  peculiar  to  their  compiler. 

Listings  Two  to  Five  are  “stan¬ 
dard”  C  and  should  be  portable  to 
other  C  compilers.  My  library  follows 
the  usage  of  the  Unix  (version  7)  li¬ 
brary  and  differs  only  slightly  from 
the  library  distributed  with  C/80  and 
some  other  C  compilers.  Listing  Sev¬ 
en  (page  83)  shows  the  library  func¬ 
tions  used  by  the  optimizer. 

Although  I  have  not  tested  it,  the 
new  Small-C  library6  appears  to  con¬ 
tain  suitable  routines,  with  the  excep¬ 
tion  of  the  memory  management  rou¬ 
tines  malloc(  )  and  free)  ).  The 
optimizer  frees  blocks  of  memory 
without  regard  to  the  order  in  which 


they  were  allocated.  The  C/80  library 
(version  3)  also  contains  suitable  rou¬ 
tines,  provided  that  the  error  return 
value  is  changed  to  0  (NULL)  from  -1. 
Listings  for  suitable  memory  manage¬ 
ment  routines  are  also  given  in  Chap¬ 
ter  8  of  Kernighan  and  Ritchie.7 

Availability 

I  will  return  a  copy  of  the  source  code 
and  an  executable  .COM  file  (for 
CP/M  2.x)  to  anyone  who  sends  me  a 
postage-paid  disk  mailer,  a  formatted 
5 '/4-inch  disk,  and  $10.00.  I  can  write 
a  large  number  of  single-sided,  soft- 
sectored  formats  but  do  not  have  the 
hardware  for  hard-sectored  or  8-inch 
disks. 

References 

>D.  E.  Cortesi.  Dr.  Dobb's  Journal. 
No.  83  (November  1983)  p.  10,  No. 
87  (January  1984)  p.  18,  and  No.  91 
(May  1984)  p.  12. 


2Jim  Gilbreath  and  Gary  Gilbreath. 
Byte.  (January  1983)  p.  283. 

3J.  E.  Hendrix.  Dr.  Dobb's  Journal. 
No.  74  (December  1982)  p.  16,  and 
No.  75  (January  1983)  p.  48. 

4Brian  W.  Kernighan  and  P.  J.  Plau- 
ger.  Software  Tools  in  Pascal.  Addi¬ 
son- Wesley,  1981.  See  also  Brian  W. 
Kernighan  and  P.  J.  Plauger’s  Soft¬ 
ware  Tools  (Addison- Wesley,  1976), 
which  gives  the  same  tools  in  Ratfor, 
a  Fortran-based  language. 

5Alfred  V.  Aho  and  Jeffrey  D.  Ull- 
man.  Principles  of  Compiler  Design. 
Addison- Wesley,  1979. 

6James  Hendrix  and  Ernest  Payne. 
Dr.  Dobb’s  Journal.  No.  91  (May 
1984)  p.  50,  and  No.  92  (June  1984) 
p.  56. 

7Brian  W.  Kernighan  and  Dennis  M. 
Ritchie.  The  C  Programming  Lan¬ 
guage.  Prentice-Hall,  1978. 

DDJ 


Peephole  Optimizer  (Text  begins  on  page  56) 
Listing  One 

int  ifj,a,n| 
whi 1 e  (i  «*  0) 

{  i  *  j  ♦  n; 

•  *  i  ♦  j| 
if  <j  =■  1) 


(a)  Fragment  of  C  code 


l  start  of  loop 


LHLD 

1 

i  evaluate  i  ««  0 

nov 

A.H 

ORA 

L 

JN2 

•  Q 

1  jump  out  of  loop  if  not  true 

LHLD 

j 

i  i  3  j  ♦  n 

ICH6 

LHLD 

n 

DAD 

D 

SMLD 

i 

LHLD 

i 

l  at  *  i  ♦  j 

XCH6 

LHLD 

j 

DAD 

D 

SHLD 

at 

LHLD 

.» 

1  evaluate  j  ■■  1 

OCX 

H 

NOV 

A.H 

ORA 

L 

JN2 

.h 

l  jump  if  not  true 

L  X  I 

H,  0 

;  j  *  0 

SHLD 

j 

JMP 

.  f 

;  1  oop 

DS 

O 

I  continue  Kith  rest  of  program 

Assemb 1 y 

1 anquaqe 

compiler  output 

(Comments 

added ) 

l  start  of  loop 

LHLD 

i 

t  evaluate  i  ■■  O 

MOV 

A.H 

ORA 

L 

JN2 

.0 

1  jump  out  of  loop  if  not  true 

LHLD 

» 

;  i  ■  j  ♦  n 

ICH6 

LHLD 

n 

DAD 

D 

SHLD 

l 

t  redundant  LHLD  deleted 

XCHG 

LHLD 

i 

DAD 

D 

Dr.  Dobb’s  Journal,  August  1985 


67 

613 


Peephole  Optimizer  (Listing  continued,  text  begins  on  page  56) 

SHLD  m 

LHLD  j  I  evaluate  j  **  l 

OCX  H 

HOV  A,  H 

ORA  L 

JNZ  . f  ;  JNZ  to  J MP  improved 

;  LXI  H, 0  removed  following  in  line  test 

SHLD  i 

JMP  *f 

•gi  DS  O  3  continue  with  rest  of  program 


(c)  Optimized  assembly  language  code  End  Listing  One 


Listing  Two 


/I  ootdefs.h  —  Definitions  and  declarations  for  peephole  optimizer,  t/ 

/||  It  1 1 II II  St  I!  II  MU  lilt  I!  It  III  I II II II 

I  Copyright  1984  bv  David  L.  Fox 

•  All  rights  reserved. 

•  Permission  is  or  anted  for  unlimited  personal,  non-commercial  use. 

II  |  III  |««  1 1  III  I  HUM  llllt  MU  Ml  lit  lit/ 

/I  Standard  definitions  (stdio.h)  1/ 

•define  EOF  -1 

•define  NULL  0 

•define  TRUE  I 

•define  FALSE  0 

•define  MAXSTR  256  /I  Maximum  string  length  •/ 

•define  FILE  int  /•  File  descriptor  type  1/ 

/I  Definitions  for  optimizer  1/ 

•define  MAXLINES  500  /•  Lines  of  source  in  memory  1/ 

•define  JTS1ZE  10  /I  Entries  in  juap  list  table  1/ 

•define  LBLI.EN  13  /I  Maximum  label  length  1/ 

•define  OPTIONS  "B:Ft JtLiUUiV*  / I  Legal  options  1/ 

•define  L6LTERM  /I  Character  which  terminates  a  label  1/ 

•define  SYMLEN  8  / I  Symbol  (label)  length  1/ 

•define  UNDEF  0  /I  Undefined  symbol  1/ 

•define  1NBUF  1  /I  Symbol  defined  and  in  buffer  1/ 

•define  OUTBUF  2  /I  Symbol  written  to  output,  removed  froa 

buffer  1/ 

•define  UNJMFO  3  /•  Symbol  is  label  of  JMP,  replaced  with 

destination  1/ 

/I  Structure  declarations  1/ 

struct  Ikline  <  /I  Linked  list  of  lines  1/ 

struct  Ikline  Inextlnt  / I  Link  to  next  line  1/ 
char  lint  / I  This  line  1/ 

>1 

struct  trfmn  (  /I  Transf ormation  froa  old  to  new  code  1/ 

int  noldt  /I  Number  of  lines  in  old  construction  1/ 

int  nnewi  / I  Number  of  lines  in  new  construction  1/ 

struct  11  line  loldt  /I  Linked  list  of  old  reg.  exprs.  1/ 

struct  Ikline  Inew;  /I  Linked  list  of  new  substitutions  1/ 

struct  trfmn  Inexttri  /•  Link:  to  next  transf  ormation  1/ 

>1 

struct  jmplst  (  /I  List  of  lines  with  jumps  to  a  label  1/ 

int  n,mpi 

int  jl inest JTS1 ZE1|  ft  Line  nos.  containing  jump  to  label  1/ 

struct  jmp_lst  Ijlnext; 

>« 

struct  symt  (  / I  Symbol  table  entry  1/ 

char  tnameISYMLEN+1 J; 

int  lineno:  / 1  Line  number  in  buffer  1/ 

char  scodci  /I  Statusi  undefined,  in  buffer, 

written  out,  replaced  1/ 

struct  jmp_ 1st  I jmplst | 

Struct  symt  Isnext; 

>« 

/ I  Definitions  for  regular  expression  code  1/ 

•define  MAXPAT  (2IMAXSTR) 

•define  CLOSIZE  I  /•  Size  of  a  closure  entry  1/ 

•define  CLOSURE  ’•’ 

•define  BOL  ,A’ 

•define  BOLSTR  mf'm  /•  String  consisting  of  BOL  •/ 

•define  EOL 
•define  ANY 
•define  CCL  ’ C* 

•define  CCLEND 
•define  NEGATE 

•define  NCCL  /I  Cannot  be  the  sane  as  NEGATE.  1/ 

•define  LIT CHAR  *c’ 

•define  ENDSTR  »\0* 

•define  NEWLINE  *\n' 

»d»Unm  ESCAPE  ’  \\’ 

•define  DASH 


68 


Cl  A 


Dr.  Dobb’s  Journal,  August  1985 


•define  6RPST  ’(' 
•  def i n*  GRPEND  ' ) ' 
•define  6RPFAT 
fdifint  DITTO  -1 

/•  Udefine  ZBO 


/•  Cod**  for  grouping  and  subpattern*  1/ 


/t  Insert  this  define  if  you  have 
a  ZBO  processor.  1/ 


End  Listing  Two 


Listing  Three 


It  optglob.c  --  Global  variable  declarations  for  optimizer.  •/ 

/tlllt  l!!t!!!ltl!t!!t!!!ltttl!!!tlt  till 

•  Copyright  1904  by  David  l.  Fox 

I  All  rights  reserved. 

*  Permission  is  granted  for  unliaited  personal,  non-commerci  al  use. 

Illllltlltlllllllllltllllllltllllltllll/ 


char  lllinepi 
int  nlines  ■  0; 
int  frstln; 
int  curlm 
FILE  infd| 

FILE  outfd: 
unsigned  *ax lines  ■ 
HAXLINES; 

int  verbose  ■  TRUEi 
int  tapoot  ■  TRUE  I 
int  optmd  »  Oi 
char  toptarq; 
struct  trfan  ttrans; 


/•  Array  of  pointers  to  lines  1/ 

/I  Number  of  lines  1/ 

/•  Index  of  oldest  line  in  buffer  •/ 
/I  Current  line  in  buffer  •/ 

/I  Input  file  descriptor  1/ 

/I  Output  file  descriptor  1/ 

It  Lines  in  buffer  1/ 

/•  Verbose  output  flig  •/ 

/•  Flag  for  jump  optimization  1/ 

It  Index  of  next  argument  1/ 

It  Pointer  to  option  argument  1/ 


•include  "b: optdef s. h" 

•include  "b: ootglob. c* 

•include  "bsrestuf.c" 

ft  Main  program  1/ 

main(arac,  arqv) 
int  arge; 
char  ItarQv; 

( 

char  ini i ne(MAXSTR) ,  ts>  tlinesi 
int  c,  i| 
char  tp,  tq; 

FILE  patfd; 

char  If  gets  O ,  IneelineO,  ImakepatO,  istrcpyO; 
struct  symt  Isymp,  IchksymO; 


char  Ipatfil*  «  "opt. dat\0\0\0\0\0\0\0"; 

char  llbl  •  *Ca-zA-Z_\.  Ha-zA-Z0-9_  Jl"; 
char  I  jmpins  «  “\t JICEMMOPZ  HVt  "; 

char  lucjap  ■  "UJHPVfi  /I 


/•  File  for  optimization 
data  1/ 

It  Label  template  1/ 

It  Jump  instruction 
template  1/ 
Unconditional  jump  1/ 


char  tlblpat,  I jmppat,  lucjpat; 


/•  Pointers  to  patterns  made 
from  reg.  expr.  1/ 


struct  symt  frstsym  ■  ( 

It  anas*  1/ 

-I.  /•  lineno  I / 


0,  It  sc  ode  1/ 

NULL,  It  jap  1 st  I / 

NULL);  It  snext  1/ 


struct  symt  tsymtab  *  Ifrstsym; 


It  Start  of  symbol  table  I / 


It  Global  variable  declarations  for  regular  expression  code  I / 


char  pat(NAXFAT); 
int  Iparen,  rparen; 
char  IgstartllvJ; 
char  tgendUO); 
char  llgot 


It  Space  for  pattern  •/ 

It  Parenthesis  counts  1/ 

It  Pointers  to  start  of  grouped  text  •/ 

It  Pointers  to  end  of  qrouped  text  1/ 

It  Pointer  to  start  of  last  group  1/ 

End  Listing  Three 


Listing  Four 


It  opt.c  --  Assembly  language  optimizer  1/ 

/n  it  t  iii  it  1 1  tut  it  tiiitin  unit  it  tut 

I  Copyright  1984  by  David  L.  Fox 
I  All  rights  reserved. 

I  Permission  is  granted  for  unliaited  personal,  non-commercial  use. 
IIMIIIIIItttlltltllllllllllllllllllltt/ 

It 

Performs  peephole  and  jump  optimization  of  an  assembly  languaoe 
source  file.  Transformations  for  peephole  optiaizer  are  specified 
in  file  opt.dat  as  follows: 

regular  expressions 
♦or  matched  lines 
X 

replacement  lines 
XX 

Command  line  syntax  is 

opt  (  -bnn  -ffile  -o  -Ire  -jre  -ure  -v  3  infile  outfile 
where  the  options  are 

-bnn  Use  nn  lines  in  the  FIFO  buffer  (def aul t*1000) . 

-ffile  Read  peephole  optimization  information  f rn*  file  instead 
of  opt.dat. 

-o  Do  not  do  jump  optimization. 

-Ire  Labels  defined  by  regular  expression  re. 

-jr*  Jump  instructions  defined  by  reqular  expression  re. 

-ure  Unconditional  jump  instruction  defined  by  regular 
expression  re. 

-v  Verbose  output  off. 

1/ 


/*  Initialization  t / 


frstln  »  0; 

curln  »  maxlines  -  1; 


It 

It 

if 


Cre.t.  patterns  tor  jumps  and  labels 
Al locate  memory.  1/ 

< (Iblpat  »  sbrk  (MAXSTR1)  ==  -l  •• 
(jmppat  *  sbrk (MAXSTR) )  *»  -j  •< 
(uejpat  =  sbrk (MAXSTR) 1  ««  -j) 
•rror^"Not  enough  memory") ; 


from  regular  expressions 


»/ 


It  Create  patterns.  1/ 

P  *  Iblpat; 

makepat ( 1 bl ,  ' \0* ,  Iblpat,  Lp), 

P  *  jmppat; 

makepat  (jmpins,  -VO-,  jmppat,  bp), 
P  *  uejpat; 

makepat (uc jmp ,  ’\0’,  uejpat,  Lp); 


It  Allocate  memory  for  groups.  1/ 
tor  (i  «  1,  i  <  10; 

gstarttil  *  sbrk (MAXSTR) ; 
if  (gstartllO)  **  -l) 

error ("Not  enough  memory*); 


while  <<c  =  getopt<argc,  argv,  OPTIONS))  !«  EOF) 


switchlc  1  0x20)  { 

case  'b’»  /•  Set  FIFO  buffer  size.  1/ 

maxlines  »  atoi (optarg) ; 
break; 

case  *’!  n  Sat  data  tile  name.  »/ 

strepy (patf i 1 e,  optarg); 
break; 

case  1'*  tt  R.e.  for  labels  1/ 

P  *  Iblpat; 

makepat (optarg,  f\0’,  Iblpat,  &p) , 
break; 

case  ' j’ t  /$  R.e.  for  jumps  1/ 

P  =  jmppat; 

makepat (optarg,  ’  \0’,  jmppat,  tp) ; 
break; 

case  ’0,:  tt  jump  optimization.  1/ 

jmpopt  ■  Ijmpopt; 
break; 

case  ’u’:  /I  R.e.  for  unconditional  jumps  1/ 

p  *  uejpat; 

makepat (optarg,  ’  \0’,  uejpat,  &p) j 
break; 

case  ’ v’s  /I  Switch  verbose  output.  •/ 

verbose  *  !  verbose; 
break ; 

def aul t: 

r  ,  error ("usage:  opt  C  -bnn  -ffile  -Ire  -jre  -ure  -v  D\ 

C  inf i le  I  outfile  J  J");  J 

> 


It  Read  optimization  data  file.  1/ 
patfd  *  mustopen (patf i 1 e,  "r")i 
get  tmpl (patfd); 
f cl ose (patf d) ; 

/*  Allocate  memory  for  FIFO  buffer.  •/ 
if  (flinep  =  sbrk (maxi inestsizeaf (char  I)))  ** 
error (“Not  enough  memory"); 

It  Set  up  i/o  channels.  1/ 
outfd  *  stdout; 


Dr.  Dobb’s  Journal,  August  1985 


71 


Peephole  Optimizer  (Listing  continued,  text  begins  on  page  56) 
Listing  Four 


) 

if  (n  >  0)  /•  No  Match,  skip  to  next  transf ormat ion.  */ 

continual 

if  (verbose)  /»  Print  last  line  Matched.  tl 

fprintf (stderr ,  ‘MATCH - Xs",  linep(lnj); 

It  Replace  matched  lines  with  improved  code,  tl 
if  ( fsubst (ln,p) ) 

error ("substitute  failed"); 

) 

> 

/I  unjump  —  Route  jumps  to  jumps  directly  to  target  1/ 

/I  syp  is  pointer  to  symbol  table  entry  for  label  on  JMP  •  / 

unjunp (syp) 
struct  symt  tsyp; 

( 

char  tp,  dpat [MAXSTR) ; 
int  i,  errflg; 

struct  symt  Idsyp,  lisjmp()| 
struct  jmp_lst  tjpi 

if  (syp->scode  ■■  OUTBUF)  < 

It  Target  is  not  in  memory.  •/ 

fprintf (stderr,  "Xs  not  in  buffer\n",  syp->sname)| 
for  (jp  *  syp->jmplst;  jp  !■  NULL;  jp  «  jp->jlnext) 

free(jp);  /•  Release  memory  used  by  juap  list.  tl 

errflg  ■  TRUE; 

) 

else  (  It  Target  is  in  memory.  1/ 
errflq  »  FALSE; 

if  (<dsyp  ■  i s jmp (1 inep [syp->l inenol) )  ■»  NULL) 

(  message (1  inep(syp->l inenol ) | 

error ("Not  a  jump*)!  It  "Can’t  happen.”  1/ 

) 

/ I  Make  literal  pattern  for  substitutions  1/ 
for  (p»syp- >sname,  i*0;  Ip  !*  ’ \0’ |  ♦♦p> 

(  dpatM  ♦♦)  ■  LITCHARj 

dpatM**)  •  Ip) 

) 

dpat 1 1 ♦♦  )  -  CLOSURE | 
doat  [  i  ♦♦  J  »  LITCHAR; 
dpatti**)  «  LBLTERM; 
doat (i  1  *  ’ \y* j 

/I  Replace  destination  label  for  every  entry  in  jump  list.  1/ 
jp  ■  syp- > jmp 1 st | 

for  (jp  *  syp->jmplst|  jp  !■  NULL  Id  jp->njmp  >■  Oj  jp  ■  jp->jlnext) 
(  for  (i»0;  i  <  jp->njmps  **i ) 

(  if  ( f tubl i ne ( jp-> jl inesl i ),  dpat,  dsyp- >sname) ) 

(  errflq  ■  TRUE | 

fprintf (stderr, 

"Cannot  substitute  V.t  in  Xs\n", 
dsyp- >sname,  1 i nepC  jp->  j 1 inesC i ]]) | 

1 

/I  Add  new  reference  to  jump  list  •  / 
add  jmp (dsyp->snaae ,  jp- >jl inesCi ] ) | 

) 

f ree( jp)  | 

) 

) 

syp->jmplst  ■  NULL; 

if  (!errflg)  /*  Delete  label  from  target.  tl 
{  if  ( ! subl ine (syp->l ineno,  dpat,  "”)) 

fprintf (stderr f 

"Failed  to  delete  Xs  in  Xs\n", 
dpat,  linep[syp->linenol)| 
syp->scode  =  UNJMPDi 

) 

> 

/•  1/0  routines  for  optimizer  1/ 


It  oettmpl  —  Get  templates  for  changes.  1/ 

qettmpl (fd) 

FILE  fdi 
( 

l  nt  n; 

struct  trfmn  It; 

struct  lkline  II,  till,  Imm,  InewlkO; 
char  1 ineCMAXSTRl ,  Is,  Ipi 

char  IfgetsO,  ImakepatO,  ImakesubO,  IstrcpyO; 

/I  Allocate  memory  for  first  transf ormat i on.  •/ 
t  *  trans  =  (struct  trfmn  I)  sbrk (sizeof (struct  trfmn)); 
t->nex ttr  ■  NULL | 


while  (<s  ■  f gets (1 ine,  MAXSTR,  fd))  !-  NULL) 

(  loaren  *  rparen  *  0;  It  Initialize  parenthesis  counters.  I / 

II  «  Set  —  >ol d | 
t->nold  *  t- >nnew  ■  0; 
while  (Is  !»  ’X*  M  s  !«  NULL) 

{  o  •  pat; 

if  (makepat(s,  ’\n'(  pat,  Sp)  ■*  NUlL) 

(  message  (1  me) ; 

error  ("bad  r ,e. ")j 

) 

I  ■  newlklll,  pat)| 

II  ■  ll->nextln; 

♦♦t->nold;  /I  Increment  line  count.  1/ 

s  ■  fqetsdine,  MAXSTR,  fd); 

) 

if  (s  !*  NULL  tl  strncmp(s,  "XX",  2)  !■  0) 

{  mm  ■  lt->new| 

while  Ms  ■  f  gets  (1  ine,  MAXSTR,  fd))  !-  NULL  U 


strncap(s,  “XX",  2)  !■  0)  ( 

It  Get  replacement  strings.  1/ 
if  (eakesub (1 ine,  ’\n’,  pat)  ■■  NULL) 

(  message (1 ine) | 

error ("bad  sub  string") | 

) 

1  ■  newlk(mm,  pat); 
mm  ■  ll->nextln; 

♦♦t->nnew; 

) 

) 

/•  Allocate  memory  for  next  transf oraation.  1/ 
t->nexttr  »  (struct  trfmn  I)  sbrk (sizeof (struct  trfmn)); 
t  ■  t- >next tr i 
t-)nexttr  ■  NULL; 


/I  newlk  —  Create  a  new  lkline  structure.  1/ 

struct  lkline  I 
newlkdkp,  str) 

struct  lkline  lllkp;  It  Address  of  pointer  to  next  member  of 
linked  list.  1/ 
char  Istr; 

( 

char  IstrcpyO; 
struct  lkline  11 ; 

if  (O  »  (struct  lkline  I ) sbrk (si zeof (struct  lkline)))  ■■  NULL) 
error ( "memory  full"); 

II kp  ■  1 ; 

if  ((l->ln  *  sbrk (str 1 en (str )♦ 1 ) )  ■*  NULL) 
error ("memory  full"); 
strcpy(l->ln,  str); 
l->nextln  »  NULL; 
returnO ) ; 

) 


/ I  outline  —  Output  a  line  from  FIFO  buffer.  1/ 

out) ine() 

( 

struct  symt  Is.  tislabO,  liSjmpO; 
struct  ,mp  lit  t ) p ; 

if  (lineplfrstlnl  !»  NULL)  <  /•  If  line  not  deleted  1/ 

fputsll ineplfrstln],  outfd); 

/ 1  Check  for  labels  and  jumps.  1/ 
if  < (s  ■  i si ab ( 1 i nep( frstln]))  !*  NULL) 
s->scode  ■  OUTBUF; 

if  ( (s  *  is jmp (lineplfrstlnl) )  !«  NULL) 

{  s->scode  *  OUTBUF; 

It  Delete  jump  list.  1/ 

for  ( jp=s-> jmplst;  jp  '■  NULL;  jp  *  jp->jlnext) 
f reel jp) ; 
s-> jmplst  *  NULL; 

) 

--nl ine*: 

free  1 1 1 nep( frst In ) ) ; 

) 

It  Check  for  buffer  wrap  around,  tl 
if  (♦♦frstln  :•«  maxlines)  ( 
frstln  *  O; 

) 

) 


/•  stteepy  --  Copy  string  between  start  and  end  pointers,  tl 
char  I 

stteepy (dest ,  start,  end) 
char  Idest,  tstart,  tend; 

< 

char  Ip; 

for  (p  »  dest;  start  <  end;  ) 

Ip**  *  Istart**; 

Ip  ■  ’ \0' ; 
return (dest ) ; 

) 

/It  II I  III  It  It  II I  Ml  II I  III  It  tin  tut  tut  11/ 

It  Symbol  table  routines  for  optimizer,  tl 
It  addsvm  —  Add  a  label  to  the  symbol  table.  •/ 

struct  symt  I 
addsvm (name) 
char  Inane; 

( 

char  tp,  !q,  IstrcpyO; 

static  struct  symt  Is  *  (frstsym,  Isret; 

strepy (s->sname,  name); 

( Continued  on  page  74) 


72 


Dr.  Dobb's  Journal,  August  1985 


Peephole  Optimizer  (Listing  continued,  text  begins  on  page  56) 
Listing  Four 


s->lineno  =  -1; 
s->scode  *  INBUFj 

if  ((s->snext  =  aal 1 oc (si zeof (struct  syat)))  «  NULL) 
error ( "syabol  table  overflow*)! 
sret  *  s; 
s  *  s->snext; 
s->snext  =  NULL! 
s->jmplst  ■  NULL | 
return (sret ) ; 

) 

/•  addjmp  --  Add  line  number  to  list  of  jumps  to  label.  1/ 

add j«p (naee,  lineno) 
char  Inaae; 
int  lineno i 
t 

char  to,  tq: 

struct  syat  Isymp,  IfindsyaO,  taddsymOi 
int  n: 

struct  j»o  1st  lljpi 

if  ((syao  *  f i ndsyn (name) >  **  NULL) 

(  syap  =  addsya (naae) :  /I  Symbol  not  found,  add  it  to  table.  1/ 

symp-^scode  *  UNDEF! 

) 

jp  ■  tsymp-> jmpl stj 

/I  skip  to  end  of  jump  list  1/ 

while  (tjp  !*  NULL  t,t,  (tjp)->njmp  >»  JTSIZE) 

jp  3  & ( l ,p) - > jl nex t :  /•  Skip  to  last  block  of  juap  list.  1/ 

if  <tjp  bm  NULL)  /t  Allocate  space  for  new  block.  1/ 

{  if  Uljp  ■  aal  loc  (sizeof  (struct  jap_lst)))  ■*  NULL) 

errorC'jump  list  overflow"); 
else  /I  Initialize  new  block.  1/ 

{  (t jp)->njmp  *  0; 

(I jp)->jlnext  *  NULL; 


(ljp)->jlinesl (t jp)->nj»p^*l  *  linenoj 
} 

It  findsym  —  Linear  search  of  syabol  table.  */ 


struct  syat  » 
f indsya(naae) 
char  Inane: 

( 

struct  syat  Is: 

for  (s  =  fcfrstsya;  s->snext  !*  NULL;  s  *  s->snext) 
if  (strcmp (s->snaae,  naae)  **  0) 
return (s) ; 

return (NULL) ; 

> 

It  chksya  —  Check  for  a  labeled  line  or  line  with  a  jump.  1/ 

struct  syat  I 
chksym (curl n) 
int  curln; 

{ 

char  IsttecpyO; 

struct  syat  tsyp,  Is,  laddsyaO,  lislabO,  lisjapO; 

if  (syp  *  i si ab  (1 inepCcur In] ) ) 
syp->lineno  *  curln; 

if  (syp->l ineno  *  -1)  It  First  time  for  this  line.  •/ 
syp->lineno  *  curln: 
if  ( ( s  =  i s  ,ap ( 1 i neplcur In] ) )  NULL) 
add ,ap(s->snaae,  curln); 
if  (syo  f*  NULL) 

{  if  (aatch  (1  meplcurln],  ucipat)  !  *  NULL) 

return(syp)|  / 1  Labeled  unconditional  jump.  I / 

) 

return (NULL); 

} 

It  islab  —  If  str  points  to  a  valid  label  return  pointer  to  its 
entry  in  syabol  table,  else  return  NULL.  1/ 

struct  syat  I 
l  si ab (str  ) 
char  Istr: 

{ 


Dr.  Dobb's  Journal,  August  1985 


74 

617 


Peephole  Optimizer  (Listing  continued,  text  begins  on  page  56) 
Listing  Four 


char  *p,  tq,  namelLBLLEN] ,  lamatchO,  tsttecpyO; 
struct  symt  tsyp,  laddsymd; 


p  *  1 b 1  pat; 

if  (<q  ■  amatch(str,  str,  fcp))  f*  NULL)  ( 

if((syp  *  f indsym (sttecpy (name,  str, 
syp  =  addsym (name) ; 

} 

) 


return (syp)  ; 
) 


q)))  33  NULL)  ( 


/*  lSjmp  —  If  line  contains  a  jump  instruction 

return  pointer  to  its  destination  label  symbol  table  entry 
otherwise  return  NULL.  1/ 
struct  symt  t 
i s jmp ( 1 ine) 
char  lline; 

( 

char  Ip,  Iq,  Ir,  namelLBLLEN] ; 
struct  symt  tsyp,  !findsya()| 
char  lamatchO,  tsttecpyO; 

syp  3  NULL; 

if  Up  *  matchdine,  jmppat ) )  !■  NULL)  ( 
q  =  lblpat; 

if  Ur  3  amatch  (p,p,lrq)  )  !*  NULL)  ( 
sttecpy (name,  p,  r); 
if  (<syp  *  f indsym(name) )  33  NULL) 
syp  3  addsym (name) ; 

) 

) 

return (syp) ; 

) 


/lliltllltilltllltltllltlttttllt/ 

/I  Utility  routines  1/ 

/I  oetoot  --  Unpack  options  from  arq  list.  1/ 

/I  Arqc  and  arqv  are  argument  count,  and  values  from  main. 

Optstr  is  a  strino  of  legal  option  letters. 

betoot  returns  the  next  option  letter  from  arqv. 

If  the  letter  in  optstr  is  followed  by  the  external 
pointer  optarq  is  set  to  point  to  the  arqument 
following  the  option  letter. 

The  external  variable  optind  is  set  to  the  index  of  the 
next  arqument  in  the  arqv  array  to  be  processed. 

If  an  illegal  option  is  found,  getopt  returns  ’?’ 

If  the  next  argument  in  argv  is  not  an  option 
qetopt  returns  EOF 


I  / 

getopt (arqc,  argv,  optstr) 
int  argc; 

char  targvl],  loptstr; 

( 

static  char  tcp; 

char  tp; 

char  tstrchrOt 

i f (ootind  33  0) 

cp  =  arqvO+optind)  ♦  1;  /•  First  call  1/ 

if (largvlootind)  !■  5!  optind  >■  arqc) 

return(EOF);  It  No  more  options  1/ 

i  f  ( ico  33  ’  -’  ) 

(  ♦♦optind; 

return(EOF);  /I  End  of  options  1/ 

) 

lf((p  3  strchr (optstr ,  lcp  +  +  ) )  33  NULL) 

(  f puts ( “unknown  optiont  ",  stderr); 

putc ( I (cp-1 ) ,  stderr); 
putc(‘\n’,  stderr); 
ifdcp  33  *  \0') 

cp  3  argvl ♦♦optind]  ♦  1; 
return (’?'); 

) 

i  f  ( I  <pM  )  33  *  s  * ) 

(  ifdcp  33  ’\0')  /I  6et  arqument  •/ 

optarq  =  arqv[ ♦♦opt ind 1 s 

else 

optarq  3  cp; 

co  3  arqvt *+001 i nd 1  ♦  1: 

) 

else 

i f ( Icp  33  ' \0* ) 

cp  3  arov[*+optind)  ♦  It  /I  Set  up  for 

next  arqv.  1/ 

returndp) ; 

> 

/*  message  —  Send  a  message  to  the  console  (stderr)  and  return.  1/ 
message (s) 
chir  ls(); 

( 

fputs(s,  stderr); 
putc ( * \n’ ,  stderr ) ; 
return (0) ; 

} 


/I  mustopen  —  Attempt  to  open  a  file,  exit  on  failure.  1/ 

FILE 

mustopen (name,  mode) 
char  tname,  Imode; 

< 

FILE  fdi 

i f (  (f d  3  (open (name,  mode))  33  NULL) 

(  (puts (name,  stderr); 

error ("i  cannot  open  file"); 

) 

return (fd) ; 

) 

/I  messaqe  —  Send  a  messaae  to  the  console  (stderr)  and  exit.  •/ 
error  <s> 
char  Is: 

( 

fputs(s,  stderr): 
putc('\n’ ,  stderr) ; 
f cl ose (stderr ) : 

End  Listing  Four 


Listing  Five 


/ I  restuf.c  ~  Regular  expression  routines.  1/ 

/I  These  routines  are  translations  into  C  of  the  regular  expression 
I  matchinq  and  pattern  generation  routines  in 

I  B. M.  kerniQhan  and  P.J.  Plauqer,  "Software  Tools  in  Pascal “ 

I  chapters  3  and  6. 

I  The  ma,or  extension  is  the  addition  of  code  to  allow  matching  and 

I  substitution  of  parts  of  expressions  usinq  \(...\>  for 

I  groupinq  and  \n  for  replacement. 

*/ 

It  match  —  Find  match  anywhere  on  line.  1/ 


matchdin.  pat) 
char  llin,  Ipat; 
char  lamatchO; 

( 

char  Ipos,  list,  Ip; 

1st  >  lint 

for  (pos  3  NULL;  llin  !3  ENDSTR  It,  pos  33  NULL;  ♦♦lin) 
(  p  3  pat; 

pos  3  amatchdst,  lin,  l<p); 

) 

return(pos) : 

J 


/•  amatch  —  Anchored  match  1/ 

It  Look:  for  match  between  lin  and  pat.  1/ 

/I  Return  pointer  to  next  unmatched  character  in  line, 
or  NULL  if  no  match.  1/ 

It  1st  is  pointer  to  start  of  line.  1/ 

It  lin  is  pointer  to  current  position  in  line.  •  / 

It  Ippat  is  pointer  to  current  position  in  pattern, 
updated  if  match  found.  1/ 

char  I 

amatchdst.  lin.  ppat) 
char  list,  llin,  llppat; 

< 

auto  char  lip,  tap,  Itp; 
auto  int  level,  skip; 
char  tpatlNAXPAT]; 
char  IstrcpyO; 

while  (llppat  !■  ENDSTR) 

(  switch(llppat)  < 

case  CLOSURE!  < 

Ippat  ♦■  patsize(tppat) ;  /•  Step  over  closure.  •/ 

for  dp  3  lin;  lip  !»  ENDSTR;  ) 

if  (!  omatchdst,  tip,  Ippat)) 
break ; 

if  ( I  (tp  *  Ippat  ♦  patswel  Ippat) )  33  6RPEND) 


(  skip  3  patsize(tp);  It  At  end  of  group,  skip 

over  group  closure.  I / 

} 

else 

skip  3  0; 

It  lp  ooints  to  input  character  that  made  us  fail.  1/ 

/•  Match  rest  of  pattern  against  rest  of  input.  1/ 

It  Shrink  closure  by  1  after  each  failure.  •/ 
while  dp  >3  lin) 

<  tp  3  Ippat  ♦  patsi ze ( Ippat)  ♦  skip; 

if  (skip) 

gendUparen)  3  gstartClparen]  ♦ 

(int)  dp  -  lgp) ; 


76 

Rift 


Dr.  Dobb’s  Journal,  August  1985 


) 


) 


if  ((mp  >  analchdst,  Ip,  t>  tp  >  >  f  =  NULL) 
if  (skip  33  0) 

<  Ippat  =  tp; 

retur n (mp) ; 

) 

else 

(  tppat  ♦  =  patsi z e ( tppat )  ♦ 

return ( lp) j 

) 


else 


lp— I 


return  (NULL)  ; 

/I  If  mp  *«  NULL  failure,  else  success.  •/ 


case  GRPST:  {  /»  Start  of  aroup  •/ 

mo  *  Ippat; 

tppat  ♦*  patsizef Ippat) ; 
level  =  ♦♦Iparen; 
strcpy  (ostart [level ],  lin); 
lap  *  lint 

if  ((lin  *  aaatch (1st,  lin,  ppat))  33  NULL) 
(  Ippat  3  mp; 

— 1 paren; 
return (NULL) | 

) 


gendClevell  «  qstartC level )  ♦  (int)(lin  -  lgp); 
break; 


case  GRPEND;  (  It  End  of  qroup  1/ 
Ippat  *3  patsize(tppat) ; 
♦♦rparen; 
return (lin); 

) 


skip; 


case  GRPPAT;  (  /%  Match  previously  found  qroup.  •/ 

if  ((level  »  (tppat) ( 1 3  -  ’O’)  <  1  S'  level  >  9) 

<  message ( tppat ) ; 

error C  aaatch!  can't  happen*); 

) 

tppat  patsize(tppat) ; 

if  (gendClevell  >  gstartllevell  +  MAXSTR/2) 

return(NULL) ;  It  Group  is  too  long  for  tpat.  I / 


rase  NCCL: 

if  (II  pos  !  3  NEWLINE  M<  !  1  ocate  ( 11  pos,  klpatUl))) 
advance  =  1; 

break ; 

default; 

message (pat ) ; 

error ("in  omatch:  can't  happen”); 

) 

if  (advance  >«  0) 

(  tlinp  ♦=  advance; 

return(TRUE) ; 

) 

else 

return(FALSE) ; 

) 

/ t  patsize  —  Returns  size  of  pattern  entry  at  pat.  1/ 

patsi ze (pat ) 
char  tpat; 

{ 

switch  (tpat) 

( 

case  LITCHAR: 
case  faRPPAT: 

return(2> ; 
case  BOL; 
case  EOL; 
case  ANY i 
case  GRPST: 
case  GRPEND: 

return( 1 ) ; 
case  CCLi 
case  NCCL; 

r eturn(pat[ 11  ♦  2) ; 
case  CLOSURE; 

return(CLOSIZE) ; 


default; 

message (pat ) ; 

error ( "in  patsize:  can’t  happen"); 

> 


It  Make  temporary  pattern  from  matched  qroup.  1/ 
for  (tp  3  tpat,  lp  3  gstartClevel 1;  lp  <  gendClevell; 
(  Itp**  *  LITCHAR; 

ttp**  *  tip**: 

) 

ttp  >  ENDSTR; 
tp  3  tpat; 

i  f  (  (lin  E  amatchdst,  lin,  8>tp)  )  *■  NULL) 
return(NULL) ; 

break; 

) 

def aul t: 

if  (omatchdst,  Uin,  tppat)) 

{  tppat  *3  patsize(tppat); 

) 

else  { 

return(NULL) ; 

) 

) 

) 

return (lin); 

) 


It  omatch  —  Match  one  pattern  element  at  pat.  1/ 


omatchdin,  lino,  oat) 
char  tlin.  tllino.  «oat; 
l 

char  tloos: 
int  advance; 


advance  3  -1; 
i pos  3  11 ino; 


if  (II pos  33  ENDSTR) 

return (FALSE) ; 

SNi tch( tpat ) 

( 

case  LITCHAR; 

if  ( tl pos  33  pat  I  1 1 ) 
advance  3  I; 

break; 
case  BOL; 

if  (lpos  33  lin) 

advance  *  0; 

break ; 
case  ANY: 

if  (11 pos  '»  NEWLINE) 
advance  3  1 ; 

break; 
case  EOL: 

if  ( 1 1 pos  33  NEWLINE) 
advance  3  0; 


break; 


case  L'CL; 


if  (locatedlpos,  KpattlJ))) 
advance  3  1; 

break : 


It  locate  --  Look  for  c  in  character  class  at  pat.  t / 

locate(c,  oat) 
char  c,  tpat; 

( 

It  int  ii 

It 

It  It  Size  of  class  is  at  pat,  characters  follow,  tl 
It  for (i  =  toat;  i  >  0;  — i > 

/t  (  if  (c  33  patCi)) 

It  l  return ( TRUE ) ; 

It  } 

It  > 

It  return (FALSE) ; 

C  code  commented  out.  1/ 

•asm 

;  Assembly  language  version  for  speed 


POP 

B 

ireturn  address 

FOP 

H 

;  pat 

POP 

D 

sc 

PUSH 

D 

; restore 

PUSH 

H 

;  the 

PUSH 

b 

;  stack 

MOV 

C.M 

(get  count  (tpat) 

HVI 

B,  0 

MOV 

A.E 

;c  into  A 

I  NX 

H 

ipoint  to  first  character 

•i f def 

ZOO 

DB 

OEDH.oBlH  ; Z80  CPIR  instruction 

JZ 

1 ocf nd 

5  jump  if  C  found 

•  else 

1  oc  1  p: 

MOV 

E,M 

;get  char 

CMP 

E 

;compare  with  c 

JZ 

1 ocf nd 

{jump  if  match 

I  NX 

H 

; increment  pointer 

DLX 

b 

1  decrement  count 

MOV 

D,  A 

;save  A 

MOV 

A.B 

ORA 

C 

; test  for  BC  33  0 

MOV 

A,  D 

{restore  A 

JNZ 

loclp 

; loop  if  EC  1 3  O 

•endi f 

LX  I 

H, FALSE 

; return  FALSE  if  not  found 

JMP 

1  ocend 

jexit  from  end  for  profiler 

locfnd: 

LXI 

H, TRUE 

; return  TRUE  if  found 

1 ocend ; 

DS 

0 

•endasm 


It  makepat  —  Make  pattern  from  arq,  terminate  at  delim.  tl 
char  t 

makepat (ara.  oelim.  stpat,  ppat) 
char  delim,  taro,  tstpat,  tlopat; 
l 

char  tlo.  tstaro; 

char  tlastp,  Itmo,  lescO; 


79 


Dr.  Dobb’s  Journal,  August  1985 


Peephole  Optimizer  (Listing  continued,  text  begins  on  page  56) 
Listing  Five 


starq  ■  arq* 
lastp  *  ippat; 

while  (larq  '*  delim  tt  lar a  !*  ENDSTR) 
(  lo  •  louat; 

if  (larq  *=  ANY) 

addpat (ANY, stpat.ppat)  ; 


else  if 
else  if 

else  if 

( 

> 

else  if 

( 


) 

else  if 
{ 


) 

else  if 
( 


> 

else  if 

{ 


> 


(larq  =*  BOL  tt  arg  *■  starg) 
addpat (B0L, stpat.ppat)} 

(larq  «  EOL) 

addpat (EOL,  stpat.ppat)} 

(larq  »»  CCL) 

if  ( !  Qetccl (taro,  ppat,  stpat)) 
return (NULL) } 

(laro  *3  CLOSURE  tt  arq  >  starq) 

if  (Kip  *  lastp)  *»  BOL  II  lip  **  EOL  II  lip  *=  CLOSURE) 
ret urn (NULL) }  It  Closure  on  a  null  strinq 
not  allowed.  1/ 

stclose (onat.lasto, stpat)} 

(taro  ■*  ESCAPE  tt  argCl)  *■  ’(’) 
addpat (GRPST,  stpat,  ppat); 
arq  ♦  =  2; 

if  (♦♦lparen  >  9)  r eturn (NULL ) ; 

if  ((arq  *  nakepat(arg,  delim,  pat,  ppat))  !*  NULL) 

--arg; 

el  se 

return (NULL) | 

( larg  ESCAPE  tt  argCll  ••  ’)’> 
addpat (6RPEND,  stpat,  ppat); 

♦♦rpar en; 
return (ar q*2) ; 

(larq  =*  ESCAPE  I*  CO’  <  argtl)  tt  argil]  <»  ’9’)) 
addpat (GRPPAT,  stpat,  ppat); 
addpat (arqt 1 ),  stpat,  ppat); 

♦♦arq; 


else 

(  addpat (LI TCHAR,  stpat,  ppat)| 

tmp  *  esc (arg) ; 
addpat ( larq ,  stpat,  ppat); 
arg  =  t»p  -  1; 

) 

lastp  *  lp: 
arq**  s 

) 

if  (larq  '»  delim) 

return (NULL) ; 

if(!  addpat (ENDSTR,  stpat,  ppat)) 
return (NULL) ; 
if (lparen  1 •  rparen) 
return (NULL) ; 
return (arq) ; 

) 


addpat (c,  stpat,  ppat)  /•  Add  c  at  ppat,  check  for  overflow.  1/ 

char  c,  llppat,  Istpat; 

( 

if  (Ippat  -  stpat  >  MAXPAT) 

return (FALSE) ; 

I  (Ippat)**  *  ci 
return ( TRUE) ; 

) 


/•  Qetccl  --  Expand  char  class  at  arq  into  pat.  •/ 

qetccl (arop,  oato,  stoat) 
char  ilarop,  fipatp,  istpat; 

{ 

char  tccst; 

(larqp)**;  / I  skip  [  1/ 

if  (tlarqp  ■*  NEGATE) 

(  addpat (NCCL,  stpat,  patp); 

(larqp)**) 

) 

else 

addpat(CCL,  stpat,  patp); 
ccst  ■  ipatp; 

addpat (0, stpat,  patp);  / I  space  for  count  1/ 
dodash (CCLENO.  argp,  stpat,  patp); 
tccst  ■  » patp  -  ccst  -  I; 
return ( Itarqp  *»  CCLEND) ; 

) 

/•  stclose  —  Insert  closure  entry  at  Ipatp.  1/ 

stclose (patp,  lasto.  stpat) 
char  llpatp.  llastp,  Istpat; 

( 

char  too,  tq; 

for  (pp  *  Ipatp  -  1}  pp  >*  lastp;  pp — > 

(  q  =  DP  ♦  LLOSIZE: 

audpatdpp,  stpat,  tq); 

) 

ipatp  ♦  *  LLOSIZE: 
llastp  *  CLOSURE ; 

) 


/I  esc  —  Map  istr  into  escaped  character,  return  pointer  to 
next  character.  •/ 


char  I 
esc (str ) 
char  Istr; 

( 

int  n,  c; 
char  Ip; 
char  IstrcpyO; 

if  (Istr  '«  ESCAPE) 

return(*+str ) ; 

>Ui  l*  (»<p. Istr. 1)1  .=  ENDS1R)  /•  ESCAPE  not  special  at  end.  »/ 

return ( ♦♦str ) ; 

el  se 

C  if  (Ip  **  ’N’  II  lp  **  ’n') 

( 

) 

else 

( 

i 

else 

( 

) 

else 

( 

) 

else 

( 

) 

else 

( 


el  se 

( 

) 

) 

) 

It  dodash  —  Expand  set  at  srcliJ  into  dest(j), 
stop  at  delim.  1/ 

dodash (delim,  srcp,  stdest,  destp,) 
char  llsrcp,  lldesto.  tstdest,  delim) 

( 

char  isrc,  Icp,  ttmp; 
int  k,  junk) 

src  ■  cp  «  tsrcpi 

while  (Isrc  •«  delim  tt  Isrc  '»  ENDSTR ) 

<  if  (Isrc  ESCAPE) 

(  tmp  *  esc (src) ; 

addpat (Isrc,  stdest,  destp); 
src  *  tmp- 1 ; 

) 

else  if  (Isrc  !«  DASH) 

addoatdsrc,  stdest,  destp); 
else  if  (  src  =■  cp  II  srctlJ  **  ENDSTR) 

addpat (DASH,  stdest,  destp);  It  literal  -  tl 
else  if  ( i sal num ( I (sr c-1 ) )  tt  l sal num (src C 1 J ) 
tt  t(src-l)  <•  srct IT) 

(  for(k  »  l(src-l)  ♦  1;  k  <■  srcCU;  **k) 

addpat (k,  stdest,  destp); 

src**) 

> 

else 

addpat (DASH,  stdest,  destp); 

src**; 

t 

Isrcp  -«  Isrcp  -  src; 

> 

It  makesub  Hake  substitution  string  from  arg  in  sub.  •/ 
char  I 

makesubtarq,  delim,  sub) 
char  larq,  delim,  Isub; 

< 

char  lp,  la,  tstsub,  ttmp) 
stsub  ■  sub) 

while  (larq  delim  tt  larq  !»  ENDSTR  tt  larq  ’\n’) 

<  If  (larq  ■»  't') 

<  addpat  U)I TTO,  stsub,  tsub); 

addpatf’O’,  stsub,  tsub)) 

) 

else  if  (laro  «»  ESCAPE  tt  arotl)  >  ’O’  tt  argCl)  <■  ’9’) 
(  addpat (DI TTO,  stsub.  tsub); 


Istr  ■  ’\n’; 

return(strcpy(**str, **p) ) ; 

if  (Ip  «*  ’  T  ’  II  Ip  *»  ’  t'  ) 

Istr  »  ' \ t '  5 

return (str cpy (**str , **p ) ) ; 

if  (tp  ««  ’ B ’  1 1  Ip  «=  ’b* ) 

Istr  *  ’\b'; 

return (str cpy (♦♦str, **p) ) ; 

if  (dp  I  0x20)  ■=  ’  r  ’ ) 

•str  »  ’\r’i 

ret urn (str cpy (**str, **p) ) ; 

if  (tp  «*  ’0'  tt  (»(p*l)  I  0x20)  **  ’x’)  It  Hex  tl 

o  ♦«  2; 

•str  *  Oxf t  t  xtoi (p) ; 
return(strcpy(**str,p  ♦  »  2)); 

if  dp  >*  ’0'  tt  tp  *7')  It  Octal  repr  esentati  on  I 

for  (n«0,c*o:  do  >»  '0'  tt  lp  <»  '7')  tt  n<*3;  +*n,*+p) 

(  c  ■  (c<<3)  ♦  (lp  -  ’O’ ) ; 

i 

•str  »  c: 

r eturn (strcpy (**str,p) ) ; 


•str  *  Ip; 
return (♦♦p) ; 


80 


Dr.  Dobb’s  Journal,  August  1985 


addpat  (arcjC  1  ] ,  stsub,  fcsub); 

♦♦arq; 

) 

else 

(  tmp  =  esc(arg); 

addpatdarq,  stsub,  fcsub); 
arq  =  tmp  -  1; 

) 

♦♦ara; 

} 

i f  (Isrq  1 ■  del  is) 

return (NULL) |  It  hissing  delimiter  tl 
if  < !  addoat(’\0',  stsub.  fcsub)) 

return (NULL) ;  It  Np  space  for  sub  string  %/ 

else 

return (ara) i 

) 

It  subst  —  Substitute  “sub"  for  occurrences  of  pattern,  t / 
/*  Modified  for  ootimizer.  •  / 

subst (In.  tr) 
int  lnt 

struct  trfen  Itr; 

( 


It  catsub  —  Add  replacement  text  to  end  of  new.  tl 

catsup (pi.  o2,  sub.  new,  pp3) 
char  tpl,  Sp2,  tsub,  Inew,  ttpp3| 

{ 

char  Ip,  *q,  fescd; 

for  (p  »  subs  «p  !*  ENDSTRi  ++P) 

<  if  (Ip  =*  DITTO) 

( 

if  (pm  >  'o'  pm  <•  ’9'» 

{  pi  ■  astarttpt l J-’O’ J; 

p2  «  aendtpt 1 l-’O*  J ; 

) 

for  (q  *  pli  q  <  p2|  ♦♦q) 

(  addpatdq,  new,  pp3)  | 

) 

♦♦01 

) 

else 

(  addpat (Ip,  new,  pp3); 

> 

) 

) 

It  delln  —  Delete  a  line.  1/ 


struct  lkline  loo,  !nn{ 
char  InewlineO; 
int  line,  n; 


lparen  *  rparen  =  0} 
nn  •  tr->new; 
oo  *  tr->old; 

for  (n  »  tr->nold{  n  )  Oi  --n) 


< 


if  (dine  -  In  -  n  ♦  I)  <  0) 

line  ♦■  maxlines;  It  Buffer  wrap  around,  tl 

if  (n  <■  tr->nold  -  tr->nnew)  < 

del  In (line);  It  Not  enough  replacements,  tl 
oo  ■  oo->nextlni 


) 

else  ( 


1 


if  ( Isubl  inedine,  oo->ln,  nn->ln)) 
return (FALSE ) I 

el  se 

<  oo  *  oo->nextlm 

nn  ■  nn->nextln; 

I 


) 

for  (n  ■  tr->nnewj  n  >  tr->noldj  — n)  ( 
newline(“\n") | 

if  ( Isubl ine(curln,  BOLSTR,  nn->ln)) 
return (FALSE) i 

el  se 

nn  »  nn->nextln( 

) 

return(TRUE) | 

) 


It  More  lines  to  add.  tl 


It  subline  —  Do  substitution  on  one  line,  tl 
sublinedine,  pat.  sub) 
int  line: 
char  Ipat,  Isubs 
( 

char  newCMAXSTKJt 

char  Ipl,  Ip2,  Ip3,  Hast,  lop; 

int  subbedi 

char  laaiatchO.  IstrcpyO,  laallocOi 


0 2  »  new; 
subbed  ■  FALbEi 
last  -  NULLs 
pi  *  linepUineli 
while  (Ipl  I  *  LNUSTR ) 

<  if  (I subbed) 

(  pp  *  oati 

p3  ■  amatch d ineptl ine),  pi,  tpp)| 

) 

else 

p3  «  NULL | 

if  (p3  !■  NULL  tl  last  !-  p3) 


(  subbed  ■  TRUEj  It  replace  matched  text  1/ 

catsubf  pi,  p3,  sub,  new,  Iip2): 
last  *  p3; 

) 

if  (p3  ■■  NULL  !•  p3  **  pi) 

(  addoatdpl,  new,  tp2); 

♦♦Pll 

) 

else  It  Skip  matched  text.  1/ 

pl  *  p3* 

) 

if  (subbed) 

(  if  ( 'addpat  (ENDSTR,  new,  tp2)  > 

{  return (FALSE) i 

) 

else  {  It  Copy  new  line  into  linep  array,  tl 
freed  inept  line!); 

if  (dineptlineJ  »  mal  loc  (str  1  en  (new)  +1 ) )  ■»  NULL) 
error ('not  enough  memory*) ; 
strcpydi  nepl  line],  new); 

) 

) 

return(subbed) ; 

) 


del  In (In) 

int  In; 

( 

freedineotlnl) ; 
lineptlnl  *  NULL; 
> 


Listing  Six 


SHLD\t\([\.a-zA-Z_JC\._a-iA-Z0-9+\-]l\) 

A\tLHLD\t\l 

X 

SHLD\t\l\t;  redundant  LHLD  deleted 
XX 

\tM0V\t A, H 
A\tORA\tL 

\tJNZ\t\ ( t\. _a-zA-Z  J£ \. _a-zA-ZQ-9)l\) 

A\tLX I \tH, U 
X 

\tMOV\t A, H 
\tORA\tL 

\tJNZ\t\l\t;  LX1  H,0  removed  following  in  line  test 
XX 

\tCALL\t\ (tcenJX. V) 

\tJNZ\t\(t\._a-zA-ZH\.  _a-z A-ZO-9 Jl\  ) 

A\tLX I \tH, 0 
X 

\tCALL\t\l 

\tJNZ\t\2\t;  LX1  H,  0  removed  followina  library  test 
XX 

\tCALL\t\ (lcen)\. \) 

VtJZ\t\(C\.  a-zA-Z)C\._a-zA-Z0-9JI\) 

A\tLXl\tH.U 

X 

NtLALL\t\l 

\tJZ\t\2\t:  LX  1  H, 0  replaced  followina  library  test 
UDCXUH 

XX 

\tCALL\t\ (Icen]\. \) 

\tJZ\t\ft\.  a-zA-Z JC\.  a-zA-Z0-9JI\> 

A\tLXl\tM. 1$ 

X 

\tCALL\t\ 1 

\tJZ\t\2\t:  LXI  H,  1  replaced  following  library  test 
XX 

A\ t  JZ\t\(l\._a-z A-Z  H\._a-z A-ZO-9  Jl\) 

A\tJMP\t\  (£\. _a-z A-Z JC\.  a-zA-Z0-9JI\) 

A\1 \ ( i \t. t\ ) 

X 

\tJNZ\t\2\t;  JZ  around  jump  removed 
\  1  \3 
XX 

A\tJNZ\t\(t\._a-zA-ZJt\. _a-z A-ZO-9] l\ ) 

A\tJhP\t\<t\.  a-zA-Z )t\.  a-zA-ZO-9] l\) 

A\l\(»\t. l\) 

X 

\tJZ\t\2\t;  JNZ  around  jump  removed 

\1\3 

XX 

PUSMVtH 

A\tLXl\tH.\!l\.  a-z A-Z H \ .  a-z A-Z0-9JI\) 

A\tPUP\tD 


X 

XCHGXt ; PUSH  H  POP  D  around  LXI  H  changed  to  XCHG 
\tLXI \tH, \1 
XX 

PUSHVtH 

A\tLHLD\t\(t\._a-zA-ZK\.  a-zA-Z0-9] I \) 

A\tP0P\tD 

X 

XCHGXt | PUSH  H  POP  D  around  LHLD  changed  to  XCHG 

\tLHLD\t\l 

XX 

STA\t\(t\. a-zA-Z  Jt\.  a-z A-ZO-9 ♦\-3l\) 

A\tLDA\t\l 


End  Listing  Five 


621 


Peephole  Optimizer  (Listing  continued,  text  begins  on  page  56) 


x 

STA\t\l\ti  redundant  LDA  deleted 
XX 

\tJMP\t 

A\(A\t['D)C'6SW)V> 

X 

\tJMP\t 

l  unreachable  code:  \1 

XX 

URfcT* 

A\t 

X 

\  tkfcl 

St i  unreachable  code: 

XX 

StJhPStSUS.  a-zA-ZHV.  a-zA-Z0-91«\) 

A\l\llVt.l\> 

X 

MDSStOSt i  ,ump  to  next  location  removed 
\  1  \2  . 

n  End  Listing  Six 

Listing  Seven 


It  Library  routines  used  bv  optimizer. 


tl 


atoi (str) 

char  Istr; 
ex  1 1  ( ) 
f c lose  <  fd) 

PILE  fd; 

char  I f aets  < str ,  nbytes, 
char  Istr: 
int  nbytes: 

FILE  fd: 


It  Convert  str  to  integer.  »/ 

/•  Return  to  CP/M  •/ 

It  Close  file  with  descriptor  fd.  «/ 

fd)  It  Read  one  line  (maximum  of  nbytes)  tl 
It  from  file  into  str.  1/ 


FILE  (open (name,  mode)  /( 
char  (name,  (mode; 
fprmtf(fd,  format  l,  aro  3 


FILE  fd: 
cnar  (format: 

fputststr.  fd)  It 

char  (str: 

FILE,  fd: 

free(block)  / I 

char  (block;  /( 

isalnum(c)  /( 

char  cj  /( 

char  (mal loc (nbytes )  /( 

int  nbytes:  It 

outc(c,  fd)  / I 

char  c: 

FILE  fd; 

char  (sbrk (nbytes)  It 

int  nbytes;  It 

char  lstrchr(str,  c)  It 

char  (str,  c;  It 

strcap(strl.  str2)  It 


char  (strl,  (str2:  /* 

It 

char  tstrcpy (dest .  source) 
char  (dest,  (source) 


strlen(str)  It 

char  (str; 

strncaip  (str  1 .  str2,  n)  /• 

char  (strl,  (str2;  It 

int  n:  It 

xtoi(str)  /( 

char  (str;  It 


Open  file  name  with  mode="r"  or  "wM.  (/ 
...  )  /(  Formatted  output  routine,  tl 


Output  str  to  file  fd.  (/ 


Release  block  of  memory  allocated  by 
malloc.  (/ 

Returns  non-zero  (true)  if  c  is  a  tl 

letter  or  diqtt.  tl 

Return  pointer  to  block  of  nbytes  (/ 

of  available  memory,  tl 

Output  character  c  to  file  fd.  tl 


Return  pointer  to  nbytes  of  memory,  tl 
Cannot  be  freed.  •/ 

Return  pointer  to  first  occurrence  of  c  tl 
in  str.  tl 

Compare  strinas  1  and  2.  Return  0  if  tl 
strl*=str2,  negative  if  strl(str2.  or  tl 
positive  if  strl)str2.  tl 

It  Copy  string  at  source  to  dest.  tl 

Return  length  of  str.  1/ 

Compare  first  n  characters  of  strl  and  tl 
str2.  Return  0  if  identical,  negative  (/ 
if  strl<str2  or  positive  if  strl>str2.  •/ 
Return  integer  value  of  hexadecimal  tl 
representat i on  in  str.  tl 


End  Listings 


(Continued  from  page  29) 


Dr.  Dobb's  Clinic 


(Listing  continued,  text  begins  on  page  20) 


Listing  Four 

/*  Mitche 11-Moore  QRNG  in  C.  Presumes  the  existence  of  a 
/•  simpler  QRNG  named  rOrandO  and  its  seeder,  rOseed(x). 


»/ 

»/ 


extern  void  r0seed(); 
extern  unsigned  rOrandO; 

static  unsigned  xa[55]; 
static  int  j,k; 


rseed (x) 
unsigned  x; 

rOseed(x);  /*  initialize  randOO  predictably  */ 
for( j  =  0;  j  <  55;  ++j)  xa[J]  =  rOrandO; 

J  =  23; 
k  =  54; 

) 


rand ( ) 

{ 

unsigned  x; 

..  _  —  r  v  i  xa  [  j  ] ; 

=  55;  — j; 

=  55;  — k; 

End  Listings 


if  ( j  =  =  0 )  j 
if  (k-=0)  k 
returnC  x) ; 


83 


Dr.  Dobb's  Journal,  August  1985 


Small-C 

Update 


by  J.  E.  Hendrix 

What's  the  author  of  Small-C  V2  been 
up  to  lately ?  Improvements ,  bug  fixes , 
and  a  macro  assembler. 


It  has  been  about  a  year  since  the  last  Dr.  Dobb’s  article 
on  the  Small-C  compiler,  and  many  readers  may  be 
wondering  about  further  developments.  I  have  received 
a  number  of  queries,  mostly  about  structures  and  addi¬ 
tional  data  types,  but  for  several  reasons  I  have  not  devel¬ 
oped  Small-C  further. 

For  one  thing,  additions  would  affect  nearly  every  part 
of  the  compiler,  so  it  makes  sense  to  clean  up  the  overall 
structure  of  the  compiler  before  proceeding.  Rather  than 
bloating  the  compiler  with  code,  I  would  like  to  stream¬ 
line  its  design,  reduce  its  size,  and  improve  its  efficiency 
before  adding  more  features.  Then  there  is  the  need  to 
free  Small-C  from  dependence  on  other  packages  for  as¬ 
sembling  and  linking  its  output.  I  consider  this  capability 
more  important  than  additional  compiler  features.  All  of 
this  amounts  to  a  considerable  project,  and  I  just  haven't 
had  the  time  recently  to  spend  on  it. 

I  also  wonder  whether  Small-C  really  needs  to  become  a 
full  C  compiler  to  be  a  useful  tool.  It  is  already  suitable  for 
many  applications;  text  processing  and  system  utilities  are 
obvious  examples.  With  plenty  of  full-featured  C  compil¬ 
ers  available  at  reasonable  prices,  I  haven't  felt  a  great 
urge  to  take  Small-C  beyond  its  present  entry-level  stature. 

I  have  sent  the  compiler  to  individuals  and  companies 
involved  in  auto-ignition  control;  manufacturing  of  CRT 
terminals,  electronic  games,  microcomputers,  and  circuit 
cards;  mental  health;  publishing;  materials  management; 
telecommunications;  engineering  research;  control  of  de¬ 
vices  like  transmitters  and  telescopes;  and  education.  I 
have  noticed  an  increasing  interest  in  Small-C  in  education 
circles-  high  school,  vocational-technical,  and  college.  Be¬ 
cause  it  is  small,  is  written  in  a  popular  high-level  lan¬ 
guage,  and  generates  readable  assembly  language  code, 
Small-C  is  ideal  for  studying  what  compilers  do  and  how 
they  work.  Its  one-pass  algorithm  and  the  fact  that  input 
and  output  default  to  the  console  make  it  especially  conve¬ 
nient  for  studying  the  relationship  between  compiler  input 
and  output.  The  Small-C  compiler  is  also  a  great  guinea 
pig  for  term  projects,  giving  hands-on  experience  in  com¬ 
piler  construction  to  would-be  computer  scientists.  And 
The  Small-C  Handbook  makes  it  easy  for  users  to  quickly 
familiarize  themselves  with  the  language  and  the  compiler. 

I  maintain  a  mailing  list  of  Small-C  users  whom  I  try  to 
keep  informed  of  bug  fixes  and  improvements.  But  be¬ 
cause  Small-C  may  be  distributed  freely  (on  a  noncom¬ 
mercial  basis),  many  people  have  copies  that  they  ob¬ 
tained  indirectly  and  that  may  be  out  of  date.  If  you  have 
version  2.1  with  the  new  library  that  was  introduced  in 
May  and  June  1984  (DDJ  No.  91  and  No.  92),  then  the 
fixes  reported  below  will  bring  you  up  to  date. 

I  am  still  distributing  Small-C  for  $25,  The  Small-C 
Handbook  for  $  1 7.95,  and  a  package  of  Small-C  software 
tools  for  $35.  Naturally,  complete  source  code  is  included. 
To  order,  send  a  check  or  money  order  to  me  at  the  ad¬ 
dress  listed  at  the  end  of  the  article.  Include  $4  for  post- 


,/.  E.  Hendrix,  Box  8378,  University,  MS  38677. 


84 


Dr.  Dobb’s  Journal,  August  1985 


£500 


age  and  packaging  plus  $6  for  overseas  air  mail.  Send  a 
self-addressed,  stamped  envelope  for  information. 

People  frequently  ask  for  diskette  formats  other  than  8- 
inch  SSSD  and  North  Star  5^-inch,  making  it  necessary 
for  me  to  refer  them  elsewhere,  and  many  people  to  whom 
I  have  sent  Small-C  and  the  tools  have  machines  with  dif¬ 
ferent  diskette  formats,  CPUs,  and  operating  systems.  In 
most  cases,  however,  I  do  not  know  where  they  stand  in 
porting  the  software  or  how  willing  they  are  to  take  refer¬ 
rals.  So  if  you  are  running  Small-C  2.1  (with  the  new  li¬ 
brary)  on  anything  except  an  8-inch  or  North  Star  CP/M 
machine  and  are  willing  to  help  distribute  it,  please  let  me 
know.  Indicate  (1 )  your  machine,  (2)  your  operating  sys¬ 
tem,  (3)  the  diskette  formats  you  can  supply,  and  (4) 
whether  or  not  you  have  the  Small-Tools  package. 

Redundant  Code 

David  Bookbinder  has  observed  that  Small-C  generates 
redundant  code  for  deallocating  local  variables  at  the  end 
of  a  function  when  the  last  statement  is  a  return.  The 
same  is  true  if  a  return  is  the  last  statement  in  any  com¬ 
pound  statement  that  declares  local  variables.  This  is 
harmless,  but  it  unnecessarily  adds  to  the  size  of  programs 
and  it  really  is  untidy. 

Likewise,  if  the  last  statement  in  a  function  is  a  goto, 
unnecessary  code  for  deallocating  local  variables  and  a 
RET  is  generated:  because  a  goto  would  block  the  exit 
path  from  a  function,  there  is  no  need  for  such  code.  (The 
compiler  already  eliminates  the  RET  if  the  last  statement 
is  a  return.)  There  is  no  problem  with  goto’s  terminating 
nested  compound  statements  because  goto’s  are  not  al¬ 
lowed  if  variables  are  declared  anywhere  after  the  open¬ 
ing  brace  of  the  function  body  so  there  are  no  variables 
to  deallocate. 

The  following  patch  eliminates  this  redundant  code: 

( 1 )  Add  the  following  line  to  the  end  of  the  file  CC.DEF: 

#define  STLABEL  14 

(2)  Change  the  line  in  newfunc(  )  (file  CC12.C)  that  con¬ 
tains  a  call  to  statement(  )  to  read: 

statement(  ); 

ffifdef  STGOTO 

if( lastst  !=  STRETURN  &&  lastst  !=  STGOTO) 
ffret(  ); 

#else 

iff  lastst  !=  STRETURN)  ffret(  ); 

#endif 

(3)  Change  the  line  in  statement(  )  (file  CC13.C)  that 
contains  a  call  to  dolabel(  )  to  read: 

else  if(dolabel(  ))  lastst  =  STLABEL; 

(4)  Change  the  line  in  compoundf  )  (file  CC13.C)  that 
contains  a  call  to  modstk(  )  to  read: 

tfifdef  STGOTO 


if( lastst  !=  STRETURN  &&  lastst  !=  STGOTO) 
#else 

iff  lastst  !=  STRETURN) 

#endif 

modstk(savcsp,  NO);  /*  delete  local  variable 
space  */ 
csp  =  savcsp; 

(5)  Recompile,  assemble,  and  link  the  compiler. 

FPUTC  Placement 

A  problem  has  been  reported  concerning  the  placement  of 
the  module  FPUTC  in  the  relocatable  library  CLIB.REL. 
Its  placement  between  FPUTS  and  FREAD  makes  it  pre¬ 
cede  PUTCHAR,  which  makes  reference  to  it.  Because 
L80  can’t  load  a  module  that  it  has  already  passed,  pro¬ 
grams  (like  “words”  on  page  31  of  The  Small-C  Hand¬ 
book)  that  reference  putchar(  )  but  not  fputc(  )  will  not 
link  properly.  The  correction  for  this  condition  is  to  move 
FPUTC  down  in  CLIB.REL  so  that  it  falls  between  EOPEN 
and  FREE.  This  is  done  by  invoking  LIB80  as  follows: 

A>LIB80 
*NEWLIB  = 

*CLIB<  . .  FPUTS> 

*CLIB<FREAD  .  .  FOPEN> 

*CLIB<FPUTC> 

*CLIB<FREE  .  .  > 

*/E 

You  should  also  change  the  placement  of  FPUTC  in  the 
file  NEWLIB3.SUB  so  it  will  order  the  modules  correctly 
if  you  ever  build  a  new  library  from  scratch.  As  a  precau¬ 
tion,  you  should  have  LIB80  list  the  contents  of  the  new 
library  (see  notes  below)  then  verify  that  the  order  exactly 
matches  NEWLIB3.SUB.  Finally,  delete  CLIB.REL  and 
rename  NEWLIB.REL  to  CLIB.REL. 

fflushf  )  and  Auxiliary  Buffers 

The  function  fflush(  )  attempts  to  flush  an  auxiliary  buff¬ 
er  even  if  the  file  were  opened  for  reading  only.  In  this 
case,  the  auxiliary  flush  routine  returns  an  error  condition 
when  a  normal  return  without  taking  any  action  would 
have  been  appropriate.  Before  auxiliary  buffering  was 
added,  it  was  not  necessary  to  check  the  open  mode  be¬ 
cause  the  “dirty  buffer”  status  was  being  checked  and  it 
could  not  have  been  set  for  read-only  files.  This  fix  veri¬ 
fies  the  open  mode  before  any  attempt  to  flush  a  buffer. 
An  attempt  to  flush  a  closed  file  now  gives  a  normal  re¬ 
turn.  Modify  fflush(  )  as  shown  below,  compile,  assemble, 
and  replace  in  CLIB.REL: 

FFLUSH.C 

fflush(fd)  int  fd;  { 

if(Umode(fd)  &  WRTBIT)  ( 

if((Uauxsz  &&  Uauxsz[fd]  &&  Uauxfl(fd))  1 ! 
(lisatty(fd)  &&  Udirty [ fd ]  && 


86 


Dr.  Dobb's  Journal.  August  1985 


U sector( fd,  WRTRND)))  { 
Useterr(fd); 
return  (ERR); 

} 

} 

return  (NULL); 


Reset  Auxiliary  Buffer  Pointers 

When  an  fd  that  has  an  auxiliary  buffer  (because  of  a 
previous  call  to  auxbuf(  ))  is  closed  and  reopened,  the 
next-byte  and  end-of-data  auxiliary  buffer  pointers  are 
not  reset.  This  causes  improper  reading  and  writing.  You 
must  revise  two  library  modules  to  correct  this  problem: 
CSYSLIB  and  AUXBUF.  These  modules  should  be  revised 
as  indicated  below,  compiled,  assembled,  and  replaced  in 
CLIB.REL: 

CSYSLIB. C 

int 

*  Uauxsz,  /*  addr  of  Uxsizef  ]  in  AUXBUF  */ 
Uauxin,  /*  addr  of  Uxinit(  )  in  AUXBUF  */ 
Uauxrd,  /*  addr  of  Uxread(  )  in  AUXBUF  */ 
Uauxwt,  /*  addr  of  Uxwrite(  )  in  AUXBUF  * / 
Uauxfl,  /*  addr  of  Uxflush(  )  in  AUXBUF  */ 

Uopen(fn,  mode,  fd)  char  *fn,  *mode;  int  fd;  ( 
char  *fcb; 

if(!strchr(“rwa”,  ’"mode))  return  (ERR); 
Unextc[fd]  =  EOF; 
if(Uauxin)  Uauxin(fd); 
if(strcmp(fn,  “CON:”)=  =0)  ( 

Udevice[fd]  =  CPMCON;  Ustatus[fd]  = 
RDB1T  !  WRTBIT;  return  (fd); 


AUXBUF.C 

extern  int  *Uauxsz,  Uauxin,  Uauxrd,  Uauxwt, 
Uauxfl,  Ustatus[  ]; 

auxbuf(fd,  size)  int  fd;  char  *size;  (  /*  fake 
unsigned  */ 

if(!Uniode(fd) !  1  Isize  !  1  avail  (NO)  < 
size  1 !  Uxsize[fd]) 
return  (ERR); 

Uxaddr[fd]  =  malloc(size);  Uxinit(fd); 
Uauxin  =  Uxinit; 

/*  tell  Uopen(  )  where  Uxinit(  )  is  */ 
Uauxrd  =  Uxread; 

/*  tell  Uread(  )  where  Uxread(  )  is  */ 
Uauxwt  =  Uxwrite; 

/*  tell  Uwrite(  )  where  Uxwrite(  )  is  * / 
Uauxsz  =  Uxsize; 

/*  tell  both  where  Uxsize[  ]  is  */ 
Uauxfl  =  Uxflush 

/*  tell  fflush(  )  where  Uxflush(  )  is  */ 
Uxsize[fd]  =  size; 

/*  tell  Uread(  )  that  fd  has  aux  buf  */ 
return  (NULL); 

} 

r 

**  Initialize  aux  buffer  controls 

7 

Uxinit(fd)  int  fd;  { 

Uxnext[fd]  =  Uxend[fd]  =  Uxaddrffd]; 
Uxeof[fd]  =  NO; 


Optimizing  Tests  Against  Zero 

Small-C  optimizes  tests  against  the  value  zero.  However, 
in  its  enthusiasm,  it  overlooks  certain  unary  operators  that 
might  spoil  its  efforts.  So  it  optimizes  if(!(i=  =0)) . . . ;  as 
though  it  were  if(i=  =0) . . . ;.  You  can  fix  this  by  making 
the  changes  indicated  below  to  file  CC32.C  of  the  compiler: 

CC32.C 


hierl  3( lval )  int  lval[  ];  { 

else  if  (match(“~”))  {  /*  ~  */ 

return  (lval[7]  =  0); 

} 

else  if  (match(“!”))  {  /*  !  */ 

return  (lval[7]  =  0); 

} 

else  if  (match(“  —  ”))  {  /*  unary  —  */ 

return  (lval[7]  =  0); 

} 


Dr.  Dobb' s  Journal.  August  1985 


87 

625 


Lexcmp(  ) 

Lexcmp(  )  erroneously  returns  zero,  indicating  a  match, 
when  the  first  bytes  that  are  not  identical  are  upper  and 
lower  cases  of  the  same  letter;  for  instance,  “Happy”  and 
“HAY”  appear  to  match.  Revise  lexcmp(  )  as  indicated 
below,  recompile,  assemble,  and  replace  in  CLIB.REL: 

LEXCMP.C 

lexcmp(s,  t)  char  *s,  *t;  { 

while)  lexorder(*s,  *t)  =  =  0) 
if(*s++)  ++t; 
else  return  (0); 
return  (lexorder(*s,  *t)); 

} 

char  Ulex[  1 28]  =  { 

/****  NUL  ■-  /  ***•/ 

0,  1,  2,  3,  4,  5,  6,  7,  8, 

9,  10,  11,  12,  13,  14,  15,  16,  17, 

18,  19,  20,  21,  22,  23,  24,  25,  26, 

27,  28,  29,  30,  31,  32,  33,  34,  35, 

36,  37,  38,  39,  40,41,42,43,44, 

45,  46,  47, 

/****  q_9  ****/ 

65,  66,  67,  68,  69,  70,  71,  72,  73, 

74, 

/****  .  .  <  =  >  ?  @  ****/ 

48,49,  50,  51,  52,  53,  54, 

/****  A-Z  **** / 

75,  76,  77,  78,  79,  80,  81,  82,  83, 
84,85,86,87,88,89,90,91,92, 

93,  94,95,  96,  97,98,99,  100, 

/****  [  \  j  *  u*  ♦***/ 

55,  56,  57,  58,  59,  60, 

/***♦  a_z  ****/ 

75,  76,  77,  78,  79,  80,  81,  82,  83, 

84,  85,86,  87,  88,  89,  90,  91,  92, 

93,  94,  95,  96,  97,98,99,  100, 

/***♦  {  1  }  ~  ****/ 

61, 62,  63,  64, 

/****  DEL  ****/ 

101 


This  patch  also  makes  a  minor  change  for  improved  effi¬ 
ciency  and  removes  leading  zeroes  from  the  values  in 
Ulex[  ]  so  they  will  not  look  like  octal  values  to  full  C 
compilers. 

Macro  Storage 

Experience  has  shown  that  the  amount  of  storage  space 
allocated  for  macro  replacement  strings  is  probably  too 
small  for  the  number  of  macros  allowed.  Also,  the  num¬ 
ber  of  macros  allowed  may  not  be  large  enough  for  some 
programs. 

The  following  parameters  in  CC.DEF  have  been  in¬ 
creased  to  help  this  situation: 


#define  MACNBR  130 

#define  MACQSIZE  (MACNBR*7) 

You  will  need  to  recompile  the  compiler  to  effect  this 
change.  However,  if  you  are  not  having  problems  with 
overflowing  macro  storage  space,  you  may  ignore  this 
change. 

fread(  )  and  fwritef  ) 

The  functions  fread(  )  and  fwritef  )  do  not  return  proper 
values.  They  each  take  a  pair  of  arguments  indicating 
how  many  items  to  transfer  and  the  size  (in  bytes)  of  an 
item.  They  are  supposed  to  return  the  number  of  “items” 
actually  transferred,  but  instead  they  are  returning  the 
number  of  “bytes”  transferred. 

This  edit  fixes  these  problems  and  makes  some  changes 
for  improved  efficiency  in  write(  ).  Fortunately,  these 
changes  are  easily  made  without  recompiling  the  compil¬ 
er.  Look  for  these  functions  in  the  files  FREAD.C  and 
FWRITE.C.  Using  AR.COM,  extract  these  files  from 
CLIB.ARC  and  change  them  as  indicated  below: 

FREAD.C 

fread(buf,  sz,  n,  fd)  char  *buf;  int  sz,  n,  fd;  ( 
return  (read(fd,  buf,  n*sz)/sz); 


FWRITE.C 

fwrite(buf,  sz,  n,  fd)  char  *buf;  int  sz,  n,  fd;  { 

if( write( fd,  buf,  n*sz)  =  =  —  1 )  return  (0); 
return  (n); 

} 

writeffd,  buf,  n)  int  fd,  n;  char  *buf;  { 
char  *cnt;  /*  fake  unsigned  */ 
cnt  =  n; 

while(cnt - )  { 

Uwrite(*buf+  +,  fd); 
if(Ustatus[fd]  &  ERR  BIT)  return  ( —  1 ); 

} 

return  (n); 

} 

Then  compile  them  and  put  them  in  CLIB.REL  as  indicat¬ 
ed  in  the  notes  at  the  end  of  this  article.  Finally,  using 
AR.COM,  put  them  back  into  CLIB.ARC. 

Uputsecf  ) 

The  function  Uputsec(  )  in  CSYSLIB  always  assumes  that 
the  sector  being  written  is  at  the  end  of  the  file.  Conse¬ 
quently,  it  always  initiates  the  next  sector  with  the  value 
1  A.  This  is  bad  if  you  are  using  random  access  techniques, 
because  it  overwrites  existing  data  with  1 A  bytes. 

This  fix  makes  Uputsec(  )  realize  that  it  is  not  neces¬ 
sarily  at  end  of  file.  Using  AR.COM,  extract  CSYSLIB. C 
from  CLIB.ARC  and  change  it  as  indicated  below: 


88 


Dr.  Dobb's  Journal,  August  1985 


CSYSLIB.C 

Uputsec(fd)  int  fd;  { 

if(fflush(fd))  return  (NO); 

Uadvance(fd); 

if(Ustatus[fd]&EOFBIT  ! !  Usector(fd,  RDRND)) 
pad(Ubufptr[fd],  CPMEOF,  BUFSIZE); 
return  (YES); 


Then  compile  it  and  put  it  in  CLIB.REL  as  explained  in 
the  notes.  Finally,  using  AR.COM,  put  it  back  into 
CL1B.ARC. 

hier1(  ) 

A  problem  in  hier  1  (  )  (file  CC31.C)  causes  the  operators 
+  =  and  —  =  in  expressions  like  i  +  =  p  (where  p  is  a 
pointer)  to  erroneously  assign  to  p  instead  of  i.  If  p  were  a 
local  pointer,  then  the  assignment  is  made  to  the  address 
corresponding  to  the  value  of  i,  thus  corrupting  the  sys¬ 
tem.  This  problem  occurred  because,  in  the  effort  to  as¬ 
sign  attributes  to  a  subexpression  from  included  primi¬ 
tives  and  subexpressions,  nothing  was  done  to  isolate  the 
attributes  of  the  receiving  field. 

This  is  fixed  by  allocating  a  short  temporary  array 
lval3[2]  for  passing  to  store(  ).  Using  AR.COM,  extract 
everything  from  CC.ARC  (or  just  CC3.C,  CC31.C, 
CC32.C,  and  CC33.C  if  you  already  have  CC7.REL  files). 
Change  hier(  )  in  CC31.C  as  indicated  below: 

CC31.C 

hierl(lval)  int  lval[  ];  { 

int  k,lval2[8],  lval3[2],  oper; 

if(k=  =0)  { 
needlval(  ); 
return  0; 

} 

lvaI3[0]  =  lval[0]; 
lval3[  1  ]  =  lval[  1  ]; 
if  (lval[  1  ])  { 
if(oper)  { 
push(  ); 
rvalue(lval); 

} 

store(lvaJ3); 
return  0; 

} 

Then  compile  the  compiler  (only  part  3  if  you  already 
have  CC7.REL  files  for  the  other  parts)  and  link  the  parts 
with  CLIB.REL,  giving  a  new  CC.COM.  Finally,  replace 
CC31.C  in  CC.ARC. 

Leading  Blanks 

Frank  Hayes  has  reported  that  because  CP/M  Plus 
doesn’t  load  the  command  tail  into  its  buffer  (at  80H) 


with  a  leading  blank,  as  CP/M  2.2  does,  theSmall-C  func-  I 
tion  Uparsef  )  misses  a  character,  often  causing  it  to  fail 
to  recognize  a  redirection  specification. 

This  correction  to  Uparse(  )  removes  the  assumption  of 
a  leading  blank,  making  Uparse(  )  work  correctly  with 
either  CP/M  2.2  or  CP/M  Plus.  Using  AR.COM,  extract 
CSYSLIB.C  from  CLIB.ARC.  Change  the  first  few  lines  in 
Uparse(  )  to  read  as  follows: 

CSYSLIB.C 

Uparse(  )  { 

char  *count,  *ptr; 
count  =  128; 

ptr  =  Ualloc((count  =  *count&255)+  1,  YES); 
strncpy(ptr,  129,  count); 

Uvec[0]  =  Uargl; 


Compile  and  assemble  CSYSL1B  and  replace  it  in  CLIB¬ 
.REL.  Using  AR.COM,  also  replace  the  source  in  CLIB¬ 
.ARC.  Finally,  compile,  assemble,  and  link  any  Small-C 
programs  that  run  under  CP/M  Plus. 

Notes 

( 1 )  I  n  the  source  statements  shown  above,  the  capital  letter 
“U”  prefixes  many  global  variable  and  function  names. 
Older  copies  of  Small-C  used  the  underscore  character 

in  this  position  to  avoid  conflicts  with  user-declared 
names.  Because  some  versions  of  Macro-80  will  not  handle 
a  leading  underscore  on  external  references,  underscores 
were  changed  to  the  letter  “U”  to  make  them  acceptable  to 
Macro-80.  There  is  no  significance  to  the  use  of  upper  case 
except  that  it  stands  out  in  the  source  listing  as  a  unique¬ 
ness  prefix  instead  of  as  part  of  the  name  proper.  In  mak¬ 
ing  corrections,  you  should  use  “U”  or  “  _  ”  according  to 
the  usage  in  your  existing  runtime  library. 

(2)  The  commands  to  replace  a  module  in  CLIB.REL  are: 

L1B80 

*NEWLIB  = 

*CLIB<  .  .  prevmodule> 

*module 

*CLIB<nextmoduIe . .  > 

*/E 

“Prevmodule”  is  the  name  of  the  module  preceding  the 
one  being  replaced,  “module”  is  the  one  being  replaced, 
and  “nextmodule”  is  the  one  following  the  one  being  re¬ 
placed.  This  will  leave  the  old  library  named  CLIB.REL 
and  create  a  new  one  named  NEWLIB.REL.  When  you 
are  sure  the  new  library  is  okay,  delete  the  original  library 
and  rename  NEWLIB.REL  to  CLIB.REL.  To  find  the  or¬ 
der  of  the  modules  in  the  original  library  or  to  verify  the 
new  library,  execute  the  following  commands: 

LIB80 

*libname/L 

*C 


Dr.  Dobb’s  Journal,  August  1985 


90 

fi?7 


“Libname"  is  the  name  of  the  library  being  checked. 


Addendum — Small-Mac  Assembler 

A  companion  assembler  to  the  Small-C  compiler  is  now 
available.  Small-Mac  is  a  complete  relocatable  macro  as¬ 
sembler  package  written  in  Small-C,  for  use  with  the 
Small-C  compiler.  It  runs  on  8080/Z80  machines  under 
CP/M.  Its  features  include  low  cost,  ease  of  use,  CPU 
adaptability,  source  code  distribution,  and  educational 
value. 

The  following  programs  are  included: 

MAC  macro  assembler 

LNK  linkage  editor 

LGO  load-and-go  loader 

LIB  library  manager 

CMIT  CPU  configuration  utility 

DREL  dump  relocatable  files 

MAC  is  a  two-pass,  table-driven,  relocatable  macro  as¬ 
sembler.  It  “learns”  the  target  machine  from  a  machine 
instruction  table  (MIT),  which  is  created  with  a  text  edi¬ 
tor  and  compiled  with  the  CMIT  configuration  utility.  It 
generates  relocatable  object  modules  in  the  8-bit  Micro¬ 
soft  format.  MAC  is  invoked  with  a  simple  command  syn¬ 
tax  and  issues  descriptive  error  messages. 

LNK  combines  specified  object  modules  with  modules 
found  in  specified  libraries  to  create  executable  programs. 
Its  default  output  is  a  standard  .COM  file.  However,  it  can 
generate  “load-and-go”  (.LGO)  files  for  execution  at  any 
desired  address. 

LGO  loads  and  optionally  executes  .LGO  files.  It  pro¬ 
vides  a  convenient  way  to  invoke  operating  system  exten¬ 
sions  at  cold  start  time. 

LIB  builds,  maintains,  and  lists  the  contents  of  LNK 
compatible  libraries. 

CMIT  compiles  machine  instruction  tables,  lists  them, 
and  optionally  configures  the  assembler  with  the  resulting 
object  table. 

DREL  produces  a  formatted  dump  of  .REL  and  .LIB 
files. 

Small-Mac  instruction  operands  may  contain  expres¬ 
sions  of  any  complexity.  Expression  operators  and  prece¬ 
dence  rules  follow  the  C  language.  The  Small-Mac  macro 
facility  is  easy  to  learn,  remember,  and  use.  Conditional 
assembly  and  repeat  pseudo-ops  are  not  supported  at  this 
time. 

Small-Mac  comes  on  two  8-inch  SSSD  diskettes  con¬ 
taining  both  source  and  object  files.  A  64-page  manual  is 
included.  Send  $30  (plus  $4  for  postage  and  handling, 
plus  $6  for  overseas  air  mail)  to: 

J.  E.  Hendrix 

Box  8378 

University,  MS  38677 


91 


Dr.  Dobb' s  Journal.  August  1985 


Asynchronous  Protocols 


by  David  W.  Carroll 


From  its  quiet  beginnings  in  1977 
in  Ward  Christensen's  MODEM 
program  for  data  exchanges  be¬ 
tween  dissimilar  CP/M  8-bit  comput¬ 
ers,  the  XMODEM  communications 
protocol  has  grown  until  it  is  now  sup¬ 
ported  by  almost  every  commercial 
communications  package  for  8-  and 
1 6-bit  micros,  by  numerous  public  do¬ 
main  terminal  programs,  by  most  re¬ 
mote  bulletin  board  and  file  exchange 
systems,  and  even  by  a  recent  imple¬ 
mentation  on  CompuServe.  But  is 
XMODEM  the  only  answer?  This  arti¬ 
cle  focuses  on  the  state  of  currently 
available  asynchronous  protocols  and 
on  the  reasons  for  their  development. 

Why  Protocols? 

Software  communications  protocols 
are  often  necessary  to  allow  commu¬ 
nications  between  computer  systems. 
Predefined  protocols  are  required  in 
many  cases  to  allow  reliable,  error- 


Background 

Most  microcomputer  systems  sup¬ 
port  asynchronous  serial  communica¬ 
tions.  This  means  that  each  byte  of 
data  is  sent  as  a  self  contained  unit; 
the  data  flow  is  made  up  of  discrete 
characters,  each  completely  indepen¬ 
dent  from  the  others.  To  allow  the  re¬ 
ceiving  computer  to  detect  and  inter¬ 
pret  incoming  data,  a  start  bit  is  sent, 
followed  by  a  given  number  of  data 
bits  making  up  the  character  (usually 
5,  7,  or  8),  followed  by  one  or  two 
stop  bits.  Bell  Laboratories  developed 
this  technique  in  1924  for  use  in  tele¬ 
typewriter  communications  systems. 
The  receiving  system  must  resynch¬ 
ronize  with  each  character  sent. 

In  the  1950s,  military  and  com¬ 
mercial  teletypewriter  systems  imple¬ 
mented  a  simple  method  of  error 
checking  by  adding  a  bit  to  the  data 
to  indicate  the  parity  of  the  data  bits 
making  up  each  character.  Parity 


A  status  report  on  what  Bell  Labs ,  with  some  help 
from  Ward  Christensen  and  others ,  hath  wrought 


free  data  communications  between 
dissimilar  computer  systems.  Lack  of 
hardware  DMA  or  interrupt  capabili¬ 
ty,  error  checking  and  correction  of 
data,  binary  file  transmission,  use  of 
different  microprocessors  and  disk 
operating  systems,  and  provision  for 
micro-to-mainframe  links,  multiple 
file  transfers,  and  unattended  opera¬ 
tion  are  several  specific  reasons  for 
using  protocols. 


David  W.  Carroll,  P.O.  Box  699, 
Pine  Grove,  CA  95665. 

Copyrght  ®  1985  by  David  W. 
Carroll. 


checking  indicates  to  the  receiver 
that  the  number  of  “1”  bits  in  the 
character  should  be  either  odd  or 
even  (hence  “odd”  and  “even”  parity 
schemes).  Parity  checking  provides 
about  a  94  percent  probability  of  de¬ 
tecting  an  error  in  received  data. 
There  is  no  method  for  correcting  a 
detected  parity  error. 

A  5-bit  code  (like  Baudot)  allows 
for  a  total  of  32  characters  (58  char¬ 
acters  with  the  figures/letters  mode- 
shifting  scheme  used  in  telex  sys¬ 
tems)  and  is  still  used  in  telex 
communications  today.  Many  other 
character-coding  schemes  have  been 
developed,  but  the  7-bit  United 


Dr.  Dobb's  Journal,  August  1985 


92 

R?q 


States  American  Standard  Code  for 
Information  Interchange  (USASCII 
or  simply  ASCII),  defined  in  the 
ANSI  X3.1  standard  of  1967,  is  the 
most  widely  used  standard  for  mi¬ 
cros.  This  code  has  1 28  possible  char¬ 
acters  and  includes  numbers,  upper- 
and  lower-case  English  alphabet, 
punctuation,  and  control  codes. 

The  7-bit  ASCII  code,  with  or  with¬ 
out  parity  checking,  has  long  been  a 
standard  for  time-shared  mainframe 
remote  terminal  communications, 
mainly  because  the  Teletype  ®  model 
33  machine  was  the  most  common 
time-sharing  terminal  during  the 
1960s  and  1970s. 

Simple  Protocols 

The  advent  of  microcomputers  and 
low-cost  modems  in  the  past  10  years 
has  generated  a  need  for  a  simple,  in¬ 
expensive  and  reliable  way  to  ex¬ 
change  program  and  data  files  con¬ 
taining  8-bit  binary  data,  as  well  as 
text  files  made  up  of  standard  ASCII 
characters.  The  IBM  PC  further  ex¬ 
panded  this  need  with  its  extended  8- 
bit  character  set  made  up  of  256 
characters.  As  more  and  larger  data 
files  are  being  transmitted  over  ordi¬ 
nary  telephone  lines,  more  elaborate 
error  detection  and  automatic  correc¬ 
tion  are  also  required. 

In  asynchronous  time-sharing  re¬ 
mote  links,  data  is  sent  arbitrarily  by 
the  transmitting  system,  without  re¬ 
gard  for  the  capability  of  the  receiv¬ 
ing  system  to  accept  it.  When  the 
character  is  sent  on  to  a  terminal  sim¬ 
ply  for  display  or  for  storage  in  a 
memory  buffer,  this  does  not  present 
a  problem.  However,  when  files  of 
data  are  transferred,  the  receiving 
system  periodically  must  dump  the 
data  to  a  disk  file.  In  many  hardware 
configurations,  this  requires  all  of  the 
system’s  resources;  it  can  no  longer 
handle  incoming  data  during  the 
dump  period.  Thus,  to  avoid  data  loss 
the  receiver  must  have  some  way  to 
halt  the  data  flow  from  the  sender. 

Some  simple  protocols  have  been 
developed  to  allow  unformatted  text 
transmission  between  mainframes 
and  microcomputers.  Three  basic 
methods  control  the  data  flow:  hand¬ 
shaking,  delay,  and  interruption.  Use 
of  handshaking  involves  sending  a 

93 


character  (usually  a  carriage  return) 
at  the  end  of  each  line  and  requires  the 
return  of  a  character  (usually  a  line 
feed)  before  flow  resumes.  The  second 
alternative  is  for  the  sending  system  to 
delay  a  predetermined  number  of  sec¬ 
onds  after  transmitting  each  line  of 
text  to  allow  the  receiving  system  to 
empty  its  data  buffer  when  required; 
this  method  works,  but  it  can  slow 
down  communications  dramatically. 
The  interruption  method  has  the  sys¬ 
tem  send  data  until  it  receives  an  in¬ 
terrupt  character  then  pause  until  it 
receives  the  resume  character.  This 
protocol  is  perhaps  the  most  efficient 
because  it  allows  continuous  transmis¬ 
sion  until  the  buffer  is  full,  ensuring 
maximum  throughput. 

CR-LF  Protocol 

One  common  file  transfer  method 
used  to  prevent  buffer  overflow  in 
time-sharing  systems  is  to  transmit 
one  ASCII  line  of  text  at  a  time,  in¬ 
cluding  the  carriage  return  character, 


then  to  wait  for  the  receiver  to  send  a 
line  feed  as  acknowledgement.  Often 
a  delay  of  several  seconds  is  added  be¬ 
fore  the  next  line  is  transmitted.  CR- 
LF  is  the  most  common  example  of  the 
handshaking  technique,  although 
prompt  sensing  is  another  popular 
method  used  for  uploading  to  an  edi¬ 
tor  or  BASIC  interpreter. 

XON — XOFF  File  Capture  Protocol 

Possibly  the  most  common  text  file 
transfer  protocol  in  use  today  is  the 
file  capture  or  XON-XOFF-protocol 
offered  on  almost  all  time-shared 
mainframe  computers.  This  protocol 
detects  two  characters  msually  AS¬ 
CII  DC1  and  DC 3  (*Q  and  *S). 
XOFF  or  “S  pauses  transmission  and 
XON  or  “Q  resumes  transmission. 
This  protocol,  another  holdover  from 
teletypewriter  terminals,  was  used  on 
teleprinters  for  paper  tape  operations. 

Today,  this  file  capture  mode  is 
used  to  transfer  text  files  between 
dissimilar  computer  systems.  Data  is 


Dr.  Dobb's  Journal,  August  1985 


captured  to  a  memory  buffer,  and 
XOFF  is  sent  when  the  buffer  is  near¬ 
ly  full.  When  transmission  stops,  the 
data  in  the  buffer  is  written  to  disk. 
Care  must  be  taken  to  ensure  that  the 
text  file  does  not  contain  any  “Q  or 
*S  characters  (as  in  WordStar  files). 

This  protocol  is  supported  by  Com¬ 
puServe,  The  Source,  all  three  major 
data  networks,  and  most  time-shar¬ 
ing  host  computers  and  bulletin 
board  systems.  It  supports  only  parity 
checking  and  7-bit  ASCII  file  trans¬ 
fers  on  most  systems. 

HEX  File  Conversions 

Many  older  host  computers  and 
front-end  communications  processors 
do  not  support  8-bit  data  transfers 
over  asynchronous  links.  How  then 
can  these  systems  (like  CompuServe 
until  recently)  send  binary  data  files? 

A  program  converts  the  binary 
data  to  ASCII  characters  represent¬ 
ing  the  hexidecimal  value  of  the  data. 
Thus,  each  8-bit  data  byte  ends  up  as 
two  ASCII  characters.  For  example: 


Binary 

ASCII 

01101011  — 

6B 

1 110  0001  — 

El 

0011  1111  — 

3F 

These  “characters”  can  be  sent  easily 
as  text  -but  what  about  errors?  To 
resolve  this  problem,  a  line  format  is 
used  that  includes  a  checksum  of  the 
characters,  similar  to  Intel  Hex  for¬ 
mat  (CP/M-80,  etc.).  Obviously,  this 
method  is  both  inefficient  and  time 
consuming:  it  involves  translations  at 
both  ends  and  more  than  doubles  the 
transmitted  size  of  the  original  file. 

CompuServe  supports  this  proto¬ 
col,  but  it  is  becoming  less  popular 
than  some  others,  like  XMODEM. 

XMODEM 

Determined  to  solve  the  problem  of 
file  transfer,  Ward  Christensen,  a 
skilled  programmer  in  Chicago,  de¬ 
signed  a  block-oriented  file  transfer 
technique  that  allows  for  text  and  bi¬ 
nary  file  transfer  with  simple,  yet  ef¬ 
fective,  error  checking.  His  first  ver¬ 
sion  of  the  program,  called  MODEM, 
was  written  in  September  1977  and 
appeared  on  CP/M  User  Group  Disk 
25.  It  allowed  two  microcomputer  us¬ 


ers,  each  running  the  CP/M  80  oper¬ 
ating  system,  to  exchange  both  ASCII 
text  files  and  8-bit  binary  .COM  files 
by  modem  or  direct  connect.  (In  the 
early  days,  there  were  many  different 
types  of  CP/M  systems,  and  most  disk 
formats  were  not  compatible,  so  di¬ 
rectly  transferring  files  over  a  com¬ 
munications  link  was  often  the  only 
way  to  move  programs  from  one  type 
of  computer  to  another.) 

MODEM  protocol  transfers  are 
made  up  of  blocks  of  132  bytes:  four 
header  bytes,  consisting  of  a  start  of 
header  (SOH)  ASCII  character,  the  8- 
bit  block  number,  the  one’s  comple¬ 
ment  of  the  block  number,  and  the  8- 
bit  checksum,  plus  1 28  data  bytes.  The 
receiver  controls  the  sending  of  each 
block  and  any  necessary  retransmis¬ 
sions  by  sending  an  acknowledge 
(ACK),  negative  acknowledge  (NAK), 
or  cancel  (CAN)  ASCII  code  in  re¬ 
sponse  to  each  block.  Timeouts  are 
used  as  a  further  check  of  transmission 
accuracy  and  for  resynchronization. 

By  1979,  BYE,  an  unattended  sys¬ 
tem  control  program,  and  XMODEM, 
the  unattended  version  of  MODEM, 
were  operating  in  bulletin  boards 
across  the  country.  Most  file  transfer 
systems  were  based  on  MODEM,  BYE, 
and  XMODEM,  as  well  as  on  the  pro¬ 
grams  written  (in  part  by  Ward  Chris¬ 
tensen)  for  one  of  the  first  microcom¬ 
puter-based  Computer  Bulletin  Board 
System  (CBBS)  ®  in  Chicago. 

A  large  number  of  “improve¬ 
ments”  to  the  first  MODEM  program 
have  been  written,  and  dozens  of 
commercial  and  public  domain  pro¬ 
grams  now  support  the  XMODEM 
protocol.  Recently,  the  basic  protocol 
definition  was  expanded  to  include 
optional  Cyclic  Redundancy  Check 
(CRC)  error  checking,  an  improve¬ 
ment  on  the  simple  checksum  method 
used  in  the  original  programs.  CRC 
ensures  a  99.99  percent  probability  of 
detecting  errors.  The  CRC  polynomi¬ 
al  is  specified  by  the  CCITT  interna¬ 
tional  telecommunications  standards 
organization. 

MODEM  7  Protocol 

Mark  M.  Zeigler  and  James  K.  Mills 
wrote  the  MODEM7  CP/M  80  pro¬ 
gram  in  1980.  The  MODEM7  proto¬ 
col  is  an  adaptation  of  the  original 


MODEM/XMODEM  protocol  de¬ 
fined  by  Ward  Christensen.  The  MO¬ 
DEM?  program  adds  the  ability  for 
wildcard  and  multiple  file  transfers 
to  the  basic  XMODEM  protocol,  as 
well  as  optional  CRC  error  checking. 

Bruce  R.  Kendall  translated  the 
MODEM7  program  to  8086  code  for 
CP/M  86  and  MSDOS  in  1983;  it  is 
still  used  by  many  as  a  primary  com¬ 
munications  program.  The  MODEM7 
protocol  is  supported  by  the  FIDO 
bulletin  board  software. 

Interestingly,  Crosstalk  and  Cross¬ 
talk  XVI  (probably  the  most  popular 
commercial  communications  pro¬ 
gram)  do  not  properly  support  XMO¬ 
DEM  transfers  to  all  systems  and  do 
not  support  CRC  mode  at  all.  A  public 
domain  subprogram  is  available  for 
1 6-bit  systems  to  correct  this  problem. 

KERMIT  Protocol 

KERMIT  (the  protocol,  not  the  frog) 
was  developed  at  Columbia  Universi¬ 
ty  and  released  to  the  public  domain 
to  allow  efficient  transmission  of  8- 
bit  binary  data  on  systems  that  are 
hardware  limited  to  7-bit  ASCII.  The 
KERMIT  method  encodes  the  binary 
data  into  acceptable  ASCII  charac¬ 
ters  for  transmission,  but  it  uses  more 
than  four  bits  of  each  character  (see 
the  HEX  scheme  above)  and  uses  a 
block  rather  than  a  line  error-check¬ 
ing  protocol  to  achieve  much  greater 
efficiency.  KERMIT  also  allows  mul¬ 
tiple  file  transfers  and  master/slave 
operations. 

KERMIT  is  available  for  most  mi¬ 
crocomputers  and  many  mainframes 
at  little  or  no  cost.  It  presently  is  sup¬ 
ported  by  the  FIDO  bulletin  board 
system  and  Telois  commercial  micro 
communications  software.  It  may  be¬ 
come  a  widely  used  standard  in  the 
near  future. 

Microcom  Networking  Protocol 

The  Microcom  Networking  Protocol 
(MNP)  is  an  interesting  attempt  by  a 
commercial  company  to  define  an  in¬ 
dustry  standard.  Microcom,  maker  of 
modems  and  communications  soft¬ 
ware,  defined  their  protocol  in  1982. 
It  has  been  accepted  by  IBM,  Tym¬ 
net,  Telenet,  and  some  others  as  a 
standard.  Also,  the  data  networks  ac¬ 
cept  it  as  a  way  to  provide  an  error- 


Dr.  Dobb's  Journal,  August  1985 


94 

631 


free  “local”  link  to  their  access  nodes. 

MNP  is  used  in  Microcom’s  ERA  2 
communications  software  and  in 
IBM’s  Personal  Communications 
Manager  program  (written  by  Micro- 
corn).  It  is  also  supported  by  the  latest 
version  of  R BBS-PC  ( 1 2-4A 1 ). 

CompuServe  A  &  B  Protocols 

CompuServe  has  defined  two  proto¬ 
cols  for  use  with  its  VIDTEX  service 
and  provides  software  to  support  them 
on  several  computers,  including  most 
TANDY  (TRS-80)  computers,  the 
Apple,  and  the  IBM-PC. 

CompuServe  A  protocol,  originally 
developed  to  allow  binary  file  trans¬ 
fers  from  the  mainframes  used  by  the 
service,  was  later  updated  to  the  cur¬ 
rent  protocol.  It  is  used  to  upload  and 
download  binary  files.  CompuServe 
B  protocol  is  used  primarily  with 
Tandy  (Radio  Shack)  TRS-80  com¬ 
puters  for  file  transfers  and  support 
of  full-color  video  graphics  (VID¬ 
TEX)  capabilities. 

CompuServe  also  supports  BIN 
and  HEX  file  7-bit  ASCII  transfers  in 
modified  Intel  Hex  format. 

ANSI X3. 28  Protocol 

With  the  demand  for  reliable  commu¬ 
nications,  it  is  surprising  that  the  ANSI 
X3.28  protocol  (originally  defined  in 
1971)  was  not  implemented  on  micro¬ 
computers  until  this  past  year.  The 
X3.28  protocol  is  similar  to  MODEM7 
and  KERMIT.  It  provides  block  trans¬ 
mission  with  CRC-type  error  checking, 
remote  system  master/slave  control, 
and  multiple  file  transfers. 

Artisoft  Inc.  of  Tucson,  Arizona, 
has  included  X3.28  protocol  support 
in  its  Envoy-PC  communications 
software,  as  well  as  file  capture  and 
XMODEM.  Recently,  X3.28  has  re¬ 
ceived  attention  as  a  possible  protocol 
for  use  in  local  area  networks. 

Specialized  Protocol  Software 

Several  software  vendors  have  devel¬ 
oped  their  own  protocols  for  use  be¬ 
tween  systems  running  their  pro¬ 
grams.  Some  of  these  include  the 
following. 

Crosstalk 

Crosstalk  and  Crosstalk  XVI  by  Mi- 
crostuf  both  support  a  256-byte 


block,  error-checking  protocol  (also 
called  CLINK)  similar  to  MODEM7 
that  allows  multiple  file  transfers. 
Later  versions  of  Crosstalk  also  sup¬ 
port  XMODEM. 

Smartcom 

Smartcom  and  Smartcom  II  support 
the  Hayes  Verification  Protocol,  a 
special  error-checking  protocol  de¬ 
signed  by  Hayes  Microcomputer 
products  for  file  transmission.  Smart¬ 
com  II  also  supports  the  XMODEM 
protocol. 

Telink 

The  Telink  protocol  is  supported  by 
the  Telink  program  and  Minitel  pub¬ 
lic  domain  communications  program, 
written  by  Tom  Jennings,  and  by  the 
FIDO  bulletin  board  system,  also 
authored  by  Jennings.  All  programs 
support  MODEM7  and  XMODEM  as 
well. 

Move-It 

Move-It  from  Woolf  Software  uses  a 
proprietary  “packet”  protocol  with 
16-bit  checksums,  message  number¬ 
ing,  and  remote  multiple  file  transfer 
capability.  Move-It  does  not  support 
XMODEM. 

MITE 

MITE  (Mycroft  Intelligent  Terminal 
Emulator)  from  Mycroft  Labs  sup¬ 
ports  all  of  the  above  protocols  (ex¬ 
cept  Telink)  plus  the  following: 

•  XMODEM  checksum  and  CRC 

•  MODEM7  checksum  and  CRC 
(called  XMODEM/Batch) 

•  MITE  batch  block  protocol  (see 
DDJ ,  August  1982) 

•IBM  PC  (text  protocol  from  IBM 
Asynchronous  Support  Program) 
•TEXT  (simple  Hex  transfer  proto¬ 
col) 

ASCOM 

The  ASCOM  program  from  Dynamic 
Microprocessor  Associates  supports 
XMODEM  (called  CPMUG)  as  well 
as  two  checksum  block  protocols, 
BLOCK  and  BLOCKV. 

Summary 

A  number  of  asynchronous  protocols 
have  evolved  to  fill  the  need  for  reli¬ 


able  file  transfers  of  both  text  and  bi¬ 
nary  data  between  various  types  of 
mainframes  and  microcomputers. 
The  most  widely  used  standard  is  the 
XMODEM  protocol  originally  de¬ 
fined  by  Ward  Christensen,  but  even 
this  protocol  has  four  possible  for¬ 
mats  (checksum/CRC  and  single 
file/batch).  Most  commercial  and 
public  domain  file  transfer  programs 
support  at  least  the  basic  checksum, 
single  file  version  of  the  XMODEM 
protocol.  XMODEM  is  simple,  fairly 
unambiguous,  and  easily  implement¬ 
ed  in  a  variety  of  languages,  from  C 
to  BASIC. 

Transfers  from  7-bit  mainframe 
host  systems  are  still  a  clumsy  and 
time-consuming  process.  The  KER¬ 
MIT  protocol,  which  improves  the  ef¬ 
ficiency  of  these  transfers,  is  now 
available  in  a  number  of  formats  for 
many  different  computers. 

Several  other  proprietary  protocols 
have  been  developed,  but  none  has  re¬ 
ceived  the  widespread  acceptance  of 
XMODEM.  With  XMODEM  so  well 
entrenched,  it  is  unlikely  to  be  re¬ 
placed  by  another  protocol  for  several 
years,  if  at  all. 

DD| 

Reader  Ballot 

Vote  for  your  favorite  feature/article. 

Circle  Reader  Service  No.  194. 


95 

632 


Dr.  Dobb’s  Journal,  August  1985 


The  main  topic  of  this  month’s  col¬ 
umn  is  the  Unix1  “make”  utility. 
We’ll  talk  about  how  make  works,  re¬ 
view  a  few  versions  that  run  under 
MSDOS,  and  present  the  complete 
source  for  a  rather  stupid,  but  none¬ 
theless  functional,  version  of  make 
for  MSDOS. 

However,  before  leaping  into 
make,  I'd  first  like  to  thank  everyone 
who  sent  in  sort  routines.  Several  of 
the  routines  are  quite  spiffy;  we’ll 
definitely  have  another  sorting  col¬ 
umn  in  the  next  few  months. 

Next,  some  good  news  for  Lattice 
C  users:  they’ve  finally  cleaned  up 
their  manual.  The  old  manual  was  so 
poorly  organized  as  to  make  the  com¬ 
piler  almost  useless  to  someone  who 
wasn’t  thoroughly  familiar  with 
Unix.  Critical  information  was  scat¬ 
tered  over  several  appendixes  and  a 
myriad  of  technical  bulletins.  This 
wouldn’t  have  been  a  problem  if  Lat¬ 
tice  had  published  a  new  index  with 
each  addendum  to  the  manual,  but  no 
such  luck.  As  far  as  I  can  tell,  the  new 
manual  uses  the  same  text  as  the  old 
one;  however,  it's  now  organized  into 
a  coherent  whole  with  a  real  index 
(they  even  highlight  function  names 
in  blue).  If  you  own  version  2. lx  or 
later  of  the  compiler,  you  can  get  a 
free  update  to  the  new  version  (2.15) 
and  a  copy  of  the  new  manual  by 
sending  Lattice  your  original  release 
disks.  Older  versions  of  the  compiler 
(including  Microsoft  and  Lifeboat 
versions)  can  be  updated  for  a  fee 
ranging  from  $50  to  $100,  depending 
on  the  version.  If  you  have  an  older 
version  of  the  compiler  and  just  want 
the  new  manual,  send  Lattice  the  ta¬ 
ble  of  contents  and  index  from  the  old 
manual  and  they’ll  send  you  the  new 
one  (but  no  software  update). 

Finally,  while  rooting  through  a 
friend’s  back  issues  of  the  PC  Tech 
Journal,  I  discovered  MSDOS’s  un¬ 


documented  SWITCHAR  feature  in 
an  article  by  J.  Eric  Roskos  (August 
1984,  page  73).  It’s  a  constant  annoy¬ 
ance  to  me  that  command.com  insists 
on  using  \  to  separate  directory 
names  in  a  path  and  /  as  a  command 
line  switch  designator  (Unix  pro¬ 
grammers  are  used  to  /  and  -,  respec¬ 
tively).  This  seemed  all  the  more 
onerous  when  I  discovered  that 
MSDOS  itself  accepts  either  a  \  or  / 
in  a  path  name.  Only  command.com 
and  a  few  utility  routines  insist  on 
specializing  the  two. 

Well,  it  turns  out  that  if  you  in¬ 
clude  the  line 

SWITCHAR  =  - 

in  your  config.sys  file,  command.com 
will  use  a  -  instead  of  a  /  as  its  com¬ 
mand  line  switch  designator  (you  can 
replace  the  -  with  any  printing  char¬ 
acter).  Because  /  is  no  longer  special, 
command.com  passes  it  through  to 
DOS  where  it’s  accepted  as  a  legal 
path  name  designator.  Moreover,  the 
path  printed  by  the  “prompt  $p” 
command  will  now  use  slashes  in¬ 
stead  of  back-slashes  when  it  displays 
the  path! 

I’ve  no  idea  how  this  mechanism 
actually  works,  though  I  suspect  it 
operates  in  ways  similar  to  an  envi¬ 
ronment  variable.  If  anyone  can  en¬ 
lighten  me,  please  write.  On  page  1 1 3 
of  the  same  issue  of  the  PC  Tech 
Journal,  Daniel  Frank  shows  how  to 
use  DOS  function  37  (also  undocu¬ 
mented)  to  get  the  current  switch 
character.  Frank  also  presents  a  few 
SWITCHAR  caveats.  To  summarize: 
RESTORE  can  get  very  confused  if 
the  SWITCHAR  is  set  when  you  run 
BACKUP,  so  don’t  use  the  SWIT¬ 
CHAR  if  you’re  doing  a  backup.  The 
same  article  gives  a  patch  to  RE¬ 
STORE  to  fix  this  problem;  the  inter¬ 
ested  reader  can  find  it  there. 


Unfortunately,  in  a  fit  of  perversi¬ 
ty,  Microsoft  elected  not  to  support 
SWITCHAR  in  DOS  version  3.  The 
operating  system  spits  out  the  mes¬ 
sage:  “Unrecognized  command  in 
CONFIG.SYS.”  Argh.  All  is  not  lost, 
though.  Unipress,  the  manufacturer 
of  Ps-make,  reviewed  below,  sells  a 
program  that  changes  the  switch 
character,  and  this  program  does 
work  with  version  3. 

Make 

Make  is  one  of  those  deceptive  utili¬ 
ties:  you  can’t  figure  out  why  anyone 
would  bother  to  use  it  until  you  actu¬ 
ally  do  use  it,  then  you  can’t  figure 
out  how  you  got  along  without  it. 
Make  takes  as  input  a  makefile, 
which  describes  all  the  modules  in  a 
large  program  and  the  relationships 
between  the  modules.  Using  the 
makefile,  make  determines  which 
modules  must  be  recompiled  at  any 
given  moment  and  then  compiles 
them  (and  only  them).  It’s  like  an  in¬ 
telligent  batch  utility  that  knows  how 
C  programs  and  C  compilers  work 
and  does  only  those  actions  that  are 
absolutely  necessary. 

The  best  way  to  explain  make  is 
with  an  example.  You’ve  an  execut¬ 
able  program  called  farm.exe.  The 
original  source  is  split  up  into  three 
modules:  cow.c,  pig.c,  and  farm.c.  All 
three  modules  have  an  #include 
<stdio.h>  statement  in  them.  In  ad¬ 
dition,  cow.c  and  pig.c  have  an  in¬ 
clude  <animals.h>  statement. 
Now,  if  you  change  something  in 
cow.c,  you  need  to  recompile  cow.c 
and  then  relink  the  new  cow.obj  into 
farm.exe.  If  you  change  something  in 
animals. h,  you  need  to  recompile 
both  cow.c  and  pig.c  then  relink.  If 
you  change  stdio.h,  you  need  to  re¬ 
compile  everything.  Make  describes 
the  relationships  between  these  vari¬ 
ous  files  as  “dependencies.”  The 


Dr.  Dobb’s  Journal,  August  1985 


96 

RW 


makefile  is  a  list  of  these  dependen¬ 
cies.  A  makefile  for  the  program  just 
described  is  shown  in  Figure  1 
(below). 

The  #  designates  a  comment  line. 
Farm.exe  “depends  on”  cow.obj,  pig 
.obj,  and  farm.obj.  This  means  that  if 
any  of  the  files  cow.obj,  pig.obj,  or 
farm.obj  have  been  changed  more  re¬ 
cently  than  farm.exe,  then  farm.exe 
must  be  remade — by  executing  the 
line:  link  \lc\s\c  cow  pig  farm,farm, 
con,\lc\s\lc.  Similarly,  if  any  of  the 
files  pig.c,  stdio.h,  or  animals. h  have 
been  changed  more  recently  than 
pig.obj,  then  pig.obj  must  be  remade 


(using  the  command  lc  -ms  -i\lc\ 
-i\lc\s\  pig).  The  actions  associated 
with  each  set  of  dependencies  can  ex¬ 
tend  to  several  lines.  To  activate  the 
process,  you  just  type  the  command 
make.  Make  reads  in  the  makefile 
and,  using  the  information  found 
there,  figures  out  what  to  do  and  does 
it. 

Make  supports  several  options  that 
make  your  life  a  little  easier.  You 
may  use  macros  to  save  some  typing. 
A  macro  is  created  by  placing  the  fol¬ 
lowing  definition  at  the  head  of  the 
makefile: 

name  =  stuff 


where  name  is  the  macro  name  and 
stuff  is  the  text  to  be  substituted.  In 
the  makefile  the  macro  is  expanded 
by  $(name).  Our  original  makefile, 
rewritten  using  macros,  is  shown  in 
Figure  2  (page  97). 

You  can  simplify  the  makefile  even 
further.  Notice  that  all  ,c  files  are 
converted  to  .obj  files  with  the  same 
set  of  actions.  Make  provides  a  mech¬ 
anism  for  doing  this  sort  of  repetitive 
action,  called  a  “generic  dependen¬ 
cy”.  Consider  the  makefile  shown  in 
Figure  3  (page  98). 

The  lines: 


# 

#  Make  farm  using  the  Lattice  C  compiler. 

# 


farm.exe:  cow.obj  pig.obj  farm.obj 

link  \lc\s\c  cow  pig  farm, farm, conAlc\s\lc 

cow.obj:  cow.c  stdio.h  animals. h 

lc  -ms  -i\lc\  -i\lc\s\  cow 


pig.obj:  pig.c  stdio.h  animals. h 

lc  -ms  -i\lc\  -i\lc\s\  pig 


farm.obj:  farm.c  stdio.h 

lc  -ms  -i\lc\  -i\lc\s\  farm 

Figure  1. 

A  Simple  Makefile 


# 

#  Make  farm  using  the  Lattice  C  compiler. 

# 


INCLUDES  =  stdio.h  animals. h 
OBJECTS  =  cow.obj  pig.obj  farm.obj 
COMPILE  =  lc  -ms  -i\lc\  -i\lc\s\ 


farm.exe: 

$(OBJECTS) 

link  \lc\s\c  $(OBJECTS), farm, con, \lc\s\lc 

cow.obj: 

$(INCLUDES)  COW.C 

$(COMPILE)  COW 

pig.obj: 

$(INCLUDES)  pig.c 

S(COMPILE)  pig 

farm.obj: 

stdio.h  farm.c 

S(COMPILE)  farm 

Figure  2. 

A  Makefile  with  Macros 


.c.obj: 

lc  -ms  -i\lc\  -i\lc\s\  $* 

say  “to  turn  a  .c  file  into  an  .obj  file, 
execute  the  line  lc  -ms  -i\lc\  -i\lc\s\ 
$*”  where  $*  is  a  predefined  macro 
that  evaluates  to  the  root  portion  of 
the  file  being  made  (the  one  to  the 
left  of  the  :  on  the  dependency  line). 
For  example,  given  the  dependency 
line: 

foo.obj:  foo.c  rat.h 

$*  will  evaluate  to  the  string  foo.  In 
the  case  of  this  generic  dependency, 
make  assumes  that  all  .obj  files  de¬ 
pend  on  a  .c  file  having  the  same  root 
name.  So,  when  foo.obj  is  being 
made,  a  dependency  on  foo.c  is  as¬ 
sumed,  and  it  need  not  be  listed  on 
the  dependency  line. 

There  are  several  commercially 
available  make  utilities  that  run  on 
MSDOS.  (See  the  review  of  MSDOS 
C  compilers  on  page  30  for  compilers 
that  include  a  make  utility  in  the 
standard  distribution  package.  You 
can't  have  a  make  for  CP/M  because 
you  can’t  time  and  date  stamp  a  file.) 

I  looked  at  three  of  these:  Polymake 
(by  Polytron),  Ps-make  (by  Unipress 
Software),  and  Pmaker  (by  Phoe¬ 
nix).2  Of  these.  Polymake  (at  $99)  is 
both  the  least  expensive  and  the  most 
complete  implementation  of  make. 
Ps-make  (at  $179)  is  missing  some  of 
the  features  of  Polymake.  Pmaker, 
the  Phoenix  product  (at  $195),  is 
both  the  most  expensive  and  the  most 
limited  of  the  three  versions.  The 
Pmaker  manual  is  extremely  sparse. 


97 


Dr.  Dobb's  Journal,  August  1985 


Pmaker  supports  virtually  no  com¬ 
mand  line  options  and  only  a 
stripped-down  set  of  internal  utilities 
(no  predefined  macros,  no  generic  de¬ 
pendencies).  It  does  come  with  a  util¬ 
ity  that  creates  makefiles  (by  going 
into  your  source  code  and  looking  for 
#include  statements),  but  because 
my  makefiles  are  often  a  little  weird, 

1  found  this  utility  pretty  useless. 
Frankly,  I  don’t  think  that  Pmaker  is 
worth  the  money.  In  fact,  the  version 
of  make  presented  at  the  end  of  this 
column  is  almost  as  powerful  as  the 
Phoenix  product  -  and  it’s  free.  I’ll 
therefore  limit  my  remarks  to  Poly¬ 
make  and  Ps-make. 

Polymake 

Polymake  supports  all  the  make  fea¬ 
tures  described  above.  It  has  13  com¬ 
mand  line  options: 

-a 

Rebuild  all  targets,  even  if  time  and 
date  say  that  you  don’t  have  to. 

-b  <file> 

Look  for  a  file  called  builtins.mak 
Polymake  supports  a  default  actions 
file  in  which  you  define  things  like 
.c.obj.  or  macros  that  are  used  a  lot. 

-c 

Create  a  batch  file  rather  than  doing 
the  action  this  lets  you  use  Poly¬ 
make  with  DOS  version  1 . 

-d 

Echo  an  execution  trace  as  actions 
are  performed  debug  mode. 

-f  <file> 

Use  <file>  as  the  makefile  instead 
of  “makefile.” 

-i 

Ignore  errors  returned  from  the 
programs. 

-k 

Keep  working  and  ignore  error  codes 
returned  from  a  process.  (Like  the 
Unix  version,  if  any  program  invoked 
by  Polymake  returns  a  value  other 
than  0,  Polymake  terminates.  This 
can  be  annoying  if  you  want  to  com¬ 
pile  85  modules  and  go  to  lunch,  redi¬ 
recting  all  the  error  messages  into  an 
error  log  file.  The  -i  option  suppresses 
this  automatic  termination.  From 
reading  the  manual,  I’m  not  clear 
about  the  differences  between  -i  and 
-k.  I’ve  been  using  -k.) 


-n 

Print  out  the  various  actions  but 
don’t  actually  execute  them — this  is 
useful  for  debugging  a  makefile. 

-P 

Print  out  a  representation  of  the 
makefile  after  it’s  been  interpreted. 


-q 

Don’t  print  out  error  messages  from 
commands. 

-r 

Don’t  use  any  builtins.mak  files  (see 
option  -b  above). 

-s 


# 

#  Make  farm  using  the  Lattice  C  compiler. 

# 

INCLUDES  = 

OBJECTS  = 

stdio.h  animals. h 
cow.obj  pig.obj  farm.obj 

.c.obj: 

Ic  -ms  -i\lc\  -i\lc\s\  $* 

farm.exe: 

$(OBJECTS) 

link  \lc\s\c  $(OBJECTS), farm, con, \lc\s\lc  $* 

cow.obj: 

pig.obj: 

farm.obj: 

$(INCLUDES) 

$(INCLUDES) 

stdio.h 

Figure  3. 

A  Generic  Makefile 

# 

#  Make  farm  using  the  Lattice  C  compiler. 

# 

farm.exe: 

cow.obj  pig.obj  farm.obj 

link  \lc\s\c  cow  pig  farm,farm,con,\lc\s\lc 

cow.obj: 

cow.c  stdio.h  animals. h 

Ic  -ms  -i\lc\  -i\lc\s\  cow 

pig.obj: 

pig.c  stdio.h  animals. h 

Ic  -ms  -i\lc\  -i\lc\s\  pig 

farm.obj: 

farm.c  stdio.h 

Ic  -ms  -i\lc\  -i\lc\s\  farm 

# 

#  Null  targets  not  needed  in  a  real  makefile: 

# 

animals. h: 
stdio.h: 
pig.c: 
cow.c: 

farm.c: 

Figure  4. 
An  Mkfile 


Dr.  Dobb’s  Journal.  August  1985 


99 


Don’t  echo  commands  (silent). 

-t 

Touch.  (Perhaps  the  most  useful  op¬ 
tion  supported  by  make,  -t  is  for  the 
situation  where  you  change  a  com¬ 
ment  in  stdio.h  and  now  make  thinks 
that  all  97  modules  of  your  program 
monster.exe  have  to  be  recompiled; 
make  -t  modifies  all  the  times  and 
dates  for  files  to  be  made  so  that  they 
look  as  if  they’ve  been  recompiled, 
but  it  doesn’t  actually  recompile 
them.  Polymake  also  comes  with  a 
touch  utility  (invoked  from  MSDOS 
with  “touch  <file  list>”)  that  sets 
the  time  and  date  of  the  indicated 
files  to  the  current  time  and  date.) 

There’s  also  a  rich  set  of  prede- 


fined  macros: 

$(name) 

Expand  macro  “name” 

$$ 

$  sign 

$@ 

Name  of  current  target 

$* 

Root  of  current  target 

$< 

Source  being  used  to 
make  current  target 

$? 

All  sources  that  have 
been  modified  more  re¬ 
cently  than  current  target 

$(  ) 

Null  string 

$(mflags) 

Command  line  flags  (so 
that  make  can  invoke  it¬ 
self  recursively) 

$(cwd) 

Current  working 
directory 

Finally,  Polymake  supports  several 
internal  options,  as  well  as  generic 
dependencies.  Although  I  won’t  de¬ 
scribe  all  of  them  here,  one  is  quite 
useful:  the  local  input  script.  This 
feature  is  for  programs,  such  as  li¬ 
brarians  and  linkers,  that  sometimes 
need  to  use  input  files  rather  than  the 
command  line.  For  example,  you  can 
have  so  many  files  to  link  together 
that  they  won’t  all  fit  on  a  DOS  com¬ 
mand  line.  In  this  case,  you  put  the 
command  line  into  a  file  then  call  link 
with  @file  given  on  the  command 
line.  Local  input  scripts  let  Polymake 
create  these  input  files  for  you.  The 
example  given  in  the  manual  illus¬ 
trates  the  process  pretty  well: 

text.exe  :  tl.obj  t2.obj  t3.obj 
link  <@< 
tl  + 


t2  + 
t3 

test.exe 

test.map 

\lc\s\lc.lib 

< 

All  text  between  the  second  and  third 
<  sign  is  put  into  a  file  called 
lis_qqq.tmp.  Link  is  invoked  with  the 
line  link  @lis_qqq.tmp.  The  @  in 
this  example  can  be  replaced  by  any 
printing  character.  For  example, 

text.exe:  tl.obj  t2.obj  t3.obj 
link  <<< 
text 

< 

will  execute  link  <lis_qqq.tmp; 
lis_qqq.tmp  will  contain  the  single 
line  “text.”  The  temporary  file  is  de¬ 
leted  when  Polymake  is  done  with  it. 

As  you  can  probably  tell,  I  like 
Polymake.  I’ve  been  using  it  for  a 
month  now  and  haven’t  found  any 
problems  yet.  My  only  complaint  is 
that  the  manual  doesn’t  have  an  in¬ 
dex  (which  will  make  it  hard  to  find 
anything  if  you’re  not  familiar  with 
the  Unix  make).  On  the  other  hand. 
Polymake  sticks  pretty  close  to  the 
Unix  model,  so  you  may  not  need  to 
consult  the  manual  very  often. 

Ps-make 

As  I  said  earlier,  Ps-make  is  more 
limited  that  Polymake,  but  it  does  do 
everything  I  consider  to  be  essential: 
generic  dependencies,  $@,  $*,  $<, 
and  macros.  Several  command  line 
options  are  supported  (there  is  some 
overlap  with  Polymake): 

-f  <file> 

Use  <file>  as  the  makefile. 

-o<file> 

Use  <file>  as  name  of  batch  file 
(see  -b). 

-b 

Create  a  batch  file  instead  of  execut¬ 
ing  commands. 

-B 

Beep  before  exiting. 

-c 

Don’t  convert  to  lower  case  when 
evaluating  rules. 

-d 

Print  file  modifications  times  used  in 
comparisons. 


-e 

Ignore  various  environment  vari¬ 
ables — the  name  of  the  default  rules 
file  (builtins.mak  in  Polymake)  is 
kept  in  an  environment  variable. 

-P 

Print  the  generated  dependency  tree. 

-s 

Don’t  execute  commands  as  they’re 
executed. 

-t 

Touch. 

-v 

Force  Ps-make  to  verify  that  a  gener¬ 
ated  file  exists  before  using  it. 

A  MAKE  Utility  in  C 

One  of  the  first  utilities  I  wrote  for 
MSDOS  was  the  version  of  make  giv¬ 
en  here.  The  program’s  called  mk 
(i.e.,  a  make  with  half  of  it  missing). 
Mk  is  pretty  stupid;  it  doesn’t  support 
macros  or  command  line  switches. 
On  the  other  hand,  it  does  do  the  crit¬ 
ical  job:  recompile  only  those  parts  of 
a  large  modular  program  that  need  it. 
1  was  glad  to  have  it  before  I  got  my 
copy  of  Polymake,  and  it  is,  after  all, 
free.  Mk  was  designed  to  help  main¬ 
tain  large  C  programs,  and  it  works 
quite  well  in  this  application,  but  it 
won’t  do  everything  that  the  real 
make  can  do. 

Mk  uses  a  makefile  called 
“mkfile,”  which,  like  the  real  make¬ 
file,  lists  all  the  dependencies  needed 
to  make  a  program.  If  mk  is  invoked 
with  a  name  as  an  argument  (i.e.,  mk 
foo.exe),  it  will  make  the  indicated 
file;  that  is,  it  looks  in  the  mkfile  for 
the  indicated  filename  to  the  left  of  a 
colon  on  a  dependency  line  and  starts 
the  make  from  that  point.  If  no  file  is 
specified  on  the  command  line,  the 
first  file  listed  in  the  mkfile  is  made. 

The  mkfile  itself  has  a  more  re¬ 
stricted  syntax  than  a  real  makefile: 

( I )  A  #  sign  in  column  1  designates 
a  comment  line.  The  #  must  be  in  the 
leftmost  column. 


100 


Dr.  Dobb's  Journal,  August  1985 


(2)  The  dependency  line  comes  first. 
It  uses  the  format  “target:  dependen¬ 
cies”.  The  colon  is  required  (an  error 
message  is  printed  if  it's  not  there). 
The  dependencies  are  delimited  from 
one  another  with  white  space  (any 
combination  of  tabs  and  blanks).  The 
dependencies  must  all  be  listed  on  a 
single  line;  however,  any  line  termi¬ 
nated  with  a  back-slash  (\)  will  be 
continued  to  the  next  line.  For 
example: 

This  is\ 

one  line\ 

of  text. 

(3)  All  files  listed  as  dependencies 
must  be  used  as  targets  somewhere  in 
the  mkfile  (i.e.,  to  the  left  of  a  colon). 
A  null  target  (one  consisting  of  a  sin¬ 
gle  filename  followed  by  a  colon  but 
no  dependencies,  this  in  turn  followed 
by  a  blank  line)  is  permitted. 

(4)  All  lines  following  the  dependen¬ 
cy  line,  up  to  a  blank  line,  are  the  ac¬ 
tions  executed  to  make  the  target. 
The  blank  line  is  required  to  termi¬ 
nate  the  block  of  actions.  Any  num¬ 
ber  of  blank  lines  are  permitted  as  a 
terminator,  but  any  nonblank  line 
following  a  blank  line  is  assumed  to 
be  a  new  dependency  line. 

(5)  Targets  having  no  dependencies 
will  always  be  made.  For  example, 

listing: 

print  foo.c  bar.c  rat.c 

will  always  execute  the  print  com¬ 
mand. 

(6)  For  the  sake  of  comparison,  all 
nonexistent  files  are  considered  to  ex¬ 
ist  and  to  be  very  old,  so  a  nonexistent 
file  listed  to  the  left  of  a  colon  will 
always  be  made.  The  dates  and  times 
associated  with  a  target  are  updated 
as  soon  as  the  target  is  made. 

Figure  4  (page  98)  is  a  mkfile  ver¬ 
sion  of  the  makefile  in  Figure  1 .  Here 
the  blank  lines  are  required,  as  are 
the  various  null  targets  at  the  end  of 
the  file  (for  animals. h,  stdio.h,  etc.). 

The  listing  for  mk  starts  on  page 
104;  mydos.h  (#included  on  line  3) 
was  presented  last  month.  There  are 
several  macros  at  the  head  of  the  list¬ 
ing.  #including  the  #define  on  line  6 


will  cause  mk  to  generate  a  trace  as  it 
parses  the  mkfile,  but  it  won’t  actual¬ 
ly  try  to  do  anything.  MAXLINE  (on 
line  8)  limits  the  number  of  charac¬ 
ters  in  a  line  (lines  continued  with  a 
terminating  \  are  considered  to  be 
one  line).  MAXBLOCK  and  MAX- 
DEP  (lines  9  and  10)  are  the  maxi¬ 
mum  number  of  actions  that  can  fol¬ 
low  a  dependency  line  and  the 
maximum  number  of  dependencies, 
respectively.  MAKEFILE  (line  12)  is 
the  name  used  for  the  makefile.  The 
three  macros  on  lines  24-26  have 
horrible  side  effects,  so  be  careful 
with  them  (don't  ever  say  skip- 
white(  +  +s)). 

I  suspect  that  the  real  make  sets  up 
a  dependency  tree,  a  multi-way  tree 
that  has  the  major  file  to  be  made  as 
its  root  and  dependencies  as  children. 
Mk  doesn’t  work  that  way.  Mk  reads 
in  the  entire  mkfile  before  it  tries  to 
do  anything.  If  it  gets  lost  (or  finds  a 
syntax  error),  an  error  message  is 
printed  and  mk  terminates. 

The  makefile  is  organized  as  a  bi¬ 
nary  tree,  ordered  alphabetically  by 
target  filename;  the  tree  nodes  are 
typedefed  on  lines  38-47.  Lnode  and 
mode  are  pointers  to  children  in  the 
tree.  The  target  filename  (the  one  to 
the  left  of  the  equals  sign)  is  used  as 
the  search  key  and  stored  in  the  be- 
ing_made  field. 

The  dependencies  and  the  actions 
following  the  dependencies  are  put 
into  two  argv-like  arrays  of  pointers 
to  strings.  The  fields  depends_on  and 
do_this  are  used  like  argv  to  access 
these  arrays.  Both  arrays  are  termi¬ 
nated  with  a  null  entry,  so  the  equiva¬ 
lent  of  argc  isn’t  required. 

Finally,  the  time  field  holds  the  file 
create  time  and  date  returned  by 
DOS.  The  time  and  date  are  concate¬ 
nated  (with  the  date  in  the  most  sig¬ 
nificant  word).  This  enables  the  time 
field  to  be  used  as  a  single  number  in 
comparisons. 

Most  of  the  routines  are  comment¬ 
ed  well  enough  to  be  self-document¬ 
ing,  but  there  are  some  exceptions. 
Gtime(  )  (lines  97— 1 37)  gets  the  time 
and  date  for  a  file  from  DOS,  concat¬ 
enates  the  time  and  date  together, 
and  returns  the  concatenated  result. 
The  functions  gregs(  )  and  dos(  ), 
used  to  access  DOS,  were  presented  in 


this  column  last  month. 

The  assignment  on  lines  129-30 
merits  some  attention.  Regs.x.dx  and 
regs.x.bx  are  both  shorts.  A  short, 
shifted  left  16  bits,  evaluates  to  0,  so 
dx  has  to  be  cast  into  a  long  before  the 
shift.  Similarly,  if  regs.x.cx  isn’t  cast 
into  a  long,  the !  will  behave  unpredict¬ 
able  depending  on  whether  sign  ex¬ 
tension  is  active.  However,  now  we 
have  to  AND  the  result  of  the  type 
conversion  with  Oxffff  to  defeat  the 
sign  extension  (or  else  we  may  end  up 
setting  the  entire  top  16  bits  to  1 ). 

The  function  getline(  )  (lines 
200-251)  is  useful  enough  to  merit 
some  notice,  too.  It  works  something 
like  gets(  )  except  that  it  recognizes 
line  continuation  (lines  ending  in  \) 
and  it  allocates  buffer  space  using 
malloc(  ).  I’ve  used  it  in  several  other 
programs. 

The  final  routine  to  look  at  here  is 
make(  )  (lines  302-381);  all  the  real 
work  performed  by  mk  is  done  here. 
Make(  )  is  recursive.  To  make  a  sin¬ 
gle  object,  you  have  to  make  the  de¬ 
pendent  files.  If  any  of  the  dependent 
files  were  remade,  you  then  have  to 
perform  the  action  on  the  current  file. 
When  make(  )  returns,  the  dates  on 
the  remade  files  will  have  been  modi¬ 
fied  to  the  current  date.  Consequent¬ 
ly,  the  time  and  date  comparisons  for 
the  target  and  the  dependent  file  (on 
line  339)  must  follow  the  recursive 
make(  )  call  (on  line  327).  If  any  of 
the  dependencies  were  younger  than 
the  target,  doaction  is  incremented 
(line  350).  We  don’t  actually  do  the 
action  on  line  350  because  we  don’t 
want  to  do  it  several  times. 

Finally,  mk  uses  a  system(  )  call 
(line  368)  to  execute  each  action  line. 
In  Unix,  system(  )  creates  a  parallel 
process  running  the  shell  then  passes 
its  argument  through  to  the  spawned 
shell.  Because  Unix  supports  true 
multitasking,  the  only  real  penalty  in¬ 
volved  in  a  Unix  system(  )  call  is  the 
time  it  takes  to  activate  the  shell. 

MSDOS  is  another  matter.  Sys- 
tem(  )  actually  reads  a  second  copy 
of  command.com  into  memory  then 
executes  the  command  line  with  this 
second  copy.  Because  command.com 
uses  about  21  K  and  because  the  pro¬ 
gram  being  invoked  takes  up  some 
space,  as  does  mk  itself  (about  1 5K  in 


Dr.  Dobb's  Journal,  August  1985 


101 

637 


my  version),  memory  can  disappear 
pretty  fast.  I’ve  a  bunch  of  memory, 
so  this  limitation  hasn’t  been  a  prob¬ 
lem,  but  it  may  be  for  you. 

There  are  two  solutions.  The  better 
of  these  works  only  if  you  aren’t  go¬ 
ing  to  use  any  functions  internal  to 
command.com  (more  importantly, 
you  can’t  run  a  batch  file  from  inside 
mk).  The  Lattice  compiler  supplies 
several  system(  )-like  procedures.  In 
particular,  the  various  fork(  )  func¬ 
tions  (forkv,  forkl,  forkvp,  forklp) 
will  execute  another  program  direct¬ 
ly,  without  the  additional  invocation 
of  command.com.  These  functions 
are  something  of  a  misnomer  as  they 
don’t  behave  like  the  Unix  fork(  ) 
function;  however,  at  the  cost  of  a  lit¬ 
tle  overhead  (you  have  to  extract  the 
program  name  from  the  action),  you 
can  use  a  fork(  )  function  instead  of  a 
system(  )  call  to  do  the  action.  If  your 
compiler  doesn’t  have  a  fork(  )  or  a 
system(  )  call,  you  can  create  one  us¬ 
ing  the  DOS  version  2  EXEC  function 
(0x4b).  I  can  print  a  system)  )  func¬ 
tion  in  this  column  at  a  later  date  if 
enough  people  need  one,  but  most 


compilers  seem  to  come  with  this 
function. 

The  second  solution  to  the  space 
problem  is  more  involved.  Instead  of 
doing  the  action  from  inside  mk,  the 
program  can  create  a  .bat  file  and 
exit  -you  can  then  execute  the  batch 
file  from  outside  mk.  If  you're  run¬ 
ning  DOS  version  1,  you  have  to  use 
this  procedure  because  the  EXEC 
function  isn’t  supported.  The  prob¬ 
lem  here  is  that  the  time  and  date  as¬ 
sociated  with  a  target  are  dynamical¬ 
ly  updated  by  mk  when  the  target  is 
remade  (on  line  374  of  the  listing). 
This  new  time  is  used  by  previous  in¬ 
vocations  of  the  subroutine  make)  ) 
to  see  if  they  need  to  do  an  action. 
Because  gtime(  ),  the  routine  that 
does  the  updating,  looks  at  the  real 
file,  it  can’t  be  used  for  updating  if 
this  file  hasn’t  actually  been  modi¬ 
fied.  So,  in  addition  to  replacing  the 
system)  )  call  on  line  368  with  a 
fprintf(  )  call,  you  need  to  replace  the 
gtime(  )  call  on  line  374  with  either  a 
representation  of  the  current  time 
and  date  or  with  some  constant  that 
looks  like  a  very  young  time  and  date 


(i.e.,  Ox7fffffffL).  I 

One  final  caveat:  system)  )  pre¬ 
pends  the  argument  /c  to  the  com¬ 
mand  when  it  executes  EXEC.  (The 
process  is  described  in  section  7  of  the 
version  3  DOS  Technical  Reference 
and  in  the  version  2  manual  some¬ 
where.)  Anyway,  if  you’ve  used  the 
SWITCHAR  feature  described  earli¬ 
er,  DOS  will  be  confused  by  the  /,  so 
set  the  switch  character  back  to  /  be¬ 
fore  you  use  mk. 

Erratum:  I’m  embarrassed  to  ad¬ 
mit  that  my  complaints  last  month 
about  the  erase-screen  and  erase-in- 
line  functions  of  ANSI. SYS  were  un¬ 
founded.  I  was  using  puts)  )  to  do  my 
output  and  the  extra  newline  charac¬ 
ter  came  from  puts)  ),  not  from 
ANSI. SYS. 

Notes 

'Unix  is  a  registered  trademark  of 
Bell  Labs. 

2Polymake,  Ps-make,  and  Pmaker 
are  registered  trademarks  of  Poly¬ 
gon,  Phoenix,  and  Unipress  Soft¬ 
ware,  Inc.,  respectively. 


C  Chest  Listing 


(Text  begins  on  page  96) 


1:  #include  <stdio.h> 
2  : 

3:  ^include  <mydos.h'> 
4:  /* - 


5 

#ifd 

e  f 

NEVER 

6 

#def 

i  ne 

DEBUG  1 

/* 

In 

c  1  ii  d 

e  f 

or  deb 

ug 

d  i  ag 

s 

n  m 

a  k  e 

(  ) 

*/ 

7 

#  end 

if 

8 

#def 

i  ne 

MAXLINE 

(80* 

1 

0) 

/* 

Maxi 

mum 

input 

1 

i  ne  1 

ength 

*/ 

9 

#de  f 

i  n  e 

MAX BLOCK 

64 

/* 

Max 

n  u  m 

be  r  of 

1 

i  ne  s 

i  n 

a  n 

act 

i  o 

n 

*/ 

10 

#def 

i  ne 

MAXDEP 

32 

/* 

Max 

n  urn 

her  of 

d 

e  pe  n  d 

an) 

i  e  s 

*/ 

1  1 

#def 

i  n  e 

COMMENT 

'  #  ' 

/* 

Deli 

m  i  t 

s  a  co 

mm 

e  n  t 

*/ 

12 

#def 

i  ne 

MAKEFILE 

"mkf 

i 

le 

t» 

/* 

Name 

0  f 

make  f 

i  1 

e 

*/ 

13 

#def 

i  ne 

OPEN 

Ox  3d 

/* 

Dos 

fun 

c  t  io  n 

ca 

11  to 

0 

)en 

a  f 

i  1 

e 

*/ 

14 

#def 

i  ne 

CLOSE 

Ox  3e 

/* 

Dos 

fun 

c  t  i  o  n 

c  a 

11  to 

c 

ose 

a 

f  i 

1  e 

*/ 

15 

#de  f 

i  ne 

DATETIME 

0x57 

/* 

"  to 

ge 

tors 

e  t 

file 

'  s 

d  a  t 

e  K 

t 

ime 

*/ 

16 

#de  f 

i  ne 

DEFTIMF, 

0x0 

/* 

The 

d  e  f 

a  u  1 1  t 

im 

e  ret 

ur  ned 

by 

gt 

i  me 

when 

17 

* 

does 

n  '  t 

exist 

. 

18 

*/ 

19 

/*-- 

— 

-  _ 

— 

_ 

— 

_ 

_ 

_ 

_ 

_ 

_ 

_ 

_ 

_ 

_ 

20 

* 

iswhite(c 

) 

e  v 

a  1  u 

a  t  e  s 

t  r 

ue  if 

c 

is  wh 

i  t 

2  S  p 

ace 

. 

21 

* 

skipwhite 

(s) 

sk 

i  ps 

the 

c  h 

arac  te 

r 

point 

e  r 

s  p 

ast 

a 

ny 

white 

22 

* 

skipnonwh 

ite  ( 

s 

) 

sk 

i  p  s 

s  p 

a  s  t 

any  n 

o  n 

-wh  i  t 

e  char 

act 

er 

s  . 

23 

*/ 

24 

#def 

i  ne 

i swh i t e ( c 

) 

o 

C  ) 

_ I 

1 

1  1  (c) 

=  ’\t 

’) 

25 

#  d  e  f 

i  n  e 

skipwhite 

(s) 

wh 

i  1  e 

(  is 

wh  i 

te ( *s  ) 

) 

+  +  S 

J 

26 

#def 

i  ne 

skipnonwh 

ite) 

s 

) 

wh 

i  1  e 

(  *  s 

&& 

!  iswh 

i  t 

e  (  *s  ) 

) 

+  +  S 

* 

*/ 


a  file 


space 


103 


Dr.  Dobb' s  Journal,  August  1985 


27 

28 

29 

30 

31 

32 

33 
3  A 

35 

36 

37 


*  The  entire  makefile  is  read  into  memory  before  it's  processed.  It's 

*  stored  in  a  binary  tree  composed  of  the  following  structures: 

*  depends  on  and  do _ this  are  argv— like  arrays  of  pointers  to  character 

*  pointers^  The  arrays  are  null  terminated  so  no  count  is  required. 

*  The  time  field  is  a  32  bit  long  consisting  of  the  date  and  time 

*  fields  retuned  from  a  DOS  0x57  call.  The  date  and  time  are 

*  concatanated  with  the  date  in  the  most  significant  16  bits  and  the 

*  time  in  the  least  significant.  This  way  they  can  be  compared  as 

*  a  single  number. 

*/ 


38 

typedef  struct  tn 

39 

( 

40 

struct  tn 

41 

struct  _tn 

42 

char 

43 

char 

44 

char 

45 

long 

46 

) 

47 

TN0DE  ; 

48 

/♦ - 

♦Inode;  /*  pointer  to  left  sub-tree  */ 
♦  mode;  /♦  pointer  to  right  sub-tree  */ 

*being_made;  /*  name  of  file  being  made  */ 
♦*depends_on ; /*  names  of  dependant  files  */ 
♦♦do_this;  /*  Actions  to  be  done  to  make  file  ♦/ 
time;  /*  time  &  date  last  modified  */ 


♦/ 


49 

static 

TN0DE 

♦Root  =  0  ; 

/* 

Root  of 

file-name  tree 

*/ 

50 

static 

FILE 

♦Makefile  ; 

/♦ 

Pointer 

to  opened  makefile 

*/ 

51 

static 

in  t 

Inputline  =  1  ; 

/* 

current 

input  line  number 

*/ 

52 

static 

char 

♦First  =  ""  ; 

/* 

Default 

file  to  make 

*/ 

53 

extern 

char 

♦malloc ( )  ; 

/* 

From  standard  library 

*/ 

54 

extern 

char 

**getblock(  )  ; 

/* 

Declared 

in  this  module 

*/ 

55 

extern 

char 

*getline( ) ; 

/* 

ditto 

*/ 

56 

extern 

TN0DE 

*  f i nd (  )  ; 

/* 

ditto 

*/ 

57 

/* - 

— 

— 

— 

— */ 

58 

char 

*gmem( 

numbytes  ) 

59 

( 

60 

/* 

Get  numbytes  from  malloc. 

Print  an 

error  message  and 

61 

* 

abort  if  malloc  fails, 

otherwise  return  a  pointer  to 

62 

* 

the  memory. 

63 

*/ 

64 

extern 

char  *calloc (  )  ; 

65 

char 

*P  ; 

66: 

67: 


if(  !(  p  =  cal 1 oc ( 1 , numby t es  )  )) 
err("0ut  of  memory"); 


68 :  return  p  ; 

69:  ) 


70:  /♦ 


*/ 


7 1 :  char 
72 :  char 
73:  { 

74: 

75: 

76: 

77: 

78: 

79: 

80: 

81  : 

82: 

83: 


♦*stov(str,  maxvect  ) 

*s  t  r  ; 

/*  "Str"  is  a  string  of  words  seperated  from  each  other  by 

*  white  space.  Stov  returns  an  argv-liffe  array  of  pointers 

*  to  character  pointers,  one  to  each  word  in  the  original 

*  string.  The  white-space  in  the  original  string  is  replaced 

*  with  nulls.  The  array  of  pointers  is  null-terminated. 

*  "Maxvect"  is  the  number  of  vectors  in  the  returned 

*  array.  The  program  is  aborted  if  it  can't  get  memory. 

*/ 

char  **vect,  **vp; 


84  : 


vp  =  vect  =  (char  *♦)  gmem(  (maxvect  +  1)  *  sizeof(str)  ); 


104 

sna 


C  Chest  Listing 


(Listing  continued,  text  begins  on  page  96) 


85 

86 

87 

88 

89 

90 

91 

92 


while(  *str  &&  — maxvect  >=  0  ) 

{ 


ski pwhi t e ( s t r  )  ; 
*vp++  =  str  ; 
skipnonwhite(str)  ; 
if(  *str  ) 


*str++  =  0; 


93 :  *vp  =  0 ; 

94:  return^  vert-  1* 

95:  ) 


96:  /* - */ 


97 

long 

gtime( 

file  ) 

98 

char 

*  f  i  1  e 

» 

99 

{ 

100 

/* 

Return  the  time  and  date 

for  f ile . 

101 

* 

102 

* 

The  DOS  time  and  date  are  concatanated  to 

form  one 

103 

* 

large  number.  Note  that 

the  high  bit 

of  this  number 

104 

* 

will  be  set  to  1  for  all 

dates  after 

2043, 

which  will 

105 

* 

cause  the  date  comparisions  done  in 

make  (  ) 

to  not  work. 

106 

* 

THIS  ROUTINE  IS  NOT  PORTABLE  (because  it  assumes  a  32 

107 

* 

bit  long  )  . 

108 

*/ 

109 

static 

REGS  regs; 

110 

short 

handle  =  0; 

/*  Place  to 

remember  file  handle 

* 

111 

long 

time ; 

112 

gregs ( 

&regs  ) ; 

113 

regs  .  h 

.ah  =  OPEN  ; 

/*  Open  the 

file 

*/ 

114 

r  eg  s  .  h 

. a  1  =  0; 

/*  for 

read 

*/ 

115 

regs  .  x 

.  dx  =  ( short)  file  ; 

116 

i f (  dos(&regs)  &  CARRY  ) 

117 

( 

118 

/*  File  doesn't  exist.  Return  a  huge 

date 

&  time 

*/ 

119 

return  DEFTIME; 

120 

) 

121 

else 

122 

( 

123 

handle  =  regs. x. ax; 

124 

regs.x.bx  =  handle; 

/*  Put  file 

handle 

in  BX 

*/ 

125 

regs. h. ah  =  DATETIME; 

/*  Get  or  set  date 

and  time 

*/ 

126 

regs.h.al  =  0; 

/*  Get  the  date  and  time 

*/ 

127 

if(  dos(  Sregs  )  &  CARRY 

) 

128 

: 

err("D0S  returned  error  from 

date/ time  request") 

» 

129 

: 

time  =  ((long)(  regs.x.d 

x  )  <<  16) 

1 

130 

: 

( ( long) (  regs . x  .  cx 

)  &  OxffffL) 

’ 

131 

: 

regs. h. ah  =  CLOSE  ; 

/*  Close  the 

file 

handle 

*  / 

132 

: 

regs.x.bx  =  handle  ; 

133 

: 

i f (  dos(  Xregs  )  &  CARRY 

) 

134 

: 

err("D0S  returned  error  from 

file 

close  request" 

); 

135 

return  time; 

136 

) 

137 

j 

138 

/* — 

*/ 

106 


Dr.  Dobb’s  Journal.  August  1985 


139:  TNODE  *makenode() 

140:  { 

141:  /*  Create  a  TNODE,  filling  it  from  the  makefile 

142;  *  and  return  a  pointer  to  it.  Return  NULL  if  there  are  no  more 

143:  *  objects  in  the  makefile. 

144:  */ 

145:  char  *line,  *lp; 

146:  TNODE  *  n  o  d  e  p  ; 

147:  unsionoH  h  o  ►  ~  - 

148:  /*  First,  skip  past  any  blank  lines  or  comment  lines. 

149:  *  Return  NULL  if  we  reach  end  of  file. 

150:  */ 

151:  do { 

152:  if(  (line  =  ge tl i ne (MAXLINE , Make f ile ) )  ==  NULL  ) 

153:  return(  NULL  ); 

154:  }  while  (  *line  ==  0  | |  *line  ==  COMMENT  ); 

155:  /*  A  this  point  we've  gotten  what  should  be  the  dependancy 

156:  *  line.  Position  lp  to  point  at  the  colon. 

157:  */ 

158:  for(  Ip  =  line;  *lp  &&  *lp  !=  ;  lp++  ) 

159:  ; 

160:  /*  If  we  find  the  colon  postion,  lp  to  point  at  the  first 

161:  *  non-white  character  following  the  colon. 

162:  */ 

163:  if (  *lp  !=  ' :  '  ) 

164:  err(  "missing  );  /*  This  will  abort  the  program  */ 

165:  else 

166:  for(  *lp++  =  0;  iswhite(*lp)  ;  lp++  ) 

167:  ; 

168:  /* 

169:  *  Allocate  and  initialize  the  TNODE; 

170:  */ 

171:  nodep  =  (TNODE  *)  gmem(  s i zeo f ( TNODE )  ); 

172:  nodep->being_made  =  line  ; 

173:  nodep->time  =  gtime(  line  ); 

174:  n od e p-> d e pe n d s_on  =  stov(  lp,  MAXDEP  ); 

175:  nod ep->do_this  =  getblock(  Makefile  ); 

176:  return(  nodep  ); 

177:  ) 

178:  /* - */ 

179:  d e pend a n c i e s ( ) 

180 :  ( 

181:  / *  Manufacture  the  binary  tree  of  objects  to  make.  First 

182:  *  is  a  pointer  to  the  first  target  file  listed  in  the 

183:  *  makefile  (ie.  the  one  to  make  if  one  isn't  explicitly 

184:  *  given  on  the  command  line.  Root  is  the  tree's  root  pointer. 

185:  */ 

186:  TNODE  *node; 

187:  if(  node  =  makenode()  ) 

188:  ( 

189:  First  =  node->being_made  ; 

190:  if(  !tree(node,  SRoot )  ) 

191:  err("Can't  insert  first  nod  e  into  tree  !  !  ! \n " )  ; 


Dr.  Dobb's  Journal,  August  1985 


107 

641 


C  Chest  Listing  (Listing  continued,  text  begins  on  page  96) 


192: 

while(  node  =  makenode() 

) 

193: 

if(  !  t  r  e  e (  node. 

&Root  )  ) 

194: 

f  ree (  node  ) ; 

195: 

return  1 ; 

196: 

} 

197: 

return  0; 

198:  ) 

199: 

/* - 

* 

200 

char 

*getline(  maxline,  fp  ) 

201 

FILE 

*  f  p  ; 

202 

{ 

203 

/*  Get  a  line  from  the  stream  pointed  to  by 

f  p  . 

204 

*  "Maxline"  is  the  maximum  input  line  size 

(including  the 

205 

*  terminating  null,  A  \  at  the  end  of  line 

i  s 

206 

*  recognized  as  a  line  continuation,  (the 

1  ines 

207 

*  are  conca tanated ) .  Buffer  space  is  gotten  from  malloc. 

208 

*  If  a  line  is  longer  than  maxline  it  is  truncated 

(  ie  . 

209 

*  all  characters  from  the  maxlineth  until 

a  \ n  or 

EOF  is 

210 

*  encountered  are  discarded. 

211 

* 

212 

*  Returns:  NULL  on  a  malloc  failure  or  end 

of  file 

. 

213 

*  A  pointer  to  the  malloced  buffer  on  success. 

214 

*/ 

215 

static  char  *buf  ; 

216 

register  char  *bp  ; 

217 

register  int  c,  lastc; 

218 

/*  Two  buffers  are  used.  Here,  we  are  getting  a 

worst-case  buffer 

219 

*  that  will  hold  the  longest  possible  line.  Later  on  we 

*  the  string  into  a  buffer  that's  the  correct  size. 

'll  copy 

220 

221 

*/ 

222 

if(  !(bp  =  buf  =  malloc(maxline) )  ) 

223 

return  NULL; 

224 

while(  1  ) 

225 

( 

226 

/*  Get  the  line  from  fp.  Terminate  after 

ma  x 1 i ne 

227 

*  characters  and  ignore  \n  following  a 

\. 

228 

*/ 

229 

Inputline++;  /*  Update  input 

line  number  * 

230 

for(  lastc=0;  (c  =  fgetc(fp))  !=  EOF  && 

c ! = ' \n ' ; 

lastc  = 

231 

if(  — maxline  >  0  ) 

232 

*bp++  =  c; 

233 

. 

if(  !(  c  ==  '  \n '  &&  lastc  ==  'W  )  ) 

234 

t 

break ; 

235 

else  if(  maxline  >  0  )  /*  erase 

the  \  * 

/ 

236 

—  bp  ; 

237 

} 

238 

*  b  p  =  0  ; 

239 

if(  (c  ==  EOF  &&  bp  ==  buf)  ||  ! ( b p  =  ma 1 loc ( ( bp-buf )  + 1 ) )  ) 

240 

( 

241 

/*  If  EOF  was  the  first  character  on 

the  line 

o  r 

242 

*  malloc  fails  when  we  try  to  get  a 

buffer  , 

quit  . 

243 

*/ 

■J08  Dr.  Dobb’s  Journal,  August  1985 

GAO 


244 

245 

246 


f  ree ( buf  )  ; 
return  (  NULL  )  ; 


247 

248 

st  rc  py 

( 

bp  , 

buf  )  ; 

/* 

/* 

249 

free 

( 

buf 

); 

/* 

250 

251 

return 

) 

( 

bp 

); 

/* 

Copy  the  worst-case  buffer  to  the  one  */ 
that  is  the  correct  size  and  ...  */ 
free  the  original,  worst-case  buffer,  */ 
returning  a  pointer  to  the  copy.  */ 


252:  /* 


*/ 


253 

254 

255 

256 

257 

258 

259 

260 

261  : 
262  : 

263 

264 

265 

266: 

267 

268 

269 

270 

271 


char 

FILE 

{ 


**ge  t  b 1 oc  k (  fp  ) 

*  f  p ; 

/*  Get  a  block  from  standard  input.  A  block  is  a  sequences  of 

*  lines  terminated  by  a  blank  line.  The  block  is  returned  as 

*  an  array  of  pointers  to  strings.  At  most  MAXBLOCK  lines  can 

*  be  in  a  block.  Leading  white  space  is  stripped. 

*/ 


char 
i  nt 

do  ( 


*p,  *  1 i nes [ MAX BLOCK ] ,  **blockv  =  lines  ; 
blockc  =  0; 


if(  !(  p  =  getline(MAX LINE, Makefile)  )) 
break; 


skipwhite(p)  ; 

i f (  ++blockc  <=  MAXBLOCK  ) 
*blockv++  =  p; 

else 

err("action  too  long  (max 


%d  lines)",  MAXBLOCK); 


while(  *  p  ); 


272: 

273: 

274: 

275: 

276 

277 

278 


279: 
280:  ) 


/* 

* 


* 

*/ 


Copy  the  blockv  array  into  a  safe  place.  Since  the  array 
returned  by  getblock  is  NULL  terminated,  we  need  to 
increment  blockc  first. 


blockv  =  (char  **)  gmem(  (blockc  +  1)  *  si zeof ( blockv [ 0 ] )  ); 
movmem(  lines,  blockv,  blockc  *  si zeof ( block v [ 0 ] )  ); 
blockv [ blockc ]  =  NULL  ; 

return  blockv ; 


281:  /*- 


-*/ 


282  : 
283: 
284: 
285: 
2  86: 


err(  msg,  param  ) 
char  *msg; 

( 

/*  Print  the  error  message  and  exit  the  program. 

*/ 


287 

288 

289 

290 


f p r i n t f ( s t d e r r , "Mk  (%s  line  % d):  ",  MAKEFILE,  Inputline  ); 
f p r i nt f ( s t der r  ,  msg,  param  ); 
ex i t ( 1 ) ; 


291 :  ser r ( 
292 :  char 
293:  { 

294  : 

295: 

296: 


msg,  param  ) 

*msg  ,  *param; 

/*  same  as  err()  except  the  parameter  is  a  string  pointer 

*  instead  of  an  int. 

*/ 


Dr.  Dobb's  Journal,  August  1985 


109 

643 


C  Chest  Listing  (Listing  continued,  text  begins  on  page  96) 


f printf ( stder r , "Mk  (%s  line  %d):  ",  MAKEFILE,  Inputline  ); 
f printf ( stderr ,  rasg,  param  ); 
e  x  i  t  (  1  )  ; 


301:  /*- 


302 :  make ( what ) 
303 :  char  *what ; 


Actually  do  the  make.  The  dependancy  tree  is  descended 
recursively  and  if  required,  the  dependencies  are 
adjusted.  Return  1  if  anything  was  done,  0  otherwise 


TNODE  *snode; 

TNODE  *dnode; 

int  doaction 

static  char  *zero 
char  ** 1 i ne  v 


/*  Source  file  node  pointer  */ 
/*  Dependant  file  node  pointer  */ 
/*  If  true  do  the  action  */ 


(char  *  )  0  ; 
&zero  ; 


314:  (f  if  def  DEBUG 

315:  static  int  recurlev  =  0;  /*  Recursion  level  */ 

316:  printf("make  (lev  %d):  making  <%s>\n",  recurlev,  what  ); 

317:  #end i f 

318:  if(  !  (snode  =  find(what,  Root))  ) 

319:  serr("Don't  know  how  to  make  <%s>\n",  what  ); 

320:  if(  !*(linev=  snode->depends_on ) )  /*  If  no  dependancys  */ 

321:  doaction++;  /*  always  do  the  action.  */ 


for(  ;  *linev  ;  linev++  ) 


/*  Process  each  dependancy  */ 


fifdef  DEBUG 


#end i f 


fifdef  DEBUG 


#  e  n  d  i  f 


(fifdef  DEBUG 


(fend  i  f 


(fifdef  DEBUG 


fend i f 


recur  1 e v++ ; 
make(  *  1 i ne  v  ); 
recurlev — ; 

if(  !(dnode  =  find(*linev,  Root))  ) 

serr("Don't  know  how  to  make  <%s>\n"  ,  *linev  ); 

printf("make  (lev  % d):  source  file  ",  recurlev); 
ptime(  what,  snode->time  ); 

printf("make  (lev  %d):  dependant  file  ",  recurlev); 
ptime(  *linev,  dnode->time  ); 

if(  snode->time  <=  dnode->time  ) 

( 

/*  If  source  node  is  older  than  (time  is  less  than) 

*  dependant  node,  do  something.  If  the  times  are 

*  equal,  assume  that  neither  file  exists  but  that 

*  the  action  will  create  them,  and  do  the  action. 
*/ 

printf("make  (lev  %d):  %s  older  than  %s\n", 
recurlev,  what,  *linev  ); 

doaction++ ; 


R  Ad 


Dr.  Dobb’s  Journal.  August  1985 


# i f d ef  DEBUG 


352 

353 

354 

355 

356 

357 

358 

359 

360 

361 

362 

363 

364 

365 

366 

367 

368 

369 

370 


(fend  i  f 

) 


else 

printf("make  (lev  % d):  %s  younger  than  %s\n", 

recurlev,  what,  *linev  ); 


if (  doaction  ) 


# i  f  de f  DEBUG 


lend i f 


#if ndef  DEBUG 


#end  i  f 


printf ( "make  (lev  %d):  doing  action:\n", 

recurlev,  *linev,  what); 

for(  linev  =  snode->do_this ;  *linev;  linev++  ) 

printf("%s\n",  *linev);  /*  Echo  action  to  screen  * / 

if(  s y s t em( *line v )  ) 

ser r ( "Can ' t  process  <%s>\n",  *linev  ); 


371 

372 

373 


/* 

* 

*/ 


Change  the  source  file's  time  to  reflect 
any  modification. 


3  74 

375 

376 


snode->time  =  gtime(  snode->being_made  ) ; 


377:  #i fdef  DEBUG 

378:  printf ( "make  (lev  % d):  exiting\n",  recurlev  ); 

379 :  #endif 

380:  return  doaction; 

381  :  ) 


382:  /* - 

383:  *  Tree  routines: 

384:  */ 

385:  TNODE  *find(  key,  root  ) 

386:  char  *key; 

387:  TNODE  *root; 

388:  { 

389:  /*  If  key  is  in  the  tree  pointed  to  by  root,  return  a  pointer 

390:  *  to  it,  else  return  0. 

391:  *' 

392:  register  int  notequal  ; 

393:  register  TNODE  *rval  ; 

394:  if(  (root) 

395  :  return  0 ; 

396:  if(  ((notequal  =  s t r cmp ( r oot->bei ng_mad e , key ) )  ) 

397:  return(  root  ); 

398:  return(  find(  key,  (notequal  >  0)  ?  root->lnode  :  root->rnode)  ); 

399:  } 

400:  /* - */ 

401:  tree(  node,  rootp  ) 

402:  TNODE  *node,  **rootp  ; 

403:  ( 

404:  /*  If  node's  key  is  in  the  tree  pointed  to  by  rootp,  return  0 

405:  *  else  put  it  into  the  tree  and  return  1. 

406:  */ 

407:  register  int  notequal  ; 

408:  register  TNODE  *rval  ; 


Dr.  Dobb’s  Journal,  August  1985 


113 


C  Chest  Listing 


(Listing  continued,  text  begins  on  page  96) 


409:  if(  *rootp  ==  NULL  ) 

410:  { 

411:  ♦rootp  =  node; 

412:  returnl; 

413:  ) 

414:  if(  ! (notequal  =  strcmp(  ( *r oo t p ) ->bei ng_mad e ,  node->being_raade ) ) ) 

415:  returnO; 

416:  return(  tree(  node,  notequal  >  0  ?  & ( * r oo t p ) - >  1  nod e 

417:  :  & ( *r oo t p ) ->r nod e )  ); 

418:  ) 

419:  /* - */ 

420:  main(  argc,  argv  ) 

421 :  char  **argv  ; 

422:  ( 

423:  /*  A  stupid  version  of  the  unix  make  utility. 

424:  * 

425:  * 

426:  */ 

427:  i f (  ! (Makefile  =  f open (MAKEFILE ,  "r"))  ) 

428:  err(" can't  open  %s\n",  MAKEFILE  ); 

429:  if(  ! dependancies( )  ) 

430:  err("Nothing  to  make"); 

431:  else 

432:  make(  argc  >  1  ?  argv[l]  :  First  ); 

433:  ) 

434:  #ifdef  DEBUG 

435:  /* - 

436:  *  Msc.  Debugging  routines 

437:  */ 


438:  ptime(  file,  t  ) 


439:  char  * f i le  ; 

440 :  long  t ; 

441 :  ( 

442:  /*- Print  out  the  time  and  date  field  of  a  TN0DE  as 

443:  *  "mm-dd-yy  hh:mm:ss" 

444:  *  File  is  the  file  name. 

445:  */ 

446:  int  date,  time; 

447  :  date  =  (t  >•>  16)  &  OxffffL  ; 

448:  time  =  t  &  OxffffL  ; 

449:  printf("%s:  ",  file  ); 

450:  printf ( "%02d-%02d-%02d ,  ",  (date  >>  5  )  &  OxOf,  date  &  Oxlf, 

451;  80  +  ((date  >>  9)  &  0x7f)  ); 

452:  printf("%02d:%02d:%02d,  ",  (time  >>  11)  &  Oxlf,  (time  >>  5)  &  0x3f, 

453:  (time  <<  1)  &  0x3e  ); 

454:  printf ("\n") ; 

455:  ) 

456:  /* - */ 


457:  pnode(  node  ) 

458:  TNODE  *node; 

459:  ( 

460:  /*  Print  out  the  tree  node  pointed  to  by  "node" 

461:  */ 


114 


Dr.  Dobb's  Journal,  August  1985 


462 

463 

464 

465 

466 

467 

468 

469 

470 

471 

472 


char  **linev ; 

pr  1  n  t  f  (  "+ - \n"  )  ; 

printf("|  node  at  0xlx\n",  node); 

pr int  f (  "  + - \n"  )  ; 

prlntf("  Inode  =  0x%x,  mode  =  Ox%x\n",node->lnode,node->rnode); 
printf("  time  =0x%lx="  ,node->time  ); 

ptime(  node->time  ); 

printf("  target  =  <%s>\n"  ,  node->being_made  ); 

printf("  dependancys:\n"  ); 

for(  linev  =  node->depends_on ;  *linev;  pr int f ( " | \ t%s\n" ,  *linev++) ) 


473:  printf("|  actions:\n"  ); 

474:  for(  linev  =  node->do_this ;  *linev;.  printf ( "  | \t%s\n" ,  *linev++)) 

475:  ; 

476:  printf("+ - \n"); 

477:  ) 


478:  /* 


*/ 


479 :  t  rav ( 

root  ) 

480:  TNODE 

♦root ; 

481  :  { 

482: 

/* 

483: 

* 

484: 

*/ 

Do  an  in-order  traversal  of  the  tree,  printing  the 
node's  contents  as  you  go. 


485: 

486: 


if (  root  ==  NULL  ) 
return; 


487: 

488: 

489: 

490:  } 

491 :  #endif 


trav(  root->lnode  ); 
pnode(  root  )  ; 
trav(  root->rnode  ); 


End  Listing 


Dr.  Dobb's  Journal.  August  1985 


115 

647 


16-BIT  SOFTWARE  TOOLBOX 


by  Ray  Duncan 

Bug  Report:  TEE  Filter 

Dr.  Fred  Sinai  of  Hampton,  Virginia, 
writes:  “There  is  a  slight  bug  in  your 
listing  of  TEE  in  the  April  1985  ‘16- 
Bit  Software  Toolbox’  column.  Actu¬ 
ally  the  bug  is  in  DOS,  but  that’s  life. 
If  the  file  does  not  end  with  a  CR/LF, 
we  get  a  ‘disk  is  full’  message.  DOS  is 
forcing  CR/LF  to  the  console  and 
confusing  the  character  count.  This 
problem  may  be  covered  up  by  elimi¬ 
nating  the  pair  of  lines 

cmp  ax,nehar 

jne  tee7 

before  the  label  tee4.  Of  course  this 
reopens  the  disk  full  problem,  when 
redirecting  the  standard  output.  I 
could  not  find  a  truly  satisfactory  fix.” 

Some  Musings  about  DRI 

Most  of  you  have  probably  seen  the 
recent  splashy  full-page  ads  for  Digi¬ 
tal  Research’s  GEM  that  are  appear¬ 
ing  in  nearly  every  magazine  except 
Sports  Illustrated.  These  ads  lead 
you  to  believe  that  you  can  get  a  16- 
color  equivalent  of  Macintosh  Mac¬ 
Paint  for  the  IBM  PC  by  just  picking 
up  GEM  and  GEM-Paint  for  a  few 
pence  at  your  corner  computer  store. 

Beware:  The  fine  print  in  the  ad 
says  “appropriate  graphics  hardware 
required.”  The  appropriate  hardware 
turns  out  to  be  the  IBM  Enhanced 
Graphics  Adaptor,  which  will  set  you 
back  about  $1200  for  the  required 
controller  board,  memory  expansion, 
and  higher  resolution  RGB  monitor. 
If  you  use  GEM  on  an  “ordinary” 
IBM  graphics  adaptor,  the  icons,  win¬ 
dows,  and  all  the  rest  are  in  vibrant 
black  and  white. 

Speaking  of  Digital  Research,  look 
before  you  leap  into  spending  any 
money  on  the  product  that  DRI  is  ad¬ 
vertising  (with  tongue  in  cheek,  I’m 
sure)  as  “Concurrent  PCDOS.”  It’s 


concurrent,  I  guess,  but  it  certainly 
doesn’t  resemble  current  versions  of 
PCDOS.  “Concurrent  PCDOS”  does 
not  support  the  hierarchical  file  struc¬ 
ture  or  any  of  the  Unix-like  executive 
services  that  are  present  in  MSDOS  or 
PCDOS  versions  2  and  3.  In  other 
words,  “Concurrent  PCDOS”  sup¬ 
ports  only  the  functionality  of 
MSDOS  or  PCDOS  version  1,  which 
was  more  or  less  a  clone  of  CP/M. 

Thus,  almost  any  of  the  more  pow¬ 
erful  MSDOS  applications  you  are 
likely  to  buy  will  not  run  properly  un¬ 
der  “Concurrent  PCDOS.”  Further¬ 
more,  a  recent  review  in  PC  Tech 
Journal  demonstrated  that  a  set  of 
programs  run  concurrently  under 
“Concurrent  PCDOS”  will  actually 
take  longer  to  complete  than  if  you 
simply  run  them  consecutively  under 
normal  MSDOS.  You  can  draw  your 
own  conclusions,  but  Digital  Re¬ 
search’s  hopes  of  recapturing  its  old 
dominance  of  the  personal  computer 
operating  system  market  seem  more 
like  delusions  to  me. 

More  on  Square  Roots 

Tom  Prince,  of  Marblehead,  Massa¬ 
chusetts,  writes: 

“I  was  interested  in  your  discus¬ 
sion  of  square  rooting  in  the  May 
1985  column.  A  similar  algorithm  in 
8088  code  appeared  in  Dr.  Dobb’s 
over  a  year  ago,  encouraging  me  to 
add  a  floating  point  square  root  to  my 
collection  of  Z80  code.  Since  the  Z80 
has  enough  registers  to  perform  32- 
bit  calculations,  it  can  do  the  square 
root  much  faster  than  the  8080  or 
even  8088  can  do  a  floating  point  di¬ 
vide.  Imagine  my  surprise  to  see  an 
integer  square  root  that  takes  ten 
times  as  long  as  a  divide,  even  when 
coded  in  assembler!  Even  a  conver¬ 
sion  to  floating  point  to  take  the  root 
ought  to  be  faster. 

“So  I  wrote  up  the  following  code 


116 


for  a  well-known  ‘32-bit’  processor.  It 
turns  out  to  be  slower  than 
sqrt(float(  ))  but  certainly  doesn’t 
take  ten  times  as  long  as  an  integer 
divide.  Of  course  it  takes  longer  than 
the  three  integer  divides  that  are  in¬ 
cluded  and  would  run  faster  if  coded 
in  assembler.  Would  it  be  too  much  to 
ask  to  provide  high-level  language 
versions  of  routines  like  these  that 
work  perfectly  well,  if  a  bit  slower?” 

Mr.  Prince’s  code  can  be  found  in 
Listing  One  (page  122). 

Mac  Fan  Strikes  Back 

Mr.  S.  Allen,  of  Ojai,  California, 
writes: 

“I  am,  and  have  been  for  quite  a 
while,  an  ardent  admirer  of  DDJ.  I 
have  been  hard-pressed  to  find  a 
magazine  of  such  technical  excel¬ 
lence  aimed  at  the  more  advanced 
computer  user. 

“1  do,  however,  have  a  GRIPE!  I 
was  quite  excited  when  you  began  the 
16-Bit  Toolbox.  I  expected  to  find 
droves  of  information  on  the  latest 
hardware  innovations  and  the  sup¬ 
port  software  to  go  with  them.  You 
can  imagine  my  chagrin  when  I  dis¬ 
covered  a  thinly  disguised  ‘The  IBM 
PC  is  Wonderful’  column.  To  call  the 
IBM  PC,  with  its  8088,  a  16-bit  ma¬ 
chine  is  pushing  things  a  little,  and 
then  to  have  the  writer  launch  irra¬ 
tional  tirades  against  a  true  16-bit 
machine  (the  Mac)  lit  my  Bulls — t 
High  Level  Light. 

“Please,  be  honest.  If  you  are  going 
to  have  an  IBM  PC  column,  call  it 
that.  If  you  are  going  to  have  a  16-bit 
column,  let’s  have  it  be  about  16-bit 
machines.  From  reading  the  present 
16-Bit  Toolbox,  I  must  assume  that 
the  writer  is  either  in  IBM’s  or  Intel’s 
pocket.  Your  magazine  has  a  long¬ 
standing  reputation  for  impartiality. 
I  implore  you,  do  not  destroy  it.” 

Well,  I  suspect  that  the  flip-flop 


C  AO 


Dr.  Dobb’s  Journal,  August  1985 


controlling  Mr.  Allen’s  Bulls — t 
High  Level  Light  is  in  need  of  re¬ 
placement.  Let  me  first  assure  DDJ 
loyal  readers  that  I  am  not  in  either 
IBM  or  Intel’s  pocket.  If  I  were,  I’d 
ask  them  for  a  suitably  monstrous 
amount  of  money,  retire,  and  pass 
this  column  on  to  other  younger  and 
more  energetic  souls. 

People  who  actually  read  this  col¬ 
umn  know  that  it  is  heavily  devoted 
to  reports  of  bugs  in  Microsoft,  IBM, 
or  Intel  products,  so  that  program¬ 
mers  can  work  around  them.  These 
bug  reports  hardly  constitute  adver¬ 
tisements  for  the  companies  involved 
(except,  I  guess,  for  those  who  have 
the  philosophy  that  any  publicity  is 
good  publicity).  I  defy  anyone  to  look 
back  at  the  previous  columns  and 
anywhere  find  the  message  “The 
IBM  PC  is  Wonderful.” 

As  for  our  emphasis  on  the  Intel 
8086  family  of  microprocessors  and 
the  MSDOS  operating  system,  these 
remain  the  dominant  force  in  the  per¬ 
sonal  computer  world  and  seem  likely 
to  remain  so  for  the  forseeable  future; 
consequently,  they  are  of  primary  in¬ 
terest  to  those  of  us  who  write  pro¬ 
grams  for  a  living.  Whether  the  8088 
is  really  a  1 6-bit  processor  or  not  is  a 
moot  point;  it  runs  the  same  machine 
code  as  the  8086,  it  acts  like  an  8086, 
and  it  is  everywhere.  Quibbling  over 
the  bus  width  is  a  waste  of  our  time. 

We  certainly  are  interested  in  ex¬ 
tending  our  coverage  of  other  16-bit 
processors,  and  as  people  who  actual¬ 
ly  read  this  column  will  also  know,  we 
have  given  considerable  coverage  to 
the  68000  and  Macintosh  over  the 
last  few  months  and  will  continue  to 
do  so  as  long  as  our  readers  indicate 
an  interest  in  it. 

Regarding  68000 
Assemblers 

Mr.  Michael  Aichlmayr,  of  Tacoma, 
Washington,  writes: 

“In  regards  to  Mr.  Howell’s  re¬ 
marks  in  your  May  1985  column,  I 
would  like  to  add  myself  to  the  list  of 
people  who  feel  it  is  truly  possible  to 
build  an  efficient  assembler  for  the 
68000 — one  that  would  run  in  a  rea¬ 
sonable  amount  of  memory  and  exe¬ 
cute  in  a  productive  amount  of  time. 

“I  don’t  feel  that  the  68(x)xx  fam¬ 


ily  is  nearly  as  complicated  as  Mr. 
Howell  implies.  I  think  elegant  sim¬ 
plicity  is  a  more  accurate  statement. 
Any  good  programmer  could  make 
great  use  of  generic  addressing 
modes  and  instructions.  I  think  I 
would  hazard  to  go  so  far  as  to  say 
that  I  believe  it  could  be  done  in  C 
and  run  in  64K  on  a  68(x)xx  proces¬ 
sor  (the  horrible  overhead  of  the  [In¬ 
tel]  80(x)xx  would  make  it  a  real 
challenge).  I  have  a  6809  assembler 
written  in  C  that  runs  easily  in  64K  of 
memory  on  every  machine  for  which 
it  is  compiled. 

“It’s  very  sad  to  realize  that  the 
abundance  and  low  cost  of  memory 
have  given  programmers  an  excuse  to 
be  gross  and  inefficient.  I  have  seen 
code  that  could  run  in  my  HP  29C 
calculator  take  up  more  than  64K  of 
memory  and  ’require’  a  128K  IBM 
PC  or  compatible  to  run.  It  makes  me 
think  that  if  a  machine  came  stan¬ 
dard  with  a  megabyte  of  RAM,  some¬ 
one  would  come  up  with  a  desk  calcu¬ 
lator  that  would  hog  the  machine. 

“Mr.  Howell  remarks  that  he  has  a 
6809  system  that  he  likes  better  than 
his  IBM  compatible.  I  direct  his  at¬ 
tention  to  the  TSC  (Technical  Sys¬ 
tems  Consultants)  68000  macro  as¬ 
sembler.  I  have  been  able  to  coax  the 
assembler  to  run  in  32K  on  a  6809 
system  running  under  the  FLEX  oper¬ 
ating  system.  I  realize  it  does  not  pro¬ 
duce  relocatable  object  modules; 
however,  that  piece  of  code  would  be 
fairly  small  given  the  architecture. 

“Having  a  6809  machine  (which  I 
built),  a  Mac  (which  I  tolerate),  and 
a  DG  One  IBM  compatible  (which  I 
use),  I  think  I  can  safely  assume  that 
I  have  some  knowledge  of  hardware 
and  software.  I  find  it  very  aggravat¬ 
ing  that  IBM  chose  the  80(x)xx  archi¬ 
tecture  over  that  of  the  68(x)xx,  but  I 
have  never  seen  a  processor  more 
crippled  than  that  in  the  Mac. 

“I  eagerly  await  the  arrival  of  the 
Atari  ST,  which  I  have  had  the  op¬ 
portunity  to  play  with  for  a  while.  If 
all  appearances  are  correct,  this  is  a 
good  use  of  a  68000.  The  machine 
runs  at  8  MHz,  has  32K  [of  RAM 
devoted  to]  memory-mapped  color 
graphics,  and  is  said  to  come  stan¬ 
dard  with  5 1 2K  of  RAM  for  $699.00! 
I’m  not  sure  what  the  GEM  OS  is  go¬ 


ing  to  do  to  the  thing  since  I  have  lit¬ 
tle  regard  for  DRI,  but  it  doesn’t  look 
bad  so  far.” 

More  Mac  Feedback 

Kirk  Kerekes,  of  Tandata  Inc.,  in 
Tulsa,  Oklahoma,  submitted  a 
thoughtful  and  interesting  letter: 

“As  circumstances  seem  to  have 
started  a  debate  in  your  column  re  the 
Mac  as  a  development  environment,  I 
felt  it  appropriate  to  provide  some 
facts  to  fuel  the  debate  and  perhaps 
redirect  it  to  more  substantive  issues. 

“FACT:  There  is  no  assembler  that 
I  know  of  for  the  Mac  that  requires 
the  use  of  two  (or  more!)  Macs.  This 
myth  has  been  floating  around  for 
quite  a  while  and  was  recently  men¬ 
tioned  in  your  column.  This  myth 
doubtless  arises  from  the  fact  that 
there  are  two  Apple-issue  debuggers 
that  use  a  second  computer  as  a  con¬ 
trol  device  to  allow  undisturbed 
screen  displays  on  the  system  being 
debugged.  As  there  are  around  half  a 
dozen  debuggers  that  do  not  require  a 
second  unit,  and  at  least  one  of  the 
two-machine  debuggers  will  use  any 
reasonable  terminal  as  a  remote  con¬ 
troller,  I  see  no  reason  to  regard  this 
situation  as  anything  but  a  blessing 
for  the  developer.  The  debuggers  are 
available  as  part  of  the  Inside  Mac 

supplement  program  and  are  distrib¬ 
uted  as  a  group. 

“If  you  are  doing  development  on 
the  Mac  and  are  a  fan  of  debuggers, 
then  the  $100  price  tag  of  the  supple¬ 
ment  could  be  justified  for  them 
alone.  (I’ve  gotten  at  least  $100 
worth  of  micro- floppies  through  my 
supplement  subscription,  just  valued 
as  media!) 

“FACT:  There  are  numerous  com¬ 
plete,  stable  high-level  languages  for 
the  Mac.  A  recent  glance  reveals  at 
least  three  Forths,  half  a  dozen  Cs,  a 
Pascal  or  two,  Microsoft  BASIC,  and 
NEON,  an  object-oriented  Forth-like 
language.  I  can  state  that  at  least  one 
product  of  a  professional  nature, 
MegaMax  C,  is  ‘real,’  well-supported, 
and  quite  clean.  Assembler  fans  can 
obtain  the  MacAsm  mentioned  in 
your  column  or  ask  almost  anybody 
for  a  copy  of  the  prerelease  versions  of 
the  Apple  Editor/Assembler/Linker/ 
Resource  Compiler  package.  By  the 


Dr.  Dobb’s  Journal,  August  1985 


117 

649 


time  this  might  see  print,  the  official 
release  of  the  MDS  + Inside  Mac 
package  should  have  occurred.  This 
package  is  already  on  the  certified  de¬ 
veloper’s  price  list.  The  price  is  lower 
than  any  complete  serious  develop¬ 
ment  environment  available  for 
MSDOS. 

“FACT:  The  bloated  size  of  most  of 
the  early  Mac  applications  is  the  re¬ 
sult  of  the  use  of  Lisa  Pascal  as  the 
development  environment.  There  is 
absolutely  nothing  about  the  Mac 
that  requires/generates  bulky  code.  I 
have  created  useful  Mac-interface 
programs  with  a  load  module  size  less 
than  1 K  using  the  MegaMax  C  com¬ 
piler.  An  ‘empty’  C  program  (i.e., 
main(  )  { }  )  yields  a  load  module  of 
about  300  bytes.  Most  of  this  appears 
to  be  information  that  any  applica¬ 
tion  stores  for  use  by  the  Finder.  I 
just  tried  the  same  experiment  with 
Computer  Innovations  C86  under 
MSDOS  and  got  a  5K  EXE  file  result, 
due  to  C86’s  insistence  on  loading  the 
standard  I/O  library  whether  or  not 
it  is  invoked. 

“POINT:  Nobody  at  Apple  denies 
that  the  Mac  concept,  from  mouse  on 
up,  is  derived  from  the  Xerox  PARC 
research.  The  point  in  the  Mac  is  to 
take  the  PARC  concepts  and  imple¬ 
ment  them  in  an  affordable  machine. 


Go  price  a  Xerox  Star  and  then  price 
a  Mac  (first,  find  a  Star!).”  [As  this 
column  went  to  press,  Xerox  an¬ 
nounced  a  new  incarnation  of  the 
Star,  the  model  6085,  that  costs 
$4995  with  1  Mb  of  RAM,  10  Mb 
hard  disk,  a  15-inch  display,  and  a 
mouse ....  RD] 

“OPINION:  I  develop  professional¬ 
ly  on  MSDOS,  CP/M,  and  Mac  sys¬ 
tems.  I  use  the  major  development 
products  in  each  environment  for 
both  C  and  assembly  language.  It  is 
my  opinion  that  there  are  better  de¬ 
velopment  environments  available  for 
the  Mac  than  for  MSDOS  or  CP/M. 
The  Mac  development  products  tend 
to  be  less  expensive,  too.  There  are 
also  some  real  losers  available  for  all 
these  environments.  I’m  not  going  to 
name  names;  we  all  have  our  own  fa¬ 
vorite  turkeys. 

“OPINION:  There  are  a  number  of 
programming  professionals  who  fee) 
threatened  by  the  Mac.  It  is  not  my 
purpose  to  speculate  why  this  should 
be  the  case.  It  is  my  purpose  to  assert 


that  no  professional  need  feel  threat¬ 
ened  by  the  Mac.  With  the  tools  cur¬ 
rently  available,  it  is  a  joy  to  develop 
on,  and  there  are  enough  ‘goodies’  in 
the  system  toolbox  to  keep  just  about 
anybody  happy.  There  is  definitely  a 
learning  curve  involved,  enough  of 
one  to  be  a  barrier  to  dabblers,  but 
anyone  familiar  with  good  modern 
programming  practices  can  produce 
useful  programs  in  very  short  order. 
The  Mac  environment  punishes  slop¬ 
py  programming  practices  and  great¬ 
ly  rewards  programmers  who  plan 
thoroughly  before  they  code. 

“Maligning  the  Mac  is  not  going  to 
make  it  go  away.  Being  annoyed  by 
Steve  Jobs  or  marketing  hype  is  not 
realistic.  The  only  tool  Apple  has  to 
battle  IBM  in  the  public  mind  is  flam¬ 
boyance.  Steve  Jobs’  opinions  may  not 
be  yours,  but  you  know  his  name, 
Playboy  readers  know  his  name,  and 
an  amazingly  large  percentage  of  the 
general  public  knows  his  name.  Now, 
what  is  the  name  of  the  CEO  of  IBM? 
Of  any  officer  of  IBM?  See  how  it 
works? 

“I  propose  that  further  discussion 
of  development  on  the  Mac  actually 
center  on  development  on  the  Mac 
.  .  .  not  on  myths  and  spurious  side 
issues!  There  is  plenty  to  discuss,  be¬ 
lieve  me!” 

In  reply,  I  only  question  whether 
the  only  tool  Apple  has  to  battle  IBM 
is  flamboyance.  The  tried  and  true 
tools  of  delivering  quality  products  on 
time  seem  to  have  served  some  compa¬ 
nies  pretty  well  (such  as  Compaq, 
which  has  taken  on  IBM  head-on  and 
is  doing  better  than  Apple  at  it  in 
many  respects).  The  rest  of  Mr.  Ker- 
ekes’  points  are  well  taken,  and  indeed 
we  will  try  to  stick  to  substantive  dis¬ 
cussion  of  the  Mac  in  the  future.  By 
the  way,  the  CEO  of  IBM  is  John 
Akers  (last  I  heard);  although  this 
fact  probably  isn't  a  part  of  the  gener¬ 
al  knowledge  of  the  average  Playboy 
reader,  it  is  fairly  common  knowledge 
in  the  computing  community. 

Reading  about  the  Mac 

I  have  run  across  two  recent  articles 
about  the  Macintosh  that  are  both 
helpful  and  balanced  (unlike  the  ra¬ 
bid  pro  or  con  articles  that  are  preva¬ 
lent  in  the  popular  computer  press). 


120 

650 


Dr.  Dobb's  Journal,  August  1985 


The  first  is  simply  a  detailed,  objec¬ 
tive,  nonhysterical  review  of  the  Mac¬ 
intosh  written  by  Larry  Press  of  Small 
Systems  Group;  this  review  appeared 
in  Abacus,  Vol.  2,  No.  2  (Winter 
1985),  page  51.  The  author  is  an  expe¬ 
rienced  computer  user  with  no  partic¬ 
ular  fish  to  fry  who  is  oriented  toward 
the  use  of  the  Macintosh  in  education¬ 
al  contexts. 

Abacus,  by  the  way,  is  an  intriguing 
publication  that  shows  signs  of  devel¬ 
oping  into  a  unique  and  useful  maga¬ 
zine.  The  quality  of  writing  is  high, 
the  percentage  of  advertisements  low, 
there  is  virtually  no  boosterism  of 
“in”  computers  or  software  products, 
and  the  magazine  is  eclectic.  The  is¬ 
sue  mentioned  contained  articles  on 
human  computers  (how  calculating 
prodigies  perform  arithmetic),  a  re¬ 
port  on  personal  computers  at  Amos 
Tuck  School,  an  exposition  of  “The 
Applicative  Style  of  Programming,” 
a  review  of  the  special  Scientific 
American  issue  on  software,  and  a  re¬ 
port  by  chess  master  David  Levy  on  a 
match  between  himself  and  CRAY 
BLITZ  (winner  of  the  1983  World 
Computer  Chess  Championship). 
Abacus  is  published  by  Springer  Ver- 
lag  New  York  Inc.,  a  source  of  scien¬ 
tific  and  academic  texts. 

The  second  notable  article  is  “Ap¬ 
ple  Macintosh  Software”  by  Dr.  Ted 
Lewis  of  the  Computer  Science  De¬ 
partment  of  Oregon  State  University, 
published  in  IEEE  Software,  March 
1985,  page  89.  This  is  a  combination 
review  and  tutorial  of  “the  coding 
standards  and  methods  forced  upon 
application  programmers  who  accept 


the  challenge  of  writing  applications 
for  the  Macintosh.”  It  includes  a  sim¬ 
ple  example  program  with  pull-down 
menus  and  an  explanation  of  the 
structure  and  contents  of  the  result¬ 
ing  “resource  file.”  This  article  is  the 
first  reasonable  explanation  I  have 
seen  of  this  subject  and  is  orders  of 
magnitude  more  comprehensible 
than  the  Apple  documentation. 

Dr.  Lewis  concludes:  “The  ideas 
and  techniques  incorporated  into  the 
Macintosh  software  are  admirable, 
but  I  am  struck  by  the  boldness  of  Ap¬ 
ple  Computer.  Programming  a  Mac¬ 
intosh  is  not  easy  for  conventional  pro¬ 
grammers,  and  this  has  contributed  to 
the  slow  appearance  of  Macintosh 
software.  Does  Apple  expect  everyone 
to  change?  Do  they  think  that  a  tech¬ 
nical  achievement  like  the  Macintosh 
is  inducement  enough  to  persuade 
programmers  to  wade  through  800 
pages  of  Inside  Macintosh  merely  to 
discover  the  merits  of  object-oriented 
programming  and  Desktop? 

“The  Macintosh  software  is  un¬ 
conventional,  nonstandard,  and  prob¬ 
ably  horribly  nonportable.  The  dia¬ 
lect  of  Pascal  used  is  related  to  UCSD 
Pascal,  but  there  is  little  hope  of  sep¬ 
arating  it  from  the  Macintosh  ROM 
routines.  Yet,  the  software  is  nearly  a 
work  of  art;  all  objects  are  organized 
in  a  logical  and  concise  manner.  The 
notion  of  a  resource  file  is  powerful 
and  will  most  likely  be  emulated  by 
programmers  everywhere.” 

DDJ 

Reader  Ballot 

Vote  for  your  favorite  feature/article. 

Circle  Reader  Service  No.  1 96. 


function  isqrt(i) 

integer*4  i,js  #32  bits 
byte  j 1 (4) 

equivalence(js,j  1(1))  #with  reversal,  j  1  (4)  is  high  byte  of  js 
#you  didn't  take  care  of  zero  and  negative  arguments! 
js=i 

#count  high  order  0  bits  8  at  a  time 

for(k0  =  0;j1(4)=  =0;k0  =  k0  +  8)js  =  ishft(js,8)  #shift  left  8  bits 
for(;js>0;k0  =  k0  +  1)js  =  ishft(js,1)#count  remaining  0  bits 
#get  initial  approximation  within  50% 
isqrt = ishft(3,ishft(3 1  -kO,- 1 )- 1 )  #3*2**((29-k0)/2),not  for  i  =  1 
do  k  =  1 ,3  #improve  by  Newton  iterations 

isqrt = ishft(i/isqrt  +  isqrt,- 1 )  #(i/isqrt  +  isqrt)/2 

return 

end 

Listing  One 

Tom  Prince's  square  root  algorithm 


Dr.  Dobb's  Journal.  August  1985 


122 

651 


The  CP/M  Exchange  RCP/M  system 
is  available  for  your  use  24  hours  a 
day,  7  days  a  week.  Reach  it  by  dial¬ 
ing  (404)  449-6588. 

Online  ...  at  Last 

For  more  than  two  years  I  have  been 
promising  to  bring  up  a  RCP/M  sys¬ 
tem  in  support  of  this  column — final¬ 
ly,  it’s  online.  1  hope  you  will  partici¬ 
pate  in  its  use  and  share  with  me  your 
thoughts  on  how  I  can  improve  it. 

To  reach  it,  call  (404)  449-6588 
any  hour  of  the  day  or  night,  seven 
days  a  week.  The  modem  is  a  Racal- 
Vadic  3452,  which  is  capable  of  com¬ 
municating  at  either  300  or  1200 
baud.  The  other  hardware  compo¬ 
nents  are  an  Ampro  single-board  Z80 
microcomputer  driving  two  5'/4-inch 
Teac  floppy  disk  drives  and  a  20  Mb 
hard  disk  drive.  This  configuration 
should  be  more  than  adequate,  at 
least  for  the  present,  to  support  the 
amount  of  activity  that  I  anticipate. 

The  main  purpose  of  the  system  is 
to  provide  improved  access  to  the  in¬ 
formation  printed  in  this  column. 
The  complete  text  of  all  the  programs 
and  updates  printed  here  will  be 
available  fordownloading.  I  hope  this 
will  stimulate  even  more  reader  par¬ 
ticipation  in  the  column  by  providing 
a  convenient  method  for  submitting 
items  of  interest.  I  will  also  maintain 
a  current  list  of  all  known  CP/M  bug 
reports  and  any  fixes  that  might  be 
available;  this  includes  application 
notes  and  fixes  published  by  DRI. 

When  your  call  is  answered,  hit  the 
return  key  several  times  to  indicate 
the  baud  rate  you  are  using.  Next  you 
will  be  asked  whether  your  terminal 
needs  nulls  for  proper  operation. 
Once  beyond  these  two  preparatory 
steps,  you  will  enter  into  the  message 
system.  From  this  point  on,  1  think 
the  system  is  self-prompting  enough 
to  get  you  started  without  requiring  a 


lengthy  tutorial. 

If  you  call  to  transfer  program 
files,  enter  a  “J”  at  the  main  menu 
prompt  tojumptoCP/M.  Once  under 
CP/M  control,  execute  the  MAP  tran¬ 
sient  program  to  get  a  system  map, 
which  will  guide  you  around  the 
system. 

Rather  than  spend  a  lot  of  time 
talking  here  about  what  you  can  ex¬ 
pect  when  calling  my  RCP/M  system, 
I  suggest  that  you  pick  up  your  phone 
and  call.  Again,  please  leave  a  mes¬ 
sage  outlining  your  impressions  of 
the  system  and  any  improvements 
that  you  might  want  to  suggest. 

The  Free  Software  Catalog 
and  Directory 

Public  domain  software  is  software 
that  legally  can  be  copied,  used,  and 
given  to  another  user  without  pay¬ 
ment  of  a  royalty  to  its  author. 

Estimates  project  that  over  40  Mb 
of  program  source  code  are  contained 
on  the  300+  volumes  of  CP/M  public 
domain  software.  In  this  vast  library 
can  be  found  just  about  any  type  of 
utility  program  that  you  might  need 
to  help  manage  your  CP/M  system.  A 
number  of  business  application  pro¬ 
grams,  mostly  written  in  CBASIC, 
are  present  as  well. 

Until  now,  many  would-be  public 
domain  software  freaks  have  been  de¬ 
feated  by  the  thought  of  searching 
through  over  5000  program  titles  to 
find  just  the  few  desired.  Fortunately, 
Robert  A.  Froehlich,  a  long-term 
member  of  SIG/M  and  an  avid  user 
of  public  domain  software,  foresaw 
this  problem  and  has  compiled  a  ref¬ 
erence  book  detailing  and  indexing 
each  program. 

The  Free  Software  Catalog  and 
Directory  is  a  475-page  836  x  1 1-inch 
book  printed  on  newsprint  stock.  It 
focuses  on  the  two  largest  and  most 
readily  available  free  software  librar¬ 


ies,  the  CP/M  Users  Group 
(CPMUG)  and  the  Special  Interest 
for  Microcomputers  (SIG/M),  while 
providing  all  the  information  neces¬ 
sary  for  personal  computer  owners  to 
take  advantage  of  this  resource. 

The  introduction  begins  by  setting 
aside  any  fears  one  might  have  about 
using  public  domain  software.  It  con¬ 
cludes  by  talking  a  little  about  the 
main  sources  of  free  software.  But 
most  important  is  the  elaborate  tu¬ 
torial — the  best  I  have  seen  for  suc¬ 
cessfully  completing  a  session  with  a 
remote  bulletin  board  system  (BBS). 
This  section  in  itself  makes  the  book 
a  worthwhile  investment  for  the  nov¬ 
ice  and  experienced  user  alike.  The 
remainder  of  the  introduction  pre¬ 
pares  the  reader  for  dealing  with 
source  code  files  and  explains  how  to 
go  about  making  changes  to  them, 
should  that  be  necessary. 

The  real  meat  of  this  book  is  con¬ 
tained  in  the  next  six  sections.  Every 
file  in  the  entire  92-volume  set  of  the 
CPMUG  library  and  the  first  162  vol¬ 
umes  of  the  SIG/M  library  is  de¬ 
scribed  in  detail.  The  first  and  second 
sections,  the  largest  of  the  six,  include 
complete  information  for  each  file  as 
follows:  filename,  size  in  Kbytes, 
CRC  checksum,  date  of  entry,  lan¬ 
guage  if  program  source  code,  au¬ 
thor’s  name,  revisor’s  name,  file  title, 
keywords  assigned  to  the  file,  and  fi¬ 
nally  a  textual  description  of  the  file. 

Of  course,  the  main  purpose  of  this 
book  is  to  provide  ready  access  to  in¬ 
formation  about  the  routines  or  pro¬ 
grams  of  interest  to  you.  The  next 
four  sections  cross  reference  each  file 
alphabetically  by  keyword,  language 
type,  author  name,  and  filename.  For 
example,  if  you  want  to  find  a  pro¬ 
gram  to  compare  two  files  for  equali¬ 
ty,  your  initial  search  might  start  in 
the  keyword  section  under  utilities. 
You  find  there  two  entries.  If  the  one 


124 


Dr.  Dobb's  Journal,  August  1985 


for  comparing  binary  files  sounds  like 
it  would  serve  well,  note  the  refer¬ 
ence:  SIG/M  volume  115.  You  can 
then  track  down  a  complete  descrip¬ 
tion  of  the  chosen  file  by  referring  to 
the  second  section  of  the  book,  which 
covers  the  SIG/M  volumes. 

As  if  all  this  weren’t  enough,  the 
book  concludes  by  listing  several 
hundred  user  groups  and  bulletin 
board  systems  by  name,  location,  and 
phone  number. 

This  book  has  become  the  most 
used  of  any  in  my  bookcase.  It  is  a 
tremendous  bargain  at  $9.95.  The 
author  has  accomplished  a  phenome¬ 
nal  task  in  both  compiling  this  much 
material  and  cross  indexing  it  for 
ease  of  access.  Whether  you  are  an 
individual  user  of  free  software  or  the 
librarian  of  a  user  group,  you  will 
benefit  from  having  this  book. 

If  you’re  having  trouble  finding  a 
source  of  free  software  in  your  local 
area,  call  the  “CP/M  Exchange” 
RCP/M  system  at  (404)  449-6588  for 
a  list  of  user  groups  where  public  do¬ 
main  software  can  be  found. 

8088:  8-  or  16-bit 
Processor? 

The  following  paragraphs,  taken  from 
the  introduction  to  the  Intel  iAPX  88 
Book ,  set  the  record  straight  on  how 
the  manufacturer  intended  the  8088 
to  be  classified: 

“This  book  describes  the  unique 
Intel  8088  microprocessor,  the  out¬ 
standing  choice  for  8-bit  microcom¬ 
puter  applications  requiring  both 
high  performance  and  low  cost. 

“The  Intel  8088  is  the  most  power¬ 
ful  8-bit  microprocessor  available  to¬ 
day,  yet  as  easy  to  use  as  other  8-bit 
microprocessors  designers  have  used 
for  years.” 

Outside  of  the  marketing  rhetoric, 
these  two  paragraphs  contain  the 
phrase  “8-bit”  often  enough  to  dispel 
any  doubt  that  the  8088  was  designed 
as  an  8-bit  processor. 

Exiting  Properly  to 
CP/M 

Two  accepted  methods  for  returning 
to  CP/M  from  an  application  pro¬ 
gram  are  documented  by  DRI  in  the 
CP/M  2.2  reference  manual.  Al¬ 
though  both  are  said  to  achieve  the 


same  result,  in  practice  they  react 
very  differently  and,  depending  on 
the  situation,  can  cause  a  great  deal 
of  frustration. 

Steve  Russell  of  SLR  Systems 
brought  this  incompatibility  to  my  at¬ 
tention.  In  the  process  of  testing  one  of 
his  assemblers,  he  stumbled  onto  a 
difference  between  exiting  to  CP/M 
by  jumping  to  memory  location  zero 
( BOOT )  and  by  calling  the  BDOS  with 
a  System  Reset  function.  The  problem 
occurred  when  he  tried  executing  sev¬ 
eral  assemblies  through  a  submit  file 
in  conjunction  with  XSUB.  The  first 
assembly  finished  successfully,  but  all 
the  others  failed  because  for  some  rea¬ 
son  XSUB  was  no  longer  active  after 
the  first  assembly. 

Apparently,  the  documentation 
wasn’t  updated  when  XSUB  was  re¬ 
leased.  When  loaded,  XSUB’s  first 
action  is  to  relocate  itself  immediate¬ 
ly  under  the  CCP  and  to  modify  the 
BOOT  address  so  that  it  no  longer 
points  to  the  BIOS  jump  table  but  to 
the  XSUB  warm  start  routines.  This 
modification  is  necessary  so  that 
XSUB  can  trap  any  further  warm 
start  requests  to  avoid  having  to  re¬ 
load  memory  with  a  fresh  copy  of  the 
operating  software.  Thus,  XSUB  re¬ 
mains  resident  and  active.  But  if  the 
BDOS  is  called  with  the  System  Reset 
function,  a  fresh  copy  of  the  operat¬ 
ing  software  is  immediately  loaded 
without  checking  whether  XSUB  or  a 
similar  program  is  active. 

Because  XSUB  was  designed  for 
use  within  SUBMIT  batch  proce¬ 
dures,  the  result  of  a  program  return¬ 
ing  to  CP/M  via  the  BDOS  reset  call  is 
that  suddenly  XSUB  no  longer  re¬ 
mains  active:  the  BOOT  vector  stored 
at  memory  location  0  has  been  reset, 
and  a  fresh  copy  of  the  operating 
software  is  loaded.  Any  other  pro¬ 
grams  in  the  same  procedure  that  de¬ 
pend  on  XSUB  to  provide  console  in¬ 
put  from  the  batch  file  will  then  fail 
to  execute  as  planned. 


DDJ 

Reader  Ballot 

Vote  for  your  favorite  feature/article. 
Circle  Reader  Service  No.  197. 


Dr.  Dobb’s  Journal,  August  1985 


127 


''-If-'--"— . 

eIse  «d. 

rS^?m^®<nw^|e-^^®5fyn?s?  r0"S'  end 

repeo,  ~Wg' ed,ents^mgrecf,ent< 

untn  f,ake^a«ce,350l 

nhl  e,£>Psea_time=dn 


:  Stout: 

00  Hard  Disk 


Dr.  Dobb's  Journal 


Editorial 

E4itar-M-Chkf  Michael  Swaine 
Mmpag  Editor  Frank  DeRose 
Technical  Editor  Alex  Ragen 
Editorial  Assistaot  Sara  Noah 
Cuatrifcutiag  Editors  Robert  Blum. 

Dave  Cortesi, 

Ray  Duncan. 

Allen  Holub 
Copy  Editor  Vince  Leone 
Typesetter  Jean  Aring 
Production 

Art  Director  Shelley  Rae  Doeden 
Product ioo  Assistant  Alida  Hinton 
Carer  Artist  Tom  Upton 
Qrctriatioa 

Sub.  Kulfiibneut  Mgr.  Stephanie  Barber 
Subscription  Mgr.  Maureen  Snee 
Book  Marketing  Mgr.  Jane  Sharninghouse 
Single  Copy  Sales  Mgr.  Kathleen  Boyd 
Administration 
Finance  Manager  Sandra  Dunie 
Business  Manager  Betty  Trickett 
Accounts  Payable  Snpr.  Mayda  Ixtpez-Quintana 
Accounts  Payable  Asst.  Denise  Giannini 
Biding  Coordinator  Laura  Di  Lazzaro 
Adarinistrathr 

Coordinator  Kobi  Morgan 
Advertising 
Adrertising  Director 
Shawn  Horst  (415)424-0600 
Adrertising  Saks 

Walter  Andrzejewski  (617)  567S36I 
Lisa  Boudreau  (415)  424-0600 
BethDudas  (714)643-9439 
Michele  Beaty  (31 7)  875-0557 

Adrertising  Systems  Manager 
Ron  Copeland  (415)424-0600 
Adrertising  Admiuistratirr  Manager 
Anna  Kittleson  (415)  424-0600 


M&T  Publishing.  Inc. 

Chairman  of  the  Board  Otmar  Weber 
Director  C.F.  von  Quad t 
President  Laird  Foshay 


Dr.  Dobb's  Journal  (USPS  307690)  is  published 
monthly  by  M&T  Publishing.  Inc.,  2464  Embarcadcro 
Way.  Palo  Alto,  CA  94303,  (415)  4244)600.  Second 
class  postage  paid  at  Palo  Alto  and  at  additional  entry 
points. 

Address  correction  requested.  Postmaster:  Send  Form 
3579  to  Dr.  Dobb's  Journal,  P.O.  Box  27809,  San 
Diego,  CA  92 1 28.  ISSN  0278-6508 

Subscription  Rates:  $25  per  year  within  the  United 
Stales,  $46  for  airmail  to  Canada,  $62  for  airmail  to 
other  countries.  Foreign  subscriptions  must  be  pre-paid 
in  US.  Dollars,  drawn  on  a  U.S.  Bank.  For  subscription 
problems,  call:  outside  of  CA  800-321-3333;  within  CA 
619-485-9623  or  566-6947. 

Foreign  Distributor:  Worldwide  Media  Service,  Inc., 
386  Park  Ave.  South,  New  York.  NY  10016.  (212) 
686-1520  TELEX.  620430  (  WUI ) 

Entire  contents  copyright  °  1985  by  M&T 
Publishing,  Inc.  unless  otherwise  noted  on 
specific  articles.  All  rights  reserved. 


People's  Computer  Company 

Dr.  Dobb's  Jooral  is  published  by  M&T  Publishing, 
Inc.  under  license  from  People's  Computer  Company, 
2682  Bishop  Dr.,  Suite  107,  San  Ramon,  CA  94583,  a 
non-profit,  educational  corporation. 


September  1985 
Volume  10,  Issue  9 


CONTENTS 


Editorial  Calendar 

The  editorial  calendar  for  the  first  three  months  of  1986  has  been  set.  In 
January  we  will  focus  on  programming  for  the  68000,  in  February  we  will 
present  a  sampling  from  the  structured  languages  Pascal,  Ada,  and  Mo- 
dula  2,  and  finally  in  March  we  will  look  ahead  to  what  computing  will  be 
like  in  the  future,  with  particular  concentration  on  parallel  processing. 
Article  submissions  and  new  product  announcements  should  be  in  our 
hands  no  later  than  three  months  in  advance  of  any  issue. 

Late  Breaking  News 

In  “16- Bit  Software  Toolbox”  in  June  and  again  this  month  Ray  Duncan 
mentioned  patches  distributed  by  MicroPro  Technical  Support  that  allow 
WordStar  to  use  the  HP  LaserJet’s  boldface,  italics  and  various  fonts.  As 
this  issue  was  going  to  press  Ray  received  a  letter  from  MicroPro  stating 
that  the  LaserJet  patches  were  intended  as  a  temporary  fix  and  are  no 
longer  available.  Updated  printer  enhancement  disks  that  include  LaserJet 
support  have  been  sent  to  all  MicroPro  dealers. 

Corrigendum 

In  last  month’s  review  of  C  Compilers  we  accidentally  left  out  the  address 
and  telephone  number  of  Rational  Systems,  manufacturer  of  Instant  C,  an 
interpreter  for  the  C  programming  language.  We  apologize  to  the  folks  at 
Rational  for  this  omission  and  include  their  address  and  phone  number 
below  for  the  information  of  our  readers: 

Rational  Systems,  Inc. 

P.O.  Box  480 
Natick,  M A  01760 
(617)653-6194 


This  Month's  Referees 

Dennis  Allison,  Stanford  University 
Allen  Holub,  DDJ  Contributing  Editor 


4 


RRfi 


Dr.  Dobb's  Journal,  September  1985 


Dr.  Dobb’s  Journal 


ARTICLES 

Parallel  Pattern  Matching  and  Fgrep 

by  Ian  Ashdown 

46 

The  Aho-Corasick  algorithm  for  searching  for  multiple 
patterns  in  parallel  is  implemented  in  the  Unix  utility 
fgrep.  (Reader  Ballot  No.  195). 

Bose-IMelson  Sort 

by  Joe  Celko 

68 

The  attractions  and  limitations  of  the  unstable  sort. 

(Reader  Ballot  No.  1 96). 

Two  T^X  Implementations  for  the  IBM  PC 

by  Richard  Furuta  and  Pierre  A.  Mac  Kay 

80 

In  honor  of  algorist  Donald  Knuth  a  review  of  two  small 
TgXs.  (Reader  Ballot  No.  197). 

COLUMNS 


Dr.  Dobb's  Clinic 

by  D.  E.  Cortesi 

10 

A  summary  of  papers  on  compiler  design;  the  fastest  way 
to  request  disk  input  in  MSDOS.  (Reader  Ballot  No.  190). 

C  Chest 

by  Allen  Holub 

18 

A  simple  arithmetic  expression  analyzer  illustrates  the 
fundamental  operations  of  recursive  descent  compilers. 

(Reader  Ballot  No.  192). 

Unix  Exchange 

by  Axel  Schreiner 

30 

Communication  between  Unix  and  processes:  the  theory 
of  signals;  individual  signals;  how  you  control  them. 

(Reader  Ballot  No.  191). 

Software  Designer 

by  Michael  Swaine 

42 

A  survey  of  sort  and  search  algorithms.  (Reader  Service  No. 
194). 

Mac  Toolbox 

by  John  Bass 

94 

John  Bass  inaugurates  this  new  column  with  MacSCSI,  a 
do-it-yourself  hard  disk  interface  for  the  Mac.  (Reader 
Service  No.  198). 

CP/M  Exchange 

by  Bob  Blum 

110 

Farewell  to  CP/M  Plus;  one  user’s  impressions  of  the 
AMPRO  Little  Board.  (Reader  Service  No.  199). 

1 6-Bit  Software  Toolbox 

by  Ray  Duncan 

114 

The  HP  LaserJet;  the  IBM  and  Microsoft  Macro 
Assemblers;  Numeric  coprocessors;  Micro  Solutions 
UNIFORM;  Control-C  under  MSDOS.  (Reader  Service  No. 
200). 

DEPARTMENTS 

Editorial 

6 

Letters 

8 

DDJ  Classifieds 

100 

Of  Interest 

122 

Advertiser  Index 

128 

Dr.  Dobb's  Journal,  September  1985 


5 

657 


EDITORIAL 


At  Micro  Cornucopia's  recent  Semi-Official  Gathering  of  hardware  de¬ 
signers,  I  heard  George  Morrow  express  the  computer  vendor’s  dilem¬ 
ma:  supplying  solutions  without  knowing  the  problems.  There  is,  Mor¬ 
row  explained,  a  very  high  wall.  Computer  hardware  manufacturers  dwell  on 
one  side,  and  in  the  dense  jungle  on  the  other  side  prowl  strange  questing 
beasts  called  customers.  The  manufacturers  blindly,  randomly  throw  ma¬ 
chines  over  this  wall.  Occasionally  one  of  them  is  rewarded  with  the  sound  of 
feeding  somewhere  beyond  the  wall,  and  they  all  rush  to  throw  their  machines 
at  this  latest  feeding  spot. 

Publishing  a  magazine  for  advanced  programmers  is  not  quite  such  a  ran¬ 
dom  process:  there  are  various  means  by  which  we  can  know  our  customers. 
We  read  what  you  write  on  the  postage-paid  response  cards  included  in  every 
issue.  We  do  regular  reader  surveys,  because  interests  can  change  quickly  in  so 
volatile  a  field  as  computer  programming.  And  we  have  begun  a  spot  survey 
that  will  give  us  quick  information  about  our  readers’  interests. 

Listening  to  your  feedback  has  helped  us  in  shaping  an  editorial  calendar 
for  1986.  On  your  advice,  we  will  be  looking  more  closely  at  advanced  proces¬ 
sors  in  1986,  while  not  abandoning  machines  of  the  8080/6502  class.  We 
asked  for  feedback  on  our  hardware  issue  in  July,  and  although  you  were  not 
univocal,  we  think  we  got  the  message.  We  will  consequently  not  run  a  hard¬ 
ware  issue  next  year,  but  will  run  hardware  articles  whenever  they  seem  ap¬ 
propriate  and  good. 

The  trick  is  to  satisfy  the  diverse  interests  simultaneously.  As  this  issue 
exemplifies,  we  are  focusing  more  on  algorithms,  and  in  other  ways  attempt¬ 
ing  to  present  tools  that  are  as  portable,  as  broadly  useful  as  possible.  But  to 
follow  that  path  to  the  end  would  mean  never  publishing  any  assembly  code, 
and  that  would  cut  out  some  of  the  best  pieces  that  come  into  the  office. 

So  we’ll  continue  the  juggling  act.  We’ll  publish  articles  focusing  on  partic¬ 
ular  topics,  presenting  specialized  programming  tools,  sometimes  with  code 
written  in  assembly  language  when  that’s  the  best  medium  for  it,  but  always 
with  tips  on  ways  to  port  these  tools  to  other  environments.  We’ll  maintain  an 
emphasis  on  the  underlying  algorithms.  And  we’ll  try  to  maintain  balance  in 
the  magazine,  making  sure  that  we’re  supplying  something  for  everyone. 

But  we  are  still  interested  in  good  assembly  language  programming;  in  fact, 
our  first  issue  of  1986  will  focus  on  programming  for  the  Motorola  68000 
processors.  It’s  not  too  late  to  send  us  your  68 K  code,  but  don’t  wait:  the 
deadline  for  submissions  is  October  1. 

Of  course,  some  68000  machines  try  to  pass  as  8088s,  like  the  Amiga,  the 
Mac-killer  that  Commodore  has  thrown  over  the  wall  into  the  IBM  area,  just 
south  of  a  mob  of  hostile  dealers.  My  friend  Norm  down  at  the  Fab  Lab  has 
been  researching  that  commonly  observed  phenomenon  of  two  people  achiev¬ 
ing  the  same  breakthrough  at  the  same  time.  Norm’s  preliminary  findings 
indicate  that  this  may  be  part  of  some  larger  resonance  in  technological  deci¬ 
sion  making.  For  example,  just  as  IBM  dropped  the  5!4-inch  floppy  disk. 
Commodore  picked  up  that  medium  as  the  means  of  getting  software  onto  the 
Amiga  quickly — IBM  PC  software,  that  is.  Commodore  hopes  people  will  use 
the  Amiga  as  an  IBM  PC  with  good  games  until  the  native-mode  and/or 
graphics  interface-based  software  arrives.  If  the  software  doesn’t  arrive — ah, 
is  that  why  they  didn’t  put  the  company  name  on  the  machine? 

Michael  Swaine 


6 


Dr.  Dobb’s  Journal,  September  1985 


Hardware  vs.  Software 

Dear  DDJ, 

I  just  received  the  July  issue  of  Dr. 
Dobbs.  In  answer  to  the  question  you 
pose  in  your  editorial:  No,  do  not  in¬ 
clude  hardware  articles.  Other  maga¬ 
zines,  such  as  Byte ,  do  a  fine  job  of 
covering  hardware.  Few  magazines 
do  such  a  fine  job  of  covering 
software. 

If  you  expand  your  scope  to  in¬ 
clude  hardware,  you  will  dilute  your 
focus,  and  certainly  reduce  your  val¬ 
ue  to  me. 

Guy  Scharf 
2163  Jardin  Drive 
Mountain  View,  CA  94040 

Dear  DDJ , 

In  answer  to  your  editorial  query 
about  hardware  articles,  I’m  in  favor 
of  having  semi-annual  hardware  is¬ 
sues,  with  monographs  and  project 
papers  available  from  you  (at  a  nomi¬ 
nal  fee,  of  course).  There  are  many  of 
us  that  can’t  afford  the  big  bucks  for 
some  of  the  hardware  items,  but  have 
the  skills  to  build  them  if  the  instruc¬ 
tions  are  adequate. 

Vladimir  Ushakoff 

4753  Preston-Fall  City  Rd., 

SE 

Fall  City,  WA  98024-5705 
Dear  DDJ , 

I  have  thoroughly  enjoyed  the  hard¬ 
ware  articles  in  recent  issues  and 
would  like  to  see  more  of  them.  I  had 
begun  to  think  no  one  cared  about  the 
hardware  hacker  anymore.  I  can  see 
your  point,  though,  that  Dr.  Dobb's 
may  not  be  the  appropriate  vehicle 
for  such  articles  since  it  really  is  a 
software  journal. 

Why  not  start  a  separate  journal,  a 
hardware  companion  to  DDJ ?  It  could 
contain  reviews  of  new  chips  with  ap¬ 
plication  examples  and  surveys  of  dif¬ 


ferent  classes  of  chips  (display  or  disk 
controllers,  for  example)  as  well  as 
construction  articles.  From  the  re¬ 
sponse  you  report  to  the  recent  hard¬ 
ware  articles,  I  think  you  can  see  the 
enthusiasm  there  might  be  for  such  a 
project. 

Try  one  issue  dedicated  entirely  to 
hardware  and  see  how  it  is  received. 
You  already  have  the  best  software 
journal.  Why  not  have  the  best  hard¬ 
ware  one  too? 

David  Nye 

209  W.  Lowe’s  Creek  Rd. 

Eau  Claire,  WI  54701 

Dear  DDJ , 

This  is  in  answer  to  Michael  Swaine’s 
question  about  coverage  of  hardware 
in  Dr.  Dobb’s  Journal  It  seems  to  me 
that  there  is  only  one  way  to  reply: 
there  are  already  plenty  of  articles  on 
hardware  tinkering  in  other  journals, 
and  it  is  out  of  character  for  DDJ  to 
use  its  limited  space  for  such  articles. 
An  occasional,  exceptional  article  is 
just  tolerable.  Reviews  of  innovative 
hardware,  such  as  have  appeared  in 
DDJ  through  all  its  years  of  publica¬ 
tion,  are  helpful,  as  even  the  ads  are. 
Anything  more  than  such  as  these  is 
inappropriate  and  decreases  the  val¬ 
ue  of  the  journal  to  those  of  us  who 
subscribe  to  and  read  it  for  what  it 
claims  to  be:  a  journal  of  “Software 
Tools  for  Advanced  Programmers.” 
Dr.  Dobb’s  is  unique  and  uniquely 
valuable,  and  let’s  keep  it  that  way. 

In  our  department,  we  have  a  com¬ 
plete  file  of  DDJ,  some  volumes  of  it 
(especially  those  with  articles  on 
Forth)  getting  dog-eared,  and  all  of 
them  well  used.  Of  other  journals, 
only  a  few  back  issues  have  been 
saved  by  our  engineers,  for  articles  on 
roll-your-own  computers  or  compo¬ 
nents  that  they  may  build — someday. 
I  would  wager  that  this  is  pretty 


much  the  way  it  is  with  most  DDJ 
readers. 

Even  in  the  days  when  many,  if  not 
most,  personal  computer  users  oper¬ 
ated  with  homemade  hardware  from 
kits,  DDJ  addressed  software  rather 
than  hardware  needs.  In  these  days  of 
(almost)  standardized  and  universal¬ 
ly  available  hardware,  the  audience 
for  useful  software  articles  is  larger 
and  still  growing. 

Wayne  C.  Williams 
Greenville,  NC  27834 

Unix  Exchange 

Dear  DDJ, 

I  have  a  comment  on  Axel  Shreiner’s 
June  1985  Unix  Exchange  column  on 
tricks  with  the  C  preprocessor.  In  the 
section  on  program  argument  stan¬ 
dards,  a  set  of  argument  parsing  mac¬ 
ros  are  presented  after  the  author 
writes  “it  would  be  so  simple  to  devel¬ 
op  a  standard  as  in”  the  macros. 
Why,  I  ask,  is  a  standard  being  pro¬ 
posed  that  has  already  been  imple¬ 
mented  in  a  software  tool  that  is 
readily  available?  The  getopt  com¬ 
mand  line  option  parser  makes  it  sim¬ 
ple  to  parse  Unix  command  line  op¬ 
tions  consistently,  and  unlike  the 
proposed  macros,  is  well  accepted. 
Public  domain  versions  of  getopt 
have  been  posted  to  the  USENET.  In 
my  opinion,  the  code  written  with  get¬ 
opt  is  more  readable  than  the  macros 
presented  in  the  article.  One  reason 
for  this  might  be  that  the  macros  do 
not  behave  anything  like  functions  in 
the  C  language,  and  so  might  have 
unpredictable  behavior. 

Should  you  be  interested,  I  would 
be  happy  to  send  a  printed  version  of 
the  getopt  parser  for  your  readers. 
Gary  Perlman 
Wang  Institute 
Tyng  Road 

Tyngsboro,  MA  01879  ddi 


Dr.  Dobb’s  Journal.  September  1985 


8 


DR.  DOBB'S  CLINIC 


by  D.  E.  Cortesi 


Pascal  Sources 

We  did  some  reading  before  writing 
last  month’s  diatribe  on  Turbo  Pas¬ 
cal.  Hating  to  let  good  research  go  to 
waste  and  with  the  hope  of  finding 
one  or  two  compiler  implementors  in 
the  readership,  we  thought  we’d  list 
some  of  the  better  sources.  All  of 
these  are  from  the  British  journal 
Software:  Practice  and  Experience 
( SP&E ).  You  should  be  able  to  find 
back  issues  in  a  university  library. 

What  To  Optimize 

A  lot  of  data  has  been  published  on 
the  way  people  use  language  features, 
with  profound  implications  for  the 
design  of  a  compiler’s  generated 
code.  The  first  paper  on  this  sub¬ 
ject — and  one  of  the  most-cited  com¬ 
puter  science  papers  ever — is  D.  E. 
Knuth’s  “An  Empirical  Study  of  For¬ 
tran  Programs”  (SP&E,  Vol.  1,  No. 
2,  2nd  Quarter  1970).  In  this  pioneer 
study  of  static  and  dynamic  usage  of 
language  features,  Knuth  found  what 
many  others  have  since  verified:  the 
most-used  features  are  the  simplest 
ones,  and  the  80-20  rule  is  a  good  ap¬ 
proximation  for  dynamic  execution 
frequency  in  most  programs. 

A  more  recent  work  in  this  line  is 
M.  Shimasaki,  et  al.,  “An  Analysis  of 
Pascal  Programs  in  Compiler  Writ¬ 
ing”  (SP&E,  Vol.  10,  No.  2,  February 
1980).  This  team  studied  the  static 
and  dynamic  frequency  with  which 
Pascal’s  features  were  used,  examin¬ 
ing  several  Pascal  compilers  written 
in  Pascal.  In  static  usage,  they  found 
that  30-40%  of  all  statements  were 
assignments,  30—40%  were  procedure 
calls,  and  most  of  the  rest  were  IFs. 
The  proportion  of  procedure  calls 
was  much  higher  than  Knuth  and 
others  had  found  in  Fortran  and  PL/I 
programs;  furthermore,  about  half  of 
the  procedures  were  named  only 


once!  This  may  not  be  typical  of  ordi¬ 
nary  Pascal  programs,  as  compilers 
are  more  likely  to  be  planned  and 
written  in  top-down  modular  style. 

When  they  compiled  the  compilers 
to  P-code  and  instrumented  the  P- 
machine,  they  found  that  40%  of  all 
executed  instructions  were  load-to- 
stack  operations.  On  breaking  that 
down,  they  discovered  9%  of  all  oper¬ 
ations  were  “load  constant,”  19% 
were  “load  global  variable,”  while 
1 1%  were  loads  from  some  level  other 
than  global.  Of  the  latter,  71%  (7.8% 
of  all  operations)  loaded  a  local  of  the 
active  procedure  (this  presumably  in¬ 
cluded  loading  parameters).  The  re¬ 
mainder — only  3%  of  all  opera¬ 
tions — referred  to  locals  of  some 
intermediate  procedure. 

In  “A  Contextual  Analysis  of  Pas¬ 
cal  Programs”  (SP&E,  Vol.  12,  No. 
2,  February  1982),  R.  P.  Cook  and  I. 
Lee  reported  a  static  analysis  of 
120,000  lines  of  Pascal  code.  They 
produced  some  striking  numbers:  for 
example,  that  89%  of  all  subpro¬ 
grams  had  fewer  than  five  local  vari¬ 
ables  or  that  47%  of  all  parameters 
passed  were  constants.  99%  of  all 
subprograms  had  five  or  fewer  pa¬ 
rameters,  and  92%  had  no  more  than 
two.  These  are  important  findings  if 
you  are  designing  a  compiler’s  sub¬ 
routine  calling  sequence.  In  a  ma¬ 
chine  with  a  few  registers  and  a  stack, 
should  you  try  to  pass  parameters  in 
registers?  Definitely  yes:  if  you  have 
as  few  as  two  scratch  registers,  nine 
subprograms  in  ten  will  receive  all 
their  parameters  in  registers! 

Cook  and  Lee  substantiated 
Knuth’s  observation  that  the  simplest 
features  are  the  most  used.  Among 
many  other  numbers,  they  reported 
that  94%  of  all  array  references  used 
but  a  single  subscript,  of  which  19% 
were  written  as  a  constant  and  63% 


as  the  name  of  a  simple  variable.  A 
compiler  that,  when  looking  at  a  sub¬ 
script,  checks  for  “name,  delimiter” 
and  “constant,  delimiter”  before  go¬ 
ing  into  a  full  expression  parse  is  a 
compiler  that  will  save  time,  four 
tries  in  five. 

As  to  stack-frame  usage,  they 
found  references  to  intermediate- 
level  names  to  comprise  less  than  2% 
of  all  references  occurring  in  proce¬ 
dures  and  less  than  3%  of  those  in 
functions.  This  “would  seem  to  indi¬ 
cate  that  expensive  mechanisms  to 
implement  up-level  frame  addressing 
are  largely  unnecessary.”  It  would 
also  seem  to  validate  the  design 
choice  made  in  C — to  have  only  local 
and  global  name  scopes. 

We  can’t  resist  pointing  out  that 
this  kind  of  survey  doesn’t  require 
deep  theoretical  insight  or  great  orig¬ 
inality.  The  information  is  genuinely 
significant  and  useful,  but  generating 
it  requires  only  persistence,  curiosity, 
read  access  to  a  quantity  of  source 
code,  and — for  dynamic  studies — 
write  access  to  the  source  code  of  a 
compiler.  These  things  aren’t  rare 
among  DDJ  readers;  if  you  were  to 
study,  say,  the  usage  of  C  constructs 
in  public-domain  code,  DDJ  would  be 
pleased  to  publish  the  results.  And 
you  don’t  have  to  do  a  broad-spec¬ 
trum  census  like  the  ones  described 
here.  A  survey  of  a  single  feature — 
the  use  of  operators  in  expressions, 
the  number  of  locals  or  parameters, 
even  the  length  of  names — is  worth 
doing,  especially  since  one  such  sur¬ 
vey  is  likely  to  generate  tools  that  can 
be  used  in  others. 

How  to  Optimize 

We  did  run  across  a  couple  of  papers 
that  were  truly  original  and  insight¬ 
ful.  No  compiler  designer  should 
overlook  them. 


10 


Dr.  Dobb’s  Journal,  September  1985 


The  first  is  J.  Welsh’s  “Economic 
Range  Checks  in  Pascal”  ( SP&E ,  Vol. 
8,  No.  1,  January  1978).  This  paper 
doesn’t  deserve  the  obscurity  into 
which  it  seems  to  have  fallen.  Welsh 
describes  how  he  made  a  Pascal  com¬ 
piler  use  all  the  information  available 
to  it  to  identify  which  expressions 
might  violate  a  subrange  or  array 
bound  at  runtime  and  which  expres¬ 
sions  absolutely  could  not.  Knowing 
that,  the  compiler  could  generate  code 
for  runtime  range  checks  where  they 
were  needed  and  omit  them  where 
they  were  not.  The  result  was  “highly 
successful”  at  reducing  the  cost  of 
runtime  checks  to  levels  that  are  toler¬ 
able  in  production  code. 

The  implications  of  Welsh’s  meth¬ 
ods  go  well  beyond  range  checks  on 
array  subscripts  and  assignments  to 
subrange  variables.  After  all,  binary 
integers  are  only  a  subrange  of  the 
natural  numbers,  so  Welsh’s  tech¬ 
nique  applies  equally  well  to  any  inte¬ 
gers.  For  example,  it  would  allow  the 
compiler  to  insert  code  to  check  for 
integer  overflow  where  it  might  hap¬ 
pen  and  omit  it  where  it  can’t.  Con¬ 
ceivably,  the  compiler,  having  for- 
seen  potential — or  inevitable! — inte¬ 
ger  overflow,  could  try  to  make  this 
impossible  by  rearranging  an  expres¬ 
sion  or  converting  an  intermediate  re¬ 
sult  to  real. 

Better  still,  the  technique  permits 
the  compiler  to  know  exactly  how 
much  binary  precision  is  required  by 
many  integer  variables  and  many  in¬ 
termediate  results.  Therefore,  the 
compiler  could  allocate  some  vari¬ 
ables  as  bytes  even  when  the  pro¬ 
grammer  had  declared  them  as  plain 
integer;  or  allocate  byte-sized  tempo¬ 
rary  variables  in  some  expressions;  or 
do  some  integer  operations  (especial¬ 
ly  comparisons)  using  faster  byte¬ 
mode  instructions. 

Best  of  all,  a  compiler  using 
Welsh’s  technique  could  give  the  pro¬ 
grammer  marvelous  feedback.  It 
could  issue  warning  messages  like: 
“This  expression  gets  a  runtime 
check  because  variable  K  might  ex¬ 
ceed  a  value  of  135.”  You  would  look 
at  your  code  and  say,  “Oh,  so  it 
might,  I  better  fix  that,”  or  you  might 
say,  “No,  it  can’t,  I  better  declare  K 
as  a  subrange  instead  of  integer  so  my 


intentions  will  be  clearer.” 

Finally,  anyone  who  cares  about 
compiler  design  should  read  D.  Han¬ 
son’s  “Simple  Code  Optimizations” 
( SP&E ,  Vol.  13,  No.8,  August  1983). 
This  is  a  superb  paper,  at  once  practi¬ 
cal  and  educational.  From  the  ab¬ 
stract:  “The  simplicity  of  most  pro¬ 
grams  suggests  that  straightforward 
optimizations  would  produce  the 
greatest  dividends.  This  paper  de¬ 
scribes  three  such  optimizations  suit¬ 
able  for  one-pass  compilers  . . .  none 
requires  major  changes  to  the  size  or 
structure  of  the  compiler  [but  each] 
results  in  at  least  a  10%  reduction  in 
object  code  size  and  a  corresponding 
reduction  in  execution  time.” 

Getting  Standard 

If  you  want  to  write  a  verifiably  stan¬ 
dard  Pascal  compiler — or  bring  an 
old  one  up  to  snuff  (hint,  hint) — you 
could  begin  in  worse  ways  than  by 
getting  in  touch  with  the  Software 
Consulting  Services  of  Lehigh  Uni¬ 
versity  (Ben  Franklin  Technology 
Center  125,  Murray  H.  Goodman 
Campus,  Lehigh  University,  Bethle¬ 
hem,  PA  18015).  There  you  can  ob¬ 
tain  the  latest  version  of  the  British 
Standards  Institute’s  Pascal  Valida¬ 
tion  Suite  and  Quality  Control  Pack¬ 
age.  The  Suite  contains  740  test  pro¬ 
grams.  The  Package  consists  of  the 
Standard  Pascal  Model  Implementa¬ 
tion  (a  complete  Pascal  compiler  in 
ISO  standard  Pascal),  a  program  that 
audits  any  Pascal  source  for  ISO  va¬ 
lidity  (it  has  been  applied  to  itself,  of 
course),  and  a  set  of  tools  that  let  you 
automate  your  test  runs  under  Unix. 

The  notice  of  this  offer  ( Sigplan 
Notices,  Vol.  20,  No.  6,  June  1985) 
neglects  to  mention  price  or  media 
formats.  But  it  exists,  so  you’ve  got 
no  excuses  left,  Phillipe  .... 

MSDOS  Disk  Speed 

When  we  published  your  figures  on 
throughput,  we  remarked  on  how  un¬ 
likely  some  of  the  MSDOS  numbers 
seemed.  Users  of  IBM  PCs  and  Ze¬ 
nith  ZlOOs  reported  throughput 
rates,  using  5-inch  diskette  drives,  of 
9  kilobytes  per  second  and  more. 
These  were  strikingly  faster  than  the 
rates  reported  by  any  8-bit  system 
with  similar  disks  and  competitive 


with  8-bit  systems  using  hard  disks 
and  M-drives. 

How  does  MSDOS  do  it,  we  asked, 
and  perhaps  we  gave  the  impression 
we  doubted  the  numbers.  (We  never 
supposed  the  numbers  were  false;  we 
did  suspect  that  some  unreported 
buffering  or  caching  factor  in 
MSDOS  was  producing  apparent  high 
speed  while  not  doing  the  same 
amount  of  I/O  as  other  systems.) 

Well,  we’ve  measured  the  effect 
with  our  own  stopwatch  and  it’s  real, 
alright.  We  still  don’t  know  how  it’s 
done,  but  we  do  know  how  your  pro¬ 
grams  can  tap  into  it  for  a  major 
speed  improvement. 

Five  Ways  To  Read 

There  are  five  distinct  ways  for  a  pro¬ 
gram  to  request  disk  input  in  MSDOS 
on  an  IBM  PC  or  compatible.  The 
modern,  preferred  way  is  to  open  a 
“handle”  for  the  file  with  DOS  func¬ 
tion  3Dh  then  use  function  3Fh  to  ask 
for  input  of  one  byte  up  to  64K. 

Two  ways  are  based  on  the  CP/M- 
compatible  functions  that  use  a  File 
Control  Block  (FCB).  The  program 
must  first  open  an  FCB  with  DOS 
function  OFh  and  establish  the  Disk 
Transfer  Area  (buffer  address)  with 
function  lAh.  Then  it  can  request 
that  one  record  be  transferred  from 
the  file  to  the  buffer  with  function 
14h.  The  size  of  a  record  is  set  in  the 
FCB;  it  may  be  any  number  from  one 
byte  up  to  64K.  Alternatively,  the 
program  may  use  function  27h  to  re¬ 
quest  reading  any  number  (from  one 
to  64K)  of  records  of  the  current  size, 
so  long  as  their  aggregate  length 
doesn’t  exceed  64K. 

The  fourth  method  of  input  is  to 
use  DOS  interrupt  (not  function)  25h. 
This  reads  raw  sectors  unrelated  to 
the  file  structure  of  a  disk.  In  order  to 
use  it  for  file  input,  your  program 
would  have  to  interpret  the  FAT,  disk 
directory,  and  subdirectories  on  its 
own. 

The  fifth  way  is  to  call  on  the  IBM 
or  compatible  ROM  BIOS  through  in¬ 
terrupt  13h.  Like  DOS  interrupt  25h, 
this  provides  raw  sector  input  unre¬ 
lated  to  the  file  system.  It’s  even  more 
device  oriented,  however,  in  that  you 
have  to  address  data  by  side  and  cyl¬ 
inder,  whereas  the  DOS  interrupt 


Dr.  Dobb's  Journal,  September  1985 


11 

661 


uses  a  scheme  of  “logical  sector  num¬ 
bers.” 

We  set  out  to  see  which  of  these 
ways  was  the  fastest  and,  if  possible, 
to  figure  out  which  of  them  produced 
those  high  throughput  numbers  and 
why.  We  ran  a  series  of  experiments 
on  a  DOS  2.1  system  in  an  XT-ized 
PC.  We  confined  our  recorded  mea¬ 
surements  to  the  double-sided  dis¬ 
kette  drive;  hard  disks  vary  widely 
from  brand  to  brand,  and  we  couldn  t 
find  out  any  of  this  one’s  vital  statis¬ 
tics  such  as  number  of  tracks  and  cyl¬ 
inders  or  seek  times. 

First  we  formatted  a  new  diskette. 
On  it  we  created  a  file  of  16  cylinders 
( 144K)  using  the  BASIC  program  in 
Listing  One  (page  17).  That  file, 
which  commenced  in  cylinder  1,  was 
the  data  source  for  all  experiments. 
The  object  was  to  see  how  fast  it 
could  be  read. 

The  DOS  command  Copy  set  the 
initial  hurdle.  We  read  the  file  with 

COPY  TESTF1LE.001  NUL 

and  timed  it:  9.8  seconds.  That’s 
quick;  just  how  quick  wasn’t  clear  un¬ 
til  we  tried  other  programs. 

Measure  Molasses 
Our  first  trial  was  written  in  BASIC 
(see  Listing  Two,  page  17).  BASIC 
makes  it  easy  to  calculate  a  program’s 
elapsed  time.  Unfortunately,  we  had 
no  difficulty  timing  this  one  with  a 
hand-held  watch;  it  took  122  seconds 
to  read  the  file.  Although  there’s  no 
good  reason  why  an  I/O-bound  job 
should  run  any  faster  when  compiled, 
we  figured  it  couldn’t  very  well  run 
slower.  It  didn’t,  but  at  64  seconds  it 
still  wasn’t  what  we’d  call  fast. 

Maybe  one  of  the  other  compiled 
languages  would  do  better?  We  com¬ 
piled  the  program  of  Listing  Three 
(page  17)  with  Microsoft  Pascal  ver¬ 
sion  3.3.  It  ran  in  61  seconds.  Then  we 
tried  the  program  in  Listing  Four 
(page  17),  compiled  by  Microsoft  C 
version  3.0.  Sixty-one  seconds,  ho 
hum.  Here’s  how  four  language  im¬ 
plementations  do  at  reading  a  sequen¬ 
tial  text  file;  the  numbers  are  multi¬ 
ples  of  the  Copy  command’s  time: 


BASICA 

12.5 

BASCOM 

6.5 

Pascal  3.3 

6.2 

C  3.0  6.2 

You  can  say  this  makes  the  lan¬ 
guages  look  bad,  or  you  can  say  it 
makes  Copy  look  extremely  good. 
But  how  does  Copy  do  it? 

Down  to  Brass  Stacks 

The  remaining  tests  had  to  be  done  at 
the  assembly-language  level.  Rather 
than  writing  and  assembling  pro¬ 
grams  for  each  of  many  tests,  we  used 
the  Assemble  command  of  Debug  to 
write  short  programs.  These  could  be 
parameterized  by  setting  counts  in 
their  registers  and  by  modifying  con¬ 
stants  with  Enter.  The  entry  of  the 
handle-input  test,  for  example,  resem¬ 
bled  Listing  Five  (page  16).  Once  that 
code  was  entered,  the  program  could 
be  set  up  for  a  certain  input  length  by 
entering  a  new  immediate  value  into 
one  instruction.  For  example, 

-e  10e,48 

sets  up  to  read  two  cylinders’  worth  of 
data  on  each  DOS  call. 

We  took  a  sequence  of  measure¬ 
ments,  varying  the  number  of  bytes 
requested  per  DOS  interrupt.  For 
handle  input,  the  results  were: 


CX= bytes 

Seconds 

200h 

61.8 

1200h 

13.6 

2400h 

10.2 

4800h 

12.0 

9000h 

10.2 

Reading  a  handle  by  sector-sized 
chunks  (200h  bytes)  is  clearly  not  a 
good  idea  (the  similarity  of  that  time 
to  those  turned  in  by  the  C  and  Pascal 
compilers  is  suggestive,  however). 
Reading  by  tracks  (1200h  bytes)  is 
five  times  faster,  and  reading  by  cyl¬ 
inders  (2400h)  is  faster  by  a  remark¬ 
able  factor  of  six.  But  block  sizes  be¬ 
yond  9K,  one  cylinder,  don’t  give 
further  improvement. 

The  new  handle  operations  (there 
must  be  a  better  name  for  them)  are 
supposed  to  replace  the  older  CP/M- 
like  ones.  To  do  that,  they  had  better 
be  no  slower,  and  our  tests  indicate 
they  are  not.  First  we  tried  reading 
single  records  with  function  Ofh  while 
varying  the  size  of  the  record: 


Record 

Seconds 

80h 

64.5 

1200h 

13.8 

2400h 

10.0 

4800h 

12.0 

9000h 

10.0 

The  results  are  practically  identical  to 
those  for  function  3Fh  above — differ¬ 
ences  of  0.2  second  aren’t  significant 
for  hand-made  timings.  The  time 
when  reading  128-byte  records  is  sug¬ 
gestively  close  to  the  BASCOM  time. 

When  we  held  the  record  size  con¬ 
stant  at  200h  and  read  different  num¬ 
bers  of  records  with  function  27h,  we 
got  identical  times.  The  lesson  is  that 
no  matter  which  DOS  function  you 
use,  you  should  request  at  least  4,608 
(nine  times  512)  bytes  at  a  time.  To 
ask  for  less  is  to  multiply  your  diskette 
input  time  by  a  factor  of  3  to  6. 

Greased  Lightning 

An  assembly  program  reading  full 
cylinders  can  approach  the  speed  of 
the  Copy  command.  Is  that  the  limit? 
We  suspected  as  much  but  continued 
our  experiments. 

When  we  used  the  ROM  BIOS  in¬ 
terrupt  13h  to  read  the  same  cylin¬ 
ders,  the  best  time  we  could  achieve 
was  1 3  seconds,  30%  longer  than  the 
best  time  we  got  with  DOS  input  re¬ 
quests!  Now  we  know  two  things:  the 
DOS  BIOS  doesn’t  use  the  ROM  BIOS 
for  its  disk  input,  and  DOS’s  BIOS 
does  it  better. 

That  made  it  mandatory  to  try  out 
DOS  interrupt  25h,  presumably  the 
foundation  of  the  DOS  file  services. 


Here  are  the  results: 

CX  =  sectors 

Seconds 

lh 

56.0 

2h 

31.0 

4h 

19.6 

8h 

12.5 

9h 

6.8 

12h 

6.8 

24h 

10.2 

48h 

8.5 

Now,  ponder  those  numbers  a  mo¬ 
ment.  The  best  times,  as  before,  oc¬ 
cur  when  the  program  asks  for  one 
track  or  one  cylinder  per  call.  Our 
program  set  up  by  forcing  the  disk  to 
cylinder  0;  during  the  timed  portion, 


14 

662 


Dr.  Dobb’s  Journal,  September  1985 


it  read  cylinders  1-16,  inclusive. 
That’s  32  tracks  read  plus  16  one- 
track  seeks.  It  takes  200  milliseconds 
for  a  5-inch  diskette  drive  to  rotate 
once.  There  are  just  34  periods  of 
200-milliseconds  in  6.8  seconds. 
Therefore,  DOS  sequential  input  can 
reach  and  sustain  an  input  rate  of  one 
track  per  disk  rotation! 

Hurrah  for  DOS;  no  faster  input 
rate  is  possible.  But  we  are  left  with 
some  mysteries.  What  does  DOS  do 
differently  from  the  ROM  BIOS  to  get 
such  performance  out  of  the  diskette 
adaptor?  Why  is  4-cylinder  input 
slower  than  2-cylinder;  why  is  3-cylin¬ 
der  input  slower  still?  How  does  the 
Copy  command  interface  to  DOS?  It 
consistently  shaved  a  couple  of  tenths 
of  a  second  off  the  best  time  we  could 
get  out  of  handle  or  FCB  input. 

To  summarize  these  results  for¬ 
mally:  for  1  ^  n  ^  8,  if  a  program 
requests  n+ 1,  512-byte  sectors  on 
each  input  operation,  its  input  time 
will  be  roughly  proportional  to  1  /n. 
We  can  see  the  practical  difficulties 
of  giving  every  open  file  a  4,608-byte 
buffer,  but  still,  isn’t  it  sad  how  slow 
your  compiled  programs  run  com¬ 
pared  to  the  Copy  command? 

DD| 

Reader  Ballot 

Vote  for  your  favorite  feature/article. 

Circle  Reader  Service  No.  1 90. 


Dr.  Dobb's  Clinic  (Text  begins  on  page  10) 
Listing  One 


lie 

120 

130 

140 

150 

160 

170 

180 

190 

200 

210 

220 


'Create  a  16-cylinder  file  on  a  d/s,  5-inch  diskette 
OPEN  "TESTFILE. 001"  FOR  OUTPUT  AS  *1 
LET  D$  =.  SPACES (254)  plus  CR,  LF  =  256  bytes 
FOR  CYL  f  1  TO  16 

FOR  SIDE  =  0  TO  1 

FOR  SECTOR  =  1  TO  9  DOS  2.x  and  above 
FOR  BYTES  =  1  TO  512  STEP  256 
PRINTI1 , D$ 

NEXT  BYTES 
NEXT  SECTOR 
NEXT  SIDE 
NEXT  CYL 

close  #1  End  Listing  One 


Listing  Two 

100  'Read  a  file  and  see  how  long  it  takes 
110  '  —  function  to  convert  TIMES  to  seconds 
120  DEF  FNSEC(X$)  «  3600  *  VAL (LEFTS (XS, 2 ) )  + 

60  *  VAL(MIDS(X$,4,2) )  + 

VAL  (RIGHTS  (XS,  2 )  ) 

130  OPEN  "TESTFILE. 001"  FOR  INPUT  AS  #1 
140  STARTSS  =  TIMES 
150  WHILE  NOT  EOF(l) 

160  LINE  INPUT#1,DS 
170  WEND 

180  STOPS $  =  TIMES 

190  PRINT  FNSEC  ( STOPS  S ) -FNSEC  (STARTSS)  ;"  seconds."  End  Listing  Two 


Listing  Three 

program  readtest (output) ; 
var 

fin  :  text; 

dat  :  string (255); 

begin 

assign (fin, 'TESTFILE. 001 ' ) ; 
reset (fin) ; 

writeln( 'start  timing ' ,chr (7) ) ; 
while  not  eof(fin)  do 

readln (f in, dat) ; 
writeln( 'stop  timing' ,chr (7) ) ; 

end-  End  Listing  Three 

Listing  Four 

♦include  <stdio.h> 
main() 

{ 

FILE  "fin; 
char  dat [256] ; 

if  (NULL  1=  (fin  =  f open ( "TESTFILE.  001", "r")  ) ) 

fputs ( "start  timing  \x7\n",stderr) ; 
while(NULL  1=  fgets (dat, 255, f in) )  ; 
j  fputs ( "stop  timing  \x7\n",stderr) ; 

else 

,  fputs  ("can't  open  file\n«,stderr);  End  Listing  Four 


Listing  Five 


Odebug 
-f  0  Lffff  0 

-e  80  1 A : TESTFILE .001' , 0 
-a  100 

0044:0100  mov  dx,80  ;  filespec  string 
0044:0103  mov  ax,3d00  ;  open  it 
0044:0106  int  21 

0044:0108  mov  bx,ax  ;  handle  in  BX 
0C44:010A  mov  dx,200  ;  data  to  DS:200 
0C44:010D  mov  cx,200  ;  block  size 
0044:0110  mov  ax,3f00  ;  read  CX  bytes 
0044:0113  int  21 
0044:0115  cmp  ax,cx  ;  read  ok? 
0044:0117  jz  110  ;  if  so  repeat 
0044:0119  nop  ;  breakpoint  here 
0C44 :01lA  mov  ax,3e00  ;  close  BX=hdl 
0044 : 011D  int  21 

0C44:011F  imp  100  ;  set  up  for  next 
0044:0121  ~C 


End  Listings 


17 


Dr.  Dobb's  Journal,  September  1985 


66.3 


How  Compilers  Work 


by  Allen  Holub 


—A  Simple  Desk  Calculator 


Over  the  years  DDJ  has  published  sev¬ 
eral  recursive  descent  compilers,  most 
notable  among  which  are  the  various 
versions  of  Small-C.  This  month  we’re 
going  to  talk  about  how  this  sort  of 
compiler  works.  In  the  interest  of  clar¬ 
ity  we’ll  reduce  the  problem  from  rec¬ 
ognizing  a  real  computer  language  to 
analyzing  a  small  arithmetic  expres¬ 
sion,  a  function  that  is  part  of  a  desk 
calculator  program.  The  same  tech¬ 
niques  are  applicable  to  both.  Our  ex¬ 
pression  analyzer  will  take  as  input  an 
ASCII  string  representing  an  arithme¬ 
tic  expression.  Only  numbers,  paren¬ 
theses,  and  the  operators  +  —  /  and  * 
are  legal.  The  analyzer  will  return  the 
result  of  the  evaluation.  For  example, 
if  you  give  it  the  string  (3+  1)*2  the 
analyzer  will  return  the  number  8. 
The  routine  is  pretty  stupid,  but  the 
purpose  of  this  exercise  is  to  under¬ 
stand  compilers,  not  to  analyze  com¬ 
plicated  expressions. 

Every  compiler  has  three  function¬ 
ally  distinct  parts.  These  parts  are  of¬ 
ten  combined,  but  it’s  best  to  look  at 
them  as  separate  functions.  The  first 
part  of  the  compiler  is  a  “token”  rec¬ 
ognizer.  A  token  is  some  collection  of 
ASCII  characters  from  the  input 
stream  that  are  meaningful  to  the 
compiler  when  taken  as  a  group.  That 
is,  a  program  can  be  seen  as  a  collec¬ 
tion  of  tokens,  each  of  which  is  made 
up  of  one  or  more  sequential  ASCII 
characters.  For  example,  the  ASCII 
character  ;  is  a  token  in  C,  similarly 
the  keyword  while  is  a  token.  The 
matter  is  complicated  by  operators 
like  +  ,  ++  and  +=,  all  of  which  are 
single  tokens.  A  token  is,  then,  a  sort 
of  programming  atom,  an  indivisible 
part  of  the  language  (you  cannot,  for 
example,  say  wh  ile)  and  a  token  rec¬ 
ognizer  is  a  subroutine  that,  when 
called,  will  return  the  next  token  from 
the  input  stream.  Usually  tokens  are 


represented  internally  as  an  enumer¬ 
ated  type  or  as  a  set  of  integer  values 
corresponding  to  #defines  in  a  header 
file  somewhere,  and  the  token  recog¬ 
nizer  returns  this  integer  value. 
(Small-C  doesn’t  do  this.  Rather,  var¬ 
ious  subroutines  within  the  compiler 
retrieve  the  tokens  from  input  one  at  a 
time  as  they  are  needed.) 

The  second  part  of  the  compiler, 
and  the  part  that  does  most  of  the 
work,  is  the  “parser.”  The  verb  to 
parse  retains  its  meaning  when  ap¬ 
plied  to  compilers:  “to  resolve  (a  sen¬ 
tence,  etc.)  into  its  component  parts 
of  speech  and  describe  them  gram¬ 
matically.”1  Just  replace  the  word 
“sentence”  with  “program.”  Com¬ 
puter  languages  may  be  described  by 
means  of  a  formal  grammar  (we’ll 
look  at  these  in  a  moment)  and  the 
parser  breaks  up  a  program  into  its 
component  parts  and  interprets  the 
parts  in  a  larger,  grammatical  con¬ 
text.  That  is,  a  parser  organizes  the 
tokens  returned  from  the  token  rec¬ 
ognizer  in  such  a  way  that  the  com¬ 
piler  can  generate  code  conveniently. 

A  good  (though  not  very  practical) 
way  to  look  at  the  process  is  as  the 
creation  of  a  “parse  tree.”  For  exam¬ 
ple,  The  expression  (a  —  b)*(c  —  d) 
can  be  organized  into  the  tree  shown 
in  Figure  1  (page  20). 

The  third  part  of  the  compiler,  the 
code  generator,  traverses  the  parse 
tree  in  an  orderly  way,  generating 
code  according  to  certain  rules.  For 
example,  if  we  do  a  “post  order”  tra¬ 
versal  of  the  tree  shown  in  Figure  1 
(visit  the  left  node,  the  middle  node, 
the  right  node  and  then  the  root  re¬ 
cursively)  the  tokens  will  be  read  in 
the  following  order: 

(  a  b  —  )  (  c  d  —  )  * 

Now,  the  expression  may  be  evaluat¬ 


ed  by  applying  the  following  rules  to 
each  token  in  the  tree  as  it  is  visited: 

1 )  if  the  token  is  a  parenthesis,  do 
nothing; 

2)  if  the  token  is  a  variable  (a,b,c  or 
d),  push  the  variable  onto  a  stack; 

3)  if  the  token  is  a  minus  (  — ),  pop 
two  items  off  the  stack,  subtract 
them  and  push  the  result; 

4)  if  the  token  is  an  asterisk  (*),  pop 
two  items  off  the  stack,  multiply 
them  together  and  push  the  result. 

When  you’re  done  parsing  (travers¬ 
ing  the  tree),  the  answer  will  be  on 
the  top  of  the  stack.  Owners  of  Hew¬ 
lett  Packard  calculators  will  be  famil¬ 
iar  with  the  process.  In  a  real  compil¬ 
er,  the  actual  rule  applied  will  be 
some  function  of  the  type  of  token 
found  and  the  position  of  that  token 
in  the  parse  tree. 

There  are  several  flavors  of  pars¬ 
ers.2  Most  compilers  use  table  driven 
parsers  for  several  reasons.  It’s  easier 
to  automate  compiler  creation  with 
table  driven  parsers;  they’re  also  more 
efficient.  The  Unix  utility  YACC  (Yet 
Another  Compiler  Compiler),  when 
given  a  formal  description  of  a  pro¬ 
gramming  language,  creates  a  set  of 
tables  that  can  be  used  by  a  generic, 
table  driven  parser.  Similarly,  LEX 
(LEXical  analyzer)  can  output  a  C 
program  that  recognizes  tokens.3 

Unfortunately,  most  public  do¬ 
main  compilers  (and  many  commer¬ 
cial  ones)  don’t  use  the  more  sophisti¬ 
cated  table  driven  methods  (Small-C 
is  no  exception).  These  compilers  use 
a  parsing  method  known  as  “recur¬ 
sive  descent.”  Recursive  descent 
parsers  are  easier  to  understand  than 
their  table  driven  cousins.  However, 
they  have  disadvantages.  They  are  in¬ 
herently  inefficient,  using  large 
amounts  of  stack  space,  and  con- 


18 


Dr.  Dobb’s  Journal,  September  1985 


struction  of  them  cannot  be  automat¬ 
ed.  To  change  the  way  a  recursive  de¬ 
cent  compiler  works,  you  have  to 
change  the  compiler  itself.  To  change 
a  table  driven  compiler  you  need  only 
change  the  table.  So,  maintenance  is 
a  problem  with  recursive  descent 
compilers. 

Grammars:  Representing 
Computer  Languages 

The  best  way  to  start  writing  a  pro¬ 
gram  is  to  reduce  the  problem  to 
some  sort  of  symbolic  form.  Pseudo¬ 
code,  flow  charts,  Warnier-Orr  dia¬ 
grams  are  all  examples  of  this  kind  of 
symbolic  reduction.  Compilers  are  no 
exception  to  this  process.  When  writ¬ 
ing  a  compiler,  you  start  by  repre¬ 
senting  the  programming  language  to 
be  compiled  in  a  formal,  symbolic 
format  called  a  “grammar.”  Any  one 
programming  language  can  be  de¬ 
scribed  by  several  grammars.  The 
type  of  parser  you’re  going  to  use  will 
determine  which  of  these  is  best  for 
your  application.  The  most  useful  no¬ 
tation  used  for  grammars  is  the 
Backus-Naur  Format  (abbreviated 
BNF),  which  we’ll  see  in  a  moment. 

In  order  to  create  our  expression 
analyzer,  we  need  to  start  with  a 
grammar.  The  first  question  to  ask  is: 
what  exactly  is  an  expression?  You’ll 
remember  from  high  school  that  an 
expression  is  composed  of  factors.  A 
factor  by  itself  (i.e.  a  single  number) 
is  an  expression,  as  are  two  factors 
separated  by  an  operator.  BNF  repre¬ 
sentations  of  these  two  rules  are: 

<expression>  ::=  <factor> 

< expression >  ::  = 

<factor>  <operator>  <factor> 

We  can  save  some  typing  by  using  the 
vertical  bar  to  represent  OR: 

<expression>  ::=  <factor> 

1  <factor>  <operator>  <factor> 

You’ll  note  in  these  definitions  that  no 
element  of  the  BNF  definition  of  the 
expression  is  a  real  symbol,  one  that 
can  actually  be  found  in  the  input 
stream.  That  is,  <factor>  and  <op- 
erator>  both  have  to  be  defined  fur¬ 
ther  before  they  can  be  related  to  a 
real  program.  Symbols,  such  as  <fac¬ 


tor>,  which  need  further  definition, 
are  called  “non-terminal”  symbols. 
Symbols  that  can  be  found  in  the  in¬ 
put  are  called  “terminal”  symbols. 
The  four  terminal  symbols  that  can  be 
operators  are  +  —  /  and  *.  A  BNF 
rule  for  <operator>  is: 

<operator>  ::=  +  1  —  !  *  !  / 

Defining  a  factor  is  a  little  harder. 
A  factor  can  be  a  number  but  it  can 
also  be  another  expression  (as  in: 
a  +  b  — d,  a  +  b  is  one  factor  and  d  is 
the  second  factor).  A  BNF  definition 
of  factor  is: 

<factor>  ::=  <number> 

1  <expression> 

The  only  symbol  yet  to  be  defined  is 
<number>.  Since  a  number  is  an 
easy  thing  for  the  token  recognizer  to 
find,  we’ll  cheat  a  little  and  just  de¬ 
fine  <number>  in  English.  Our  en¬ 
tire  grammar  is  shown  in  Figure  2  (at 
right). 

Notice  that  every  non-terminal 
used  to  the  right  of  a  ::  =  is  also  de¬ 
fined  to  the  left  and  that  no  terminal 
symbols  are  found  to  the  left  of  a  ::  = . 
Let’s  test  the  grammar  by  plugging  in 
an  example:  1+2.  1  and  2  are  both 
<number>s  so  we  can  replace  them 
with  the  equivalent  non-terminal 
symbols  by  using  rule  4: 

1  +  2 

<number>  +  <number> 

According  to  rule  3,  a  single  number 
is  also  a  factor,  so  we  can  do  another 
replacement: 

<number>  +  <number> 
<factor>  +  <factor> 

The  +  can  be  evaluated  using  rule  2: 

<factor>  +  <factor> 
<factor>  <operator>  <factor> 

And  finally,  by  using  rule  1,  we  can 
replace  the  above  with  a  single 
<expression>: 

<factor>  <operator>  <factor> 
<expression> 


So,  we  can  reduce  the  input  tokens 
1  +  2  to  an  <expression>  using  the 
rules  of  the  grammar.  Therefore,  we 
conclude  that  1  +  2  is  a  legal  expres¬ 
sion  in  our  grammar. 

What  if  there’s  an  error  in  the  ex¬ 
pression?  Let’s  try  to  parse  1  +  *.  We 
can  apply  rules  3  and  6  to  yield: 

<number>  <operator>  <operator> 

and  then  apply  rule  4  to  get: 

<factor><operator>  <operator> 

But  there  is  no  rule  we  can  apply  to 
reduce  this  any  further.  So,  we  con¬ 
clude  that  1  +  *  is  not  an  expression 
as  defined  by  our  grammar. 

Parsing  with  a  Grammar 

So  a  parser  can  be  seen  as  a  program 
that  reduces  a  collection  of  input  to¬ 
kens  to  a  single  non-terminal.  We 
have  just  “parsed”  the  expression 
1  +  2.  To  turn  a  parser  into  a  real  com¬ 
piler,  we  need  to  make  it  do  something 
active  too,  namely,  generate  code.  So, 
we  associate  an  action  rule  with  each 
grammatical  rule.  Our  grammar, 
slightly  shuffled  around  and  with  ac¬ 
tion  rules  added  is  shown  in  Figure  3 
(page  20).  Every  time  we  apply  a 
grammatical  rule,  we’ll  also  perform 
the  action  specified  in  the  equivalent 
action  rule.  1  +  2,  parsed  with  the 
grammar  in  Figure  3  is  shown  in  Fig¬ 
ure  4  (page  20).  A  somewhat  more  in¬ 
volved  example  is  given  in  Figure  5  (at 
left).  You’re  beginning  to  see  how  the 
process  works.  If  the  action  rules  had 
generated  the  code  necessary  to  per¬ 
form  the  operation,  rather  than  actu¬ 
ally  doing  the  operation  itself,  we’d 
have  a  compiler. 

As  you’ve  probably  noticed,  the 
grammar  just  defined  isn’t  very  use¬ 
ful.  At  very  least  we’d  like  to  have 
parentheses  and  negative  numbers. 
We’d  also  like  to  be  able  to  negate  an 
entire  expression  (i.e.  -(17*11)). 
You’ll  also  notice  in  the  above  exam¬ 
ples  that  the  expression  is  just  parsed 
left  to  right,  with  all  possible  substi¬ 
tutions  made  as  we  parse.  A  more  re¬ 
alistic  grammar  performs  its  substi¬ 
tutions  in  a  somewhat  more  complex 
way,  and  the  grammar  has  to  reflect 
this  complexity.  In  addition,  a  gram- 


Dr.  Dobb's  Journal,  September  1985 


19 

665 


Figure  1 

Parse  Tree  for  the  Expression  (a  -  b)  *  (c  -  d) 


1)  <expression> 

2)  <operator> 

3)  <factor> 

4)  <number> 


::=  <factor> 

I  <factor>  <operator>  <factor> 
+!-!*!/ 

::=  <number>  !  <expression> 

::=  A  string  of  ASCII  characters  in  the  range  0'  to  '9' 


Figure  2 

A  Simple  Expression  Recognition  Grammar 


Grammar: 

1)  <expression>  ::  =  <factor> 

2)  <expression>  ::=  <factor>  <operator>  <factor> 

3)  <operator>  +!-!*!/ 

4)  <factor>  ::  =  <number> 

5)  <factor>  ::  =  <expression> 

6)  <number>  ::  =  Any  string  of  ASCII  characters  in  the  range  'O'  to  '9\ 

Action  rules: 

1)  Do  nothing. 

2)  Pop  two  objects  off  the  stack,  apply  the  operator  remembered  in  rule  3  and  then 

push  the  result. 

3)  Remember  the  operator  for  rule  2. 

4)  Push  the  number  onto  the  stack. 

5)  Do  nothing. 

6)  Translate  the  ASCII  string  into  a  number. 


Figure  3 

Adding  Actions  to  the  Grammar 


1 

+ 

rule: 

2 

action: 

<number> 

+ 

2 

6 

Translate  ASCII  to  int 

<factor> 

+ 

2 

4 

Push  1 

<factor> 

+ 

<number> 

6 

Translate  ASCII  to  int 

<factor> 

+ 

<factor> 

4 

Push  2 

<factor>  <operator> 
<expression> 

<factor> 

3 

2 

Remember  the  + 

Pop  the  1  &  2,  apply 
result. 

Figure  4 

Parsing  1+2  Using  Grammar  in  Figure  Three 


mar  has  to  be  organized  so  that  the 
parser  can  always  tell  what  rule  to 
apply  based  on  the  current  input  sym¬ 
bol  and  the  current  rule  being  pro¬ 
cessed.  A  better  expression  recogniz¬ 
ing  grammar  is  given  in  Figure  6  (at 
left).  We’ll  use  this  grammar  in  our 
actual  program.4 

A  Recursive  Descent  Parser 

The  best  way  to  see  how  a  parser 
works  is  to  look  at  one.  Before  dis¬ 
cussing  the  parser  proper,  I  want  to 
talk  a  little  about  how  the  program 
(Listing,  page  25)  is  organized.  The 
actual  subroutines  in  the  parser  are 
highly  recursive.  As  such,  they’ll  use 
up  a  lot  of  stack  space  as  they  work. 
Because  of  this  stack  usage,  we  want 
to  pass  as  few  parameters  as  possible 
to  the  subroutines  (because  all  these 
parameters  take  up  stack  space).  So, 
we  make  global  those  variables  that 
would  normally  be  passed  to  the  sub¬ 
routines  as  arguments.  However,  this 
practice,  introduces  new  problems.  In 
C,  all  non-static  global  variables  are 
shared  between  all  modules  in  a  pro¬ 
gram.  But  the  expression  parser  is 
probably  going  to  be  a  library  routine 
and  we  don’t  want  it  to  interfere  with 
the  normal  workings  of  the  rest  of  a 
program.  Moreover,  we  don’t  want 
the  programmer  to  have  to  remember 
that  certain  globals  are  used  by  a  par¬ 
ticular  library  routine  and  can’t  be 
used  anywhere  else.  So,  we  make  the 
globals  static.  We’ll  also  make  static 
those  subroutines  that  are  only  used 
internally.  Now,  however,  we  need 
some  way  to  initialize  the  static  glo¬ 
bals  from  outside  the  parser  module. 
We  do  this  initialization  with  the 
“access  routine”  starting  on  line  84  of 
the  Listing  (the  only  externally  ac¬ 
cessible  subroutine  in  the  module). 
This  access  routine  (called  parse(  )) 
does  nothing  but  initialize  our  globals 
and  then  call  expr(  )  to  do  the  work. 

Another  organizational  concern  is 
the  main(  )  routine  on  lines  34-79. 
The  primary  purpose  of  main(  )  is  to 
test  parse(  ),  thus  the  #ifdef/#endif 
on  lines  32  and  81.  DEBUG  is  not 
#defined  when  we  compile  for  inclu¬ 
sion  in  a  library.  The  main(  )  routine 
given  is  moderately  useful  in  its  own 
right.  You  can  enter  the  expression 
(17/(2*12))  from  the  command  line 
or  you  can  just  type  expr  and  then 


22 


Dr.  Dobb’s  Journal,  September  1985 


enter  expressions  as  the  program 
prompts  you,  sort  of  a  rudimentary 
desk  calculator. 

Moving  back  to  parsers,  there  are  a 
few  things  to  notice  about  the  gram¬ 
mar  in  Figure  6.  First,  the  left-most 
symbol  following  the  ::  =  is  always  ei¬ 
ther  a  terminal  or  the  same  non-ter¬ 
minal  for  all  rules.  That  is,  all  rules 
associated  with  <expr>  have  <fac- 
tor>  as  their  left-most  symbol.  The 
left-most  symbol  of  all  <factor> 
rules  is  either  a  terminal  ((or  — )  or 
the  non-terminal  <constant>.  The 
left-most  symbol  of  a  constant  has  to 
be  an  ASCII  digit.  This  property  of 
the  grammar  is  required  by  the 
parser  so  that  it  can  know  what  rule 
to  apply  in  a  given  situation.  For  ex¬ 
ample,  when  evaluating  an  <expr>, 
the  parser  will  always  apply  a  rule  as¬ 
sociated  with  <factor>  first. 

A  second  property  of  the  grammar 
is  that  the  definitions  for  <expr> 
and  <factor>  are  recursive.  An 
<expr>  is  defined  in  terms  of  other 
<expr>s.  The  recursion  in  <fac- 
tor>  is  two  levels  deep.  A  <factor> 
is  defined  in  terms  of  an  <expr>, 
which  is  in  turn  defined  in  terms  of  a 
<factor>.  The  recursion  in  the 
grammar  suggests  that  we  can  also 
use  recursion  in  a  parser  that  imple¬ 
ments  the  grammar. 

So,  given  an  appropriate  grammar, 
we  can  translate  that  grammar  di¬ 
rectly  into  a  parser.  In  the  program 
given  here,  all  non-terminal  symbols 
in  the  grammar  have  an  equivalent 
subroutine  with  the  same  name.  The 
routine  for  <expr>  starts  on  line 
104,  for  <factor>  on  line  124  and 
for  <constant>  on  line  155. 

Looking  again  at  the  grammar  in 
Figure  6,  we  see  that  the  first  thing 
done  in  all  the  <expr>  rules  (1-5). 
is  to  look  for  a  <factor>.  Similarly, 
the  first  thing  the  subroutine  expr(  ) 
does  is  call  the  subroutine  factor(  ) 
(on  line  108).  Looking  back  at  the 
grammar,  the  next  thing  <expr> 
does  is  look  for  a  terminal  symbol  (ei¬ 
ther  a  *  /  +  —  or  a  null  string).  The 
equivalent  code  is  the  switch  on  lines 
1 1 0—1 1 7.  The  default  case  takes  care 
of  the  null  terminal  (rule  1).  The  re¬ 
cursive  evaluation  of  <expr>  in 
rules  2—5  is  also  done  in  the  switch. 
On  line  1 19  expr(  )  returns  the  evalu¬ 
ated  expression. 


Apply 

Action: 

rule: 

1+2-3 

- 

Start  here 

<number>  +  2  —  3 

6 

Translate  "  1 "  to  int 

<factor>  +  2-3 

4 

Push  1 

<factor>  <operator>  2-3 

3 

Remember  + 

<factor>  <operator>  <number>  -  3 

6 

Translate  "2"  to  int 

<factor>  <operator>  <factor>  -  3 

4 

Push  2 

<expression>  -  3 

2 

Pop  two  numbers,  apply  +  and 

push  result. 

<factor>  -  3 

1 

Do  nothing. 

<factor>  <operator>  3 

3 

Remember  — 

<factor>  <operator>  <number> 

6 

Translate  "3"  to  int 

<factor>  <operator>  <factor> 

4 

Push  3 

<expression> 

2 

Pop  two  numbers  off  the  stack, 

subtract  and  push  the  result. 

Figure  5 

Parsing  1 

+  2 

-  3 

<expr> 

::=  <factor> 

(D 

1  <factor>  *  <expr> 

(2) 

1  <factor>  /  <expr> 

(3) 

1  <factor>  +  <expr> 

(4) 

1  <factor>  -  <expr> 

(5) 

<factor> 

::=  (  <expr>  ) 

(6) 

!  -  (  <expr>  ) 

(7) 

1  <  constant  > 

(8) 

1  -  <constant> 

0) 

<constant>  ::  =  A  string  of  ASCII  characters  in  the  range  0'-'9'. 

Figure  6 

A  More  Realistic  Expression  Recognizing  Grammar 


Factor(  )  is  somewhat  more  com¬ 
plex.  It  first  checks  (on  lines 
128-132)  for  the  leading  minus  sign 
required  by  rules  7  and  9.  After  strip¬ 
ping  off  the  minus,  rules  6  and  7  be¬ 
come  identical,  similarly  rules  8  and 
9  are  identical  once  the  minus  is  gone. 
So,  factor(  )  now  decides  which  rule 
to  process  by  looking  for  a  leading  ( 
(on  line  134).  If  it  doesn’t  find  the 
parenthesis,  rule  8  is  processed  (line 
135)  by  calling  the  subroutine  con¬ 
stant  ),  otherwise  rule  6  is  processed 
by  skipping  past  the  parenthesis  and 
then  calling  expr(  )  (lines  138-139). 
We  can  also  do  some  error  checking 
here  by  looking  for  a  close  parenthe¬ 
sis  when  expr(  )  returns  (lines 
143-147). 

The  final  part  of  the  parser  is  the 
routine  constant!  )  on  lines  155-169. 
This  routine  is  essentially  atoi(  ), 
however  it  advances  the  string  point¬ 
er  past  the  end  of  a  number  and  flags 
an  error  if  a  number  isn’t  found. 

You’ll  note  that  in  this  program 
(and  in  the  Small-C  Compiler)  the 
three  functional  parts  of  the  compiler 


are  merged  together.  There  is  no  ex¬ 
plicit  token  recognizer,  rather  each 
routine  is  responsible  for  advancing 
the  global  string  pointer  (Str)  past 
the  token  being  processed.  Similarly, 
the  code  generation  part  of  the  com¬ 
piler  is  integrated  into  the  parser.  In 
our  example,  code  generation  is  re¬ 
placed  by  the  various  return  state¬ 
ments.  In  a  real  compiler  the  routine 
factor(  )  would  generate  code  to  push 
a  value  onto  a  run-time  stack  rather 
than  return  a  value.  The  switch  on 
lines  1 10  to  117  would  be  replaced  by 
something  like: 

switch(  *Str  ) 

{ 

case  ‘-I-’: 

Str  H — h; 
expr(  ); 
codegenf  1 ); 
break; 

case  ‘  — 

Str+  +; 
expr(  ); 


Dr.  Dobb's  Journal,  September  1985 


23 

667 


codegen(2); 

break; 

/*  etc.  */ 

} 

Since  the  code  to  push  one  number 
onto  the  stack  is  generated  in  fac- 
tor(  ),  The  first  number  will  be 
pushed  by  the  factor(  )  call  on  line 
108.  By  the  time  expr(  )  returns,  the 
code  to  push  the  second  number  will 
have  been  generated  (by  the  factor(  ) 
call  inside  expr(  )).  The  call  to  code- 
genii)  inside  the  switch  generates 
the  code  needed  to  pop  two  numbers 
off  the  stack,  add  them  together,  and 
push  the  result.  The  codegen(2)  call 
behaves  similarly,  but  it  subtracts 
rather  than  adds. 

So,  that’s  the  bulk  of  the  problem. 
Hopefully  a  better  understanding  of 
what’s  going  on  will  help  when  you 
try  to  sort  out  the  workings  of  compil¬ 
ers  such  as  Small-C. 


Footnotes 

1  The  Compact  Edition  of  the  Ox¬ 
ford  English  Dictionary  (Oxford; 
Oxford  University  Press,  1971)  p. 
2083. 

2  A  good,  short,  description  of  table- 
driven  parsing  techniques  can  be 
found  in:  Dr.  Henry  A.  Seymour, 
“An  Introduction  to  Parsing,”  Dr. 
Dobb's  Journal ,  98  (December, 
1984),  pp  78-86.  A  more  in-depth 
look  at  the  subject,  and  at  compiler 
design  in  general,  can  be  found  in: 
Alfred  V.  Aho  and  Jeffrey  D.  Ull- 
man,  Principles  of  Compiler  De¬ 
sign ,  Reading,  Addison-Wesley, 
1979;  P.  M.  Lewis,  D.  J.  Rosenk- 
rantz  and  R.  E.  Stearns,  Compiler 
Design  Theory  (Reading:  Addison- 
Wesley,  1976). 

3  Axel  T.  Schreiner  and  H.  George 
Friedman,  Jr.,  Introduction  to 
Compiler  Construction  with  Unix, 
(Englewood  Cliffs:  Prentice-Hall, 


1985)  is  the  best  guide  to  YACC 
that  I  know  of.  It  takes  you,  step  by 
step,  through  the  entire  process  of 
generating  a  C  compiler  using 
YACC  and  LEX.  More  terse  de¬ 
scriptions  of  both  programs  are  in: 
Stephen  C.  Johnson,  “Yacc:  Yet 
Another  Compiler-Compiler,” 
Unix  Programmer’s  Manual  Vol.  2 
(New  York:  Holt,  Rinehart  and 
Winston,  1979)  pp.  353-387  and 
“Lex  A  Lexical  Analyzer  Genera¬ 
tor,”  ibid.,  pp.  388-400. 

4  A  grammar  for  the  C  language  is  in 
Kernighan  &  Ritchie,  The  C  Pro¬ 
gramming  Language  (Englewood 
Cliffs:  Prentice-Hall)  1978  p. 
214-219. 

DDJ 


Reader  Ballot 

Vote  for  your  favorite  feature/article. 
Circle  Reader  Service  No.  1 92. 


C  Chest  Listing  (Text  begins  on  page  18) 


1: 

2: 

3: 

4: 

5s 

6: 

7s 

8s 

9: 

10s 

11 

12 

13 

14 

15 

16 

17 

18 

19 

20 
21 
22 

23 

24 

25 

26 

27 

28 

29 

30 

31 

32 

33 

34 

35 

36 


♦include  <stdio.h> 


/* 

* 

* 

* 

* 

* 

* 

* 

* 

* 

* 

* 

* 

* 

* 

* 

* 

* 

* 

* 

* 


EXPR.Cs  (C)  Copyright  1985,  Allen  I.  Holub.  All  rights  reserved 


Evaluate  an  expression  pointed  to  by  str.  Expressions  evaluate 
right  to  left  unless  parentheses  are  present.  Valid  operators 
are  *  +  -  /  for  multiply  add,  subtract  and  divide.  The  expres¬ 
sion  must  be  formed  from  the  following  character  sets 
{  0123456789+-* ( )/  }.  White  space  is  not  allowed. 


<expr> 


<factor> 


<constant> 


<factor> 

|  <factor>  *  <expr> 
j  <factor>  /  <expr> 
j  <factor>  +  <expr> 
j  <factor>  -  <expr> 

(  <expr>  ) 

|  -(  <expr>  ) 
j  <constant> 
j  -<constant> 

A  string  of  ASCII  chars  in  the  range  • 0 ' — • 9 ' . 


*  Global  variabless 

V 


static  char  *Str  ;  /*  Current  position  in  string  being  parsed  */ 

static  int  Error  ;  /*  #  of  errors  found  so  far  V 


/* - */ 

♦ifdef  DEBUG 


main(argc,  argv) 
char  **argv; 

{  (Continued  on  next  page ) 


25 

rrb 


Dr.  Dobb’s  Journal,  September  1985 


C  Chest  Listing 


(Listing  Continued,  text  begins  on  page  18) 


37: 

38: 

39: 

40: 

41: 

42: 

43: 

44: 

45: 

46: 

47: 

48: 

49: 

50: 

51: 

52: 

53: 

54: 

55: 

56: 

57: 

58: 

59: 

60: 

61: 

62: 

63: 

64: 

65: 

66: 

67: 

68: 

69: 

70: 

71: 

72: 

73: 

74: 

75: 

76: 

77: 

78: 

79:  } 

80: 

81:  #endif 

82:  /* - 

83: 

84:  int 
85:  char 
86:  int 
87:  { 

88: 

89: 

90: 

91: 

92: 

93: 

94: 

95: 

96: 

97: 

98: 

99: 

100:  } 

101: 

102:  /* - 


/*  Routine  to  exercise  the  expression  parser.  If  an 

*  expression  is  given  on  the  command  line  it  is 

*  evaluated  and  the  result  is  printed,  otherwise 

*  expressions  are  fetched  from  stdin  (one  per  line) 

*  and  evaluated.  The  program  will  return  -1  to  the 

*  shell  on  a  syntax  error,  0  if  it's  in  interactive 

*  mode,  otherwise  it  returns  the  result  of  the 

*  evaluation. 

*/ 

char  buf[133],  *bp  =  buf  ; 
int  err,  rval; 

if(  argc  >  2  ) 

{ 

fprintf (stderr ,  "Usage:  expr  [<expression>] ") ; 
exit (  -1  ) ; 

} 

if(  argc  >  1  ) 

{ 

rval  =  parse(  argv[l],  &err  ); 

printf (err  ?  "***  ERROR  ***"  :  "%d",  rval  ); 

exit (  rval  ) ; 

} 

printf ( "Enter  expression  or  <CR>  to  exit  program\n" ) ; 

while (  1  ) 

{ 

printf ("?  ") ; 

if  (  gets (buf)  ==  NULL  II  !*buf  ) 
exit ( 0 ) ; 

rval  =  parse(buf,  Serr) ; 
if (  err  ) 

printf  ("***  ERROR  ***\n"); 

else 

printf ("%s  =  %d\n",  buf,  rval); 

} 


V 


parse(  expression,  err  ) 

♦expression ; 

*er  r ; 

/*  Return  the  value  of  "expression"  or  0  if  any  errors  were 

*  found  in  the  string.  "*Err"  is  set  to  the  number  of  errors. 

*  "Parse"  is  the  "access  routine"  for  expr().  By  using  it  you 

*  need  not  know  about  any  of  the  global  vars  used  by  expr(). 
*/ 

register  int  rval; 

Error  =  0; 

Str  =  expression; 
rval  =  expr ( ) ; 

return(  (*err  =  Error)  ?  0  :  rval  ); 


- */ 

(Continued  on  page  28) 


Dr.  Dobb's  Journal,  September  1985 


26 

669 


C  Chest  Listing 


(Listing  Continued,  text  begins  on  page  18) 


103: 

104: 

105: 

106: 

107: 

108: 

109: 

110: 

111: 

112: 

113: 

114: 

115: 

116: 

117: 

118: 

119: 

120: 

121: 

122: 

123: 

124: 

125: 

126: 

127: 

128: 

129: 

130: 

131: 

132: 

133: 

134: 

135: 

136: 

137: 

138: 

139: 

140: 

141: 

142: 

143: 

144: 

145: 

146: 

147: 

148: 

149: 

150: 

151: 

152: 

153: 

154: 

155: 

156: 

157: 

158: 

159: 

160: 

161: 

162: 

163: 

164: 

165: 

166: 

167: 

168: 

169: 


static  int  expr() 

int  lval ; 

lval  =  factor  ( )  ; 

switch  (*Str) 

{ 

case  '+':  Str++;  lval  +=  expr();  break; 

case  :  Str++;  lval  -=  expr();  break; 

case  Str++;  lval  *=  expr();  break; 

case  ' / ' s  Str++;  lval  /=  expr();  break; 

default  :  break; 

} 

return (  lval  ) ; 

} 

/* - 

static  int  factor () 

{ 

int  rval  =  0  ,  sign  =  1  ; 

if  (  *Str  ==  ) 

{ 

sign  =  -1  ; 

Str++; 

} 

if  (  *Str  1=  ' ('  ) 

rval  =  constant (); 

else 

{ 

Str++; 

rval  =  expr ( ) ; 

if  (  *Str  ==  ' ) '  ) 

Str++ ; 

else 

{ 

pr intf ( "Mis-matched  parenthesis\n" ) ; 
Error+-t-  ; 

} 

} 

return  (rval  *  sign); 

} 

/* - 

static  int  constant () 

{ 

int  rval  =  0  ; 

if (  ! isdigit (  *Str  )) 

Er ror++; 

while  (  *Str  &&  isdigit (*Str)  ) 

{ 

rval  =  (rval  *  10)  +  (*Str  -  '0')  ; 

Str++; 

} 

return (  rval  ) ; 

} 


V 


End  Listing 


28 

R7n 


Dr.  Dobb’s  Journal,  September  1985 


UNIX  EXCHANGE 


by  Axel  Schreiner 


This  column  is  reprinted  from  the  German  quarterly  unix/ 
mail  (Hanser  Verlag,  Munich,  Germany).  It  is  copyright¬ 
ed  ®  1984  by  Axel  T.  Schreiner,  Ulm,  West  Germany.  It 
may  be  reproduced  as  long  as  the  copyright  notice  is  in¬ 
cluded  and  reference  is  made  to  the  original  publication. 

/lib/libc.a(signal.o) 

Signal(2)'  does  not  generate  a  signal  but  is  the  meager 
protection  offered  against  the  cruelty  of  kill(2).  This  col¬ 
umn  demonstrates  how  to  deal  with  signals.  Because  this 
topic  presupposes  a  broad  range  of  knowledge,  we  start 
with  a  theoretical  overview. 

Theory 

Signal  is  the  term  for  a  number  of  events  that  a  process 
may  encounter  in  a  more  or  less  unforeseen  fashion: 

SIGHUP 

may  happen  when  the  controlling  terminal  is  switched  off 
(i.e.,  once  the  interface  loses  the  carrier) 

SIGINT 

can  happen  if  the  interrupt  key  is  pressed  at  the  control¬ 
ling  terminal,  usually  break  or  del 
SIGQUIT 

can  happen  if  the  quit  key  is  pressed  at  the  controlling 
terminal,  usually  A  \ 

SIGKILL 

is  signal  9,  which  can  be  sent  explicitly  with  the  kill  com¬ 
mand  or  the  kill(2)  system  call 
SIGSYS 

results  from  a  system  call  with  bad  parameters — primari¬ 
ly  if  programs  from  a  foreign  operating  system  are  run 
under  Unix 

SIGPIPE 

is  sent  to  the  writer  of  a  pipe,  if  the  corresponding  reader 
exits 

SIGALRM 

is  received  by  a  process  once  a  time  interval  set  up  by 

alarm(2)  has  expired 

SIGTERM 

is  signal  1 5,  which  is  sent  by  kill  as  a  default. 

There  are  16  signals,  ranging  from  1  (SIGHUP)  to  16. 
They  result  from  events  at  the  controlling  terminal, 
through  errors  in  the  process  itself,  or  through  actions  of 
other  processes.  Signal  16  has  no  predefined  meaning. 
The  list  above  does  not  contain  the  names  of  the  signals 
provoked  by  arithmetic  errors  (e.g.,  division  by  zero),  by 

Dr.  Dobb’s  Journal,  September  1985 


unknown  machine  instructions,  or  by  access  to  nonexist¬ 
ing  memory  regions;  these  situations  are  somewhat  ma¬ 
chine  dependent. 

The  system  call  kill(pid,  sig)  sends  signal  sig  to  the  pro¬ 
cess  pid.  Kill(2)  returns  a  0  on  success  and  —  1  if,  for 
example,  a  signal  is  sent  to  a  nonexisting  process.  The 
super-user  may  send  signals  to  arbitrary  recipients;  every¬ 
body  else  can  send  signals  only  to  processes  with  the  same 
user  number.  There  is  a  process  0,  but  if  a  signal  is  sent  to 
pid  0,  it  goes  to  all  processes  in  the  user’s  own  process 
group.  The  super-user,  by  sending  a  signal  to  pid  —  1, 
reaches  all  processes  in  the  entire  system  with  the  excep¬ 
tion  of  processes  0  and  1.  This  call  is  really  only  for  the 
benefit  of  /etc/init  (i.e.,  for  the  process  controlling  the 
timesharing  operation). 

If  a  process  does  not  take  defensive  measures,  receiving 
a  signal  is  deadly.  For  some  signals,  a  memory  dump  is 
produced  as  a  file  core  (assuming  the  process  has  appro¬ 
priate  privileges  with  respect  to  the  working  directory,  the 
terminated  program  text,  etc.).  SIGINT  and  SIGQUIT  dif¬ 
fer  precisely  in  that  only  SIGQUIT  produces  a  memory 
dump.  As  a  rule,  you  can  terminate  a  process  only  by 
using  the  interrupt  key.  If  the  system  is  short  of  disk 
space,  the  super-user  might  occasionally  execute 

find  /  -name  core  -a  -exec  rm  {}  \; 

to  locate  and  remove  all  memory-dump  cores. 

A  process  can  defend  itself  against  all  signals  with  the 
exception  of  SIGKILL. 

#include  <signal.h> 

signal(sig,  SIG_IGN); 

SIG-IGN  requests  that  the  signal  sig  not  be  received  by 
the  process  at  all. 

int  f(  ); 

signal(sig,  f); 

Receipt  of  the  signal  sig  causes  the  function  f  to  be  called, 
which  receives  the  signal  number  as  an  argument. 

#include  <signal.h> 

signal(sig,  SIG_DFL); 

30 

671 


I  This  finally  restores  the  default  setting  (i.e.,  recognition 
that  a  signal  is  deadly  and  might  produce  a  memory 
dump). 

Signal  states  (i.e.,  SIG-DFL,  SIG—IGN,  or  acceptance 
by  a  function)  are  preserved  across  fork{2).  SIG-IGN  or 
SIG-DFL  also  remain  in  effect  across  exec(2).  A  catch 
function  set  up  with  signal(2),  of  course,  cannot  be  re¬ 
tained  across  exec(2)  (this  system  call  eliminates  program 
text  and  data  in  the  running  process);  signals  connected  to 
a  function  are  therefore  implicitly  returned  to  SIG-DFL 
during  exec(2). 

Once  a  catch  function  has  been  set  up  using  signal(2),  it 
is  invoked  only  once  for  most  signals.  As  soon  as  the  pro¬ 
cess  receives  the  signal  (i.e.,  even  before  the  catch  func¬ 
tion  is  actually  called,  the  signal  setting  is  returned  to 
SIG-DFL  for  the  next  occurrence  of  the  signal.  That  re¬ 
sults  in  a  race  condition:  if  a  signal  is  received  twice  in 
rapid  succession,  it  is  very  likely  to  be  deadly. 

A  catch  function  usually  has  the  following  form: 

#include  <signal.h> 

static  trap(sig) 
register  int  sig; 

{ 

signal(sig,  SIG—IGN); 
signal(sig,  trap); 

} 

main(  ) 

{ 

signal(SIGINT,  trap); 

} 

At  the  beginning  of  the  function,  the  signal  state  is  set  to 
SIG-IGN.  At  the  end,  the  signal  is  reconnected  to  the 
same  function.  In  between  there  can  be  essentially  arbi¬ 
trary  program  text;  however  (on  the  PDP-11,  for  exam¬ 
ple),  at  this  point  the  floating-point  registers  have  not 
been  saved. 

A  catch  function  will  typically  set  a  global  variable  that 
is  inspected  in  the  main  program  at  a  suitable  point.  As  an 
alternative,  we  can  jump  back  into  the  main  program: 

#include  <setjmp.h> 

#include  <signal.h> 

static  jmp_buf  reset; 

static  trap(  ) 

{ 

longjmp(  reset,  1); 

} 

I  main(  ) 


{ 

setjmp(  reset); 
signal(SIG!NT,  trap); 


Setjmp(3)  arranges  that  a  subsequent  call  of  longjmp(  ) 
has  a  target  point:  the  program  then  will  return  a  second 
time  from  the  call  to  setjmp(  ).  The  value  returned  is  0  the 
first  time  and  the  value  of  the  second  argument  to 
longjmp(  )  thereafter.  Once  the  program  returns  to  the 
main  routine,  a  major  command  loop  can  be  restarted  and 
so  on.  This  is  the  customary  method  for  error  and  inter¬ 
rupt  handling  in  programs  such  as  ed. 

A  process  can  die  or  enter  a  signal-catching  function 
only  if  it  is  ready  for  execution.  If  a  process  is  swapped,  it 
must  be  brought  back  into  main  memory  before  it  can  die! 
This  is  especially  true  while  executing  code  in  a  device 
driver;  a  process  can  be  blocked  by  the  driver  so  that  it  does 
not  become  ready  to  execute  even  when  it  receives  a  sig¬ 
nal-such  processes  can  usually  be  eliminated  only  by  a 
system  restart.  If  you  write  device  drivers,  you  should  ar¬ 
range  for  suitable  mechanisms  (ioctl(2))  in  the  event  a 
magnetic  tape  transport  or  a  floppy  disk  drive  goes  on  the 
blink. 

Take  definitions  in  this  section  with  a  grain  of  salt: 
Berkeley  Unix  has  more  signals  than,  say,  Unix  Version  7, 
and  there  is  even  an  additional  signal  state  (SIG— HOLD) 
which  eliminates  the  race  condition  in  Unix  Version  7.  In 
an  attempt  to  be  generally  applicable,  our  discussion  is 
limited  to  Unix  Version  7. 

Process  Group  and  Controlling  Terminal 

Starting  Unix  primarily  means  getting /etc/init  to  execute 
as  process  1 .  When  the  timesharing  service  is  started,  this 
process  reads  the  file  /etc/ttys  and  generates  one  process 
for  each  terminal,  which  is  marked  appropriately  in  this 
file.  Each  new  process  is  the  first  one  in  a  new  process 
group,  being  the  first  in  a  chain  of  descendants  to  open  a 
connection  to  a  terminal  device.  The  terminal  itself  is  the 
controlling  terminal  of  the  process  group. 

The  controlling  terminal  is  inherited  during  fork(2)  and 
exec(2)  even  if  there  is  no  further  file  connection  to  it!  The 
only  way  to  obtain  a  process  without  a  controlling  termi¬ 
nal  is  to  generate  it  via  process  1  (in  the  file  / etc/rc ,  from 
which  the  timesharing  service  is  started)  and  never  to  con¬ 
nect  such  a  process  to  a  terminal. 

/bin/sh(wait) 

Signals  caused  at  a  terminal  (i.e.,  SIGHUP,  SIGINT,  and 
SIGQUIT)  are  always  sent  to  all  processes  in  a  process 
group.  An  interactive  shell  (i.e.,  one  connected  to  a  termi¬ 
nal  for  standard  input  and  output)  has  to  defend  itself 
against  SIGINT  and  SIGQUIT,  otherwise  a  terminal  ses¬ 
sion  would  be  quickly  over. 

Every  shell  ignores  SIGQUIT.  An  interactive  shell 
catches  SIGINT,  but  the  catch  function  does  nothing.  As  a 
consequence,  the  shell  command  wait  can  be  terminated 


32 

fi72 


Dr.  Dobb's  Journal,  September  1985 


using  the  interrupt  key:  wait(2)  is  one  of  the  system  calls 
terminated  early  by  a  signal. 

Because  SIGINT  and  SIGQUIT  are  ignored  by  default  in 
a  background  process,  one  can  use  wait  $!  rather  than  the 
much  less  efficient  ps  to  discover  if  the  last  background 
process  $!  is  already  done;  either  wait  does  not  happen 
(i.e.,  the  command  returns  immediately),  or  the  user  inter¬ 
rupts  the  wait  state  using  interrupt  and  knows  that  the 
background  process  is  still  active. 

Depending  on  the  Bourne  shell  implementation,  wait 
does  not  necessarily  accept  a  parameter.  Without  a  param¬ 
eter,  the  command  applies  to  all  background  processes 
together. 

SIGTERM  is  also  ignored  by  an  interactive  shell.  This 
implies  that  you  can  use  kill  0  in  a  dire  emergency  to  elimi¬ 
nate  all  of  your  own  processes,  because  the  shell  itself,  and 
thus  the  terminal  session,  is  not  eliminated  by  SIGTERM. 

/bin/sh(logout) 

The  C  shell  has  a  special  logout  command.  For  the 
Bourne  shell,  this  command  can  be  constructed  by  record¬ 
ing  the  process  number  of  the  login  shell  using  the  follow¬ 
ing  commands  in  $HOME/. profile: 

SHELLID=  $$;  export  SHELLID 

Logout  then  is  the  following  shell  script  (e.g.,  in  a  file 
/u  sr/bi  n/l  ogo  ut): 

kill  -16  SSHELL1D 

With  trap  “echo  logout”  0  in  $HOME/. profile,  we  can 
also  report  that  a  terminal  session  is  about  to  be  conclud¬ 
ed  as  a  response  to  AD. 

/bin/nohup 

A  background  process  will  ignore  SIGINT  and  SIGQUIT 
but  not  SIGHUP.  This  can  cause  background  processes  to 
die  as  soon  as  the  terminal  is  turned  off  following  the  end  of 
a  terminal  session.  If  your  terminal  interface  and  driver 
operate  in  this  fashion,  you  should  issue  such  background 
commands  with  the  prefix  nohup,  which  will  insulate  the 
command  against  a  SIGHUP  signal.  Also,  your  successor  at 
the  terminal  will  be  happier,  because  standard  and  diag¬ 
nostic  output  of  your  command  will  be  redirected  to  a  file 
nohup.out  in  your  directory  and  not  clutter  up  the  screen. 

/bin/kill 

The  kill  command,  Berkeley  style,  is  used  to  send  a  signal 
to  one  or  more  processes.  Usually,  SIGTERM  is  sent,  but  a 
signal  number  may  be  specified  explicitly.  As  an  example 
of  the  kill(2)  system  call,  we  show  a  version  of  kill  where 
the  signal  number  may  be  specified  mnemonically.  The 
following  Bourne  shell  script  could  be  used: 

no  = 

case  $1  in 
-[Hh][Uu][Pp]) 

no=-l  shift;; 

Dr.  Dobb's  Journal,  September  1985 


-*) 

no=$l  shift;; 

esac 

kill  $no  $* 

If  we  move  /bin/kill,  for  example,  to  /usr/bin/kill  and 
install  this  script  as  /bin/kill  .  .  .  well,  your  system  will 
recursively  get  a  severe  headache  as  soon  as  the  next  kill  is 
issued.  Your  command  search  path  PATH,  which  can  be 
displayed  using  echo  $PATH,  normally  consists  of  the  ac¬ 
tive  directory  (i.e.,  of  nothing)  followed  by  /bin  and  then 
/usr/bin\  kill  in  the  last  line  of  the  shell  script  presumably 
will  be  found  in  /bin  (i.e.,  will  be  the  shell  script  just  issu¬ 
ing  the  command!).  To  avoid  recursion,  the  last  line  must 
reference  the  moved  original  kill  command. 

By  the  way,  a  shell  script  always  should  fully  qualify 
system  commands  or  set  PATH  so  that  private  commands 
cannot  influence  public  shell  scripts. 

The  C  shell  executes  kill  itself;  the  Bourne  shell,  howev¬ 
er,  does  not.  In  an  emergency,  a  user  may  have  too  many 
processes  and  no  way  to  kill  them  from  within  the  Bourne 
shell.  The  C  program  in  Listing  One  (page  37)  has  the 
same  capabilities  as  the  shell  script  above.  However,  if  the 
program  is  called  as  exec  ekill  .  .  . ,  it  does  not  need  a 
separate  process  table  entry,  and  it  will  return  with  a  new 
copy  of  the  Bourne  shell.  Called  without  an  argument,  the 
program  will  display  a  list  of  signal  names. 

Observe  that  exit(  )  must  be  explicitly  called  at  the  end 
of  main(  );  in  this  program,  we  define  exit(  )  so  that  either 
the  current  process  calls  up  a  Bourne  shell  using  exec(2) 
or  the  process  itself  is  terminated  using  _exit(  ).  By  the 
way,  exit(  )  cannot  be  defined  local  to  the  program  (i.e., 
using  static),  otherwise  library  functions  called  by  the 
program  could  still  drag  in  the  official  version  of  exit(  ). 

We  have  used  two  functions  that  may  be  useful  in  other 
applications: 

#include  <stdio.h> 

char  *strsave(s)  /*  save  string  dynamically  */ 
register  char  *s; 

{ 

register  char  *save  =  calloc(strlen(s)  +  1, 
sizeof(char)); 

if  (save) 

return  strcpy(save,  s); 

perror(“strsave”),  exit(l ); 

> 

Although  mentioned  in  the  C  book  by  Kernighan  and 
Ritchie,  strsave(  )  unfortunately  has  not  made  its  way 
into  the  C  library.  This  version  assumes  that  strcpy(  ) 
delivers  its  first  argument  as  a  result — which  is  claimed 
by  string(3)  but  unfortunately  not  by  the  library  that  lint 
on  our  system  uses  for  checking  library  calls  .... 

The  following  function  converts  its  string(!)  argument 
to  upper  case: 

34 

673 


#include  <ctype.h> 

char  *stoupper(s)  /*  string  to  UPPER  CASE  */ 
register  char  *s; 

{ 

register  char  *cp; 

for  (cp  =  s;  *cp;  +  +cp) 
if  (islower(*cp)) 

*cp  =  toupper(*cp); 

return  s; 

> 

Note  that  (at  least  in  some  implementations)  toupper(  ) 
will  deliver  upper  case  even  if  the  argument  is  not  a  lower¬ 
case  letter!  Actually,  this  is  quite  reasonable.  If  we  al¬ 
ready  know  the  character  to  be  a  lower-case  letter,  we 
need  not  call  islower(  );  this  will  save  only  a  few  microsec¬ 
onds  if  the  call  is  not  made  implicitly  by  toupper(  ). 

/etc/init 

Controlling  Multi-user  Service 

Process  number  1,  init,  in  some  implementations  of  Unix 
Version  7  reacts  to  a  signal  and  reads  the  file  /etc/ttys 
again.  A  terminal  can  be  included  in  multi-user  operations 
if  it  is  mentioned  in  this  file  on  a  line  starting  with  1 .  If  the 
line  starts  with  0,  the  terminal  is  excluded. 

If  init  accepts  a  suitable  signal,  we  can  control  multi¬ 
user  service  dynamically  by  changing  /etc/ttys  appropri¬ 
ately  and  then  issuing  the  signal.  This  happens  in  the  pro¬ 
gram  in  Listing  Two  (page  38),  installed  as  attach  and 
detach  in  Perkin  Elmer’s  Edition  VII;  attach  arranges  for 
the  terminals  mentioned  as  arguments  to  participate  in 
multi-user  operations  and  detach  drops  the  terminals  and 
eliminates  the  associated  processes. 

Use  these  commands  with  some  caution;  if  signals  are 
sent  to  init  in  rapid  succession,  process  1  might  be  eliminat¬ 
ed  and  with  it  essentially  the  entire  system!  The  program 
therefore  must  first  modify  the  file  for  all  arguments  and 
then  send  a  single  signal.  Because  reading  the  file  is  the 
more  expensive  operation,  we  have  elected  to  compare  each 
line  of  the  file  with  all  arguments  to  the  command. 

The  terminal  name  appears  at  the  end  of  a  line  in  the 
file.  We  are  willing  to  select  a  terminal  based  on  the  last 
few  letters  of  its  name.  Of  course,  the  argument  e  will 
then  cause  console  as  well  as  ttye  to  be  attached  or  de¬ 
tached  .... 

The  program  itself  is  quite  simple:  we  copy  /etc/ttys 
into  a  temporary  file  and  change  the  relevant  lines.  For 
efficiency,  we  compare  each  line  with  all  arguments  be¬ 
fore  writing  it  to  the  output  file.  Because  attach  is  less 
destructive  than  detach,  the  program  really  must  be 
called  as  detach  for  a  terminal  to  be  disconnected. 

If  at  least  one  line  was  changed,  we  replace  /etc/ttys  by 
the  copy  and  inform  init.  Normally  only  the  super-user 
can  change  /etc  (!),  therefore  only  the  super  user  can  cre¬ 
ate  the  copy.  Thus  renaming  should  be  possible: 


#include  <stdio.h> 

rename(new,  old)  /*  rename  a  file  */ 

char  *new,  *old; 

{ 

unlink(new); 

if  (link(old,  new)  =  =  - 1 ) 

{ 

fputs(old,  stderr); 
fputs(",  ",  stderr); 
perror(new),  exit(  1 ); 

} 

if  (unlink(old)  =  =  -1 ) 

perror(old),  exit(  1 ); 

} 

Blocking  Signals 

While  /etc/ttys  is  being  replaced,  we  would  prefer  to  be 
uninterrupted  because  we  might  otherwise  find  our  sys¬ 
tem  without  a  list  of  user  terminals  during  the  next  boot¬ 
strap.  Not  even  the  super-user  can  prevent  SIGKILL,  but 
all  other  signals  can  be  blocked  with  the  following  dis¬ 
able!  )  function;  a  subsequent  call  to  enable(  )  will  later 
restore  the  original  situation: 

#include  <signal.h> 

static  int  (*sigs[NSIG-l  ])(  ); 


disable)  ) 

{ 

register  int  i; 

for  (i  =  1;  i  <  NSIG;  ++  i) 
if  (i  !=  SIGKILL) 

sigs[i-l]  =  signal) i,  SIG_IGN); 


enable)  ) 

{ 

register  int  i; 


} 


for  (i  =  1;  i  <  NSIG;  ++  i) 
if  (i  !=  SIGKILL) 

signal(i,  sigs[i- 1  ]); 


/liblibc(abort.O) 

At  least  according  to  chapter  3  of  the  manual,  abort)  ) 
executes  the  PDP-11  iot  instruction  and  thus  normally 
will  terminate  the  process  with  a  core  dump. 

Beginning  with  Unix  Version  7,  one  can  send  signals  to 
oneself.  A  portable  way  to  terminate  a  process  with  a  core 
dump  is  the  following: 

#include  <signal.h> 


36 

674 


Dr.  Dobb's  Journal,  September  1985 


abort(  ) 

signal(SIGQUIT,  SIG—DFL); 
kill(getpid(  ),  SIGQUIT); 


/bin/stty 

Depending  on  context,  you  may  need  to  perform  various 
cleanup  tasks  at  various  points  in  a  program:  somewhere 
there  exists  a  temporary  file,  elsewhere  a  terminal  echo 
may  have  been  turned  off,  elsewhere  yet  a  file  may  have 
the  wrong  protection  keys,  and  in  well-founded  cases  a 
second  or  third  process  might  be  on  the  prowl.  It  is  rela¬ 
tively  easy  to  deal  with  such  cases,  one  at  a  time,  as  shown 
in  Listing  Three  (page  39). 

Here  echo(  )  takes  care  of  the  necessary  cleanup  opera¬ 
tions.  If  the  routine  was  called  through  a  signal,  the  same 
signal  is  subsequently  sent  back  to  the  process  itself. 

In  the  general  case,  we  should  be  able  to  stack  the  re¬ 
pairing  functions  by  using  the  following  functions;  these 
mostly  combine  the  ideas  of  disable(  )  and  abort(  ): 

int  catchall(f)  int  (*f)(  ); 

declares,  similar  to  signal(2),  f  as  a  reaction  to  all  signals 
except  SIGK1LL. 

int  cateh(f)  int  (*f)(  ); 

does  the  same,  but  only  for  those  signals  that  are  not 
ignored  at  present. 

int  recatch(i)  int  i; 

calls  the  aforementioned  function  f(  ),  with  the  number  of 
the  signal  effecting  the  call  as  an  argument-  at  the  begin¬ 
ning  of  this  function,  the  signal  itself  has  been  set  to 
SIG—DFL,  and  recatch(i)  arranges  for  the  signal  i  to  be  set 
just  as  it  was  set  with  the  last  call  of  catchall(  )  or  catch(  ). 

int  uncatch(  ) 

arranges  for  all  signals  the  setting  that  was  in  effect  be¬ 
fore  the  last  call  to  catchall(  )  or  catch(  ). 

Unix  Exchange  (Text  begins  on  page  30) 

Listing  One 

#include  <stdio.h> 

#include  <signal.h> 

#include  <ctype.h> 

#define  eq(a,b)  (strcmp((a),  (b)>  =  =  0) 

static  char  *signals[  ]  =  { 

•’HUP",  "INT",  "QUIT",  "ILL",  "TRAP", 

"IOT",  "EMT",  "FPE",  "KILL",  "BUS", 

"SEGV",  "SYS",  "PIPE",  "ALRM",  "TERM", 

0}; 

static  char  eflag;  /*  argv[0][0]  */ 

exit(code)  /*  terminate  or  new  shell  */ 

int  code; 

{ 

if  (eflag  ==  'e' 

&&  isatty(fileno(stdin)) 


The  functions  return  0  on  success  and  —  1  on  failure. 

We  can  use  these  functions  to  stack  signal  reactions.  If 
we  want  to  execute  only  the  latest  reaction,  we  conclude  a 
trap  function  using  recatch(  ).  If  all  reactions  to  the  signal 
should  be  executed,  we  specify  uncatch(  )  at  the  end  of 
each  trap  function  and  send  ourselves  the  signal  again.  At 
the  beginning  of  a  trap  function,  the  signal  should  be  set 
to  SIG— IGN  in  either  case. 

The  functions  themselves  are  not  very  complicated.  Es¬ 
sentially,  they  dynamically  manage  a  stack  on  which  the 
old  settings  of  the  signals  are  stored,  as  obtained  from 
signal(2).  See  Listing  Four  (page  40). 

catch(  )  contains  the  typical  statements  that  should  al¬ 
ways  be  used  if  SIGINT  or  SIGQUIT  should  be  caught.  The 
first  call  to  signal)  )  reports  whether  the  signal  is  current¬ 
ly  being  ignored;  in  this  case,  the  setting  of  the  signal  has 
not  yet  been  changed.  Only  if  the  signal  is  not  being  ig¬ 
nored  is  it  connected  to  the  desired  new  reaction.  These 
statements  prevent  a  background  process  (for  which  these 
signals  are  ignored  by  default)  from  being  subject  to  these 
signals  again. 

/usr/games 

What  does  the  program  in  Listing  Five  (page  40)  do?  Due 
to  a  complete  absence  of  goto  statements,  this  must  be  a 
so-called  structured  program.  According  to  an  often  pub¬ 
lished  opinion,  however,  such  programs  should  be  essen¬ 
tially  immediately  intelligible.  Hint:  Even  if  you  do  every¬ 
thing  else  as  a  super-user,  don’t  do  so  here.  Otherwise 
your  system  will  self-destruct,  and  this  columnist  shall 
disavow  any  knowledge  of  this  program. 

Notes 

1  Signal  (2)  here  denotes  the  function  signal( ),  explained 
in  chapter  2  in  the  Unix  Programmer's  Manual. 

DDJ 

Reader  Ballot 

Vote  for  your  favorite  feature/article. 

Circle  Reader  Service  No.  191. 


&&  isatty(fileno(stdout))) 

execl("/bin/sh",  "sh",  "-i",  0); 

_ exit(code); 

} 

static  int  signum(name)  /*  number  of  a  signal  */ 

char  ‘name; 

register  char  "sig  =  signals, 

‘ucase  =  stoupper(strsave(name)); 

while  (*sig) 

if  (eq(ucase,  *sig+  +  )) 

( 

cfree(ucase); 
return  sig  -  signals; 

} 

return -1 ; 

}  (Continued  on  next  page ) 


Dr.  Dobb’s  Journal,  September  1 985 


37 

675 


Unix  Exchange 

Listing  One 

(Listing  Continued,  text  begins  on  page  30) 


main(argc,  argv) 
int  argc; 

register  char  "argv; 

{ 

register  int  sig  =  SIGTERM; 

eflag  =  "argv; 

if  (*+  +  argv  &&  "argv  =  = 

{ 

if  (isdigit(*+  +  'argv)) 
sig  =  atoi('argv); 
else 

sig  =  signum('argv); 
if  (sig  <  =  0  II  sig  >  =  NSIG) 

{ 

fputs(*argv,  stderr); 

fputs(":  is  not  a  signal\n",  stderr); 

exit(1  ); 

} 

+  +argv; 

> 

\i  ('argv) 
do 

if  (isdigit("argv) 

&&  kill(atoi('argv),  sig)  ==  -1) 
perror('argv); 
while  ('+  +argv); 

else 

for  (argv  =  signals;  *argv;  +  +  argv) 
fprintf(stderr,  "%d\t%s\n", 

argv  -  signals  +  1 ,  'argv); 

exit(0); 

End  Listing  One 


Listing  Two 


#include  <stdio.h> 


#define  LEN 
#define  TTYS 
#define  TMP 
#define  TELL 


20  /'  max.  line  length  */ 

"/etc/ttys"  /'  terminal  table  '/ 

"/etc/ttys. tmpXXXXXX"  /*  temporary  copy  */ 

kill(1, 2)  =  =  -1  &&  (perrorfinit”),  exit(  1 )) 


#define  eq(a,b)  (strcmp((a),  (b))  =  =  0) 


main(argc,  argv) 
int  argc; 
char  "argv; 

{ 

register  char  "argp; 
char  buf[LEN],  "last, 

*tmp  =  mktemp(TMP), 
set  =  "argv  ==  'd7  'O’:  'V; 
int  change  =  0; 

if  (!  freopen(TTYS,  "r" ,  stdin)) 
perror(TTYS),  exit(1); 
if  (I  freopen(tmp,  "w",  stdout)) 
perror(tmp),  exit(1 ); 
while  (fgets(buf,  sizeof  buf,  stdin)) 


last  =  buf  +  strlen(buf)  -  1  ; 
if  ('last  !=  '\n') 


fputsfbuf,  stderr); 

fputs(":  too  long\n",  stderr); 

exit(1); 

> 

'last  =  '\0'; 

for  (argp  =  argv+  1;  'argp;  ++  argp) 
if  ("argp 

&&  eq(last  -  strlen('argp),  'argp)) 
break; 

if  ('argp  &&  buf[0]  !=  set) 

( 

+  +  change; 
buf[0]  =  set; 

} 

puts(buf); 

} 

if  (change) 

{ 

fflush(stdout); 
rename(TTYS,  tmp); 

TELL; 

> 

else 

unlinkltmp);^ 

'  End  Listing  Two 


Listing  Three 

#include  <signal.h> 

#include  <sgtty.h> 

#include  <stdio.h> 

static  struct  sgttyb  sgttyb; 

echo(i) 
register  int  i; 

{ 

stty(fileno(stdout),  &sgttyb); 
if  (i) 

kill(getpid( ),  i); 

} 


main( ) 

{ 

int  flags; 

gtty(fileno(stdout),  &sgttyb); 

flags  =  sgttyb. sg_ flags,  sgttyb. sg_flags  &=  ~ECHO; 
stty(fileno(stdout),  8isgttyb); 
sgttyb.sg_flags  =  flags; 


signal(SIGINT,  echo); 
echo(O); 

signal(SIGINT,  SIG_DFL); 


End  Listing  Three 

(Listing  Four  begins  on  next  page) 


38 


cere. 


Dr.  Dobb's  Journal,  September  1985 


Unix  Exchange  (Listing  Continued,  text  begins  on  page  30) 

Listing  Four 


#include  <signal.h> 

static  struct  signals  { 
int  (*s_sigs[NSIG-1])(  ); 
struct  signals  *s_prev; 

}  ‘top; 

static  struct  signals  "push( ) 

( 

register  struct  signals  ‘p  =  (struct  signals  *) 
calloc(1 ,  sizeof(struct  signals)); 

if  (P) 

p— s_prev  =  top,  top  =  p; 
return  p; 

> 


int  catchall(f) 
register  int  (*f)( ); 

{ 

register  int  i; 

register  struct  signals  *p  =  push( ); 

^  <p) 

{ 

for  (i  =  1 ;  i  <  NSIG;  +  +  i) 
if  (i  !  =  SIGKILL) 
p— s_sigs[i-1]  =  signal(i,  f); 

return  0; 

} 

return  -1 ; 

) 


int  catch(f) 
register  int  (*f)(  ); 

{ 

register  int  i; 

register  struct  signals  *  p  =  push(  ); 

if  (P) 

( 

for  (i  =  1 ;  i  <  NSIG;  +  +  i) 
if  (i  !=  SIGKILL 

&&  (p— s _ sigs[i-1  ]  =  signal(i,  SIGJGN)) 

!  =  SIGJGN) 
signal(i,  f); 

return  0; 

} 

return  -1 ; 


int  uncatch(  ) 

{ 

register  int  i; 

register  struct  signals  *p  =  top; 

if  (P) 

{ 

for  (i  =  1 ;  i  <  NSIG;  +  +  i) 
if  (i  !=  SIGKILL) 

signal(i,  p— s _ sigs[i-1  ]); 

top  -  top— s_prev; 

cfree(p); 

return  0; 

} 

return  -1 ; 


> 

int  recatch(i) 
register  int  i; 

{ 

if  (top) 

( 

signal(i,  top— s _ sigs(i-1 )); 

return  0; 

} 

return  - 1 ; 


End  Listing  Four 


Listing  Five 


#include  <signal.h> 
catch( )  {} 

main(  ) 

( 

int  vater,  ich,  sohn; 
signal!  1 6,  catch); 

for  (vater  =  0;  ich  =  getpid( );  vater  =  ich) 
switch  (sohn  =  fork( ))  { 
default: 

pause( ); 
case  -1 : 

signal(SlGALRM,  catch); 

alarm(2); 

getchar(  ); 

alarm(0); 

if  (vater) 


signal)  1 6,  catch); 
killfvater,  16); 
pause( ); 

> 

kill(sohn,  16); 
wait(0); 
exit(0); 
case  0: 


} 


> 


End  Listings 


Dr.  Dobb’s  Journal,  September  1985 


40 

677 


THE  SOFTWARE  DESIGNER 


by  Michael  Swaine 


All  code  is  built  on  algorithms,  and 
all  algorithms  have  to  be  communi¬ 
cated  in  code,  even  if  only  in  pseudo¬ 
code.  So  how  does  this  month’s  DDJ 
more  than  any  other  month’s  warrant 
description  as  an  algorithms  issue? 
Well,  we’re  presenting  two  articles 
that  emphasize  perennial  algorithmic 
topics  in  programming:  sorting  and 
searching.  Each  of  these  articles 
takes  a  somewhat  uncommon  ap¬ 
proach,  and  presents  a  program  that 
is  algorithmically  distinctive.  We 
hope  they’re  more  than  distinctive,  of 
course:  we  want  to  present  algorithms 
that  are  of  practical  use  to  advanced 
programmers,  so  we’ve  encouraged 
the  authors  to  suggest  applications  in 
which  their  approaches  might  repre¬ 
sent  the  algorithms  of  choice. 

Sorting  and  searching  algorithms 
are  probably  as  thoroughly  analyzed 
as  any  programming  techniques. 
What  follows  here  is  not  a  definitive 
survey  of  sorting  and  searching  tech¬ 
niques,  but  a  summary  of  the  common 
algorithms  with  the  generally  accept¬ 
ed  judgements  on  their  practical  uses. 
None  of  this  is  new  material,  but  it 
may  set  the  stage  for  this  month’s 
sorting  and  searching  algorithms. 

Speed  is  the  first  performance 
measure  we  think  of  applying  to  a 
sort  routine.  But  what  speed?  Worst 
case,  best  case,  or  “typical”  case? 
And  what’s  typical?  It’s  known  that 
any  algorithm  that  sorts  by  compar¬ 
ing  items  must,  for  some  sequence  of 
n  items,  use  0(n  log  n)  comparisons; 
that  is,  the  lower  bound  on  the  worst 
case  is  order-(n  log  n)  time;  but  some 
algorithms  have  much  worse  worst- 
case  times  than  this  and  still  perform 
acceptably  in  most  real-life  situa¬ 
tions.  And  speed  isn’t  everything;  it 
isn’t  even  particularly  important  in 
some  applications. 


Sometimes  a  simple  sort  algorithm 
is  the  best.  If  you’re  sorting  fewer 
than  100  items,  if  the  sort  routine  will 
only  be  used  once  or  twice  and  dis¬ 
carded,  or  if  the  data  to  be  sorted  are 
likely  to  be  already  nearly  sorted,  you 
probably  would  do  just  as  well  to  im¬ 
plement  a  simple  selection  sort.  The 
logic  of  this  algorithm  is  lucid:  find 
the  smallest  element,  put  it  at  the  top 
of  the  list,  and  iterate  down  the  list. 

algorithm  Selectionsort; 
begin 

for  i:=  1  to  n  do 
begin 
min:  =  i; 

for  j:  =  i+ 1  to  n  do 

if  a[j]<a[min]  then  min:=j; 
swap(a[i],a[min]); 
end; 
end; 

Simple  algorithms  are  simple  to  de¬ 
bug  and  make  it  easier  to  convince 
yourself  of  their  correctness.  Accord¬ 
ing  to  Robert  Sedgewick,  this  simple 
selection  sort  algorithm  is  the  tech¬ 
nique  of  choice  if  you  are  sorting  files 
with  large  records  and  small  keys  and 
actually  have  to  perform  the  rear¬ 
rangement,  rather  than  just  juggling 
indices.  And  it  may  be  the  technique 
of  choice  if  you  have  to  write  a  sort 
cold,  have  it  running  as  quickly  as  pos¬ 
sible,  and  then  discard  it.  But  this  is  an 
0(n~2)  algorithm;  many  situations  call 
for  something  more  efficient. 

Shellsort  is  a  more  efficient  algo¬ 
rithm,  and,  although  it’s  well-known, 
it  is  also  insufficiently  analyzed.  Its 
overall  efficiency  has  yet  to  be  charac¬ 
terized.  It  may  not  be  as  efficient  as, 
say,  Quicksort,  but  it  can  be  perfectly 
adequate  for  many  applications.  Its 
efficiency  depends  on  the  selection  of 
a  set  of  values  that  control  the  granu¬ 


larity  of  its  early  passes  through  the 
file.  In  the  degenerate  case  with  gran¬ 
ularity  1  (k=  1),  the  core  of  the  algo¬ 
rithm,  presented  here,  moves  gradual¬ 
ly  down  the  list,  keeping  the  upper 
portion  of  the  list,  above  the  item  un¬ 
der  consideration,  sorted.  That’s  the 
core,  and  it  is  a  sort  algorithm  unto 
itself,  but  the  full  Shellsort  does  more. 
By  beginning  with  larger  values  for  k 
and  working  down  to  k=  1,  Shellsort 
in  effect  removes  some  of  the  large- 
scale  disorder  from  the  file,  giving  the 
k  =  1  pass  better  data  to  work  with. 
That  turns  out  to  be  a  good  thing, 
since  the  core  algorithm,  hence  the 
k  =  1  pass,  is  especially  sensitive  to  or¬ 
der;  its  worst-case  performance  is 
quadratic,  its  best,  for  a  completely 
ordered  file,  linear. 

Shellsort  is  probably  the  simplest 
algorithm  that  represents  a  common 
technique  in  sort  algorithms:  shifting 
strategies  as  the  data  becomes  more 
organized.  I  n  the  case  of  Shellsort  the 
shift  is  accomplished  by  adjusting 
one  parameter,  but  in  some  variations 
on  Quicksort  quite  different  algo¬ 
rithms  are  employed  at  different 
stages  in  the  sorting. 

algorithm  Shellcore; 
begin 

for  i:  =  2  to  n  do 
begin 
j:  =  i; 

while  a[j  —  k]>a[i]  do 
begin 

a[j]:  =  a[j-kj; 
j:=j  — k; 
end; 

a[j]:  =  a[i]; 
end; 
end; 

Quicksort  was  invented  by  C.  A.  R. 
Hoare  in  1960.  It’s  been  widely  used, 


42 

678 


Dr.  Dobb’s  Journal.  September  1985 


deeply  analyzed,  and  much  tweaked 
since  then.  It  has  a  worst-case  0(iT2) 
performance,  but  has  0(n  log  n)  ex¬ 
pected  time  under  the  assumption 
that  all  permutations  of  the  to-be- 
sorted  elements  are  equally  likely. 
Quicksort  is  fast  and  relatively  mem¬ 
ory  efficient,  and  is  often  used  in  li¬ 
brary  routines. 

Quicksort  is  defined  here  as  a  re¬ 
cursive  procedure.  Its  arguments,  left 
and  right,  define  the  endpoints  of  the 
sort.  Middle  is  a  point  selected  by 
procedure  Partition.  Partition  is  any 
procedure  that  puts  the  middle-th  el¬ 
ement  (or  identical  elements)  in  its 
(their)  proper  place  and  ensures  that 
all  elements  to  the  left  of  it  (them) 
are  less  than  it  (them),  and  all  ele¬ 
ments  to  its  (their)  right  are  greater. 
M  is  the  number  of  identical  ele¬ 
ments  that  Partition  puts  in  their 
proper  place. 

algorithm  Quicksort) left, right); 
begin 

if  right> left  then 

begin 

Partition(left,middle,right); 

Quicksort(left,  middle  —1); 

Quicksort)  middle +  m,  right); 

end; 

end; 

Various  methods  have  been  em¬ 
ployed  to  improve  Quicksort’s  perfor¬ 
mance,  such  as  R.  S.  Scowen’s 
Quickersort  and  M.  H.  van  Emden’s 
Qsort  (algorithms  271  and  402  in  the 
Collected  Algorithms  of  the  ACM , 
respectively).  Unwinding  the  recur¬ 
sion  is  one  technique  that  can  in¬ 
crease  the  speed.  Another  method  in¬ 
volves  abandoning  the  algorithm  for 
something  like  Shellcore  for  very 
small  subfiles.  A  third  technique 
helps  to  avoid  worst-case  times:  it  in¬ 
volves  using  some  sort  of  randomiz¬ 
ing  technique  in  Partition  to  select 
the  middle  point. 

Heapsort  is  an  efficient  sort  algo¬ 
rithm  that  uses  a  data  structure  that 
has  applications  in  queuing.  One  of 
its  virtues  is  that  it  has  0(n  log  n)  as  a 
worst-case  time.  The  algorithm 
works  by  constructing  a  semiordered 
tree  structure  called  a  heap,  sawing 
off  the  root  (which  will  be  the  largest 
element  remaining),  rebuilding  the 


resulting  forest  into  another  heap, 
and  iterating.  Procedure  Heapprop 
rearranges  items  to  ensure  that  the 
heap  property  applies.  Makeheap 
constructs  the  initial  heap. 

algorithm  Heapsort; 
begin 

Makeheap; 

for  i;  =  n  to  2  step  —  1  do 
begin 

swap(a[  1  ],a[  i  ] ); 

Heapprop(i  —  1 ); 
end; 
end; 

Bubblesort  is  a  curious  phenome¬ 
non.  I  learned  it  early  in  my  program¬ 
ming  experience.  You  probably  did, 
too.  Does  anyone  know  why?  It  is  nei¬ 
ther  very  simple  nor  very  efficient, 
and  its  detailed  operation  is  less  clear 
than  that  of  other  sorts,  although  its 
operation  is  not  hard  to  describe.  Es¬ 
sentially,  it  consists  of  a  number  of 
passes  through  the  file,  swapping  ad¬ 
jacent  elements  as  necessary. 

algorithm  Bubblesort; 
begin 
repeat 
t:  —  a[  1  ]; 
for  j;  =  2  to  n  do 

if  a[j—  1  ]>a[j]  then 
begin 

t:  =  a[j—  1  ]; 
swap(a[j],a[j-  1]); 
end; 

until  t  =  a[  1  ]; 
end; 

How  you  sort  depends  on  what  you 
have  to  sort,  including  how  it  is  al¬ 
ready  arranged  as  well  as  other  as¬ 
pects  of  the  data.  Another  criterion 
you  might  apply  to  a  sort  algorithm  is 
stability.  If  you  intend  to  sort  on 
more  than  one  key  and  not  have  the 
sorting  on  one  key  destroy  the  sorting 
on  the  other,  you  need  a  stable  sort. 
There  are  instances  in  which  this  is 
not  necessary,  and  there  is  much 
power  in  knowing  what  you  can  get 
along  without.  In  this  issue  (page  68) 
we  present  a  sort  algorithm  that  has 
some  speed  advantage  when  stability 
is  not  a  consideration. 

Knowing  that  the  data  is  not  ran¬ 
domly  arranged  can  also  have  a 


marked  effect  on  the  expected  perfor¬ 
mance  of  the  algorithms.  One  of  the 
most  common  sorting  situations  in¬ 
volves  adding  a  few  items  to  an  al¬ 
ready  sorted  file;  here  the  more  so¬ 
phisticated  algorithms  can’t  compete 
with  such  simple  algorithms  as  Shell- 
core.  Finally,  in  sorting  files  too  large 
for  in-core  methods,  tape  or  disk  ac¬ 
cess  speed  limitations  can  override  all 
other  considerations  in  searching  for 
a  fast  algorithm.  The  basic  algorithm 
for  external  sorts  is:  sort  (relatively) 
small  chunks  of  the  file  in  memory, 
then  merge  these  sorted  chunks. 
Much  of  the  analysis  of  external  sort 
algorithms  has  to  do  with  minimizing 
trips  to  the  external  well.  In  a  virtual 
memory  system,  Sedgewick  argues,  it 
is  reasonable  for  certain  in-core  algo¬ 
rithms  (like  Quicksort)  to  ignore  the 
problem  of  external  reaches  entirely, 
since  the  sort’s  minimization  criteria 
coincide  with  those  of  the  designer  of 
the  virtual  memory  system. 

Searching  algorithms  encompass  a 
large  chunk  of  computer  science. 
Parsing,  for  example,  is  built  on  pat¬ 
tern  matching,  which  is  searching. 
Efficient  implementation  of  a  data¬ 
base  hinges  on  good  search  algo¬ 
rithms.  Move-search  routines  in 
chess  programs  implement  some  very 
complex  strategies  for  finding  things. 
Search  techniques  depend  intimately 
on  the  data  structure  being  searched, 
and  many  search  algorithms  are  fun¬ 
damentally  more  complex  than  the 
sort  algorithms  presented  here. 
Search  algorithms  are  harder  to  spec¬ 
ify  in  a  few  lines. 

The  simplest  search  algorithm  is  a 
sequential  scan  of  the  file,  and  it  has 
an  advantage  in  addition  to  its  sim¬ 
plicity:  it  does  not  upset  the  organiza¬ 
tion  of  the  file.  Generally,  though, 
speed  is  a  consideration,  and  an  algo¬ 
rithm  like  a  binary  search  is  warrant¬ 
ed.  Binary  search  splits  the  (sorted) 
file  more  or  less  in  half  at  each  step, 
restricting  its  subsequent  search  to 
the  appropriate  half  of  the  remaining 
file.  This  cuts  search  time  from  a  lin¬ 
ear  function  of  the  number  of  items  in 
the  file  to  a  logarithmic  function.  Bi¬ 
nary  search  can  proceed  implicitly  on 
a  linearly-organized  file  or  can  in¬ 
volve  explicit  construction  of  a  binary 
tree.  Other  search  methods  involve 
(Continued  on  page  67) 
44 
679 


Dr.  Dobb's  Journal,  September  1985 


Parallel  Pattern 
Matching 
and  Fgrep 

by  Ian  Ashdown 

The  file-search  utility  Fgrep  is  not  as 
flexible  as  Grep,  but  its  parallel 
pattern-matching  algorithms  allow  for 
one-pass  processing. 


Preparing  an  index  to  a  large  technical  book;  finding 
all  references  to  one  person  in  several  years’  worth  of 
minutes  of  meetings;  searching  for  all  occurrences  of 
a  specific  sequence  of  events  in  the  data  obtained  from  a 
scientific  experiment:  all  of  these  problems  and  many 
more  are  examples  of  searching  for  patterns  in  a  set  of 
data.  The  question  is:  What  is  the  most  efficient  search 
algorithm  to  use? 

For  single  patterns,  such  as  searching  for  the  phrase  “bi¬ 
nary  tree”  in  a  text  file,  two  algorithms  are  of  particular 
interest:  the  Boyer-Moore  algorithm1  and  the  Knuth-Mor- 
ris-Pratt  algorithm.2  Both  are  fast  and  reasonably  simple 
to  implement.  However,  neither  is  appropriate  when  more 
than  one  pattern  at  a  time  must  be  searched  for. 

An  algorithm  that  is  appropriate  for  multiple  patterns 
appeared  in  a  paper  published  in  the  June  1975  issue  of 
Communications  of  the  ACM ?  Entitled  “Efficient  String 
Matching:  An  Aid  to  Bibliographic  Search”  and  written 
by  Alfred  Aho  and  Margaret  Corasick  of  Bell  Labora¬ 
tories,  the  paper  presents  “a  simple  and  efficient  algo¬ 
rithm  to  locate  all  occurrences  of  any  of  a  finite  number  of 
keywords  in  a  string  of  text.”  Without  modification  to  the 
algorithm,  “keyword”  can  be  taken  to  mean  any  pattern 
and  “a  string  of  text”  to  mean  any  sequence  of  symbols, 
be  it  ASCII  text,  digitized  data  obtained  from  a  video 
camera,  or  whatever. 

The  algorithm  has  some  interesting  features.  For  in¬ 
stance,  most  pattern-matching  schemes  must  employ 
some  form  of  backtracking,  or  rescanning  of  the  string, 
when  an  attempted  match  to  a  pattern  fails  or  multiple 
patterns  are  to  be  matched.  The  Aho-Corasick  algorithm 
differs  in  that  it  searches  for  all  of  the  patterns  in  parallel. 
By  doing  so,  it  can  process  the  string  in  one  pass. 

The  algorithm  also  recognizes  patterns  that  overlap  in 
the  string.  For  example,  if  the  patterns  are  “he,”  “she,” 
and  “hers,”  the  algorithm  will  correctly  identify  matches 
to  all  three  in  the  string  “ushers.” 

As  for  speed  of  execution,  it  is  independent  of  the  num¬ 
ber  of  patterns  to  be  matched!  This  surprising  feature  is  a 
direct  result  of  searching  for  the  patterns  in  parallel. 

Curiously,  this  algorithm  has  all  but  disappeared  from 
the  literature  of  computer  science.  Of  the  hundreds  of 
textbooks  and  articles  written  since  that  time  that  discuss 
pattern  matching,  only  a  few  refer  to  the  paper,  and  none 
that  I  am  aware  of  present  the  Aho-Corasick  algorithm 
itself. 

On  the  other  hand,  if  you  work  with  Unix  you  may  have 
used  a  utility  based  on  the  algorithm:  Fgrep.  This  useful 
tool  searches  for  and  displays  all  occurrences  of  a  set  of 
keywords  and  phrases  in  one  or  more  text  files.  Although 
it  does  not  accept  wildcard  characters  in  its  patterns  like 
Unix’s  more  flexible  grep  utility,  fgrep  does  illustrate  the 
speed  of  the  Aho-Corasick  algorithm.  Typically,  fgrep  is 
five  to  ten  times  faster  than  grep  in  searching  files  for 
fixed  string  patterns. 

Ian  Ashdown,  byHeart  Software,  1089  W.  21st  St., 
North  Vancouver,  British  Columbia  V7P  2C6  Canada 


46 

680 


Dr.  Dobb's  Journal,  September  1985 


Given  the  algorithm’s  general  usefulness,  I  thought  it 
appropriate  to  reintroduce  Aho  and  Corasick’s  work  in 
this  article.  At  the  same  time,  it  seemed  a  shame  that 
fgrep  has  been  restricted  to  Unix.  Therefore,  the  source 
code  in  C  for  a  full  implementation  of  fgrep4  has  been 
included  (see  the  listing,  page  54).  This  serves  not  only  to 
demonstrate  the  algorithm  but  also  to  bring  the  idea  of  a 
public  domain  Unix  one  small  step  closer  to  reality. 

Inside  the  Machine 

The  Aho-Corasick  algorithm  consists  of  two  phases:  build¬ 
ing  a  finite  state  automaton  (FSA)  then  running  a  string  of 
data  through  it,  with  each  consecutive  symbol  considered  a 
separate  input.  Before  we  analyze  these  phases,  a  quick 
summary  of  what  finite  state  automata  are  and  how  they 
work  is  in  order.  (For  a  more  detailed  discussion,  see  Aho 
and  Ullman’s  book  on  compiler  design.5) 

In  essence,  an  FSA  is  a  conceptual  machine  that  can  be 
in  any  one  of  a  finite  number  of  “states.”  It  also  has  a  set 
of  “state  transition”  rules  and  a  set  of  “inputs,”  which 
together  with  the  set  of  states  define  what  inputs  cause 
the  machine  to  change  from  one  state  to  another.  Finally, 
because  a  machine  serves  no  purpose  without  producing 
some  output,  an  FSA  has  one  or  more  “terminal”  states. 
Output  is  produced  only  when  the  FSA  enters  such  states. 

A  physical  example  of  an  FSA  could  be  a  light  switch. 
This  device  has  two  states,  on  and  off.  Its  inputs  consist  of 
someone  pushing  the  switch  up  or  down.  On  is  a  terminal 
state,  where  the  associated  lamp  produces  visible  radia¬ 
tion.  The  state  transition  rules  can  be  presented  in  a  sim¬ 
ple  truth  table: 

Off  On 
Up  On  - 
Down  -  Off 

Alternatively,  the  rules  could  be  shown  as  a  state  transi¬ 
tion  diagram  (Figure  1 ,  page  47 )  where  no  state  transition 
on  a  particular  input  (as  shown  in  the  truth  table)  is 


Down 


Current  State:  12  3  4  5 

Failure  State:  0  0  0  12 

6  7  8  9 

0  3  0  3 

Figure  2b 
Failure  function 

Current  Output: 

State 

0 

— 

1 

— 

2 

(he) 

3 

— 

4 

— 

5 

(she,  he) 

6 

— 

7 

(his) 

8 

~ 

9 

(hers) 

Figure  2c 

Output  function 

Current 

Input 

Next 

Current 

Input 

Next 

State: 

Symbol:  State: 

State: 

Symbol: 

State: 

0 

h 

1 

4 

e 

5 

s 

3 

i 

6 

. 

0 

h 

1 

1 

e 

2 

s 

3 

i 

6 

0 

h 

1 

6 

s 

7 

s 

3 

h 

1 

0 

0 

2,5 

r 

8 

8 

s 

9 

h 

1 

h 

1 

s 

3 

0 

0 

3,7,9 

h 

4 

s 

3 

• 

0 

where 

represents  any 

symbol 

not  included 

in  the 

list  of 

symbols  for 

the  indicated  state(s). 

Figure  2d 

Move  function 

Dr.  Dobb's  Journal.  September  1985 


47 

681 


equivalent  to  a  transition  from  a  state  to  itself  (as  shown 
in  the  diagram). 

Getting  ahead  of  ourselves  for  a  moment,  let’s  look  at 
Figure  2  (page  47).  Figure  2a  shows  part  of  an  FSA  (the 
other  parts  shown  in  Figures  2b,  2c,  and  2d,  will  be  ex¬ 
plained  shortly).  By  having  sequences  of  states,  the  FSA 
can  recognize  patterns  in  an  input  stream.  For  example, 
presenting  the  string  “she”  to  the  FSA  shown  in  Figure  2a 
would  cause  it  to  change  from  State  0  to  States  3,  4,  and  5 
as  each  input  symbol  of  the  string  is  processed.  State  5  is  a 
terminal  state  that  indicates  that  the  pattern  “she”  was 
recognized. 

Two  types  of  FSA  can  be  built,  one  nondeterministic 
(Algorithm  1,  page  48)  and  the  other  deterministic  (Algo¬ 
rithm  2,  page  48).  The  nondeterministic  one  (NFSA)  uses 
functions  called  go_to,  failure  and  output,  while  the  deter¬ 
ministic  FSA  (DFSA)  uses  move  and  output.  The  differ¬ 
ence  between  the  two  is  that  the  NFSA  can  make  one  or 
more  state  transitions  per  input  symbol,  but  the  DFSA 
makes  only  one.  With  fewer  transitions  to  make,  a  DFSA 
can  process  its  input  in  less  time  than  an  equivalent  NFSA. 

The  go_to  function  accepts  as  input  parameters  the 
current  state  of  the  NFSA  and  the  current  input  symbol, 
and  it  returns  either  a  state  number  (the  go_to  state  tran¬ 
sition),  if  the  input  symbol  matches  that  expected  by  the 
patterns  being  matched,  or  else  a  unique  symbol  called 
FAIL.  The  only  exception  to  this  is  State  0:  go_to(0,X) 
returns  a  state  number  for  all  input  symbols  X.  If  symbol 
X  does  not  match  the  beginning  symbol  of  any  of  the 
patterns,  the  state  number  returned  is  0. 

The  failure  function  is  called  whenever  the  go_to  func¬ 
tion  returns  FAIL.  Accepting  the  current  state  as  its  input 
parameter,  it  always  returns  a  state  number,  the  failure 
state  transition. 

For  the  DFSA,  the  move  function  accepts  the  current 
state  number  and  input  symbol  and  always  returns  the 
next  state  number.  There  are  no  failure  state  transitions 
in  deterministic  FSAs. 

Both  the  NFSA  and  the  DFSA  use  the  output  function. 
As  defined  by  Aho  and  Corasick,  this  function  prints  the 
patterns  as  they  are  matched  by  the  FSA.  However,  the 

Input:  A  string  of  symbols  and  a  pattern-matching  machine 

consisting  of  functions  go_to,  failure  and  output. 
Output:  Matched  patterns. 

begin 

state  =  0 

while  there  are  more  symbols  in  the  string  do 
begin 

a  =  next  symbol  in  the  string 
while  go_to(state,a)  =  =  FAIL  do 
state  =  failure(state) 
state  =  go_to(state,a) 
if  output(state)  !=  NULL  then 
print  output(state) 
end 

end 

Algorithm  1 

Nondeterministic  Pattern-Matching  Machine 


definition  of  this  function  can  be  much  more  general.  In 
fact,  the  FSA  can  be  implemented  in  such  a  way  that  it 
executes  any  arbitrary  set  of  procedures  whenever  it  recog¬ 
nizes  a  pattern. 

FSAs  used  by  the  Aho-Corasick  algorithm  can  be  either 
nondeterministic  or  deterministic.  Although  a  DFSA  exe¬ 
cutes  more  quickly  than  its  equivalent  NFSA,  it  also  re¬ 
quires  more  memory  to  encode  its  state  transition  tables. 
Which  type  you  choose  for  the  algorithm  depends  upon 
the  application  and  the  constraints  of  execution  speed  and 
memory  usage. 

As  an  example,  assume  a  set  of  patterns  to  be  matched, 
“he,”  “she,”  “his,”  and  “hers,”  with  ASCII  characters  as 
the  set  of  input  symbols.  (These  have  been  purloined  from 
Aho  and  Corasick.)  The  FSAs  needed  to  recognize  these 
patterns  are  shown  in  Figure  2.  Let’s  look  at  the  NFSA 
first.  Taking  as  input  the  string  “ushers,”  the  machine  is 
run  using  Algorithm  1.  Starting  in  State  0,  the  first  sym¬ 
bol  from  the  string  is  “u.”  Because  only  symbols  “h”  and 
“s”  lead  from  State  0  to  other  states,  go__to(0,u)  returns 
0,  and  the  NFSA  remains  in  State  0. 

The  next  symbol  from  the  string  is  “s.”  As  shown  in 
Figure  2a,  the  NFSA  makes  a  go_to  state  transition  to 
State  3.  The  next  symbol  (“h”)  causes  a  go_to  transition 
to  State  4,  and  the  next  (“e”)  to  State  5.  An  output  is 
defined  for  State  5  (Figure  2c),  and  the  NFSA,  having 
recognized  two  pattern  matches,  prints  “she, he.” 

The  next  character  is  “r.”  No  go_to  transitions  are 
defined  for  State  5,  so  go_to(5,r)  returns  FAIL.  This 
causes  failure(5)  to  return  2  (from  Figure  2b),  making 
the  NFSA  perform  a  failure  transition  to  State  2.  From 
here,  Algorithm  1  executes  go_to(2,r),  which  causes  the 
NFSA  to  enter  State  8. 

Finally,  the  last  input  symbol,  “s,”  leads  the  NFSA  to 
enter  terminal  State  9,  and  the  output  defined  for  this  state 
causes  “hers”  to  be  printed.  In  all,  Algorithm  1  recognized 
the  patterns  “he,”  “she,”  and  “hers”  in  the  string  “ushers.” 
The  state  transitions  made  can  be  summarized  as: 

Input  Symbol:  u  s  h  e  r  s 

Current  State:  00345289 


Input:  A  string  of  symbols  and  a  pattern-matching  machine 

consisting  of  functions  move  and  output. 

Output:  Matched  patterns. 

begin 
state  —  0 

while  there  are  more  symbols  in  the  string  do 
begin 

a  =  next  symbol  in  the  string 
state  =  move(state,a) 
if  output(state)  I  =  NULL  then 
print  output(state) 

end 

end 

Algorithm  2 

Deterministic  Pattern-Matching  Machine 


48 

682 


Dr.  Dobb’s  Journal,  September  1985 


Now  let’s  look  at  the  DFSA.  Using  the  same  input 
string,  “ushers,”  this  machine  makes  move  state  transi¬ 
tions  in  accordance  with  Algorithm  2  and  Figure  2d.  Its 
state  transitions  can  be  summarized  as: 

Input  Symbol:  ushers 

Current  State:  0  0  3  4  5  8  9 

The  only  difference  is  that  the  failure  transition  to  State  2 
was  skipped.  By  avoiding  any  failure  transitions,  Algo¬ 
rithm  2  can  recognize  a  pattern  in  fewer  state  transitions 
and  hence  less  time  than  Algorithm  1. 

Software  Construction 

Having  seen  how  FSAs  work,  we  will  now  look  at  how  they 
are  built,  starting  with  the  computation  of  the  go_to 
function  by  Algorithm  3  (page  49). 

The  go_to  function  is  initially  defined  to  return  FAIL 
for  every  input  symbol  and  every  state.  Each  pattern  is 
run  through  procedure  “enter,”  which  tries  to  match  the 
pattern,  symbol  by  symbol  to  the  existing  partially  con¬ 
structed  go_to  function.  When  a  failure  occurs,  a  new 
state  is  created  and  added  to  the  go_to  function,  using  the 
current  symbol  of  the  pattern  as  the  input  for  the  state 


Input:  Array  of  patterns  {y[1],y[2] ....  y[k]}  where  each 

pattern  is  a  string  (one-dimensional  array)  of  symbols. 
(Function  go_to(s,a)  is  defined  to  return  FAIL  for  all 
states  "s"  and  all  input  symbols  "a"  until  defined 
otherwise  by  procedure  enter.) 

Output:  Function  go_to  and  partially  computed  function  output, 
begin 

newstatie  =  0 
for  i  =  1  to  i  =  k  do 
enter(y[i]) 

for  each  symbol  a  do 

if  go_to(0,a)  =  =  FAIL  then 
go_to(0,a)  =  0 
end 

procedure  enter(pattern)  /*  "pattern"  is  an  array  of  */ 

begin  /*  “m"  symbols  */ 

state  =  0 

j=  1 

while  go_to(state,pattern[j])  !=  FAIL  do 
begin 

state  =  go_to(state,pattern[j]) 

j  =  j+  1 

end 

for  p  —  j  to  p  =  m  do 
begin 

newstate  =  newstate  -F  1 
output(newstate)  =  NULL 
go_to(state,pattern[p])  =  newstate 
state  =  newstate 
end 

output(state)  =  pattern 
end 


Algorithm  3 

Computation  of  go__to  Transitions 


transition.  Thereafter,  each  consecutive  symbol  of  the 
pattern  causes  a  new  state  to  be  created  and  added. 

When  the  last  symbol  of  each  pattern  has  been  pro¬ 
cessed  by  procedure  “enter,”  its  associated  state  is  made  a 
terminal  state.  As  shown  in  Algorithm  3,  this  means  that 
the  output  for  the  state  is  defined  as  the  current  pattern, 
which  Algorithm  1  will  print  when  the  NFSA  is  run.  How¬ 
ever,  it  is  easy  to  see  that  you  can  rewrite  the  statement 
“output( state)  =  pattern”  in  Algorithm  3  to  assign  what¬ 
ever  procedures  to  output(state)  you  want. 

Finally,  after  all  of  the  patterns  have  been  processed, 
go_to(0,X)  is  redefined  to  return  0  for  all  symbols  X  that 
still  return  FAIL. 

When  Algorithm  3  completes,  it  has  computed  the 
go_to  function  of  the  FSA  and  partially  computed  the  out¬ 
put  function.  If  you  simulate  Algorithm  3  by  hand  with 
“he,”  “she,”  “his,”  and  “hers”  as  its  patterns,  you  will  see 
that  the  output  for  State  5  will  be  “she,”  not  “she, he.”  It 
remains  for  Algorithm  4  (page  5 1 )  to  complete  the  output 
function  as  it  computes  the  failure  function. 

Let’s  define  the  depth  of  a  state  as  the  number  of  go_to 
state  transitions  that  must  be  made  from  State  0  to  reach 
it.  For  example,  the  depth  of  State  4  in  Figure  2a  is  2, 
while  that  of  State  9  is  4.  The  failure  state  transition  for 
all  states  of  depth  1  is  State  0.  The  algorithm  used  to 
compute  the  failure  transitions  for  all  states  of  depth 
greater  than  1  can  be  expressed  in  its  simplest  form  as: 

for  each  depth  D  do 

for  each  state  S  of  depth  D-l  do 
for  each  input  symbol  A  do 
if  (T  =  go_to(S,A))  !=  FAIL  then 
begin 

R  =  failure(S) 

while  go_to(R,A)  =  =  FAIL  do 
R  =  failure(R) 
failure(T)  =  go_to(R,A) 
end 

To  demonstrate  this  using  our  example,  let’s  compute  one 
failure  transition  for  a  state  of  depth  2.  The  states  of 
depth  1  are  1  and  3.  The  only  input  symbols  for  which  the 
go_to  function  does  not  return  FAIL  are  “e”  and  “i”  for 
State  1  and  “h”  for  State  3.  Taking  State  3  for  “S”  and 
symbol  “h”  for  “A”  above,  we  have  “T”  being  4,  “R” 
being  “failure(3)”,  which  is  0,  go_to(0,h)  returning  1, 
and  finally  “failure(4)”  being  set  to  go_to(0,h).  The  fail¬ 
ure  transition  for  State  4  is  thus  State  1. 

Algorithm  4  expands  on  the  above  algorithm  in  two 
ways.  First,  it  uses  a  queue  (a  first-in,  first-out  list)  to 
maintain  the  order  of  states  by  depth  to  be  processed. 
Second,  it  accepts  as  input  the  output  function  and  com¬ 
bines  the  outputs  of  the  terminal  states  where  appropri¬ 
ate.  In  our  example,  the  output  “he”  of  State  2  would  be 
combined  with  the  output  “she”  of  State  5  to  produce  the 
final  and  correct  “she, he”  output  for  State  5. 

The  “move”  function  is  computed  from  the  go_to  and 
failure  functions  by  means  of  Algorithm  5  (page  51).  Es¬ 
sentially,  all  this  algorithm  does  is  precompute  all  possible 
sequences  of  failure  state  transitions.  It  is  similar  to  Algo- 


Dr.  Dobb's  Journal,  September  1985 


49 


rithm  4.  Because  there  is  considerable  redundancy  be¬ 
tween  them,  they  can  be  merged  to  compute  the  failure 
and  move  functions  concurrently,  as  is  shown  in  Algo¬ 
rithm  6  (page  53). 

Details,  Details 

So  far,  the  functions  go_to,  failure,  move,  and  output 
have  been  shown  as  something  that  you  assign  outputs  to 
for  specified  inputs.  This  assumes  a  much  higher-level 
programming  language  than  most  of  today’s  offerings. 
Implementing  these  algorithms  in  C,  Pascal,  BASIC,  or 
Fortran  requires  some  extra  code  and  work. 

Aho  and  Corasick  programmed  their  original  version  in 
Fortran.  This  is  evident  from  their  paper,  where  they  sug¬ 
gest  that  the  failure  and  output  functions  be  implemented 
as  one-dimensional  arrays  accessed  by  state  numbers. 
With  a  language  such  as  C  that  supports  complex  data 
structures,  the  code  for  the  functions  can  be  more  elegantly 
written  by  replacing  the  state  numbers  and  arrays  with 
dynamically  allocated  structures.  Each  structure  can  have 
as  members  pointers  to  linked  lists  of  go_to  and  move  tran¬ 
sitions,  a  pointer  to  the  appropriate  failure  transition,  and  a 
pointer  to  a  character  string  for  the  output  function. 

Aho  and  Corasick  also  note  that  the  go_to  and  move 
functions  could  be  implemented  as  two-dimensional  ar¬ 
rays,  with  the  number  of  states  formine  one  dimension  and 
the  set  of  input  symbols  forming  the  other.  However,  this 
would  require  enormous  amounts  of  memory  for  an  ASCII 
character  set  and  several  hundred  states.  Their  recommen¬ 
dation  was  that  the  go_to  and  move  transitions  for  each 
state  be  implemented  as  linked  linear  lists  or  binary  trees. 

Because  the  FSA  will  usually  spend  most  of  its  time  in 
State  0  for  large  input  symbol  sets,  it  is  advantageous  to 
have  the  go_to  or  move  functions  execute  as  quickly  as 
possible  for  this  state.  Therefore,  as  Aho  and  Corasick 
suggest,  a  one-dimensional  array  can  be  assigned  to  State 
0.  (Note  that  because  no  failure  transitions  are  defined 
for  State  0,  its  go_to  and  move  transitions  are  one  and  the 
same.)  The  array  is  accessed  directly,  using  the  input 
symbol  as  the  index,  with  the  corresponding  array  entry 
being  the  state  transition  for  that  symbol. 

An  improvement  not  covered  by  Aho  and  Corasick  can 
be  made  when  you  realize  that  several  states  often  will 
have  the  same  move  transitions  (see  Figure  2d  as  an  ex¬ 
ample).  In  such  cases,  the  state  structures  can  be  assigned 
a  common  pointer  to  one  linked  list  of  move  transitions. 

Some  applications  may  call  for  the  algorithm  to  be  cod¬ 
ed  with  predefined  patterns  in  read-only  memory  Here  it 
is  usually  desirable  to  maximize  the  speed  of  execution 
while  minimizing  the  code  size.  Aho  and  Ullman  discuss  a 
method  of  encoding  the  state  transition  tables  that  com¬ 
bines  the  compactness  of  linked  lists  with  the  speed  of  di- 


Input:  Functions  go_to  and  output  from  Algorithm  3. 

Output:  Functions  failure  and  output. 

begin 

initialize  queue 
for  each  symbol  a  do 
if  (r  =  go_to(0,a))  !=  0  then 
begin 

add  r  to  tail  of  queue 
failure(r)  =  0 
end 

while  queue  is  not  empty  do 
begin 

s  =  head  of  queue 
remove  head  of  queue 
for  each  symbol  a  do 
if  (t  =  go_to(s,a)| !  =  FAIL  then 
begin 

add  t  to  tail  of  queue 
r  =  failure(s) 

while  go_to(r,a)  =  =  FAIL  do 
r  =  failure(r) 
failure(t)  =  go_to(r,a) 
output(t)  =  output(t)  +  output(failure(t)) 
end 
end 
end 

Algorithm  4 

Computation  of  Failure  Transitions 


Input:  Function  go_to  from  Algorithm  3  and  function  failure 

from  Algorithm  4. 

Output:  Function  move. 

begin 

initialize  queue 
for  each  symbol  a  do 
begin 

move(O.a)  =  go_to(0,a) 
if  (r  =  go_to(0,a))  I  =  0  then 
add  r  to  tail  of  queue 

end 

while  queue  is  not  empty  do 
begin 

s  =  head  of  queue 
remove  head  of  queue 
for  each  symbol  a  do 

if  (t  =  go_to(s,a))  1=  FAIL  then 
begin 

add  t  to  tail  of  queue 
move(s,a)  =  t 
end 
else 

move(s,a)  =  move(failure(s),a) 

end 

end 


Algorithm  5 

Computation  of  Move  Transitions 


SO 


Dr.  Dobb’s  Journal,  September  1985 


rect  array  access.  Their  method  (which  Unix’s  yacc  com¬ 
piler-compiler  utility  uses  to  encode  its  parsing  tables)  is 
unfortunately  too  involved  to  discuss  here.  Interested  read¬ 
ers  are  referred  to  Aho  and  Ullman’s  book  for  details. 

Putting  It  All  To  Work 

Implementing  the  Aho-Corasick  algorithm  is  fairly 
straightforward,  although,  as  you  can  see,  the  code  re¬ 
quired  to  flesh  it  out  to  a  full  emulation  of  Unix’s  fgrep  is 
somewhat  involved.  If  you  want  to  use  the  algorithm  in 
other  programs,  simply  remove  the  fgrep  shell  and  add 
whatever  interface  routines  you  require. 

As  an  information  retrieval  utility  that  searches  arbi¬ 
trary  text  files,  fgrep  is  useful  but  by  no  means  complete. 
One  helpful  extension  would  be  the  ability  to  specify 
Boolean  operators  (AND,  OR,  and  XOR)  for  combina¬ 
tions  of  patterns. 

Also,  rather  than  simply  print  matched  patterns  for  its 
output,  the  FSA  can  execute  whatever  procedures  you 
choose  to  associate  with  the  patterns  as  they  are  matched. 
From  this  you  could  create  a  macro  processor,  where  the 
FSA  accepts  an  input  string,  finds  matches  to  predefined 
patterns,  substitutes  the  corresponding  macro  expansions, 
and  emits  the  result  as  an  output  string. 

For  those  with  RAM  memory  to  spare  (a  megabyte  or 
so),  consider  how  fast  a  spelling  checker  that  makes  no 
disk  accesses  beyond  initially  loading  itself  could  be  made 
to  run  . . .  fifty  thousand  or  more  words  in  RAM  and  your 
text  file  is  checked  almost  as  fast  as  it  can  be  read. 

If  you  come  up  with  an  original  and  fascinating  use  for 
the  Aho-Corasick  algorithm,  or  if  you  derive  new  utilities 
from  fgrep,  by  all  means  let  me  know  about  it.  Better  yet, 
donate  your  code  to  a  public  domain  software  group.  That 
alone  would  repay  me  for  developing  the  code  and  writing 
this  article. 

References 

1  Boyer,  R.  S.,  and  J.  S.  Moore,  “A  Fast  String  Searching 
Algorithm,”  CACM  20:10  (October  1977),  pp.  762  - 
772. 

2  Knuth,  D.  E.,  J.  H.  Morris,  and  V.  R.  Pratt,  “Fast  Pat¬ 
tern  Matching  in  Strings,”  SIAM  J.  Comp.,  6:2  (June 
1977),  pp.  323-350. 

3  Aho,  A.  V.,  and  M.  J.  Corasick,  “Efficient  String 
Matching:  An  Aid  to  Bibliographic  Search,”  CACM 
18:6  (June  1975),  pp.  333-340. 

4  Bell  Telephone  Laboratories,  Unix  Programmer’s  Man¬ 
ual ,  Volume  1,  Holt,  Rinehart  and  Winston,  1983,  pp. 
70-71. 

5  Aho,  A.  V.,  and  J.  D.  Ullman,  Principles  of  Compiler 
Design ,  Addison  Wesley,  1977,  pp.  73-124. 

6  See  also  Aoe,  J.,  Y.  Yamamoto,  and  R.  Shimada,  “A 

Method  for  Improving  String  Pattern  Matching  Ma¬ 
chines,”  IEEE  Transactions  On  Software  Engineering, 
SE-10.I  ( January  1984),  pp.  1 16- 120.  ddi 


Input:  Functions  go_to  and  output  from  Algorithm  3. 

Output:  Function  move. 

begin 

initialize  queue 
for  each  symbol  a  do 
begin 

move(O.a)  =  go_to(0,a) 
if  (r  =  go_to(0,a))  I  =  O  then 
begin 

add  r  to  tail  of  queue 
failure(r)  =  0 
end 

end 

while  queue  is  not  empty  do 
begin 

s  =  head  of  queue 
remove  head  of  queue 
for  each  symbol  a  do 
if  (t  =  go_to(s,a))  !=  FAIL  then 
begin 

add  t  to  tail  of  queue 
r  =  failure(s) 

while  go_to(r,a)  =  =  FAIL  do 
r  =  failure(r) 
failure(t)  =  go__to(r,a) 
output(t)  =  output(t)  +  output(failure(t)) 
move(s,a)  =  t 
end 
else 

move(s,a)  =  move(failure(s),a) 

end 

end 

Algorithm  6 

Merged  Computation  of  Failure  and  Move  Transitions 


(Listing  begins  on  next  page) 


Reader  Ballot 

Vote  for  your  favorite  feature/article. 
Circle  Reader  Service  No.  1 95. 


Dr.  Dobb’s  Journal,  September  1985 


53 

fiftS 


Fgrep  Listing  (Text  begins  on  page  46) 


/*  FGREP. C  -  Search  File(s)  For  Fixed  Pattern(s) 


Version  1 .03 

Modifications: 

VI. 00  (84/12/01) 
VI. 01  (85/01/01) 

VI. 02  (85/01/06) 
VI. 03  (85/02/11) 

Copyright  1985: 


February  11th,  1985 


-  beta  test  release 

-  added  -P  option 

-  improved  command  line  validation 

-  modified  "run_fsa()"  and  "bd_move() 

-  added  -S  option 

Ian  Ashdown 

byHeart  Software 

1089  West  21st  Street 

North  Vancouver,  B.C.  V7P  2C6 

Canada 


*  This  program  may  be  copied  for  personal,  non-commercial  use 

*  only,  provided  that  the  above  copyright  notice  is  included  in 

*  all  copies  of  the  source  code.  Copying  for  any  other  use 

*  without  previously  obtaining  the  written  permission  of  the 

*  author  is  prohibited. 

* 

*  Machine  readable  versions  of  this  program  may  be  purchased  for 

*  $35.00  (U.S.)  from  byHeart  Software.  Supported  disk  formats 

*  are  CP/M  8"  SSSD  and  PC-DOS  (v2.x)  5-1/4"  DSDD. 

* 

*  Notes : 

* 

*  The  algorithm  used  in  this  program  constructs  a  deterministic 
finite  state  automaton  (FSA)  for  pattern  matching  from  the  sub¬ 
strings,  then  uses  the  FSA  to  process  the  text  string  in  one 
pass.  The  time  taken  to  construct  the  FSA  is  proportional  to 
the  sum  of  the  lengths  of  the  the  substrings.  The  number  of 
6tate  transitions  made  by  the  FSA  in  processing  the  text 
string  is  independent  of  the  number  of  substrings. 


Algorithm  Source: 

"Efficient  String  Matching:  An  Aid  to  Bibliographic  Search" 

Alfred  V.  Aho  &  Margaret  J.  Corasick 

Communications  of  the  ACM 

pp.  333  -  340,  Vol.  18  No.  6  (June  ’75) 


*  USAGE:  fgrep  [-vclnhyef xps]  [strings]  <files> 


*  where: 

* 

*  -v 


* 

* 

* 

* 

* 

* 

* 

* 

* 

* 

* 

* 

* 

* 

* 

* 

* 

* 

* 

* 

* 

* 

* 

* 

* 

* 

* 

* 

* 


-c 

-1 


-h 

-y 


-e 

-f 


-x 

-P 


-s 


All  lines  but  those  matching  are  printed. 

Only  a  count  of  the  matching  lines  is  printed. 

The  names  of  the  files  with  matching  lines  are 
listed  (once) ,  separated  by  newlines. 

Each  line  is  preceded  by  its  line  number  in  the 
file. 

Do  not  print  filename  headers  with  output  lines. 
All  characters  in  the  file  are  mapped  to  upper 
case  before  matching.  (This  is  the  default  if  the 
string  is  given  in  the  command  line  under  CP/M, 
as  CP/M  maps  everything  on  the  command  line  to 
upper  case.  Use  the  -f  option  if  you  need  both 
lower  and  upper  case.)  Not  a  true  UNIX  "fgrep" 
option  (normally  available  under  "grep"  only) , 
but  too  useful  to  leave  out. 

<string>.  Same  as  a  string  argument,  but  useful 
when  the  string  begins  with  a 

<file>.  The  strings  (separated  by  newlines)  are 
taken  from  a  file.  If  several  strings  are  listed 
in  the  file,  then  a  match  is  flagged  if  any  of 
the  strings  are  matched.  If  -f  is  given,  any 
following  argument  on  the  command  line  is  taken 
to  be  a  filename. 

Only  lines  matched  in  their  entirety  are  printed. 
Each  matched  line  is  preceded  by  the  matching 
substr ing (s ) .  Not  a  UNIX  "fgrep"  option,  but  too 
useful  to  leave  out. 

No  output  is  produced,  only  status.  Used  when 
when  "fgrep"  is  run  as  a  process  that  returns  a 
status  value  to  its  parent  process.  Under  CP/M.  a 


(Continued  on  page  56) 


54 


Dr.  Dobb's  Journal,  September  1985 


Fgrep  Listing  (Listing  Continued,  text  begins  on  page  46) 


*  non-zero  value  returned  by  "exit()"  may  terminate 

*  a  submit  file  that  initiated  the  program, 

*  although  this  is  implementation-dependent. 

* 

*  DIAGNOSTICS: 

it 

*  Exit  status  is  0  if  any  matches  are  found,  1  if  none,  2  for 

*  error  condition. 

* 

*  BUGS: 

* 

*  The  following  UNIX-specific  option  is  not  supported: 

* 

*  -b  Each  line  is  preceded  by  the  block  number  in 

*  which  it  was  found 

* 

*  Lines  are  limited  to  256  characters. 

it 

V 


/***  Definitions  ***/ 

^define  TRUE 

Idefine  FALSE 

-1 

0 

♦define  MAX_LINE 

♦define  CP/M 

257 

/* 

/* 

/* 

/* 

Maximum  number  of  characters 
per  line  plus  NULL  delimiter 
Comment  out  for  compilation 
under  UNIX  */ 

♦define  CMD  ERR  0 
♦define  OPT  ERR  1 
♦define  INP_ERR  2 
♦define  STR_ERR  3 
♦define  MEM_ERR  4 

/*  Error 

codes  */ 

/***  Typedefs  ***/ 

typedef  int  BOOL; 

/*  Boolean  flag  */ 

V 

V 
V 


/***  Data  Structures  ***/ 


/*  Queue  element  */ 


typedef  struct  queue 

{ 

struct  state_el,  *st_ptr; 
struct  queue  *next_el; 

}  QUEUE; 


/*  Transition  element  */ 

typedef  struct  transition 

{ 

char  lchar;  /*  Transition  character  */ 

struct  state_el  *nextst_ptr;  /*  Transition  state  pointer  */ 
struct  transition  *next_el; 

}  TRANSITION; 

/*  FSA  state  element  */ 

typedef  struct  state_el 

{ 

TRANSITION  *go_ls;  /*  Pointer  to  head  of  "go”  list  */ 

TRANSITION  *mv_ls;  /*  Pointer  to  head  of  "move"  list  */ 

struct  state_el  *fail_state;  /*  "failure"  transition  state  */ 
char  *out_str;  /*  Terminal  state  message  (if  any)  */ 

}  FSA; 

/***  Global  Variables  and  Structures  ***/ 

/*  Dummy  "failure"  state  */ 


FSA  FAIL_STATE; 

/*  Define  a  separate  data  structure  for  State  0  of  the  FSA  to 

*  speed  processing  of  the  input  while  the  FSA  is  in  that  state. 

*  Since  the  Aho-Corasick  algorithm  only  defines  "go"  transitions 

*  for  this  state  (one  for  each  valid  input  character)  and  no 

*  "failure"  transitions  or  output  messages,  only  an  array  of 


Dr.  Dobb's  Journal,  September  1985 


56 

687 


*  "go"  transition  state  numbers  is  needed.  The  array  is  accessed 

*  directly,  using  the  input  character  as  the  index. 

V 

FSA  *MZ [128] ;  /*  State  0  of  FSA  */ 

BOOL  vflag  =  FALSE,  /*  Command-line  option  flags  */ 

cflag  =  FALSE, 

If lag  =  FALSE, 
nflag  =  FALSE, 
hf lag  =  FALSE, 
yf lag  =  FALSE, 
ef lag  =  FALSE, 
f f lag  =  FALSE, 
xflag  =  FALSE, 
pflag  =  FALSE, 
sflag  =  FALSE; 

/***  Include  Files  ***/ 

♦include  <stdio.h> 

♦include  <ctype.h> 

/***  Main  Body  of  Program  ***/ 

int  main(argc,argv) 
int  argc; 
char  **argv; 

{ 

char  *temp; 

BOOL  match_flag  =  FALSE, 
proc_f  ile  ()  ; 

void  bd_go ()  , 

bd_move  ()  , 
error  ()  ; 

/*  Check  for  minimum  number  of  command  line  arguments  */ 

if  (argc  <  2) 

error (CMD_ERR,  NULL)  ; 

/*  Parse  the  command  line  for  user-selected  options  */ 


while( — argc  &&  (*++argv)[0]  ==  &&  eflag  ==  FALSE) 

for(temp  =  argv[0]+l;  *temp  !=  ' \0 ' j  temp++) 
switch (toupper ( *temp) ) 

{ 

case  ' V' : 

vflag  =  TRUE; 
break ; 
case  'C' : 

cflag  =  TRUE; 
break ; 
case  1 L 1 : 

1 f lag  =  TRUE; 
break; 
case  ' N' : 

nflag  =  TRUE; 
break; 
case  1 H1 : 

hf lag  =  TRUE; 
break; 
case  'Y' : 

yf lag  =  TRUE; 
break; 
case  ' E' : 

eflag  =  TRUE; 
break; 
case  1 F 1 : 

f f lag  =  TRUE; 
break; 
case  1 X  '  : 

xflag  =  TRUE; 
break; 
case  ' P' : 

pflag  =  TRUE; 
break; 
case  1  S' : 

sflag  =  TRUE; 
break; 
default : 

error (OPT_ERR, NULL) ; 

(Continued  on  next  page ) 


57 


Dr.  Dobb's  Journal,  September  1985 


(Listing  Continued,  text  begins  on  page  46) 


} 

/*  "pflag"  can  only  be  TRUE  if  the  following  flags  are  FALSE  */ 

if ( vf lag  ==  TRUE  ||  cf lag  ==  TRUE  ||  lflag  ==  TRUE  || 
xf lag  ==  TRUE  | |  sf lag  ==  TRUE) 
pflag  =  FALSE; 

/*  Check  for  string  (or  string  file)  argument  */ 


if ( large) 

error (CMD_ERR, NULL) ; 

/*  Build  the  "go"  transitions.  */ 

bd_go (*argv++) ; 
arge  — ; 

/*  Build  the  "failure"  and  "move"  transitions  */ 


bd_move  ()  ; 

/*  Process  each  of  the  input  files  if  not  "stdin".  */ 

if(argc  <  2) 
hf lag  =  TRUE; 
if ( large) 

{ 

if (proc_file (NULL, FALSE)  ==  TRUE  &&  match_flag  ==  FALSE) 
match_flag  =  TRUE; 

) 

else 

while (arge — ) 

if (proc_file (*argv++,TRUE)  ==  TRUE  &&  match_flag  ==  FALSE) 
match_flag  =  TRUE; 

/*  Return  status  to  the  parent  process.  Status  is  zero  if  any 
*  matches  are  found,  1  if  none.  */ 

if (match_flag  ==  TRUE) 
exit  (0)  ; 
else 

exit  (1)  ; 


/***  Functions  and  Procedures  ***/ 


/*  PROC_FILE ( )  -  Run  the  FSA  on  the  input  file  "in_file".  Returns 
*  TRUE  if  a  match  was  found,  FALSE  otherwise. 


V 


BOOL  proc_file(in_file,prt_flag) 
char  *in_file; 

BOOL  prt_flag; 

{ 

char  buffer [MAX_LINE] ,  /*  Input  string  buffer  */ 

*nl , 

*index()  , 

*stoupper() , 

*f  gets  ()  ; 


long  line_cnt  =  0L,  /*  Line  counter  */ 

mtch_cnt  =  0L;  /*  Matched  line  counter  */ 

BOOL  mtch_flag,  /*  Matched  line  flag  */ 

run_fsa  ()  ; 

FILE  *in_fd, 

*f  open  ()  ; 
void  error  ()  ; 


if(in_file  1=  NULL)  /*  A  file  was  specified  as  the  input  */ 

if(l(in_fd  =  fopen ( in_f ile, "r " ) ) ) 
error ( INP_ERR, in_file) ; 

} 

else 

in_fd  =  stdin; 


(Continued  on  page  60) 


Dr.  Dobb's  Journal,  September  1985 


58 

689 


Fgrep  Listing  (Listing  Continued,  text  begins  on  page  46) 


/*  Read  in  a  line  at  a  time  for  processing  */ 


while (fgets (buffer , MAX_LINE, in_fd) ) 

{ 

if(nl  =  index(buf fer, '\n' ) )  /*  Remove  newline  */ 

*nl  =  '\0'f 
♦ifdef  CP/M 

if ( ff lag  ==  FALSE  ||  yflag  ==  TRUE) 
stoupper(buffer) ; 

telse 

if (yflag  ==  TRUE) 
stoupper (buffer) ; 

lendif 

line_cnt++j  /*  Increment  the  line  counter  */ 

if ( (mtch_flag  =  run_fsa (buffer) )  ==  TRUE) 

mtch_cnt++;  /*  Increment  matched  line  counter  */ 

if(cflag  ==  FALSE  &&  lflag  ==  FALSE  &&  sflag  ==  FALSE  && 
( (mtch_flag  ==  TRUE  &&  vflag  ==  FALSE)  || 

(mtch_flag  ==  FALSE  &&  vflag  ==  TRUE))) 

{ 

if ( hf lag  ==  FALSE  &&  prt_flag  ==  TRUE) 
printf("%s:  ",in_file); 
if (nf lag  ==  TRUE) 

printf ( "%051d:  " , line_cnt) ; 
puts (buffer) ; 

} 

J 

if (lflag  ==  TRUE  &&  mtch_cnt  >  0) 
printf ( "%s\n" , in_f ile) ; 
else  if(cflag  ==  TRUE  &&  sflag  ==  FALSE) 
printf (”%ld\n",mtch_cnt) ; 
if ( in_f ile  1=  NULL) 
fclose  ( in_fd)  ; 

if(mtch_cnt)  /*  Match  found  */ 

return  TRUE; 

else  /*  No  match  found  */ 

return  FALSE; 


/*  RUN_FSA() 

* 

V 


-  Run  the  finite  state  automaton  with  string  "str” 
as  input.  Return  TRUE  if  match,  FALSE  otherwise. 


BOOL  run_fsa(str) 
register  char  *str; 

{ 

register  FSA  *st_ptr; 
char  ‘message  =  NULL; 
BOOL  msg_flag  =  FALSE; 
FSA  ‘go ( ) , 

‘move  ()  ; 


st_ptr  =  NULL;  /*  Initialize  FSA  */ 

if(xflag  ==  FALSE) 

{ 

/*  Process  the  next  input  character  in  the  string  */ 


while (*str) 

{ 

st_ptr  =  move (st_ptr, *str) ; 

/*  Print  terminal  state  message  and  update  FSA  */ 


if(st_ptr  ==  0  &&  message) 

{ 

printf (" — >  %s\n" , message) ; 

message  =  NULL; 

st_ptr  =  move (st_ptr , *str) ; 

} 

str++; 
if (st_ptr ) 

if (st_ptr->out_str)  /*  Terminal  state?  */ 

if (pf lag  ==  TRUE) 

{ 

/*  Save  terminal  state  message  */ 


message  =  st_ptr->out_str ; 
msg_flag  =  TRUE; 


60 


cor\ 


Dr.  Dobb’s  Journal,  September  1985 


} 

else 

return  TRUE; 

} 

if (message)  /*  Print  any  remaining  terminal  state  message  */ 
printf(" — >  %s\n“ , message)  ; 

return  msg_flag; 

} 

else  /*  Match  exact  lines  only  */ 

{ 

while (*str) 

{ 

st_ptr  =  go  (st_ptr ,  *str++)  ; 
if ( 1 st_ptr  ||  st_ptr  ==  &FAIL_STATE) 
return  FALSE;  /*  Line  not  matched  */ 

} 

return  TRUE;  /*  Exact  line  matched  */ 

} 

} 

/*  G0()  -  Process  "litchar"  and  return  a  pointer  to  the  FSA's 

*  corresponding  "go"  transition  state.  If  the  character 

*  is  not  in  the  FSA  state's  "go"  transition  list,  then 

*  return  a  pointer  to  FAIL_STATE. 

V 

FSA  *go (st_ptr, litchar) 

FSA  *st_ptr ; 
register  char  litchar; 

l 

register  TRANSITION  ‘current; 

/*  If  State  0,  then  access  separate  State  0  data  structure  of 

*  the  FSA.  Note  that  there  are  no  failure  states  defined  for 

*  any  input  to  FSA  State  0. 

V 

if ( lst_ptr) 

return  MZ [litchar]; 
else 
{ 

/*  Point  to  the  head  of  the  linked  list  of  "go"  transitions 

*  associated  with  the  state. 

V 

current  =  st_ptr->go_ls; 

/*  Transverse  the  list  looking  for  a  match  to  the  input 

*  character. 

V 

while (current) 

{ 

if (current->lchar  ==  litchar) 
break ; 

current  =  cur rent->next_el ; 


} 

/*  Null  value  for  "current"  indicates  end  of  list  was  reachec 
*  without  having  found  match  to  input  character. 

V 

return  current  ?  current->nextst_ptr  :  &FAIL_STATE; 

} 

} 

/*  MOVE ( )  -  Process  "litchar"  and  return  a  pointer  to  the  FSA's 
*  corresponding  "move"  transition  state. 

V 

FSA  ‘move (st_ptr , litchar) 

FSA  *st_ptr ; 
register  char  litchar; 

{ 

register  TRANSITION  ‘current; 

/*  If  State  0,  then  access  separate  State  0  data  structure  of 
*  the  FSA. 

V 


(Continued  on  next  page) 


Dr.  Dobb’s  Journal.  September  1985 


61 

691 


(Listing  Continued,  text  begins  on  page  46) 


if ( 1 st_ptr) 

return  MZ[litchar]; 
else 
{ 

/*  Point  to  the  head  of  the  linked  list  of  "move"  transitions 

*  associated  with  the  state. 

V 

current  =  st_ptr->mv_ls ; 

/*  Transverse  the  list  looking  for  a  match  to  the  input 

*  character. 

V 

while  (current) 

{ 

if (current->lchar  ==  litchar) 
break; 

current  =  current->next_el; 

} 

/*  Null  value  for  "current"  indicates  end  of  list  was  reached 

*  without  having  found  match  to  input  character.  The 

*  returned  pointer  is  then  to  State  0. 

V 


return  current  ?  current->nextst_ptr  :  NULL; 

} 

} 

/*  BD_G0 ( )  -  Build  the  "go"  transitions  for  each  state  from  the 
*  command-line  arguments. 

*/ 

void  bd_go('str) 
char  *str; 

{ 

register  char  litchar; 
char  *nl, 

buffer [MAX_LINEJ ,  /*  Input  string  buffer  */ 

♦stoupper ( ) , 

*f gets  ()  , 

♦index ( ) ; 

FILE  *str_fd, 

*f  open  ()  ; 
void  error ()  , 
enter ( ) ; 

/*  Initialize  FSA  State  0  "go"  transition  array  so  that  every 

*  invocation  of  "go()"  with  "state"  =  0  initially  returns  a 

*  pointer  to  FAIL_STATE. 

V 

for (litchar  =  1;  litchar  <=  127;  litchar++) 

MZ[ litchar]  =  &FAIL_STATE; 

/*  If  the  -f  option  was  selected,  get  the  newline-separated 

*  strings  from  the  file  "str"  one  at  a  time  and  enter  them 

*  into  the  FSA.  Otherwise,  enter  the  string  "str"  into  the 

*  FSA. 

V 

if ( ff lag  ==  TRUE) 

{ 

if(l(str_fd  =  fopen(str, "r") ) ) 
error ( STR_ERR, str) ; 

while ( fgets (buf fer ,MAX_LINE, str_f d) ) 

( 

if(nl  =  index (buf fer , 1 \n' ) )  /*  Remove  the  newline  */ 

*nl  =  ' \0  1 ; 
if (yf lag  ==  TRUE) 
stoupper (buffer) ; 
enter (buf fer) ; 

} 

f close (str_f d) ; 


62 

fid? 


Dr.  Dobb's  Journal,  September  1985 


} 

else 

{ 

if (yflag  ==  TRUE) 
stouppe r (buf f er )  ; 
enter (str) ; 

} 

/*  For  every  input  character  that  does  not  lead  to  a  defined 

*  "go"  transition  from  FSA  State  0,  set  the  corresponding 

*  element  in  the  State  0  "go"  transition  array  to  indicate 

*  a  "go"  transition  to  State  0. 

V 

for(litchar  =  1;  litchar  <=  127;  litchar++) 
if (MZ [litchar]  ==  &FA IL_STATE) 

MZ[ litchar]  =  NULL; 

} 

/*  ENTER()  -  Enter  a  string  into  the  FSA  by  running  each 

*  character  in  turn  through  the  current  partially- 

*  built  FSA.  When  a  failure  occurs,  add  the  remainder 

*  of  the  string  to  the  FSA  as  one  new  state  per 

*  character.  (Note  that  '\0'  can  never  be  a  valid 

*  character  -  C  uses  it  to  terminate  a  string.) 

V 

void  enter(str) 
char  *str; 

{ 

FSA  *S, 

*g°()  , 

♦create ()  ; 

TRANSITION  ‘current, 

*insert()  ; 
char  *strsave(); 
register  char  *temp; 

register  FSA  *st_ptr  =  NULL;  /*  Start  in  FSA  State  0  */ 
register  FSA  *nextst_ptr; 
void  error () ; 

/*  Run  each  character  in  turn  through  partially-built  FSA  until 

*  a  failure  occurs. 

V 


temp  =  str; 

while ((s  =  go (st_ptr, *temp) )  1=  &FAIL_STATE) 

{ 

temp++; 
st_ptr  =  s; 


/*  Process  the  remainder  of  the  string  */ 

while (*temp) 

{ 

/*  If  a  new  state,  then  create  a  new  state  and  insert 

*  transition  character  and  "go"  transition  in  current 

*  state.  (Note  special  case  of  FSA  State  0.) 

V 

if ( !st_ptr) 

nextst_ptr  =  MZ[*temp++]  =  createO; 
else  if(l (current  =  st_ptr->go_ls) ) 

{ 

nextst_ptr  =  createO; 

st_ptr->go_ls  =  insert (nextst_ptr, *temp++) ; 

} 

else 

{ 

/*  ...  or  it  was  the  character  that  the  FSA  returned  a 

*  "failure"  for.  Find  the  tail  of  the  current  state's  list 

*  of  "go"  transitions,  create  a  new  state  and  append  it 

*  to  the  current  state's  "go"  list. 

V 


while (cur  rent->next_el) 


current  =  current->next_el ; 
nextst_ptr  =  createO ; 
current->next_el  =  insert  (riextst_ptr 

} 

st_ptr  =  nextst_ptr; 


*temp++) ; 


( Continued  on  next  page ) 


RQ3 


Fgrep  Listing  (Listing  Continued,  text  begins  on  page  46) 


/*  Make  string  terminal  state's  output  message  */ 
st_ptr->out_str  =  strsave  (str) ; 


/*  INSERTO  -  Create  a  new  "go"  transition  and  return  a  pointer 
*  to  it. 


TRANSITION  *inse rt ( st_pt r , litchar ) 

FSA  *st_ptr; 
char  litchar; 

{ 

TRANSITION  ‘current; 
char  *malloc(); 
void  error () ; 

if( I (current  =  (TRANSITION  *) malloc (sizeof (TRANSITION) )) ) 
error  (MEM_ERR, NULL)  ; 
current->lchar  =  litchar; 
current->nextst_ptr  =  st_ptr; 
current->next_el  =  NULL; 
return  current; 


/*  CREATEO  -  Create  an  FSA  state  and  return  a  pointer  to  it.  */ 

FSA  *create() 

{ 

FSA  *st_ptr; 
char  ‘malloc (); 
void  error () ; 

if(l(st_ptr  =  (FSA  *) malloc (sizeof (FSA) )) ) 
error (MEM_ERR, NULL) ; 
st_ptr->go_ls  =  st_ptr->mv_ls  =  NULL; 
st_ptr->fail_state  =  NULL; 
st_ptr->out_str  =  NULL; 
return  st_ptr; 


/*  BD_MOVE ( ) 
* 

V 


Build  the  "failure"  and  "move"  transitions  for 
each  state  from  the  "go"  transitions. 


void  bd_move() 

{ 

register  char  litchar; 

register  FSA  *r,  /*  Temporary  FSA  state  pointers  */ 

*s, 

*t ; 

FSA  ‘go  ()  , 

‘move  ()  ; 

TRANSITION  ‘current, 

‘insert  ()  ; 

QUEUE  ‘first,  /*  Pointer  to  head  of  queue  */ 

‘last;  /*  Pointer  to  tail  of  queue  */ 

void  add_queue() , 

delete_queue  ()  ; 

last  =  first  =  NULL;  /*  Initialize  the  queue  of  FSA  states  */ 

/*  For  each  input  character  with  a  "go"  transition  out  of  FSA 

*  State  0,  add  a  pointer  to  the  "go"  state  to  the  queue.  Note 

*  that  this  will  also  serve  as  the  "move"  transition  list  for 

*  State  0. 


*/ 

for(litchar  =  1;  litchar  <=  127;  litchar++) 
if(s  =  go (NULL, litchar) ) 
add_queue (&f irst, &last,s) ; 

/*  While  there  are  still  state  pointers  in  the  queue,  do  ...  */ 
while (first) 


(Continued  on  page  661 


64 


Dr.  Dobb's  Journal.  September  1985 


Fgrep  Listing  (Listing  Continued,  text  begins  on  page  46) 


/*  Remove  State  ”rn  pointer  from  the  head  of  the  queue.  */ 

r  =  f irst->st_ptr; 
delete_queue ( & f irst) ; 

/*  Skip  (terminal)  state  with  no  "go”  transitions"  */ 

if  ( lr->go_ls) 
continue ; 

/*  Make  "move"  transition  list  for  terminal  state  same  as  its 

*  "go"  transition  list. 

V 

if (r->out_str) 

r->mv_ls  =  r->go_ls; 

/*  For  every  input  to  State  "r"  that  has  a  "go"  transition  to 

*  State  "s",  do  ... 

V 

for(litchar  =  1;  litchar  <=  127;  litchar++) 

{ 

if((s  =  go (r, litchar) )  !=  &FAIL_STATE) 

{ 

/*  If  a  defined  "go"  transition  exists  for  State  "r"  on 

*  on  input  "litchar",  add  a  pointer  to  State  "s"  to  the 

*  end  of  the  queue. 

V 

add_queue  (&f  irst,  {.last,  s)  ; 

/*  Calculate  the  "failure"  transition  of  State  "s"  using 

*  the  following  algorithm. 

V 


t  =  r->fail^state; 

while  (go  (t,  litchar)  ==  &FA IL_STATE) 
t  =  t->f ail_state; 
s->f ail_state  =  go (t, litchar) ; 

) 

else 

{ 

/*  ...  otherwise  set  the  pointer  to  State  "s"  to  a 

*  pointer  to  the  precalculated  "move"  transition  of 

*  State  "r"'s  failure  state  on  input  "litchar". 

V 


s'  =  move  (r->fail_state,  litchar)  ; 

) 

/*  Add  State  "s"  as  the  "move"  transition  for  State  "r"  on 

*  input  "litchar"  only  if  it  is  not  State  0  and  “r"  is  not 

*  a  terminal  state. 

V 

if(s  &&  !r->out_str) 

if ( lr->mv_ls)  /*  First  instance  of  the  list?  */ 
current  =  r->mv_ls  =  insert (s, litchar) ; 
else  /*  No,  just  another  one  ...  */ 

current  =  current->next_el  =  insert (s , litchar ) ; 

} 

} 

) 

/*  ADD_QUEUE()  -  Add  an  instance  to  the  tail  of  a  queue  */ 

void  add_queue (head_ptr , tail_ptr , st_ptr ) 

QUEUE  **head_ptr, 

**tail_ptr; 

FSA  *st_ptr ; 

{ 

QUEUE  *pq ; 
char  *malloc(); 
void  error ( ) ; 

/*  Allocate  the  necessary  memory  and  set  the  variables.  */ 

if ( I (pq  =  (QUEUE  *) malloc (sizeof (QUEUE) )) ) 
er  ror (MEM_ERR, NULL) ; 


Dr.  Dobb’s  Journal,  September  1985 


COR 


pq->st_ptr  =  st_ptr; 
pq->next_el  =  NULL; 

if ( l*head_ptr)  /*  First  instance  of  the  queue?  */ 

*tail_ptr  =  *head_ptr  =  pq; 
else  /*  No,  just  another  one  ...  */ 

*tail_ptr  =  ( *tail_ptr ) ->next_el  =  pq; 


/*  DELETE_QUEUE ( )  -  Delete  an  instance  from  the  head  of  queue  */ 

void  delete_queue (head_ptr) 

QUEUE  **head_ptr ; 

{ 

*head_ptr  =  ( *head_pt r ) ->next_el ; 

} 

/*  STRSAVE ( )  -  Save  a  string  somewhere  in  memory  */ 

char  *strsave (str) 
char  *str; 

{ 

int  strlen () ; 
char  *p, 

♦malloc  ()  ; 
void  error ()  ; 


if(p  =  malloc(strlen(str)  +  1)  ) 
strcpy (p, str ) ; 
else 

error (MEM_ERR, NULL) ; 
return  p; 


/*  STOUPPER()  -  Map  entire  string  pointed  to  by  "str"  to  upper 
*  case. 

V 

char  *stoupper (str) 
register  char  *str; 

{ 

register  char  *temp; 


temp  =  str; 
while (*temp) 

*temp++  =  toupper(*temp) ; 
return  str; 


/*  ERROR() 

* 

V 


Error  reporting.  Returns  an  exit  status  of  2  to  the 
parent  process. 


void  error(n,str) 
int  n; 
char  *str; 

{ 

fpr intf (stderr, "\007\n***  ERROR  -  "); 
switch(n) 

{ 

case  CMD_ERR: 

fprintf (stderr, "Illegal  command  line"); 
break; 

case  OPT_ERR: 

fprintf (stderr, "Illegal  command  line  option"); 
break; 

case  INP_ERR : 

fprintf (stderr, "Can1 t  open  input  file  %s",str); 
break; 

case  STR_ERR: 

fprintf (stderr, "Can1 t  open  string  file  %s",str); 
break ; 

case  MEM_ERR: 

fpr intf (stderr, "Out  of  memory"); 
break; 
default : 
break; 

} 

fprintf (stderr, "  ***\n\nUsage :  fgrep  [-vclnhyef xps) " ) ; 
fprintf (stderr, "  [strings]  <files>\n"); 
exit(2)  ; 

} 


/***  End  of  FGREP. C  ***/ 


End  Listing 


66 


Dr.  Dobb's  Journal.  September  1985 


(Continued  from  page  44) 

using  B-trees  and  hash  functions,  two 
subjects  we  expect  to  expand  on  in 
future  issues. 

This  issue  of  DDJ  focuses  on  useful 
algorithms,  and  is  intended  to  serve 
as  a  prelude  to  a  new  emphasis  on  al¬ 
gorithmic  analysis  of  the  program 
listings  in  DDJ.  The  reason  for  such 
an  emphasis  is  simple:  to  increase  the 
portability  and  usability  of  the  tools 
we  publish.  Processor-specific  maga¬ 
zines  can  get  away  with  publishing 
processor-specific  code.  We  don’t 
want,  and  we  think  you  don’t  want 
DDJ  to  become  a  processor-specific 
magazine,  so  we’re  trying  to  broaden 
the  applicability  of  our  offerings  by 
providing  the  keys  to  allow  you  to  ex¬ 
tract  what  you  can  use  from  the  list¬ 
ings.  Those  keys  include,  we  think, 
well-documented  high  level  code  and 
clear  discussion  of  the  underlying  al¬ 
gorithms  and  useful  techniques  em¬ 
bodied  in  the  code. 

And  what  about  someday-useful 
algorithms?  The  algorithmic  cutting 
edge?  The  Fgrep  algorithm  in  this  is¬ 
sue  uses  limited  parallelism  to  gain 
speed.  Next  month  we’ll  look  at  the 
Novix  Forth  chip  and  what  it  gains 
from  a  little  parallelism.  But  deep 
parallelism  of  the  sort  realizable  in 
multiprocessor  machines  of  the  fu¬ 
ture  will  require  different  algorithms, 
different  models  of  the  computer. 
What  lies  beyond  Von  Neumann  ar¬ 
chitecture?  We  hope  to  investigate 
parallelism  in  upcoming  issues  of 
DDJ. 


DDJ 


Reader  Ballot 

Vote  for  your  favorite  feature/article. 
Circle  Reader  Service  No.  1 94. 


Dr.  Dobb’s  Journal,  September  1985 


Bose-Nelson  Sort 


orting  algorithms  can  be  divided 

ing  algorithms  known  to  us.  But  do  we 

by  joe  Celko  into  two  types:  those  that  pre- 

know  just  how  well  a  sort  can  per- 

serve  the  original  sequence  of 

form? 

the  records  (also  called  stable  sorts) 

In  theory,  the  fewest  number  of  ex- 

and  those  that  don’t.  For  example,  if 

changes  required  to  sort  a  file  of  (n) 

a  stable  alphabetic  sort  is  applied 

items  is  the  ceiling  of  log2(n!)  ex- 

twice  to  an  address  file,  first  on  city 

changes.  For  example,  for  a  file  of  five 

names  then  on  states,  the  result  will 

items,  we  have  log2(5!)  =  Iog2(120) 

be  a  list  ordered  by  cities  within 

=  6.9068,  which  rounds  up  to  seven 

states.  The  same  file  sorted  with  a 

exchanges.  The  next  question  is 

non-stable  sort  would  lose  the  city  or- 

whether  such  algorithms  perform 

dering  on  the  second  sort.  Although 

equally  well  for  different  numbers  of 

the  ability  to  use  stable  sorts  in  a  se- 

records. 

ries  makes  them  look  more  attractive 

The  answer  is  mixed:  optimal  algo- 

than  the  non-stable  sorts,  the  non- 

rithms  exist  for  some  values  and  not 

stable  sorts  are  faster. 

for  others.  No  single  algorithm  seems 

All  sorting  algorithms  consist  of  an 

to  be  minimal  for  all  values.  Many  of 

exchange  operation  that  swaps  two 

the  minimal  algorithms  are  highly 

items  in  the  file  or  array  and  puts 

specialized.  For  example,  Dr.  H.  B. 

them  into  sorted  order.  The  number 

Demuth  discovered  an  algorithm  for 

of  exchanges  done  by  a  sort  deter- 

sorting  five  items — and  only  five  in 

mines  how  fast  the  sort  runs — and 

seven  exchanges.2 

this  number  varies  with  the  existing 

Can  an  algorithm  be  generalized 

order  in  the  input  file. 

for  any  fixed  number  of  records  and 

No  sort  algorithm  is  optimal  under  all  conditions. 

This  one  is  useful  when  stability  is  not  a  consider- 

at  ion  or  when  parallel  processing  is  possible. 

This  gives  us  a  worst-case  number 

still  come  pretty  close  to  being  opti- 

of  exchanges,  a  best-case  number  of 

mal?  This  question  leads  us  to  the 

exchanges,  and  an  expected  number 

P-operator  discovered  by  R.  C.  Bose 

of  exchanges.  For  example,  the 

and  R.  J.  Nelson.3  This  is  a  recursively 

Quicksort  algorithm1  has  a  best-case 

defined  function  that  generates  ex- 

time  for  a  file  that  is  already  sorted 

change  pairs  for  a  fixed  number  of 

(most  algorithms  do  quite  well  in  this 

items. 

case)  and  it  has  a  worst  case  for  a  file 

The  bad  news  is  that  the  algorithm 

in  reverse  order.  Its  expected  perfor- 

takes  a  long  time  to  generate  these 

mance  is  (n  Iog2(n))  exchanges,  where 

exchange  pairs;  you  really  should  not 

(n)  is  the  number  of  records.  This 

use  it  directly  to  sort  a  file.  The  good 

makes  Quicksort  one  of  the  best  sort- 

news  is  that  you  need  generate  the 

pairs  only  once,  and  you  have  a  very 

good  sort  for  a  given  number  of 

Joe  Celko,  8833  Sunset  Blvd  #304, 

items.  An  application  of  this  might  be 

Los  Angeles,  CA  90069. 

sorting  a  poker  or  bridge  hand  in  a 

68 

r>rio 

Dr.  Dobb’s  Journal,  September  1985 

card-playing  program. 

The  P-operator  can  be  defined  as 
ordered  tuples  of  integers  with  a  set 
of  transformation  rules  that  are  ap¬ 
plied  left  to  right4  The  rules  appear  in 
the  Figure  (at  right)  and  can  be  used 
to  write  a  recursive  program.  The  one 
given  in  Listing  One  (page  70)  is 
credited  to  Dennis  Allison.  Another 
approach  is  to  use  an  explicit  stack 
and  push  quintuples,  as  shown  in 
Listing  Two  (page  76).  This  second 
program  will  run  faster  due  to  the  re¬ 
cursive  procedure  calls  and  lack  of 
printf  functions  in  C.  Readers  with 
accounts  on  CompuServe  can  obtain 
the  source  code  and  a  short  piece  of 
documentation  for  Listing  Two  by 
accessing  [70665,  1307]. 

What  is  happening  in  this  confus¬ 
ing  set  of  rules  is  that  the  original  ar¬ 
ray  is  divided  into  two  ordered  se¬ 
quences,  which  are  merged  into  one 
ordered  sequence.  The  two  sub¬ 
sequences,  in  turn,  are  ordered  by  re¬ 
cursive  calls  to  the  same  procedure. 
For  five  terms,  the  Bose-Nelson 
P-operator  program  will  generate  the 
following  C  program: 


P(1,  x,  y)  =  >  Swap(x,  y); 

P(2,  i,  1)  =  >  Null 

P(2,  i,  x)  =>  P(2,i,a)  P(2,(i+a),(x-a))  P(3,i,a,(i+a),  (x— a)) 

P(3,  i,  0,j,  y)  =>  Null 
P(3,  i,  x,j,  0)  =>  Null 

(These  tuples  should  never  appear,  but  they  are  given  for  completeness.) 
P(3,  i,  1,j,  1)=>P(1,i,j) 

P(3,  i,  1 ,  j,  2)  =>  P(1 ,  i,  (j+  1))  P(1,i,j) 

P(3,  i,  2,  j,  1)=>P(1,i,j)  P(1,(i+1),j) 

P(3,  i,  x,  j,  y)  =>  P(3,  i,  a,  j,  b)  P(3,  (i+a),  (x-a),  (j+b),  (y-b)) 

P(3,  (i  +  a),  (x-a),  j,  b) 

where  a  =  [x/2] 

b=  [(y+ 1)/2]  if  (x  is  even) 

[y/2]  if  (x  is  odd) 

Figure 

Transformation  Rules  for  Ordered  Tuples 


{ 

swap(  1 , 2) 
swap(4,  5) 
swap(3,  5) 
swap(3,  4) 
swap(l,  4) 
swap(  1,  3) 
swap(2,  5) 
swap(2,  4) 
swap(2,  3) 
} 


You  can  check  this  program  by  tak¬ 
ing  five  playing  cards  and  dealing 
them  face  down  in  a  row.  Turn  over 
cards  1  and  2;  if  they  are  out  of  order, 
swap  them  and  turn  them  face  down 
again.  Repeat  this  procedure  until  you 
have  made  all  of  the  swaps  in  the  pro¬ 
gram  then  turn  over  all  of  the  cards. 
They  should  be  in  sorted  order  now. 

It  is  worth  noting  that  this  pro¬ 
gram  has  nine  steps  and  that  we  al¬ 
ready  know  seven  is  the  optimal  num¬ 
ber.  So,  contrary  to  earlier  beliefs,5 
the  Bose-Nelson  does  not  produce  op¬ 
timal  sorts.  (It  is  left  as  an  exercise 
for  the  reader  to  see  how  well  the 
Bose-Nelson  sort  performs  compared  ! 


Dr.  Dobb’s  Journal,  September  1985 


69 


with  the  optimal  values.)  However, 
the  swap  pairs  do  not  depend  on  the 
previous  results,  which  makes  it  easy 
to  use  them  for  hard-wired  sorting 
machines.  It  also  makes  it  possible  to 
use  them  in  parallel  processor  com¬ 
puters:  in  the  above  five-item  sort, 
the  calls  to  swap(  1,  2)  and  swap(4,  5) 
can  be  done  at  the  same  time. 


References 

1  Hoare,  C.  A.  R.,  “Algorithm  64: 
Quicksort,”  Communications  of  the 
ACM,  4  (1961)  p.  321. 

2  Demuth,  H.  B.,  PhD  thesis,  Stan¬ 
ford  University,  1956,  pp.  41-43. 

3  Bose  and  Nelson,  “A  Sorting  Prob¬ 
lem,”  JACM,  Vol.  9  (1962)  pp. 
282-296. 


4  [RIC  1972]  Rich,  Robert  P.,  Inter¬ 
nal  Sorting  Methods  Illustrated 
with  PL/1  Programs,  Prentice  Hall, 
1972. 

5  Knuth,  Donald,  Sorting  and 
Searching ,  Addison- Wesley,  1973. 

DDJ 

Reader  Ballot 

Vote  for  your  favorite  feature/article. 

Circle  Reader  Service  No.  1 96. 


Bose-Nelson  Sort  (Text  begins  on  page  68) 

Listing  One 

*  Recursive  version  of  Bose-Nelson  sort  */ 

#include  "STDIO.H" 

finclude  "atou.c"  /*  ASCII  to  unsigned  integer  */ 

int  count  =  0; 

main  (argc,  argv) 
int  argc; 
char  argv [ ] ; 

{  int  n; 

if  (argc  <  2) 

{  printf  ("USAGE:  bose  n  >outf ile\n" ) ; 
exit  ()  ; 

} 

else 

{  n  =  (atou(*(ap  =  *++argv)  ==  ap+1:  ap) ; 

bosesort  (lf  n) ; 

printf  ("There  were  %d  swaps\n"f  count); 

} 

bosesort(i,  j) 
int  i,  j; 

{  int  m; 

if  (n  >  1) 

{  m  =  1  +  ( j-l+l)/2  -  1; 
bosesort  (i,  m) ; 
bosesort  ((m+1),  j); 
bosemerge  (i,  m,  (m+1),  j); 

} 

bosemerge  (il,  i2,  jl,  j2) 
int  il,  i2,  jl,  j2; 

{  if  ( ( i 2  ==  il)  &&  ( j 2  ==  jl)) 

{  printf  ("swap(%d,  %d);\n",  il,  jl); 
count  +=  1; 

} 

else  if  ( (i2  ==  (il  +  1))  &&  (j2  ==  jl)) 

{  printf  ( "swap (%d,  %d);\n",  il,  jl); 
printf  ("swap(%d,  %d);\n",  i2,  jl); 
count  +=  2; 

(Continued  on  page  72) 


70 


Dr.  Dobb's  Journal.  September  1985 


Bose-Nelson  Sort  (Listing  Continued,  text  begins  on  page  68) 

Listing  One 

else  if  ( (i2  ==  il)  &&  ( j 2  ==  (jl+1)) 

{  printf  ( "swap (%d,  %d);\n"f  il,  j2)  ; 
printf  ("swap(%d,  %d);\n"f  ilf  jl); 
count  +=  2; 

} 

else 

{  i_mid  =  il  +  (i2-il+l)/2  -1; 

if  (even(i2  -  il+1)  &&  (i2-il  1=  j 2— j 1 ) ) 
j_mid  =  jl  +  (j2-jl+l)/2; 
else 

j_mid  =  jl  +  (j2-jl+l)/2  -  1; 

bosemerge  (il,  i_mid,  jl,  j_mid) ; 
bosemerge  (i_mid+l,  i2,  j_mid+l,  j2); 
bosemerge  (i_mid+l,  i2,  jl,  j_mid) ; 

1 

j 

j  End  Listing  One 

even  (x)  int  x;  {  return  (1  -  (1  &  x));} 

/*  non-recursive  version  of  Bose-Nelson  sort  */ 

♦include  "STDIO.H" 

♦include  "atou.c"  /*  ASCII  to  unsigned  integer  */ 

♦define  MAXSTACK  2500  /*  pick  a  high  number  */ 

int  count  =  0; 

int  top  =  0; 

int  stack [MAXSTACK] ; 

main  (argc,  argv) 
int  argc; 
char  argv [ ] ; 

{  int  n; 

if  (argc  <  2) 

{  printf  ("USAGE:  bose  n  >outf ile\n" ) ; 
exit ( ) ; 

} 

else 

{  n  =  (atou(*(ap  =  *++argv)  ==  '-'?  ap+1 :  ap) ; 
bosesort  (n) ; 

printf  ("There  were  %d  swaps\n",  count); 

} 

} 

bosesort (n) 
int  n; 

{  int  a,  b,  i,  j,  x,  y; 
printf  ( " { \n " ) ; 
push5  (2,l,n,0,0) ; 
while  (top  >0) 
switch  (stack [top]) 

{ 


Dr.  Dobb's  Journal,  September  1985 


72 

701 


Bose-Netson  Sort 

Listing  Two 

case  0:  {  pr intf ( " }\n" ) y 
top  =  0; 

} 

break; 

case  1:  {  printf ( "swap (%d, %d) ;\n" ,  stack [top-1 ] ,  stack [top-2]  ) ; 
top  -=  3; 
count++; 

} 

break ; 

case  2:  {  i  =  stack [top-1] ; 

x  =  stack [top-2] ; 

/*  these  two  variables  are  defined  for  easy  reading  */ 
top  -=  3; 
if  (  x  !=  1) 

{  a  =  (x/2); 

push3(3,  i,  a,  (i+a),  (x-a))y 
push3(2,  (i+a),  (x-a)); 
push3  ( 2 ,  i .  a)  : 

} 

} 

break ; 


case  3:  {  i  =  stack [top-1 ] ; 

x  =  stack [top-2] ; 
j  =  stack [top-3] ; 
x  =  stack [top-4] ; 

/*  these  four  variables  are  defined  for  easy  reading  */ 
a  =  (x/2) ; 
if  (even(x)) 
b  =  (y+l)/2; 
else 

b  =  (y/2) ; 
top  -=  5; 

if  ((x  ==  1)  &&  (y  ==  D) 
push3  (1,  i,  j); 
else  if  ((x  ==  1)  &&  (y  ==  2)) 

{  push3 (1,  i,  j ) ; 
push3 (1,  i,  ( j+1 ) ) ; 

} 

else  if  ( (x  ==  2)  &&  (y  ==  1)) 

{  push3 (1,  (i+1) ,  j ) ; 

push3 (1,  i,  j); 

} 

else  {  push5(3,  (i+a),  (x-a),  j,  b) ; 

push5 ( 3 ,  (i+a),  (x-a),  (j+b),  (y-b) ) ; 

push5 ( 3 ,  i,  a,  j,  b) ; 

} 

5 

break ; 


76 


Dr.  Dobb’s  Journal,  September  1985 


default : 

{  printf  ("FATAL:  Error  in  stack\n"); 
exit  ()  ; 

} 

break ; 

}  /*  end  of  while  loop  */ 

}/*  end  of  main  */ 

even  (x) 
int  x; 

{  return  (1  -  (1  &  x) ) ; } 

push3(a,  b,  c) 
int  a,  b,  c; 

{  stack [++top]  =  c; 
stack [++top]  =  b; 
stack [++topj  =  a; 
if  (top  >  MAXSTACK) 

{  printf  ("FATAL:  stack  overflow  at  %d\n",  top); 


exit  ( )  ; 

} 

} 


push5(a,  bf  c,  df  e) 
int  a,  bf  c; 

{  stack [++top]  =  e; 
stack [++top]  =  d; 
stack [++top]  =  c; 
stack [++top]  =  b; 
stack [++topj  =  a; 
if  (top  >  MAXSTACK) 

{  printf  ("FATAL:  stack  overflow  at  %d\n",  top); 
exit() ; 

} 

} 


End  Listings 


77 


Dr.  Dobb  s  Journal.  September  1985 


7  m 


Two  TeX  Implementations 
for  the  IBM  PC 


Richard  Furuta  and  “An  exPerienced  system  program- 

process  of  working  out  this  specific 

mer,  who  wants  to  provide  the  best 

problem,  Knuth  supplied  just  about 

PierfG  A.  MacKay  possible  documentation  of  his  or  her 

every  need  that  could  be  imagined 

software  products,  needs  two  things 

for  text  typesetting.  There  are  cer- 

simultaneously:  a  language  like 

tain  things  Tj^X  does  not  do;  it  has 

for  formatting,  and  a  language  like 

no  built  in  graphics  capabilities,  and 

Pascal  for  programming.”  — Donald 

it  does  not  provide  for  arbitrary  ro- 

Knuth,  The  WEB  System  of  Struc- 

tations  of  characters  and  lines,  but  it 

lured.  Documentation. 

should  be  noted  that  there  are  very 

T^X,  as  many  readers  will  know, 

few  digital  phototypesetters  which 

is  Donald  Knuth’s  system  for  type- 

could  make  use  of  such  capacities 

setting  technical  text.  The  name  is 

even  if  they  were  offered.  There 

based  on  the  Greek  root  in  the  word 

are  good  general  mechanisms  within 

‘technical’;  it  is  in  no  way  associ- 

Tf,]X  which  may  someday  be  used 

ated  with  the  state  of  Texas,  and 

to  supply  some  of  these  special  re- 

only  remotely  associated  even  with 

finements  if  the  output  devices  for 

the  word  ‘text’.  The  shibboleth  of 

them  become  readily  available.  For 

the  true  convert  is  the  pronuncia- 

now,  T^X  offers  precisely  what  Don 

tion  of  the  final  letter,  which  should 

Knuth  claims  for  it  in  the  preface 

sound  rather  like  the  ‘ch’  at  the  end 

to  the  TjfXbook,  “a  system  intended 

of  the  name  of  J.  S.  Bach.  The  offi- 

for  the  creation  of  beautiful  books.” 

cial  derivation  is  from  tekhne=L art’ 

T^X  will  be  used,  and  is  being  used 

or  ‘craft’. 

for  everything  from  two-line  busi- 

A  system  intended  for  the  creation  of 

beautiful  books. 

Knuth  is  a  Professor  of  Computer 

ness  letters  to  railway  schedules,  but 

Science  at  Stanford  University  and 

the  heart  of  the  program  is  book- 

is  best  known  for  his  ongoing  se- 

production,  and  it  can  only  be  truly 

ries  of  books  collectively  entitled 

understood  in  that  light. 

The  Art  of  Computer  Programming , 

An  earlier  version  of  this  program, 

perhaps  the  most  important  central 

now  distinguished  as  T^X78,  was  de- 

reference  source  in  Computer  Sci- 

veloped  for  the  Stanford  University 

ence.  Tf-iX  grew  out  of  the  need  for 

DEC-10  system,  using  the  SAIL  pro- 

a  typesetting  program  that  could  be 

gramming  language.  Subsequently, 

used  to  prepare  new  volumes  in  the 

TJrjX78  was  translated  into  Pascal, 

series  in  particular,  the  need  for 

and  ported  to  a  variety  of  main- 

a  program  that  would  take  care  of 

frames.  Based  on  this  experience, 

the  delicate  formatting  needed  for 

the  program  was  completely  re- 

mathematics  typesetting.  In  the 

designed  and  reimplemented,  and  it 

is  this  newest  version  of  the  program 

Richard  Furuta  and  Pierre  A. 

that  is  today  called  T^X. 

MacKay.  Univ.  of  Washington.  Seat- 

One  of  the  commonest  criticisms 

tie  WA. 

of  T[^X78  was  its  sheer  size  and  con- 

80 

Dr.  Dobb's  Journal,  September  1985 

sequent  inaccessibility.  Most  users 
of  T^X78  would  not  readily  have  be¬ 
lieved  that  the  current,  more  pow¬ 
erful  version  of  the  program  could 
possibly  be  made  available  in  the 
small  systems  world.  But  here  it 
is  in  two  different  versions  for  the 
PC-XT  and  its  look-alikes.  As  we 
shall  demonstrate  below,  it  takes  a 
large  XT  to  run  TfeX,  and  it  takes  a 
good  deal  of  disk  space  to  store  it, 
but  both  programs  are  genuine  I^X, 
and  have  been  tested  and  validated. 

Throughout  this  review,  we  will 
refer  to  two  TEX-related  publica¬ 
tions:  The  TgXbook  and  TUGboat. 
The  Tj^Xbook  is  the  manual  that 
describes  TEX-  Written  by  Donald 
Knuth  and  published  by  Addison- 
Wesley,  it  costs  $14.95  in  book¬ 
stores.  The  TUGboat  is  the  newslet¬ 
ter  of  the  TEX  Users  Group  (also 
known  as  “TUG”).  No  matter  which 
version  of  TEX  you  purchase,  we  be¬ 
lieve  that  you  will  want  to  become 
a  member  of  the  TEX  Users’  Group. 
The  initial  yearly  membership  cost 
is  $20.00,  and  this  includes  a  sub¬ 
scription  to  the  TUGboat.  TUG  is 
the  best  way  to  keep  up-to-date  with 
the  latest  TEX  developments  and 
the  TUGboat  is  an  excellent  place 
for  you  to  share  your  own  TEX  dis¬ 
coveries.  Write  to  the  TEX  Users’ 
Group,  at  P.O.  Box  9506,  Provi¬ 
dence,  Rhode  Island  02940  for  more 
information. 

The  distribution  of  MicroTEX  and 
PCTEX  marks  a  break  from  the  pat¬ 
tern  established  under  the  sponsor¬ 
ship  of  the  TEX  Users  Group  for 
larger  systems.  The  broad  aim  for 
larger  systems  through  the  network 
of  TUG  site  coordinators  has  been 
to  place  full  source  code  in  the  hands 
of  the  systems  programmer  at  each 
location,  so  that  the  site  will  have 
full  control  over  compilation  and 
validation.  This  approach  has  de¬ 
pended  on  the  assumption  that  the 
site  will  have  a  great  deal  of  di¬ 
rectly  or  indirectly  addressable  pro¬ 
gramming  memory,  and  a  robust 
and  fully  functional  Pascal  compiler. 
(In  the  past  two  years,  experience 
has  led  to  the  formation  of  “David 
Fuchs’  Law  of  Pascal  Compilers,” 
that  TEX  will  smoke  out  a  bug  in 
every  compiler  created  earlier  than 


TEX  itself.) 

Even  on  a  mainframe  system,  a 
TEX  compilation  can  take  hours 
(some  have  looked  as  if  they  were 
going  to  run  for  days),  and  the 
compilation  time  on  a  PC  must  be 
quite  alarming.  These  two  ver¬ 
sions  of  TEX  for  the  PC  give  the 
user  the  benefit  of  the  special  ef¬ 
forts  of  two  long  established  mem¬ 
bers  of  the  TEX  community.  Lance 
Carnes,  the  author  of  PCTEX*  is  the 
TUG  site  coordinator  for  “small” 
TEX-  David  Fuchs,  the  author  of 
MicroTEX*  is  second  only  to  Don¬ 
ald  Knuth  in  his  long  association 
with  TEX-  Both  versions  produce 
an  identical  output  file  from  iden¬ 
tical  input  files  (more  later  about 
the  output  format,  called  DVI),  and 
both  have  gone  successfully  through 
the  “torture  test”  validation  pre¬ 
scribed  by  Donald  Knuth  for  true 
implementations  of  TEX .  The  im¬ 
plementation  methods,  however,  are 
quite  different.  MicroTEX  was  de¬ 
veloped  through  a  translation  from 
Pascal  to  C,  while  PCTEX  has  fol¬ 
lowed  the  usual  route  of  direct  Pas¬ 
cal  compilation.  (Microsoft’s  Pascal 
compiler  came  through  rather  hand¬ 
somely  in  this  effort,  with  only  one 
bug.)  There  are  various  arcana  com¬ 
plicating  the  above  histories.  David 
Fuchs  wrote  a  post-optimizer  for 
the  C  compiler  to  improve  perfor¬ 
mance,  and  Lance  Carnes  had  to 
work  up  a  collection  of  segmented 
pointer  types  for  Pascal  along  with 
the  address  calculation  routines  to 
go  with  them. 

Each  version  of  TEX  is  delivered 
in  a  large  box.  MicroTEX  arrives 
with  a  copy  of  The  TgXbook  (also 
available  separately  in  bookstores), 
a  92  page  installation  and  introduc¬ 
tion  manual  and  8  diskettes.  The 
diskettes  contain  TEX*  fonts,  and 
DVI-EPS,  a  program  that  prints 
TEX’S  output  files  on  the  IBM 
graphics  printer  and  its  near  rela¬ 
tives.  We  tested  MicroTEX  on  an 
XT.  We  understand  that  the  cur¬ 
rent  version  also  runs  on  an  AT. 

PC TEX’s  box  includes  a  binder 
containing  a  26  page  installation 
manual,  a  128  page  introduction 
to  PCTEX*  including  a  description 
of  their  own  macro  package,  in¬ 


tended  for  beginners,  a  summary  of 
the  .^mS-TeX  macro  package,  and  a 
reprint  of  the  manual  for  the  IATgX 
macro  package.  A  program  to  print 
output  on  IBM  and  Epson  graph¬ 
ics  printers  and  The  TgXbook  can 
be  purchased  separately.  PC  TEX 
distributes  TEX,  its  associated  font 
tables,  and  the  macro  packages  on  5 
diskettes.  If  PC-DOT,  the  program 
to  print  the  output,  is  purchased, 
an  additional  8  diskettes  contain  the 
program  along  with  its  fonts.  Two 
versions  of  TEX  are  provided — one 
for  a  PC  with  512K-bytes  of  mem¬ 
ory  and  another  for  one  with  640K- 
bytes.  MicroTEX  automatically  re¬ 
configures  itself  to  use  as  much 
memory  as  is  available,  from  512K- 
bytes  to  640K-bytes.  We  success¬ 
fully  installed  and  tested  PCTEX  on 
both  an  XT  and  an  AT. 

The  system  requirements  for 
these  two  versions  of  TEX  are  simi¬ 
lar.  Each  requires  an  IBM  PC  with 
a  hard  disk,  a  floppy  disk  drive,  and 
at  least  512K-bytes  of  RAM.  You 
will  need  about  5  megabytes  of  hard¬ 
disk  storage  although  this  amount 
can  be  trimmed  down  once  the  sys¬ 
tem  is  installed. 

Taking  the  MicroTEX  distribution 
first,  it  gets  very  high  marks  for  ease 
of  installation.  At  first  accidentally, 
but  then  deliberately,  I  made  every 
non-destructive  error  I  could  think 
of,  and  MicroTEX  was  forgiving  of 
all  of  them.  There  is  one  slight 
warning  I  would  give,  however.  Af¬ 
ter  TEX  is  loaded,  you  are  instructed 
to  copy  in  the  compressed  version  of 
the  fonts,  and  then  to  get  the  pro¬ 
cess  of  decompression  started  and 
walk  away  for  the  50  or  so  minutes 
it  takes.  Not  quite.  Immediately 
after  the  process  starts,  you  have  to 
supply  the  disk  containing  the  sys¬ 
tem’s  COMMAND.  COM  file  in  drive  A.  If 
you  have  walked  away  too  early,  you 
will  come  back  to  find  that  nothing 
at  all  has  happened  in  the  past  hour. 

Other  than  that,  everything  is 
smooth.  Within  two  hours,  TEX 
is  running,  and  you  can  try  out  the 
sample  files  on  the  directory,  one 
of  which  is  called  SAMPLE.TEX.  Mi¬ 
croTEX  offers  four  levels  of  print 
quality  on  the  Epson  printer,  and 
the  observed  times  for  each  are  given 


Dr.  Dobb's  Journal,  September  1 985 


81 

705 


below.  (SAMPLE. DVI  is  a  reasonable 
test,  since  it  loads  a  fair  number  of 
fonts.)  At  this  point,  if  you  are  run¬ 
ning  the  minimum  512K-byte  con¬ 
figuration  on  your  XT,  you  may 
run  into  trouble,  however.  Print 
your  SAMPLE .  LOG  file,  and  DOS  will 
load  the  printer  driver  into  memory. 
Now  try  to  run  TEX,  and  you  will 
be  told,  “Not  enough  memory  for 
TeX.”  The  MicroT(?X  manual  warns 
the  user  that  it  may  be  necessary 
to  leave  out  some  of  the  resident 
drivers,  but  with  DOS  3.0  in  512K- 
bytes,  it  is  necessary  to  leave  them 
all  out.  One  might  almost  imagine 
that  the  program  was  distributed  as 
an  incitement  to  purchase  that  last 
128K-bytes  of  memory. 

PCTEX  also  installs  smoothly,  al¬ 
though  we  didn’t  deliberately  try 
to  make  errors  this  time.  The  in¬ 
stallation  manual  provides  a  step 
by  step  description,  and  installing 
TEX  takes  about  half  an  hour.  In¬ 
stalling  PC-DOT  and  its  associated 
fonts  takes  another  hour  or  so — 
as  with  the  MicroT^X  installation, 
most  of  this  time  is  spent  waiting 
for  fonts  to  be  uncompressed.  Un¬ 
like  the  MicroT^X  installation,  you 
have  to  attend  to  PCTEX  during  the 
installation  process,  swapping  flop¬ 
pies  as  requested. 

The  installation  of  PCTEX  in¬ 
volves  a  few  more  steps  than  the 
installation  of  MicroTEX,  but  you 
gain  certain  advantages  as  a  result. 
To  understand  these  it  is  neces¬ 
sary  to  consider  how  TEX  is  put 
together  to  make  a  working  type¬ 
setting  system.  A  pristine  version 
of  TEX  is  a  very  minimal  typeset¬ 
ting  program  indeed.  It  provides 
the  necessary  primitive  operations 
for  character  positioning  at  the  low¬ 
est  possible  level,  a  sort  of  “assem¬ 
bler  code”  for  typesetting.  No  rea¬ 
sonable  person  would  even  consider 
formatting  a  page  at  this  level.  The 
other  side  of  the  program  is  an  ex¬ 
tremely  powerful  macro  processor. 
(The  macro  processor  was  an  af¬ 
terthought  and  an  adjunct  to  the  old 
1)3X78,  but  it  is  at  the  very  heart 
of  the  present  program.)  Only  af¬ 
ter  a  large  number  of  macros  has 
been  processed  and  evaluated  do 
you  have  a  fully  operational  type- 

82 

-7HA 


setting  system.  All  currently  avail¬ 
able  versions  of  TEX  come  with 
a  macro  file  known  as  PLAIN.TEX, 
which  forms  the  basis  for  any  further 
refinements.  PLAIN.TEX  provides 
a  great  variety  of  convenient  han¬ 
dles  for  common  typesetting  conven¬ 
tions,  preloads  a  number  of  fonts, 
and  arranges  for  the  input  of  the 
hyphenation  dictionary  that  is  one 
of  Tf^X's  most  remarkable  features. 
(The  stories  of  incompetent  hy¬ 
phenation  algorithms  in  computer- 
assisted  typesetting  systems  make 
up  quite  a  tale  of  horror.)  Get¬ 
ting  all  this  stuff  converted  to  to¬ 
kens  and  premasticated  into  an  ef¬ 
ficient  form  for  TEX  to  use  takes 
time  and  the  large-machine  distri¬ 
butions  of  TEX  actually  provide  for 
two  separate  compilations  of  the 
program,  one  known  as  INITEX  to 
do  the  massaging  and  cough  up  a 
predigested  format  file,  and  VIR- 
TEX,  a  second,  smaller  version  with  a 
weaker  digestive  system,  which  can 
read  in  the  predigested  PLAIN. FMT 
(notice  the  change  in  suffix)  at  a 
relatively  high  speed.  In  MicroTEX 
this  has  all  been  done  for  you,  and 
the  preloaded  TEX  is  instantly  ready 
to  run.  In  PCTEX  you  get  to  do 
it  yourself. 

The  executable  file  provided  with 
PCTEX  is  a  VIRTEX  with  a  special 
hook  provided  to  call  in  the  capabil¬ 
ities  of  INITEX  as  an  overlay.  The 
initial  run  of  TEX  is  done  with  a 
special  flag  on  the  command  line, 
and  is  devoted  entirely  to  masticat¬ 
ing  and  regurgitating  the  PLAIN .  TEX 
file.  (These  alimentary  metaphors 
are  extremely  pervasive  in  the  doc¬ 
umentation  of  TEX . )  From  then  on, 
although  you  will  be  told  that  the 
TEX  you  are  running  has  no  for¬ 
mat  preloaded,  the  program  will 
read  in  the  PLAIN .  FMT  file  automat¬ 
ically,  without  being  prompted,  and 
at  more  or  less  the  optimum  speed 
for  a  disk  to  memory  transfer.  As  a 
result,  PCTEX  takes  about  20  sec¬ 
onds  longer  to  go  into  action  than 
MicroTEX,  but  it  gains  in  many  im¬ 
portant  ways.  One  of  these  is  im¬ 
mediately  evident  from  listing  the 
files  in  the  TEXINPUT  directory.  As 
we  noted  above,  PCTEX  comes  with 
both  AjqS-TJjX  for  advanced  math¬ 


ematics  formatting  and  with  DTgX, 
which  is  a  powerful  document  for¬ 
matting  package  using  a  syntax  that 
allows  you  to  describe  a  document 
by  its  logical  structure  instead  of  by 
its  appearance.  Both  of  these  macro 
files  are  large — DTgX  is  immense, 
requiring  640K-bytes  to  run — and 
you  would  soon  weary  of  waiting  for 
the  macro-interpreter  to  do  its  thing 
with  them  if  you  had  to  read  them 
in  at  the  start  of  every  invocation 
of  TeX.  The  IATEX  or  the  ^S-TfeX 
format  file,  however,  can  be  read 
into  place  in  almost  the  same  time 
as  the  smaller  PLAIN. FMT. 

Another,  even  more  subtle  advan¬ 
tage  of  having  an  INITEX  available 
has  to  do  with  the  hyphenation  ta¬ 
ble.  Hyphenation  rules  differ  consid¬ 
erably  from  language  to  language, 
and  a  user  with  a  large  amount  of 
French  or  German  text  to  set  will 
more  or  less  be  forced  to  create  a 
new  FMT  file  to  get  satisfactory  re¬ 
sults.  The  creation  of  a  new  hy¬ 
phenation  table  is  non-trivial,  and 
neither  version  of  TEX  for  the  PC 
provides  the  program  that  does  it, 
but  assuming  that  PATGEN  has  been 
run  on  some  larger  system,  it  still  re¬ 
mains  that  the  only  practical  way  to 
get  at  the  new  hyphenation  patterns 
is  through  the  use  of  INITEX.  Inci¬ 
dentally,  this  question  of  hyphen¬ 
ations  is  one  of  the  only  genuine 
limitations  in  the  use  of  T]eX  for 
continuous  text.  There  is  not,  at 
present,  any  way  of  switching  from 
one  hyphenation  pattern  to  another 
in  the  course  of  a  run.  You  may 
hyphenate  efficiently  in  English,  or 
in  French,  but  not  in  both  together. 

Once  either  MicroTEX  or  PCTEX 
has  begun  reading  the  user  input  file 
there  is  very  little  difference  between 
them.  The  TEX  TRIP .  TEX  “torture- 
test”  guarantees  that  we  can  assume 
that  they  are  functionally  identical 
and  that  they  correctly  recognize 
and  process  TEX.  It  is  theoretically 
possible  that  a  TEX  might  pass  the 
torture-test  and  later  produce  a  DVI 
file  that  was  subtly  different  from 
that  produced  by  all  other  TEXs, 
but  it  has  not  happened  yet,  and 
if  it  does,  the  test  will  probably 
be  revised  to  eliminate  the  error. 
The  normal  benchmark  is  the  set- 

Dr.  Dobb’s  Journal,  September  1985 


ting  of  the  T^Xbook.  and  in  an¬ 
nouncements  in  a  recent  edition  of 
TUGboat,  the  XT  speed  for  this  ef¬ 
fort  was  given  as  26.4  seconds  a  page 
for  MicroT^X  and  “about  25  sec¬ 
onds  a  page”  for  PCTp^X.  In  normal 
use,  that  is  a  very  close  race  indeed. 
(PCT^X’s  running  time  on  the  AT 
is  about  six  seconds  a  page.) 

A  true  T^X-hacker  will  notice  an¬ 
other  difference  between  the  two 
programs.  MicroTfcX  is  based  on  the 
latest  version  of  TfeX  (we  now  have 
Version  1.4),  while  PCT^X  is  based 
on  a  somewhat  older  version  (1.0,  in 
our  case).  Usually,  this  would  not 
be  significant  since  the  change  from 
version  to  version  has  been  quite 
small.  Unfortunately,  however,  Ver¬ 
sion  1.3  incorporated  major  changes 
to  T^X’s  memory  management  rou¬ 
tines  that  greatly  reduced  the  pos¬ 
sibility  of  running  out  of  memory 
while  TjgX-ing  a  document.  We  are 
assured  that  PCT^X  will  be  brought 
up  to  date  soon. 

The  version  of  IATgX  distributed 
with  PCT^^X  is  also  outdated,  which 
may  cause  some  difficulties  if  you 
try  to  move  those  files  to  another 
computer  since  IATjr;X  is  still  being 
developed  and  is  undergoing  rapid 
change.  We  transferred  over  the  lat¬ 
est  version  of  IATjrX  and  were  able 
to  run  it  on  PCTjrjX  after  shorten¬ 
ing  one  font  name  that  was  too  long. 
If  you  are  running  a  newer  version 
of  IATjrX  on  another  computer,  we 
expect  that  you  will  want  to  up¬ 
date  PCT^X’s  copy  before  building 
the  FMT  file. 

We  understand  that  ^S-T^jX  is 
now  being  developed  using  PCT)rjX, 
so  we  believe  that  the  version  dis¬ 
tributed  here  is  completely  up  to 
date. 

Throughout  this  review,  we  have 
mentioned  TfrjX’s  device-indepen¬ 
dent  output  file.  Now,  the  time 
has  come  to  turn  back  to  general 
issues  and  to  consider  the  implica¬ 
tions  of  this. 

In  order  to  appreciate  the  na¬ 
ture  of  the  DVI  file,  it  is  helpful 
to  know  something  about  the  ac¬ 
tual  genesis  of  T^jX.  T^X  is  not 
a  ‘word-processor’  irx  any  ordinary 
sense  of  that  mildly  repellent  term. 
The  target  machine  for  the  origi- 


I  nal  program  was  a  genuine  digital 
phototypesetter,  using  silver  halide 
typesetting  film  to  record  characters 
painted  on  a  CRT.  That  particular 
machine,  in  fact,  has  the  highest 
resolution  known  in  the  industry — 
almost  four  times  that  of  its  near¬ 
est  rival.  But  Tj^X  does  not  stop 
even  there.  It  produces  a  file  for  an 
imaginary  typesetting  device  with  a 
resolution  of  1/216  printer’s  points 
(there  are  72.27  points  to  the  inch), 
and  leaves  it  to  a  second  program  to 
interpret  that  file  for  real-world  de¬ 
vices.  This  output  file  is  known  as 
the  ‘device-independent’  (DVI)  file 
and  is  guaranteed  to  be  exactly  the 
same  for  any  given  source  file  when 
produced  by  a  properly  installed 
and  tested  T^X. 

This  notion  of  device  indepen¬ 
dence  goes  far  deeper  than  that  in 
most  other  systems.  It  allows  such 
an  absolute  confidence  in  the  qual¬ 
ity  of  results  that  the  DVI  file  has 
seriously  been  proposed  as  a  stan¬ 
dard  for  document  interchange  in 
Europe.  The  suggestion  is  that 
on-line  document  retrieval  from  li¬ 
braries  be  sent  in  DVI  format  for 
selective  printing  at  the  requesting 
site.  But  this  is  not  to  claim  that 
T^^X  and  the  DVI  file  are  the  pre¬ 
ferred  system  for  all  applications. 
The  principal  virtues  of  Tj^X  carry 
with  them  certain  constraints. 

Perhaps  the  least  generally  under¬ 
stood  are  the  constraints  inherent  in 
writing  for  a  high-resolution  photo¬ 
typesetting  system.  At  the  present 
time,  and  with  the  presently  avail¬ 
able  fonts,  TjyV  optimizes  for  the 
best  output  device  available.  On 
an  Alphatype,  Autologic,  Compu- 
graphic  or  any  of  the  other  high- 
resolution  typesetters  for  which  DVI 
interpreters  have  been  written,  the 
interword  spacing  and  inter-letter 
spacing  are  evaluated  with  round¬ 
off  errors  too  small  for  the  human 
eye  to  detect.  At  the  most  popular 
laser-printer  and  electrostatic  reso¬ 
lutions  (200,  240,  and  300  dots  to 
the  inch)  round-off  errors  are  usu¬ 
ally  inoffensive,  but  they  are  nearly 
always  discernable.  It  is  simply  im¬ 
possible  to  do  justice  to  the  delicacy 
of  high-resolution  typesetting  at  300 
dots  to  the  inch.  (When  we  say  that 


a  device’s  resolution  is,  for  example, 
200  dots  to  the  inch,  we  mean  that 
the  individual  dots  making  up  an 
image  can  be  positioned  to  a  reso¬ 
lution  of  l/200<h  of  an  inch.) 

This  is  not  to  say  that  the  results 
are  bad.  Several  thousand  happy 
users  of  I)^X  have  never  seen  their 
output  at  any  resolution  better  than 
200  dots  to  the  inch,  but  others  have 
wondered  why  the  program  cannot 
be  better  tuned  to  laser-printer  res¬ 
olutions  and  to  the  newer  carefully 
designed  laser-printer  fonts  such  as 
Bigelow  and  Holmes’s  Lucida.  The 
answer  is  that  it  can  be  so  tuned, 
through  the  use  of  the  ‘T^X  Font 
Metric'  TFM  file,  a  file  which  pro¬ 
vides  T)]jX  with  all  the  vital  statis¬ 
tics  about  font  characters,  such  as  I 
height,  depth,  width,  letter-spacing 
adjustments  (kerning),  etc.  If  the 
manufacturer  or  distributor  is  will¬ 
ing  to  provide  the  necessary  infor¬ 
mation,  a  TFM  file  can  be  made  up 
to  match  any  font  currently  avail¬ 
able.  It  is  not  even  a  very  laborious 
job.  But  a  TFM  file  is  not  a  font.  It 
will  not  produce  characters  on  any 
output  device  at  all. 

In  some  senses  this  can  be  an 
advantage.  Suppose  you  wish  to 
produce  a  book  in  the  Baskerville 
font,  and  you  have  found  a  type¬ 
setter  manufacturer  who  will  pro¬ 
vide  you  with  sufficient  information 
about  its  Baskerville  fonts  to  pro¬ 
duce  the  needed  TFM  file.  For  your 
final,  high  quality  output,  you  have 
the  problem  solved;  T^jX  will  read 
the  TFM  file  and  make  all  its  calcula¬ 
tions  based  on  the  character  widths, 
etc.,  of  that  font.  But  you  do  not 
want  to  do  the  proof  copies  on  a 
slow,  expensive  photographic  pro¬ 
cessor,  and  there  is  no  300-dot/inch 
Baskerville  on  your  laser  printer,  far 
less  on  your  graphics  impact  printer. 
If  you  are  willing  to  shut  your  eyes  to 
the  bad  letter  spacing,  and  are  con¬ 
cerned  primarily  with  correcting  ty¬ 
pos  and  misspellings,  you  can  make 
some  font  that  you  do  have  on  your 
low  to  medium  resolution  printer 
masquerade  as  Baskerville.  The  la¬ 
ser  printer  output  will  look  dreadful, 
but  the  cost  of  proof  correction  will 
be  kept  within  reason.  Conversely  if 
the  laser  printer  output  is  what  you 


Dr.  Dobb’s  Journal,  September  1985 


83 

707 


really  want,  there  is  no  reason  to 
use  an  ersatz  high-resolution  font  at 
all.  Create  a  TFM  file  that  genuinely 
matches  one  of  the  fonts  specially 
designed  for  use  at  300  dots/inch 
and  your  laser-printer  output  will 
be  very  acceptable  indeed. 

It  is  even  possible  to  create  a  TFM 
file  and  an  associated  format  file  for 
a  daisy  wheel  printer.  To  run  T^X  in 
this  mode  is  to  give  up  at  least  nine- 
tenths  of  its  power  as  a  typesetting 
system,  but  it  can  be  done.  The 
important  thing  is  to  recognize  that 
letter-spacing  and  interword  spacing 
are  governed  by  the  values  supplied 
in  the  TFM  file,  and  once  the  choice 
has  been  made,  the  DVI  format  will 
record  that  choice  with  breathtak¬ 
ing  accuracy.  No  matter  what  the 
resolution  is  of  your  target  machine, 
the  DVI  file  will  represent  something 
far  better  than  the  best  that  that 
machine  is  capable  of. 

So  while  there  are  no  technical 
reasons  why  output  cannot  be 
tuned  to  particular  output  devices, 
such  tuning  is  almost  never  done 
in  practice,  and  in  fact  this  tuning 
is  usually  considered  to  be  an  un¬ 
desirable  thing  to  do.  The  reason 
for  this  is  that  tuning  introduces 
device  and  resolution  dependencies 
into  the  DVI  file  and  these  depen¬ 
dencies  will  negate  the  more  gen¬ 
eral  concept  of  device  independence 
provided  by  TfcX.  For  not  only  are 
the  positionings  of  the  characters 
of  a  page  described  in  a  device¬ 
independent  manner,  but  the  char¬ 
acters  themselves  are  also  defined  in 
a  device-independent  fashion. 

The  fonts  used  by  T^X  are  pro¬ 
duced  from  descriptions  -programs, 
if  you  will — written  in  a  language 
called  METRFONT.  A  METRFONT 
program  defines  a  font’s  characters 
by  specifying  the  movement  of  a 
pen  over  a  Cartesian  coordinate  sys¬ 
tem.  As  the  name  of  the  system 
implies,  a  “meta-font”  is  defined  in 
this  fashion  in  other  words,  a  sin¬ 
gle  METRFONT  description  can  be 
used  to  produce  a  family  of  related 
fonts  (for  example,  separate  fonts  in 
roman  and  bold  face)  by  varying  the 
parameters  that  control  values  such 
as  the  pen  shape  and  size.  The  im¬ 
portant  result  in  the  context  of  this 


review,  is  that  the  description  also 
is  resolution-independent.  In  other 
words,  the  description  will  produce 
font  bitmaps  for  use  at  different  res¬ 
olutions  when  the  appropriate  pa¬ 
rameters  are  altered. 

At  present,  only  one  generally 
available  family  of  fonts,  the  Com¬ 
puter  Modern  family,  has  been  de¬ 
fined  through  METRFONT.  Varia¬ 
tions  within  the  family  provide  ser- 
ifed  roman,  bold,  italic,  and  slanted 
faces,  a  typewriter  face,  and  a  set 
of  sans-serifed  faces,  each  in  a  range 
of  point  sizes.  T^X  use  is  tightly 
coupled  to  the  Computer  Modern 
font  family  because  of  the  flexibil¬ 
ity  afforded  by  the  METRFONT  de¬ 
scription. 

Since  the  Computer  Modern  fam¬ 
ily  of  fonts  can  be  generated  at  al¬ 
most  any  resolution  needed  by  a  de¬ 
vice,  and  since  the  DVI  file  format 
represents  the  output  with  a  pre¬ 
cision  unattainable  by  any  device, 
the  T)<]X  concept  of  “device  indepen¬ 
dence”  can  be  restated  as  providing 
the  same  output  on  every  output 
device,  constrained  only  by  the  lim¬ 
itations  of  the  device.  There  are 
two  aspects  to  this  formulation  of 
device  independence.  One  is  that  of 
source-level  independence.  In  other 
words,  every  implementation  of  TjrjX 
should  produce  an  identical  DVI  file 
from  a  T£X  source  file,  if  that 
source  file  uses  the  standard  TJ<jX 
environment — the  Computer  Mod¬ 
ern  fonts  and  the  facilities  defined 
in  The  TfcXboo/c.  The  second  aspect 
is  that  of  output-level  independence. 
A  DVI  file  produced  by  any  imple¬ 
mentation  of  should  be  able  to 
be  printed  on  any  suitable  printer,  if 
the  standard  T).]X  environment  has 
been  used.  DVI  files  can  be,  and 
frequently  are,  transferred  from  one 
model  of  computer  to  another  in  or¬ 
der  to  take  advantage  of  the  printers 
attached  to  the  particular  comput¬ 
ers.  As  a  practical  aside,  the  most 
frequently  encountered  problems  in 
developing  the  mechanisms  that  al¬ 
low  the  transferring  of  a  DVI  file 
from  one  computer  environment  to 
another  are  related  to  the  differing 
computer  architectures  and  to  the 
file  transfer  itself.  The  DVI  file  is 
defined  as  a  stream  of  8-bit  bytes. 


Finding  a  file  transfer  mechanism 
that  allows  transmission  of  all  eight 
bits  of  the  byte  sometimes  involves  a 
time  consuming  search.  Differences 
in  the  ways  in  which  these  bytes  are 
packed  into  the  computer’s  words 
also  may  cause  difficulties  that  re¬ 
quire  careful  attention  to  overcome. 

As  the  standard  T]eX  environment 
is  so  important  to  maintaining  de¬ 
vice  independence,  you  should  con¬ 
sider  the  capabilities  and  cost  of  the 
printer  that  you  will  need  carefully 
before  deciding  to  purchase  Tj^X.  To 
get  the  full  benefit  of  T)<3X,  the  out¬ 
put  device  you  choose  must  be  pow¬ 
erful  enough  to  simulate  Tj^X’s  ideal 
device  and  further,  your  device  must 
allow  you  enough  flexibility  to  allow 
you  to  use  the  TJ^X  fonts. 

What  this  means  is  that  you 
should  not  plan  to  use  a  mecha¬ 
nized  typewriter  with  T^X.  Techni¬ 
cally,  you  certainly  could  build  the 
TFM  font  width  tables  to  describe  the 
typewriter’s  characters,  but  practi¬ 
cally,  Tj^X  documents  produced  in 
this  way  will  be  incompatible  with 
everyone  else’s  and  you  will  be  un¬ 
able  to  use  most  of  T^X’s  more  pow¬ 
erful  features.  What  you  will  want 
to  have  is  either  a  printer  that  al¬ 
lows  you  to  do  full  page  bit  graphics 
or  one  that  allows  you  to  define  your 
own  characters  and  place  them  ar¬ 
bitrarily  on  the  page  (or,  of  course, 
one  that  allows  you  to  do  both). 

Very  few  printers  can  print  a  DVI 
file  directly.  Generally,  the  DVI  file 
is  converted  into  a  form  compatible 
with  the  printer  by  an  application 
program  called,  in  T^^X  lingo,  the 
Output  Driver.  As  a  practical  mat¬ 
ter,  before  you  buy  a  printer  for 
T).^X  use,  you  should  make  sure  that 
the  appropriate  Output  Driver  soft¬ 
ware  already  exists  for  your  com¬ 
puter.  Writing  such  a  conversion 
program  is  not  an  extraordinarily 
hard  programming  project,  but  get¬ 
ting  the  output  to  look  “right”  in¬ 
volves  surprising  subtleties.  It  is 
often  more  convenient  to  purchase 
the  conversion  software  so  you  can 
get  directly  to  the  matter  of  creat¬ 
ing  beautiful  documents. 

Printers  appropriate  for  TJ^X  use 
can  be  placed  into  three  different 
categories,  based  on  the  resolution 


86 

708 


Dr.  Dobb's  Journal,  September  1985 


at  which  fonts  can  be  defined  and 
at  which  elements  of  the  image  can 
be  placed. 

Low-resolution  printers  are  usu¬ 
ally  impact  printers.  The  lowest  res¬ 
olution  at  which  recognizable  out¬ 
put  can  be  produced  is  about  75 
dots/inch.  Some  printers  of  this 
type  can  print  at  resolutions  up  to 
200  dots/inch.  At  the  lowest  resolu¬ 
tions,  the  output  from  these  printers 
is  often  unreadable,  particularly  in 
mathematical  equations  and  in  foot¬ 
notes  where  the  type  sizes  tend  to 
become  quite  small.  At  the  high¬ 
est  resolutions,  the  printers  can  take 
quite  long  to  print  each  page — up  to 
five  minutes  per  page  in  some  cases. 
Both  PCI^}X  and  MicroT^jX  make 
available  software  to  print  Tf]X  out¬ 
put  on  a  number  of  these  printers 
(both  products  support  the  IBM 
Graphics  printer  and  the  Epson  RX 
and  FX  printers). 

At  the  other  end  of  the  range 
are  high-resolution  printers.  These 
print  at  resolutions  greater  than 
1000  dots/inch  and  are  generally 
phototypesetters — devices  produc¬ 
ing  output  on  photographic  paper 
that  must  be  developed  before  the 
image  can  be  inspected.  They  are 
frequently  quite  slow  (several  min¬ 
utes  per  page  of  output)  and  quite 
expensive  to  purchase  and  to  op¬ 
erate.  They  are,  however,  the 
only  acceptable  way  to  produce  out¬ 
put  suitable  for  publication  in  high- 
quality  books.  Fortunately,  a  num¬ 
ber  of  businesses  own  phototypeset¬ 
ters  and  are  equipped  to  print  T)rX 
DVI  files  from  IBM  PC  disks.  The 
advertisements  in  the  T)rX  Users 
Group  Newsletter,  the  TUGboat ,  are 
the  best  way  to  locate  these  busi¬ 
nesses. 

In  the  middle  is  an  increasingly 
important  group  of  devices:  the 
medium-resolution  printers.  Most 
of  these  printers  are  laser  printers, 
and  the  300  dot/inch  resolution  is 
becoming  somewhat  of  a  standard. 
While  it  is  not  possible  for  devices  of 
this  resolution  to  produce  true  book 
publication-quality  output  (the  ad¬ 
vertising  claims  of  manufacturers 
notwithstanding),  it  is  quite  real¬ 
istic  to  expect  to  be  able  to  use 
these  printers  to  produce  output 


for  less  demanding  applications  such 
as  newsletters,  memos,  and  proofs 
of  material  that  will  ultimately  be 
sent  out  for  typesetting  on  a  high- 
resolution  printer.  Some  of  the  laser 
printers  are  not  suitable  for  T^X  use, 
as  will  be  discussed,  but  those  that 
have  successfully  printed  T^rjX  out¬ 
put  range  in  price  from  about  $4000 
upward  and  print  at  speeds  from 
about  five  pages  per  minute  upward. 

A  laser  printer  is  built  from  a 
marking  engine  and  a  controller. 
The  marking  engine  is  what  puts 
the  marks  onto  the  paper.  The 
controller  drives  the  marking  engine 
and  provides  the  hardware  and  soft¬ 
ware  interfaces  that  are  visible  to 
the  person  programming  an  appli¬ 
cation  to  print  on  the  laser  printer. 

When  comparing  different  laser 
printers,  it  is  important  to  dis¬ 
tinguish  between  the  functions  of 
marking  engine  and  those  of  the  con¬ 
troller.  For  example,  it  is  usually 
correct  to  assume  that  maintenance 

statistics  for  one  laser  printer  will 
also  hold  for  a  different  printer  if  the 
two  use  the  same  marking  engine.  It 
is  incorrect,  however,  to  assume  that 
information  on  the  graphical  capa¬ 
bilities  of  one  manufacturer’s  laser 
printer  will  be  valid  for  another’s, 
even  if  the  two  printers  both  use  the 
same  marking  engine. 

As  illustration,  one  commonly 
used  marking  engine  is  the  Canon 
LBP-CX.  This  marking  engine,  used 
by  many  different  laser  printer  man¬ 
ufacturers,  is  the  heart  of  the  Im- 
agen  8/300,  the  QMS  800,  the 


HP  LaserJet,  and  the  Apple  Laser-  I 
Writer.  What  distinguishes  these 
laser  printers  from  one  another  are 
the  interfaces  implemented  by  their 
controllers.  The  HP  LaserJet,  for 
example,  is  essentially  an  upgrade 
for  a  mechanized  typewriter-like  de¬ 
vice.  It  does  not  allow  fonts  to  be 
loaded  over  its  hardware  interface 
line  and  full-resolution  bitmaps  are 
limited  to  about  one  quarter  page. 
We  do  not  know  of  software  that 
allows  printing  of  T^X’s  output  for¬ 
mat  on  the  HP  LaserJet  and  suspect 
that  such  software  will  be  extremely 
difficult,  if  not  impossible,  to  write 
because  of  the  printer’s  limitations. 
On  the  other  hand,  the  Apple  Laser¬ 
Writer  implements  an  interface  lan¬ 
guage  called  PostScript.  PostScript 
is  a  general  purpose  programming 
language,  Forth-like  in  appearance, 
and  is  one  of  the  most  sophis¬ 
ticated  printing  languages  avail¬ 
able  (PostScript  is  based  on  an 
earlier  Xerox-defined  printing  lan¬ 
guage,  which  is  named  InterPress). 
The  LaserJet  and  the  LaserWriter 
use  the  same  marking  engine  but 
are  radically  different  in  the  sophis¬ 
tication  of  the  functions  that  they 
provide. 

Indeed,  to  a  programmer  provid¬ 
ing  TprX  support,  the  interface  lan¬ 
guage  defined  by  the  laser  printer’s 
controller  is  usually  more  important 
than  which  marking  engine  is  being 
used.  The  interface  language  is  gen¬ 
erally  the  same  or  similar  for  a  com¬ 
pany’s  laser  printers,  even  though 
those  products  may  use  significantly 
differing  marking  engines. 

The  companies  that  make  PCT^X 
and  MicroT^X  have  announced  that 
they  will  be  supporting  'IJrX  out¬ 
put  from  the  IBM  PC  for  the  Ap¬ 
ple  LaserWriter,  the  Imagen  laser 
printers,  and  the  QMS  laser  print¬ 
ers.  Another  relatively  low  cost  la¬ 
ser  printer,  the  DEC  LN-03,  has 
been  supported,  although  not  yet  on 
the  IBM  PC.  A  full  list  of  supported 
devices  is  printed  in  each  edition  of 
the  TUGboat. 

An  additional  difference  between 
the  two  implementations  becomes 
apparent  when  comparing  the  two 
Epson  drivers.  DVI-EPS,  included 
with  the  Micro Tpr/C  package,  pro- 

87 


Dr.  Dobb  s  Journal,  September  1985 


7r\Q 


vides  four  levels  of  printing  on  the 
Epson.  We  printed  a  fairly  dense 
page  of  Tf.]X  output  at  each  of  these 
levels  (see  Figure,  page  88)  and  ob¬ 
served  that  the  time  to  print  the 
page  ranged  from  about  22 1  min¬ 
utes  at  the  best  quality  (level  1)  to 
only  3 1  minutes  at  the  lowest  qual¬ 
ity  (level  4).  Intermediate  timings 
were  12  minutes  for  level  2  and  10 
minutes  for  level  3.  In  contrast,  PC- 
DOT,  available  as  an  option  with 
PCTEX,  only  provides  two  printing 
levels.  The  draft  mode  produced 
output  similar  to  DVI-EPS’s  level 
2,  and  took  12|  minutes  for  our 
sample  page.  Final  mode  in  PCTEX 
is  comparable  to  DVI-EPS’  level  1, 
and  took  23^  minutes  to  print.  In 
all  cases,  print  time  seemed  to  de¬ 
pend  more  on  the  density  of  the 
page  than  on  other  factors. 

23  minutes  is  a  substantial  time 
to  wait  for  a  page  of  output  to 
be  printed.  Consequently,  we  be¬ 
lieve  that  DVI-EPS  is  significantly 
preferable  to  PC-DOT.  In  particu¬ 
lar,  DVI-EPS1  lowest  quality  level 
4  printing  at  under  four  minutes 
for  the  page  will  get  quite  heavy 
use  during  document  development 
and  proofing.  What  is  desperately 
needed  are  mechanisms  that  allow 
the  DVI  file  to  be  previewed  on  the 
PC’s  screen,  allowing  the  slow  print¬ 
ing  pass  to  be  bypassed  altogether. 

In  summary,  despite  the  essen¬ 
tial  similarity  of  output  from  the 
two  implementations  of  TEX  for  the 
PC,  there  are  numerous  differences 
that  allow  for  a  meaningful  compar¬ 
ison.  MicroTEX  comes  as  a  com¬ 
plete  package,  including  TEX,  the 
format  file,  the  DVI-EPS  printer 
driver  and  its  numerous  fonts  at  sev¬ 
eral  different  pixel-densities,  and  the 
authoritative  documentation  of  The 
TpfXbook  itself.  The  version  of  TEX 
is  absolutely  up  to  date,  and  the 
driver  offers  a  wide  range  of  output 
quality.  All  of  this  together  comes  to 
$495.00.  You  can  run  the  program 
in  512K-bytes  of  memory,  but  only 
at  the  price  of  some  inconvenience 
(no  resident  drivers  in  your  DOS). 

P(  'T^X  offers  various  options. 
The  basic  price  of  $279.00  gives  you 
TEX,  separately  compiled  for  two 
different  sizes  of  memory,  and  the 


iljqS-TEX,  and  DTgX  macro  files, 
together  with  the  mechanism  for 
loading  them  efficiently.  At  the  mo¬ 
ment  PCTEX  is  still  at  version  1.0  by 
contrast  with  MicroIJrjX  at  version 
1.4.  The  PC-DOT  driver  program 
at  $100.00  is  not  so  broadly  func¬ 
tional  as  the  DVI-EPS  program,  If 
you  want  The  TpjXbook,  you  must 
order  it  separately  for  $15,  but  you 
do  get  the  user  documentation  for 
DTgX  and  tljqS-TEX. 

In  our  judgment,  the  ideal  pack¬ 
age  would  be  PCTEX  for  the  TEX 
together  with  DVI-EPS  to  drive  the 
printer.  Only  those  with  larger 
pockets  might  consider  this,  since 
it  requires  buying  both  PC  TEX  and 
MicroTEX.  Otherwise,  especially  for 
those  with  technical  documentation 
in  mind,  we  strongly  favor  the 
PCTEX  offering.  We  believe  that 
the  inclusion  of  INITEX  capabilities 
in  PCTEX  is  so  important  that  it 
overrides  the  inconveniences  caused 
by  the  lag  in  versions  and  by  the 
less  functional  printer  driver. 

(Addison- Wesley  is  distributing  a 
complete  INITEX  in  the  August  up- 


91 

710 


date  of  MicroT)jfX.-Ed.) 

[This  review  was  formatted  with 
MicroTEX  on  an  IBM  AT  with  512K 
in  2 1  minutes.  The  fully-composed 
output  was  uploaded  to  a  Stanford 
mainframe  and  sent  to  an  Auto¬ 
logic  APS-micro-5  typesetter,  with 
the  results  seen  here.] 

PCTeX,  Version  1.0 
Company:  Personal  TEX,  Inc. 

20  Sunnyside,  Suite  H 
Mill  Valley,  CA  94941 
(415)  388-8853 

Price:  $279.00;  PCDOT  $100.00 

MicroTeX,  Version  1.4A1 
Company:  Addison- Wesley 
Publishing  Company,  Inc. 

Reading,  MA  01867 
(617)  944-3700 
Price:  $495.00 


DD| 


Reader  Ballot 

Vote  for  your  favorite  feature/article. 
Circle  Reader  Service  No.  1 97. 


Dr.  Dobb's  Journal,  September  1985 


MAC  TOOLBOX 


SCSI  Your  Mac 


by  John  Bass 


I  got  tired  of  waiting  on  those  slug¬ 
gish  Mac  floppy  drives,  tired  of  wait¬ 
ing  for  an  affordable  hard  disk  for  the 
Mac.  I  got  so  tired,  finally,  that  I 
went  to  work.  My  efforts  paid  off, 
and  1  was  pleased  enough  with  the  re¬ 
sult  to  think  that  others  might  appre¬ 
ciate  my  MacSCSI®  interface,  too. 

One  of  the  most  obvious  advantages 
to  doing  your  own  hard  disk  interface 
for  the  Mac  is  cost.  I  think  MacSCSI 
is  the  hard-disk  equivalent  of  DDf  s 
January  1985  “Fatten  Your  Mac” 
512K  upgrade:  the  hacker’s  hard  disk 
upgrade  at  budget  prices,  if  you’re 
willing  to  shop  around  for  disks  and 
SASI/SCSI  controllers.  Here  in  Sili¬ 
con  Valley  we  see  hard  disks  at  the 
computer  swaps  for  between  $100.00 
and  $500.00,  often  either  refurbished 
or  new  discontinued  models  that  were 
surplus.  Likewise,  SASI/SCSI  con¬ 
trollers  are  seen  at  between  $50  and 
$150.  Thus,  with  the  MacSCSI  inter¬ 
face  and  some  bargain  hunting  you 
can  put  a  hard  disk  on  your  Mac  for 
less  than  $500.  New  production  drives 
and  controllers  are  available  between 
$900  and  $3000,  depending  on  the 
size  and  performance. 

Another  advantage  is  the  experi¬ 
ence  you’ll  gain,  both  in  writing  your 
own  bells  and  whistles  into  the  sup¬ 
porting  software,  and  in  simply 
cracking  open  your  Mac  and  getting 
to  know  its  innards  better.  And  in  this 
regard,  the  MacSCSI  interface  is  un¬ 
like  the  512K  upgrade:  you  don’t 
have  to  take  a  soldering  iron  to  a  deli¬ 
cate,  expensive  logic  board  to  install 
MacSCSI.  You  don’t  have  to  modify 
the  Mac  logic  board  at  all. 

The  parts  for  this  venture  are  few. 
The  MacSCSI  host  adapter  consists  of 
four  parts:  an  NCR5380  single  chip 
SCSI  interface,  two  74LS10’s  to  pro¬ 
vide  the  address  decodes,  and  a  50  pin 
header  for  the  SASI/SCSI  cable.  The 


rest  of  a  complete  setup  is  a  SASI/ 
SCSI  controller,  hard  disk  drive, 
power  supply,  and  cables  for  power 
and  data.  You  can  make  your  own 
case  from  a  bookcase  speaker  for  a 
few  dollars,  you  can  build  a  nice  cus¬ 
tom  cabinet,  or  you  can  buy  an  off- 
the-shelf  hard  disk  enclosure  complete 
with  power  supply  and  cables. 

Figure  1  (page  95)  tells  the  story:  it 
contains  the  schematic  for  the 
MacSCSI  interface.  The  host  adapter 
is  memory  mapped  into  the  Mac’s 
ROM  address  space  above  its  last  val¬ 
id  address.  To  accomplish  this,  I  used 
selection  equations  for  the  NCR5380 
of 

NCRSEL  =  ROMSEL  *  A20  *  ATI 
NCRRD  =  NCRSEL  *  A4 
NCRWRITE  =  UDS 

By  using  address  lines  to  enable  the 
read  and  write  enables  on  the 
NCR5380,  I  avoided  needing  to  run 
any  wires  to  chips  or  to  cut  away  at 
the  main  circuit  board;  the  only  inter¬ 
face  point  to  the  Mac  is  its  ROM  sock¬ 
et.  This  scheme  does,  though,  require 
that  the  software  read  an  NCR5380 
register  at  one  address  and  write  it  at 
another  -quite  a  reasonable  tradeoff 
for  not  having  to  do  any  cuts  or  adds 
to  the  main  logic  board,  I  think. 

While  it’s  not  clear  whether  Apple 
would  honor  a  warranty  or  Apple 
Care  contract  on  a  Mac  upgraded 
this  way,  if  done  carefully  the  up¬ 
grade  will  not  physically  alter  or 
harm  the  Mac.  If  you  have  problems 
with  your  Mac  (other  than  the 
MacSCSI  upgrade),  most  helpful 
dealers  should  be  willing  to  service 
your  Mac  if  your  boards  are  not  al¬ 
tered  or  damaged.  You  may  even  find 
some  helpful  dealers  and  independent 
repair  centers  that  will  install  the  up¬ 
grade  for  you  at  their  normal  shop 


rates  (about  an  hour  of  their  time). 

If  you’re  up  for  a  project,  the  inter¬ 
face  can  be  assembled  using  off-the- 
shelf  parts  and  point-to-point  wiring. 
If  you  do  it  this  way,  you’ll  need  to 
make  a  chip  carrier  from  perf  board 
with  holes  on  0.1  inch  centers.  You 
can  salvage  the  socket  pins  for  the 
chip  carrier  by  cutting  apart  two  Au- 
gat  low  profile  28  pin  sockets  with 
machined  pins.  Then  attach  the  perf 
board  to  a  wooden  support  block  and 
drill  out  to  0.055-inch  in  the  pin  pat¬ 
tern  for  the  Mac’s  ROMs.  Keeping 
the  wooden  block  attached,  press  the 
pins  into  the  perf  board  using  a  vise 
or  press  (be  careful  not  to  crush  the 
pins).  The  wooden  block  provides  a 
die  to  protect  the  socket  pins  during 
the  insertion  process.  You  can  then 
insert  the  other  parts  into  the  perf 
board  and  wire  them  using  point-to- 
point  soldering  on  the  back  side.  Fi¬ 
nally,  and  carefully,  remove  the 
ROMs  from  the  board,  insert  them 
into  the  chip  carrier,  and  plug  the 
chip  carrier  with  the  ROMs  back  into 
the  ROM  sockets. 

I’m  skipping  any  discussion  on  how 
to  take  your  Mac  apart  and  reassem¬ 
ble  it;  for  that,  see  the  installation 
section  on  page  96. 

Now  for  the  hard  part.  I’ve  come 
up  with  several  options  for  getting  the 
SCSI  ribbon  cable  out  of  the  case, 
none  of  them  elegant.  One  is  to  cut  a 
hole  in  the  back  of  the  case  for  an 
exit.  Another  is  a  panel-mounted 
connector  installed  in  the  case  (my 
preferred  solution).  Or  you  can  run  a 
ribbon  cable  out  between  the  bottom 
case  seam  in  front,  although  you’ll 
have  to  widen  the  seam  slightly  to  do 
this.  A  fourth  solution  is  not  to  bring 
the  cable  out  at  all,  of  course.  Gener¬ 
al  Computer  has  already  demonstrat¬ 
ed  the  feasibility  of  installing  a  hard 
disk  inside  the  Mac  case.  I  haven’t 


Dr.  Dobb's  Journal,  September  1985 


94 

711 


OPT  EXT  TERM  PWR 


Mac  SCSI  host  adaptor  by  DMS  Design— Schematic  Logic  Drawing 


Mac  SCSI  Selects  Macintosh  Memory  Map 

OxE 

OxC 

Ox  A 

0x8 

0x6 

0x4 

0x2 

0x0 

0x50001 X  0x50002X 
Odd  Bytes  Even  Bytes 

Figure  2 

Device  Memory  Maps 


Bank  Address  Overlay  Overlay/ 


0 

OOxxxxx 

Rom 

Ram 

1 

2 

400xxxx 

Rom 

Rom 

50000xx 

Mac  SCSI 

Mac  SCSI 

3 

60xxxxx 

Ram 

4 

9FFFFx 

SCC  Read 

SCC  Read 

5 

BFFFFx 

SCC  Write 

SCC  Write 

6 

DFxxFF 

IWM 

IWM 

7 

VIA 

VIA 

READ  WRITE 


RESET 

INITIATOR 

START 

DATA 

TARGET 

START 

STATUS 

DMA 

START 

SCSI 

STATUS 

SELECT 

TARGET 

CMD 

TARGET 

CMD 

MODE 

MODE 

INITIATOR 

CMD 

INITIATOR 

CMD 

SCSI 

DATA 

SCSI 

DATA 

Figure  3 

Template  for  Cut  Out 


Dr.  Dobb’s  Journal,  September  1985 


Installation  Outline  for 
External  Cabling 

Materials  Required 

•Xcelite  XTD-15  screwdriver  with  a 
6-inch  extension 

•  wooden  ruler 

•  two  cotton  towels 

•  hand  drill,  14-  and  '/n-inch  drill  bits 

•  10-inch  Mill  Bastard  flat  file 

•  IC  extractor  or  a  small  screwdriver 
to  remove  the  28  pin  ROMs 

•AMP  connector  1-499970-0;  AMP 
Ground  Plane  102793-4 

•  paper  template  (see  Figure  3,  page 
95) 

Opening  the  Case 

Disconnect  the  Mac  from  all  external 
cables,  power,  mouse,  keyboard  etc. 
Place  the  Mac  facedown  on  a  towel  to 
prevent  marring  the  plastic  case.  Re¬ 
move  the  five  case  screws  with  the 
Xcelite  XTD-15  Torx  screwdriver; 
two  are  located  under  the  handle,  one 
underneath  the  battery  case  plate, 
and  two  at  the  bottom  of  the  case. 
Use  the  long  edge  of  a  wooden  ruler 


tried  this  yet. 

The  software  for  MacSCSI  is  so  far 
pretty  simple.  It  consists  of  a  routine 
to  format  the  drive  and  a  disk  driver. 
Details  of  the  operation  of  these  two 
routines  will  vary  a  little  depending 
upon  the  controller  manufacturer 
and  drive  type.  The  Listing  in  this  ar¬ 
ticle  ( page  98 )  is  for  a  XEBEC  S 1 4 1 0 
controller  with  rev  D  ROMs  and  a 
Seagate  ST506  5Mb  drive,  both  of 
which  are  common  on  the  used  mar¬ 
ket  in  Silicon  Valley.  As  a  starting 
point  I  used  the  RAMdisk  supplied 
with  the  Aztec  C  Compiler  release. 
The  strategy  was  to  use  the  basic 
RAMdisk  driver  as  a  local  cache  and 
use  a  simple  LRU  algorithm  for  re¬ 
placement.  Using  the  Aztec  C  exam¬ 
ple  for  the  explorer  desk  accessory, 
we  recoded  the  driver  into  C,  for  a 
slight  loss  in  performance.  On  a  128k 
Mac  the  cache  size  should  be  set  be¬ 
tween  1  and  10.  On  a  512k  Mac  you 
should  use  some  number  between  30 
and  300,  optimally. 

Other  development  environments 
and  C  compilers  may  be  used  as  well 


to  pry  the  case  apart.  Applying  pres¬ 
sure  to  the  power  and  I/O  connec¬ 
tors,  while  lifting  on  the  case,  should 
release  it  from  the  faceplate.  Set 
the  foil  EMI  sheild  aside  until 
reassembly. 

Cutting  the  connector  slot 

Using  the  paper  template,  mark  the 
slot  to  be  cut  into  the  back  of  the 
Macintosh.  Remove  most  of  the  plas¬ 
tic  from  the  slot  with  a  handdrill,  and 
square  off  the  edges  with  the  flat  file. 
With  the  slot  cut,  recenter  the  tem¬ 
plate,  mark  and  drill  the  ‘/s-inch  holes 
for  the  50  pin  feed-thru  header.  Bolt 
the  header  to  the  inside  of  the  case, 
with  pin  1  on  the  bottom. 

Removing  the  Motherboard 
ROMs 

Disconnect  the  diskdrive  and  analog 
board  cables  from  the  motherboard; 
remove  the  motherboard  from  the 
chassis  and  place  it  on  the  second 
towel.  The  ROMs  are  in  the  IC  sock¬ 
ets  marked  ROM  HI  and  ROM  LO. 
Using  a  chip  extractor  or  small 


with  minor  changes  in  the  coding  and 
installation  procedures.  For  Aztec  C 
the  documentation  on  the  RAMdisk 
and  explorer  desk  accessory  provide 
all  the  magic  incantations  necessary 
to  convert  the  source  files  into  an  in¬ 
stalled  driver. 

Questions,  anyone?  How  large  a 
disk,  you  ask?  It  depends  in  part  on 
the  software;  you  can  reasonably 
handle  up  to  about  five  Mb  of  storage 
on  the  Mac  without  partitioning;  at 
ten  Mb  the  number  of  files  gets  un¬ 
wieldy.  Then  you’ll  just  need  more 
sophisticated  software.  Do  you  have 
to  have  a  Fat  Mac  to  support 
MacSCSI?  No,  but  while  this  hard 
disk  upgrade  can  work  with  many  ap¬ 
plications  on  a  128k  Mac,  a  512k 
Mac  is  highly  recommended. 

What  if  you  don’t  want  to  take  the 
time  to  do  it  yourself?  For  the  lazy, 
the  MacSCSI  board  is  available  from 
Fastime,  RO.  Box  12508,  San  Luis 
Obispo,  CA  93406  for  $150.00  as¬ 
sembled  and  tested,  along  with  ma¬ 
chine-readable  copies  of  the  sources 
listed  here.  You  can  also  get  a  corn- 


screwdriver,  carefully  remove  the 
chip  marked  ROM  HI.  Place  the 
ROM  in  the  socket  marked  ROM  HI 
on  the  Mac  SCSI  board,  and  repeat 
the  procedure  for  ROM  LO.  Double 
check  the  orientation  of  the  ROMs  by 
noting  that  the  notch  on  each  chip 
points  away  from  the  50  pin  header. 

Installing  the  Mac  SCSI  board 

Orient  the  Mac  SCSI  board,  with  the 
50  pin  header  toward  the  I/O  con¬ 
nectors  on  the  motherboard,  and  sim¬ 
ply  plug  it  into  the  vacated  ROM 
sockets.  Reinsert  the  motherboard 
into  the  chassis,  taking  care  not  to 
damage  the  50  pin  header.  Reconnect 
the  diskdrive  and  analog  board  ca¬ 
bles,  followed  by  the  large  Mac  SCSI 
interface  cable. 

Closing  the  Mac  back  up 

Set  the  Macintosh  facedown  and  po¬ 
sition  the  EMI  sheild  over  the  I/O 
connectors.  Replace  the  back,  mak¬ 
ing  sure  each  side  is  completely  seat¬ 
ed,  and  secure  the  five  holding 
screws. 


plete  kit,  including  drives  from  10Mb 
to  1 10Mb,  tape  backup,  and  extend¬ 
ed  software  drivers  with  multiple  soft 
partitions  to  support  the  larger 
drives.  Drivers  for  disk  sharing  and 
the  Appletalk  file  server  are  planned 
or  under  development.  Write  for 
more  information. 

John  L.  Bass 
DMS  Design 
P.O.  Box  1456 
Cupertino,  CA  95014 


DD| 

(Listings  begin  on  page  98) 


Reader  Ballot 

Vote  for  your  favorite  feature/article. 
Circle  Reader  Service  No.  1 98. 


Dr.  Dobb's  Journal,  September  1985 


96 

713 


A lac  Toolbox  Listing  (Text  begins  on  page  94) 


all:  clean  FormatSCSI  ChkSCSI  MountSCSI  MacSCSI  BootSCSI 
echo  done 


clean : 

rm  FormatSCSI  MountSCSI  MacSCSI  BootSCSI  ChkSCSI 
rm  TestSCSI  MSCSI 
rm  *.bak 

print : 

cat  makefile  >  .bout 
cat  newpage  >  .bout 
cat  sl410.c  >  .bout 
cat  newpage  >  .bout 
cat  np_fmt.c  >  .bout 
cat  newpage  >  .bout 
cat  np_dvr.c  >  .bout 
cat  newpage  >  .bout 
cat  newpage  >  .bout 

FormatSCSI : 

cc  np_fmt.c 
cc  sl410.c 

In  -mo  FormatSCSI  np_fmt.o  sl410.o  sys : lib/mixcroot .  o  -lc 
cprsrc  DRVR  30  sys:system  FormatSCSI 
rm  np_fmt.o  sl410.o 


ChkSCSI: 

cc  np_chk.c 
cc  sl410.c 

In  -mo  ChkSCSI  np_chk.o  sl410.o  sys : 1 ib/mixcroot . o  -lc 
cprsrc  DRVR  30  sys: system  ChkSCSI 
rm  np_chk.o  sl410.o 

MacSCSI : 

cc  -bu  np_dvr.c 
cc  -bu  sl410.c 

In  -d  -n  MacSCSI  -I  28  -R  40  np_dvr.o  sl410.o  -lc  -o  MacSCSI 
cp  -f  MSCSI  TestSCSI 
cprsrc  DRVR  28  MacSCSI  TestSCSI 
rm  np_dvr.o  sl410.o 

MountSCSI : 

cc  mountscsi.c 

In  -mo  MountSCSI  mountscsi.o  sys : 1 ib/mixcroot . o  -lc 

cprsrc  DRVR  30  sys: system  MountSCSI 

cp  -f  MountSCSI  MSCSI 

cprsrc  DRVR  28  MacSCSI  MountSCSI 

rm  mountscsi.o 

BootSCSI : 

cc  bootscsi.c 

In  -mo  BootSCSI  bootscsi.o  sys : lib/mixcroot . o  -lc 
cprsrc  DRVR  30  sys: system  BootSCSI 
rm  bootscsi.o 


/* 

*  sl410.c  version  1.0,  July  20,  1985 

* 

*  MacSCSI  I/O  routine  for  XEBEC  S1410  with  ST506  drive  and  similar 

*  SASI/SCSI  controllers  that  are  pre-SCSI  standard.  Other  controller 


98 

714 


Dr.  Dobb's  Journal,  September  1985 


*  drive  combinations  will  require  changes.  This  version  was  developed 

*  on  the  Mac  under  Aztec  C. 

* 

*  Copyright  1985  by  John  L.  Bass,  DMS  Design 

*  PO  Box  1456,  Cupertino,  CA  95014 

*  Right  to  use,  copy,  and  modify  this  code  is  granted  for 

*  personal  non-comercial  use,  provided  that  this  copyright 

*  disclosure  remains  on  ALL  copies.  A_ny  other  use,  reproduction, 

*  or  distribution  requires  the  written  consent  of  the  author. 

* 

*  Sources  are  available  on  diskette  from  Fastime,  PO  Box  12508 

*  San  Luis  Obispo,  Ca  93406  —  (805)  546-9141.  Write  for  ordering 

*  information  on  this  and  other  Mac  products. 

*/ 

/* 

*  SASl/SCSI  Command  and  Sense  Blocks 

*/ 

struct  scsicmd  { 

char  sc_cmd; 

char  sc_adrH; 

char  sc_adrM; 

char  sc_adrL; 

char  sc_arg; 

char  sc_vendor; 

}? 

struct  scsisense  { 

char  ss_code; 

char  ss_adrH; 

char  ss_adrM; 

char  ss_adrL; 

} ; 

/* 

*  NCR5380  registers  on  the  MacSCSI  host  adapter  found  at 

*  location  0x500000 

*/ 

struct  NCR5380  { 

char  wr_data,  filla;  /*  force  scsi  bus  data  */ 

(Continued  on  page  1 01) 


/*  status  code  */ 

/*  address  when  valid  */ 


/*  command  code  */ 

/*  High  Byte  of  address  */ 

/*  Middle  byte  of  address  */ 

/*  Low  byte  of  address  */ 

/*  count  or  interleave  value  */ 

/*  vendor  byte  —  seek  algorithm  */ 


Dr.  Dobb's  Journal,  September  1985 


99 

715 


Mac  Toolbox  Listing  (Listing  Continued,  text  begins  on  page  94) 


char  wr_icmd, 

fillb 

/* 

initiator  command  */ 

char  wr_mode, 

fillc 

/* 

ncr5380  mode  */ 

char  wr_tcmd. 

filld 

/* 

target  command  */ 

char  wr_sele, 

fille 

/* 

select  enable  */ 

char  wr_send, 

fillf 

/* 

start  send  operation  */ 

char  wr_trec, 

fillg 

/* 

start  target  recieve  */ 

char  wr  irec, 

fillh 

/* 

start  initiator  recieve  */ 

char  f ill00. 

rd_data; 

/* 

current  scsi  bus  data  */ 

char  fill01, 

rd_icmd; 

/* 

initiator  command  status  */ 

char  fill02. 

rd_mode ; 

/* 

ncr  5380  mode  */ 

char  fill03. 

rd_tcmd; 

/* 

target  command  status  */ 

char  fill04, 

rd_bstat; 

/* 

current  bus  status  */ 

char  f ill05. 

rd_stat ; 

/* 

chip  bus  and  status  info  */ 

char  fill06. 

rd_input ; 

/* 

input  data  */ 

char  fill07, 

7 

rd_reset; 

/* 

reset  strobe  */ 

/* 


*  Phase 
*/ 

#def ine 

defines 

TCMD 

BSTAT 

P_ 

_D0UT 

0X00 

/* 

0x60  data  out  */ 

♦define 

P_ 

.DIN 

0X01 

/* 

0x64  data  in  */ 

♦define 

P. 

.CMD 

0x02 

/* 

0x68  command  */ 

♦define 

P_ 

_STAT 

0x03 

/* 

0x6C  status  */ 

♦define 

P_ 

.MOUT 

0x06 

/* 

0x70  Message  Out  */ 

♦define 

P_ 

.MIN 

0x07 

/* 

0x74  Message  In  */ 

/* 

*  Global  for  drive  size  for  use  by  format  routine 

*/ 

long  ScsiDrvSize  =  4L  *  17L  *  15 3L; 

/* 

*  ScsiReset  —  assume  this  is  the  only  host  adapter  in  system  and  do 

*  a  hard  reset  of  the  buss  and  controllers. 

*/ 

ScsiReset ()  { 

long  cntr; 
register  zero  =  0; 

register  struct  NCR5380  *ncr  =  0x500000; 

ncr->wr_data  =  zero; 
ncr->wr_mode  =  zero; 
ncr->wr_tcmd  =  zero; 
ncr->wr_icmd  =  0x80; 
for(cntr=0x40000;cntr>0 ;cntr — ) ; 
ncr->wr_icmd  =  zero; 

} 

/* 

*  ScsiCmd  -  Select  the  target  controller  0,  build  and  transfer 

*  the  command  block. 

*/ 

ScsiCmd (opcode , lun, blk , len, ctl )  { 

struct  scsicmd  cmd; 
long  cntr; 
register  zero  =  0; 

register  struct  NCR5380  *ncr  =  0x500000; 
register  char  *ptr; 

(Continued  on  next  page ) 


101 


71C 


Dr.  Dobb's  Journal.  September  1985 


Mac  Toolbox  (Listing  Continued,  text  begins  on  page  94) 
Listing  One 


} 


ncr->wr_tcmd  =  zero; 
ncr->wr_data  =  1; 
ncr->wr_icmd  =  0x05; 
for (cntr=0x40000;cntr>0  && 
ncr->wr_icmd  =  zero; 
cmd,sc_cmd  =  opcode; 
cmd.sc_adrH  =  lun<<5; 
cmd.sc_adrM  =  blk>>8; 
cmd.sc_adrL  =  blk; 
cmd.sc_arg  =  len; 
cmd. sc_vendor  =  ctl  |  1; 
ScsiOut (6, &cmd, P_CMD) ; 


/*  select  controller  * 


(ncr->rd_bstat  &  0x40)  ==  zero;cntr — ); 
/*  build  scsi  command  block  */ 

/*  force  XEBEC  ST506  Halfstep  */ 
/*  send  cmd  to  ctlr  */ 


/* 

*  ScsiOut/Scsiln  -  transfer  bytes  on  data  bus  with  req/ack  handshake 

*  In  the  interest  of  speed  we  ignore  edge  following,  the  controller 

*  will  respond  within  a  microsecond  or  so  during  a  particular  phase. 

*  The  rest  of  the  loop  is  unfolded  and  optimized  for  the  Aztec  C 

*  compiler  to  generate  9  memory  references  per  byte.  Best  case  would 

*  be  7  memory  references. 

*/ 

ScsiOut (len,ptr, phase) 
register  char  *ptr; 

{ 


register  high  =  0x11; 
register  long  low  =  0x01; 
register  char  *i,*d; 
register  zero  =  0; 

register  struct  NCR5380  *ncr  =  0x500000; 


} 


d  =  &ncr->wr_data; 
i  =  &ncr->wr_icmd ; 
ncr->wr_tcmd  =  phase; 
ncr->wr_icmd  =  0x01; 
do  { 

while ( (ncr->rd_bstat  &  0x20)  ==  zero);  /*  sync  with  req  */ 
if ( (ncr->rd_stat  &  0x08)  ==  zero)  break;  /*  if  done  */ 

*d  =  *ptr;  ptr+=low;  *i  =  high;  *i  =  low; 

*d  =  *ptr;  ptr+=low;  *i  =  high;  *i  =  low; 

*d  =  *ptr;  ptr+=low;  *i  =  high;  *i  =  low; 

*d  =  *ptr;  ptr+=low;  *i  =  high;  *i  =  low; 

*d  =  *ptr;  ptr+=low;  *i  =  high;  *i  =  low; 

*d  =  *ptr;  ptr+=low;  *i  =  high;  *i  =  low; 

while ( (ncr->rd_bstat  &  0x20)  ==  zero);  /*  sync  with  req  */ 

if ( (ncr->rd_stat  &  0x08)  ==  zero)  break;  /*  if  a  cmdblk  */ 

*d  =  *ptr;  ptr+=lpw;  *i  =  high;  *i  =  low; 

*d  =  *ptr;  ptr+=low;  *i  =  high;  *i  =  low; 

}  while  ((len  -=  8)  >0); 
ncr->wr_icmd  =  zero; 
return { 0 ) ; 


Scsi In (len,ptr, phase) 
register  char  *ptr; 

{ 

register  high 
register  long 
register  char 
register  zero 


=  0x10; 
one  =  0x01; 
*i , *d; 

=  0; 


(Continued  on  page  104) 


Dr.  Dobb’s  Journal,  September  1985 


102 


/rldC  (Listing  Continued,  text  begins  on  page  94) 

Listing  One 


register  struct  NCR5380  *ncr  =  0x500000; 


d  =  &ncr->rd_data ; 
i  =  &ncr->wr_icmd ; 
ncr->wr_tcmd  =  phase; 
do  { 

while ( (ncr->rd_bstat  &  0x20)  ==  zero); 
if ( (ncr->rd_stat  &  0x08)  ==  zero)  break; 


*ptr  =  *d; 

ptr+=one; 

*i 

= 

high ; 

*i 

= 

zero ; 

*ptr  =  *d; 

ptr+=one ; 

*i 

= 

high; 

*i 

= 

zero ; 

*ptr  =  *d ; 

ptr+=one ; 

*i 

= 

high; 

*i 

= 

zero; 

*ptr  =  *d; 

ptr+=one ; 

*i 

= 

high; 

*i 

= 

zero ; 

*ptr  =  *d; 

ptr+=one; 

*i 

= 

high; 

*i 

= 

zero ; 

*ptr  =  *d; 

ptr+=one ; 

*i 

= 

high; 

*i 

= 

zero ; 

*ptr  =  *d ; 

ptr+=one; 

*i 

= 

high; 

*i 

= 

zero ; 

*ptr  =  *d; 
((len  -=  8) 

ptr+=one ; 
>0); 

*i 

high; 

*i 

zero ; 

return { 0 ) ; 


/* 

*  ScsiStat  -  get  the  last  two  bytes  to  finish  a  command  sequence 
*/ 

ScsiStat  ()  { 

register  zero  =  0; 

register  struct  NCR5380  *ncr  =  0x500000; 
register  char  *ptr; 
short  stat; 

ptr  =  &stat; 

ncr->wr_tcmd  =  P_STAT; 

while ( (ncr->rd_bstat  &  0x20)  ==  0); 

*ptr++  =  ncr->rd_data ; 
ncr->wr_icmd  =  0x10; 
ncr->wr_icmd  =  zero; 
ncr->wr_tcmd  =  P_MIN ; 
while ( (ncr->rd_bstat  &  0x20)  ==  0); 

*ptr++  =  ncr->rd_data ; 
ncr->wr_icmd  =  0x10; 
ncr->wr_icmd  =  zero; 
ncr->wr_tcmd  =  zero; 
return (stat) ; 

} 


/* 

*  ScsiFmt  —  Do  a  default  drive  format 
*/ 

ScsiFmt  ()  { 

ScsiCmd (0x04,0,0,12,0)  ; 
return (ScsiStat  ( ) )  ; 


(ST506) 

/*  issue  format  command  */ 
/*  return  status  */ 


/* 

*  ScsiRead  and  ScsiWrite  —  do  the  I/O  for  a  specified  sector 

*  and  the  related  data. 

*/ 

ScsiRead (sector , data) 
char  *data; 

{ 


104 


Dr.  Dobb's  Journal.  September  1985 


ScsiCmd (0x08,0, sector ,1,0) ; 
Scsiln (512,data,P_DIN) ; 
return (ScsiStat ( ) )  ; 

1 

ScsiWrite (sector , data) 
char  *data; 

{ 

ScsiCmd ( 0x0A, 0 , sector ,1,0) ; 
ScsiOut (512,data,P_DOUT) ; 
return (ScsiStat  ( ) )  ; 


/*  issue  read  command  */ 

/*  transfer  in  data  */ 

/*  return  status  */ 


/*  issue  write  command  */ 

/*  transfer  out  data  */ 

/*  return  status  */ 


/* 

*  MacSCSI  non-partitioned  format  routine. 

*  This  version  was  developed  on  the  Mac  under  Aztec  C. 

* 

*  Copyright  1985  by  John  L.  Bass,  DMS  Design 

*  PO  Box -1456,  Cupertino,  CA  95014 

*  Right  to  use,  copy,  and  modify  this  code  is  granted  for 

*  personal  non-comercial  use,  provided  that  this  copyright 

*  disclosure  remains  on  ALL  copies.  Any  other  use,  reproduction, 

*  or  distribution  requires  the  written  consent  of  the  author. 

* 

*  Sources  are  available  on  diskette  from  Fastime,  PO  Box  12508 

*  San  Luis  Obispo,  Ca  93406  —  (805)  546-9141.  Write  for  ordering 

*  information  on  this  and  other  Mac  products. 

V 


struct  Volume  { 

short  drSigWord; 

long  drCrDate; 

long  drLsBkUp; 

short  drAtrb; 

short  drNumFls; 

short  drDirSt; 

short  drBILen ; 

short  drNmAlBlks ; 

long  drAlBlkSiz ; 

long  drClpSiz ; 

short  drAlBISt ; 

long  drNxtFNum; 

short  drFreeBks; 

char  drVN ; 

char  drFill [512-37] 

struct  Volume  v; 


/*  should  be  $D2D7  */ 

/*  date  and  time  of  initialization  */ 
/*  date  and  time  of  last  backup  */ 

/*  volume  attributes  */ 

/*  #  of  files  in  file  directory  */ 

/*  first  logical  block  of  file  dir  */ 
/*  #  of  logical  blocks  in  file  dir  */ 
/*  #  of  allocation  blocks  on  volume  */ 
/*  size  of  allocation  blocks  */ 

/*  #  of  bytes  to  allocate  */ 

/*  logical  block  number  of  first 

allocation  block  */ 
/*  next  unused  file  number  */ 

/*  #  of  unused  allocation  blocks  */ 

/*  length  of  volume  name  */ 

/*  volume  name  &  start  of  alloc,  map  */ 


char  zeros  [512] ; 

long  ScsiDrvSize; 

main()  { 

char  *p ; 
int  i ; 


/*  block  worth  of  nulls  */ 
/*  number  of  512  byte  sectors  */ 


# ifdef  hack 

printf ( "Scsi  reset\n"); 

ScsiReset ( )  ; 

printf ("Drive  being  formatted\n"); 
if  (ScsiFmt ( ) )  { 

printf ("Format  Failed\n"); 
exit  (1)  ; 

} 


Dr.  Dobb's  Journal,  September  1985 


105 

719 


\1dC  Toolbox  Listing  (Listing  Continued,  text  begins  on  page  94) 

#endif 

v . drSigWord  =  0xd2d7; 
v . drCrDate  =  v.drLsBkUp  =  0; 
v.drAtrb  =  0; 
v.drNumFls  =  0; 
v.drNxtFNum  =  1; 
v.drDirSt  =  4; 

printf ("Directory  Start:  %6 . 6d\n" , v. drDirSt ) ; 

Y.drBILen  =  28; 

printf ("Directory  BlkLen:  %6.6d\n",v.drBlLen) ; 

v.drAlBlSt  =  v.drDirSt  +  v.drBILen; 

printf ("First  Allocation  Blk:  %6.6d\n", V.drAlBlSt) ; 

v.drAlBlkSiz  =  512L  *  (ScsiDrvSize/640L  +  1L) ; 
v.drClpSiz  =  V.drAlBlkSiz; 

printf ("Allocation  BlockSize:  %6 . 61d\n" , v. drAlBlkSiz ) ; 

v. drNmAlBlks  =  (ScsiDrvSize-v. drAlBlSt ) / (v. drAlBlkSiz>>9 ) ; 
v.drFreeBks  =  v. drNmAlBlks ; 

printf ("Allocation  Blocks:  %6. 6d\n",v. drNmAlBlks) ; 

for (v . drVN  =  0 , p="MacSCSI " ; *p;p++)  v.drFill [v.drVN++]  =  *p; 

printf ( "Initializing  Drive\n") ; 

ScsiWrite (0, zeros) ; 

ScsiWrite (1 , zeros)  ; 

if  (ScsiWrite (2 , &v) )  { 

printf ( "Write  of  config  block  failed\n"); 

} 

for (i=3;i<ScsiDrvSize;i++)  if (ScsiWrite (i, zeros) )  { 
printf ( "Write  failed  at  block  %d\n",i); 

} 

printf ( "Checking  Drive\n"); 

for (i=0;i<ScsiDrvSize;i++)  if  (ScsiRead ( i , zeros ) )  { 
printf ("Read  failed  at  block  %d\n"ri); 

} 

printf  ( "Format  completed  ok\n"); 

} 

main ( ) 

{ 

printf ("Exit  code  %d\n" ,OpenDriver ("\P.MacSCSI") ) ; 

} 

main ( ) 

{ 

*(int  *) 0x210  =5;  /*  boot  drive  to  MacSCSI  */ 

)  End  Listing 


Due  to  a  lack  of  space  in  this  issue  the  listing  for  the  Mac  SCSI  disk  driver 
(np_dvr.c)  will  be  published  in  the  October  issue. 


106 

720 


Dr.  Dobb's  Journal,  September  1985 


The  CP/M  Exchange  RCP/M  system 
is  available  for  your  use  24  hours  a 
day,  7  days  a  week.  Reach  it  by  dial¬ 
ing  (404)  449-6588. 

Goodbye  Old  Friend 

Effective  July  1,  1985  Digital  Re¬ 
search  is  changing  the  manner  in 
which  they  support  their  products. 
Some  products,  the  more  fortunate 
ones,  will  continue  to  receive  support 
in  varying  levels,  while  other,  older 
and  more  mature  products  will  no 
longer  be  supported. 

Product  support  has  been  broken 
into  four  levels.  The  highest  level  of 
support  a  product  can  have  is  level 
“A”,  also  called  priority  support.  It 
was  not  explained  exactly  what  this 
means,  but  I  assume  that  any  support 
calls  made  for  a  level  “A”  product 
will  be  given  priority  over  other  calls. 

Level  “B”  support  is  active  sup¬ 
port.  You  may  encounter  some  delays 
in  receiving  support  for  level  “B” 
products.  Priority  is  given  to  ques¬ 
tions  submitted  on  CompuServe;  next 
phone  calls  will  be  answered;  and  fi¬ 
nally  letters  will  be  answered. 

Level  “C”  support  is  limited  to 
questions  submitted  on  CompuServe 
and  phone  calls.  Level  “D”  support  is 
for  mature  products  that  are  not  ac¬ 
tively  supported. 

What  does  all  this  mean?  To  the 
CP/M  Plus  user  it  means  that  you 
won’t  be  able  to  call  DRI  any  longer 
expecting  to  get  help,  because  CP/M 
Plus  has  been  assigned  support  level 
“D”.  Effectively,  this  appears  to 
eliminate  any  further  hope  of  buying 
an  unconfigured  copy  of  CP/M  Plus. 
I  doubt  that  dealers  will  continue  to 
sell  a  product  that  isn’t  supported  by 
the  manufacturer.  A  few  hardware 
manufacturers  are  still  packaging  it 
with  their  machines;  hopefully  they 
will  also  be  able  to  support  it. 


Lortunately  for  the  one  million  or 
so  of  us  using  CP/M  2.2,  it  was  as¬ 
signed  to  level  “C”  support.  Some 
time  still  remains  for  the  once  most 
popular  operating  system  for  micro¬ 
computers.  When  support  is  finally 
dropped  on  CP/M  2.2  I  doubt  that 
any  great  impact  will  be  felt.  There 
are  hundreds  of  reference  books 
available  for  it  and  a  good  supply  of 
heavily  experienced  programmers 
knowledgeable  on  the  system. 

AMPRO  Little  Board 

In  the  May  1985  issue  of  DDJ  Richard 
Conn  reviewed  the  AMPRO  Little 
Board  and  Bookshelf  Computers. 
Since  the  electronic  version  of  this  col¬ 
umn  is  running  24  hours  a  day  on  an 
AMPRO  Little  Board,  I  have  decided 
to  offer  some  impressions  of  this  com¬ 
puter  that  I  have  formed  as  a  user. 

Measuring  a  mere  5%-inches  X 
7%-inches,  the  AMPRO  Little  Board 
is  a  complete  64K  Z-80  computer  on 
a  single  board.  Its  size,  in  fact,  is  the 
same  as  a  mini-floppy  disk  drive;  the 
mounting  tabs  on  the  board  are 
spaced  to  match  the  mounting  holes 
on  any  standard  drive.  The  input/ 
output  facilities  include  two  asyn¬ 
chronous  serial  RS-232  ports,  a  Cen¬ 
tronics  compatible  parallel  interface, 
and  a  single  chip  floppy  disk  control¬ 
ler  capable  of  handling  up  to  four  Sc¬ 
inch  drives.  To  round  out  the  package 
is  a  well-implemented  CP/M  2.2  en¬ 
hanced  by  ZCPR3  and  a  large  set  of 
utility  programs. 

All  that  remains  to  form  a  com¬ 
plete  system  is  to  supply  a  meager  5 
watts  of  power,  a  CRT  terminal  at¬ 
tached  to  one  of  the  serial  ports,  and 
mini-floppy  disk  drives  of  your 
choice.  Linally,  a  cabinet  would  prob¬ 
ably  be  desirable  to  provide  protec¬ 
tion  for  the  delicate  components. 

If  your  need  for  external  storage 


exceeds  that  provided  by  the  floppy 
disk  drives,  a  SASI/SCSI  peer  buss 
daughter  board  is  available  as  an  op¬ 
tion.  This  board,  along  with  an  up¬ 
dated  BIOS  and  several  utility  pro¬ 
grams,  will  allow  a  hard  disk  to  be 
attached  to  the  Little  Board.  When 
used,  this  option  can  account  for  up 
to  20  Mb  of  additional  storage. 

I  don’t  pretend  to  be  an  electrical 
engineer  and  I  don’t  have  any  inside 
information  from  the  people  who  de¬ 
signed  the  Little  Board.  It’s  apparent 
to  me,  however,  that  the  principles  of 
reliability  and  cost  effectiveness  were 
held  above  all  others  when  this  com¬ 
puter  was  designed. 

The  component  density,  ICs  per 
square  inch  of  board  space,  is  far  less 
than  what  I  am  accustomed  to.  Even 
though  this  is  not  documented,  I  be¬ 
lieve  that  forced  air  cooling  of  the 
Little  Board  is  not  necessary  if  ade¬ 
quate  air  circulation  and  normal 
room  temperature  is  maintained. 
Better  yet,  if  a  cabinet  with  forced  air 
cooling  is  used,  component  life  should 
not  be  adversely  affected  by  tempera¬ 
ture  extremes.  And,  even  when  the 
board  is  attached  directly  to  the  bot¬ 
tom  of  a  floppy  disk  drive,  heat 
should  not  be  a  problem. 

Serial  Interface 

The  Z80  Dual  Asynchronous  Receiv¬ 
er/Transmitter  (DART)  is  used  to  im¬ 
plement  the  two  serial  ports.  This  de¬ 
vice  is  the  little  brother  to  the  Z80 
SIO.  They  are  practically  identical  in 
all  ways  including  software  program¬ 
ming.  So  similar,  in  fact,  that  they  can 
be  interchanged  in  the  same  socket. 
The  difference  that  exists  between  the 
two  is  the  DART’s  inability  to  handle 
synchronous  transmission  protocols 
such  as  BI-SYNC  and  SDLC. 

Operation  and  setup  of  the  DART  is 
handled  completely  through  software. 


Dr.  Dobb’s  Journal,  September  1985 


110 

721 


Programming  is  accomplished  by 
loading  the  desired  transmission  pa¬ 
rameters  into  one  or  more  of  the  five 
write  registers  contained  on  the  chip. 
For  instance,  the  common  transmis¬ 
sion  characteristics  of  word  length, 
number  of  stop  bits,  and  baud  rate  are 
all  completely  programmable  through 
software  by  loading  the  proper  values 
into  write  registers  four  and  five.  The 
more  advanced  features,  enabling  in¬ 
terrupts  and  setting  interrupt  vectors, 
are  handled  in  like  manner. 

Both  serial  channels  can  be  pro¬ 
grammed  for  a  full  range  of  baud 
rates.  Channel  A  can  be  used  up  to  a 
maximum  of  38. 4K  baud,  which  to 
my  knowledge  is  fast  enough  to  drive 
any  commercially  available  CRT  at 
its  maximum  rate.  Channel  B  is  lim¬ 
ited  to  9600  baud,  however.  Never¬ 
theless,  I  don’t  think  the  lower  baud 
rate  will  present  any  practical  limita¬ 
tions  since  most  auxiliary  devices, 
modems  and  printers,  are  unable  to 
receive  data  faster  than  at  9600  baud. 

The  otherwise  complete  serial  sec¬ 
tion  is  limited,  however,  by  the  num¬ 
ber  of  signal  lines  that  are  brought 
out  to  the  mating  connector.  In  addi¬ 
tion  to  the  input  and  output  data  lines 
and  two  ground  lines,  only  two  con¬ 
trol  lines,  one  input  and  one  output, 
are  made  available.  In  any  other  than 
the  most  simple  application,  a  single 
flow  control  line  may  prove  to  be  in¬ 
adequate. 

Parallel  Interface 

The  Centronics  compatible  parallel 
interface  is  implemented  with  a  sin¬ 
gle  8  input  D-type  latch.  Here  again, 
the  number  of  control  lines  is  limited. 
Only  the  peripheral  device  busy  line 
is  brought  in  for  flow  control.  The 
other  control  lines,  paper  empty  etc., 
are  conspicuously  missing. 

Floppy  Disk  Controller 

The  floppy  disk  controller  used  on  the 
Little  Board  is  the  Western  Digital 
1770.  It  is  similar  to  its  predecessor, 
the  WD179X,  but  also  contains  a 
digital  data  separator  and  write  pre¬ 
compensation  circuity.  The  most  no¬ 
ticeable  change,  however,  is  in  its 
size;  the  WD1770  is  completely  con¬ 
tained  in  a  single  28  pin  package. 
Only  a  few  years  ago  several  inches  of 


tightly  packed  board  space  were 
needed  to  implement  a  floppy  disk 
control  circuit.  The  1770  represents  a 
dramatic  reduction  in  chip  count  and 
overall  board  complexity  that  should 
result  in  improved  reliability. 

If  there  is  a  drawback  to  the  Little 
Board  disk  controller  it  is  its  inability 
to  control  8-inch  drives.  At  first  I 
thought  this  would  be  a  big  problem. 
Then  I  experienced  the  over  800K  ca¬ 
pacity  of  a  96TPI  format  5 '/4-inch  disk. 

Booting  Up 

Starting  up  the  Little  Board  for  the 
first  time  is  a  snap.  After  attaching 
the  floppy  disk  drives  with  a  standard 
34  pin  ribbon  cable,  all  you  need  to  do 
is  to  attach  a  CRT  terminal  set  to 
9600  baud  to  serial  channel  A.  There 
are  no  other  special  requirements. 
Simply  insert  the  CP/M  disk  into  disk 
drive  A  and  hit  reset.  The  standard 
monitor  ROM  will  load  the  operating 
system  from  disk  and  begin  its  opera¬ 
tion.  Nothing  could  be  easier. 

Once  the  system  is  up  and  running, 
you  may  want  to  change  the  default 
serial  port  parameters  or  activate  an 
option  that  automatically  loads  a 
program  at  cold  start.  A  reconfigura¬ 
tion  program  is  provided  to  enable 


you  to  make  these  changes  without 
going  through  the  hassle  of  editing 
and  reassembling  the  BIOS  portion  of 
CP/M. 

Another  option  of  the  Little  Board 
that  I  find  most  useful  is  the  ability  to 
reconfigure  dynamically  one  of  the 
four  floppys  to  that  of  another  com¬ 
puter.  For  example,  often  I  need  to 
read  disks  formatted  on  a  Morrow 
system.  To  do  this  I  simply  assign 
drive  E:  to  one  of  my  physical  drives 
and  set  the  disk  parameters  to  simu¬ 
late  the  Morrow  system.  Any  further 
access  to  drive  E:  now  causes  CP/M 
to  respond  as  if  it  were  actually  run¬ 
ning  on  the  other  computer. 

Two  associated  utility  programs 
are  provided  to  use  the  drive  E:  op¬ 
tion.  One  will  allow  you  to  set  auto¬ 
matically  drive  E:  to  any  of  about  a 
dozen  different  disk  types  simply  by 
selecting  it  from  the  menu.  The  other 
prompts  you  for  the  various  disk  pa¬ 
rameters  used  by  CP/M. 

A  host  of  other  utility  programs 
are  provided  with  the  Little  Board.  I 
have  found  all  of  them  to  be  well  im¬ 
plemented  and  very  useful. 

User  Impression 

I  have  been  using  the  Little  Board 
daily  for  several  months.  And  over 
the  last  three  weeks  it  has  been  the 
main  processor  for  my  24  hour  per 
day  Bulletin  Board  System.  During 
that  time  the  Little  Board  has  not 
been  down  a  single  moment. 

The  only  fault  that  I  find  significant 
enough  to  mention  is  the  failure  to 
bring  all  the  serial  and  parallel  control 
lines  out  to  their  connectors,  although, 
in  all  fairness,  I  haven’t  needed  them. 
As  I  mentioned  earlier,  I  have  at¬ 
tached  a  modem  and  a  serial  printer  to 
channel  B  without  problem. 

Wrapping  Up 

I  haven’t  been  able  to  finish  every¬ 
thing  that  I  wanted  to  say  about  the 
Little  Board;  I’m  working  on  adding 
a  real  time  clock  and  disk  data  inter¬ 
rupts  to  the  BIOS.  That  material  will 
have  to  wait  until  next  month. 

DDJ 

Reader  Ballot 

Vote  for  your  favorite  feature/article. 

Circle  Reader  Service  No.  1 99. 


112 

722 


Dr.  Dobb’s  Journal,  September  1985 


16-BIT  SOFTWARE  TOOLBOX 


by  Ray  Duncan 

The  programs  published  in  this 
month’s  column  are  available  for 
downloading  from  the  Laboratory 
Microsystems  RBBS  at  (213)306- 
3530  (300  or  1200  baud). 

Laserjet  Redux 

After  the  June  1985  16-Bit  Software 
Toolbox  appeared  in  print,  I  received 
some  “hot  and  bothered”  phone  calls 
from  friends  at  Hewlett-Packard 
about  the  tone  of  my  comments  on 
the  Laserjet  Printer.  They  knew  very 
well  that  I  consider  the  Laserjet  to  be 
a  fantastic  printer  and  the  greatest 
thing  since  sliced  bread,  but  they 
thought  that  readers  might  get  an  al¬ 
together  different  impression  by 
reading  the  column.  Upon  reviewing 
the  June  column,  I’m  afraid  I  must 
agree  with  them. 

First,  the  recommendation  to 
switch  to  Spellbinder  was  strictly  that 
of  the  dealer  and  is  not  an  official  po¬ 
sition  of  Hewlett-Packard.  Further¬ 
more,  Hewlett-Packard  distributes  a 
technical  bulletin  to  its  dealers  with 
information  on  converting  IBM  PC 
WordStar  for  use  with  the  Laserjet 
Printer  (though  the  conversion  doesn’t 
offer  as  many  capabilities  as  the  spe¬ 
cially  patched  version  I  got  from  Mi¬ 
croPro  Technical  Support;  but  see 
page  4),  and  the  HP- 150  and  HP-1 10 
versions  of  WordStar  support  the  La¬ 
serjet  directly.  Finally,  the  slow  speed 
(2—3  pages/minute)  is  the  result  of 
the  strategy  used  by  IBM  PC  Word¬ 
Star  for  microjustification;  the  Laser¬ 
jet  is  capable  of  8  pages/minute  when 
driven  by  other  word  processors. 

As  a  gesture  of  atonement  to  La¬ 
serjet  fans,  this  month’s  Listing  One 
(page  1 17)  is  a  file-printing  program 
in  Lattice  C  that  prints  two  pages 
across  in  “Landscape”  mode  along 
with  filenames,  a  time  and  date 
stamp,  and  page  numbers. 


We  should  note  in  passing  that  the 
new  version  of  Microsoft  Word  offers 
full  support  for  the  Laserjet  and  for 
the  IBM  Enhanced  Graphics  Adap¬ 
tor;  between  the  three,  it  should  be 
possible  to  turn  out  some  excellent- 
looking  documents. 

Microsoft  Assembler 

In  the  June  1985  column,  I  also  inad¬ 
vertently  introduced  some  confusion 
about  the  IBM  Macro  Assembler 
Version  2.  Although  my  IBM  Product 
Center  didn’t  know  about  it,  there  ac¬ 
tually  was  a  reduced  price  update  of¬ 
fer  from  IBM  for  owners  of  the  Mac¬ 
ro  Assembler  Version  1.0;  it  involved 
sending  $75  and  the  cover  sheet  from 
your  original  manual  to  IBM,  and  in 
return  they  would  ship  you  the  new 
assembler  and  documentation.  This 
update  offer  expired  at  the  end  of 
June  1985. 

I  have  since  learned  that  the  refer¬ 
ence  section  on  the  assembler  mne¬ 
monics,  which  I  complained  had  dis¬ 
appeared  from  the  manual,  was 
expanded  into  another  volume  called 
the  Macro  Assembler  Programmer's 
Reference.  This  second  manual  is 
supposed  to  be  delivered  to  you  along 
with  the  operations  manual  and  the 
disk,  but  none  of  the  stores  I’ve  been 
to  are  displaying  it  with  the  Macro 
Assembler — so  if  you  want  it,  you’ll 
have  to  know  to  ask  for  it. 

The  respective  version  numbers 
used  by  IBM  and  Microsoft  are  also 
causing  a  lot  of  confusion.  The  IBM 
Macro  Assembler  Version  2.0  ap¬ 
pears  to  be  essentially  the  same  as  the 
Microsoft  Macro  Assembler  Version 
1.25,  released  over  a  year  ago.  The 
SALUT  utility  is  apparently  IBM’s 
sole  addition  to  the  package.  There 
never  was  a  Microsoft  Macro  Assem¬ 
bler  Version  2.0;  Microsoft  jumped 
directly  to  Macro  Assembler  Version 


3  with  its  latest  release  in  order  to 
make  the  version  number  match  the 
current  release  of  MSDOS/PCDOS. 

David  Rabbers  writes:  “Microsoft 
MASM  Version  3  is  greatly  worth  the 
purchase.  It  not  only  includes  a  better 
assembler  (implements  the  rest  of  the 
80286  op-codes),  which  they  claim 
has  fewer  bugs  (at  least  some  old  ones 
are  fixed  and  I  haven’t  found  the  new 
ones  yet),  but  you  get  two  new  utili¬ 
ties  that  alone  are  worth  the  $75  up¬ 
grade  fee.  First  is  a  version  of  the 
Unix  utility  MAKE.  I  have  never  seen 
MAKE  before,  but  this  one  seems 
simplistic  though  adequate  for  any¬ 
thing  I  can  envision.  The  second  utili¬ 
ty  is  SYMDEB,  a  very  good  symbolic 
debugger.  It  is  an  enhancement  to 
DEBUG  with  a  natural  and  compati¬ 
ble  (well,  pretty  close)  extension  to 
the  DEBUG  command  syntax  .  .  .  .  ” 
SYMDEB  can  handle  symbol  tables 
generated  by  the  Microsoft  high-level 
language  compilers. 

Readers  have  reported  perplexing 
problems  when  trying  to  convert  to 
the  IBM  Macro  Assembler  2.0.  A 
number  of  programs  that  assembled 
and  ran  properly  with  IBM  Macro  As¬ 
sembler  1.0  won’t  assemble  without 
errors — or  if  they  do  assemble  without 
errors,  they  won’t  run.  An  example  of 
this  is  the  statement: 

namel  EQU  (THIS  BYTE)- 
(OFFSET  name2) 

This  does  not  give  an  error  message 
in  itself  (it  assembles,  strangely 
enough,  as  an  equals  sign  followed  by 
absolutely  nothing),  but  it  causes  a 
cascade  of  phase  errors  throughout 
the  rest  of  the  program.  Everything 
works  fine  when  the  statement  is 
changed  to: 

namel  EQU  $-name2 


Dr.  Dobb’s  Journal,  September  1985 


114 

723 


Steve  Itzkowitz  reported  another 
odd  experience  working  with  the  pro¬ 
gram  DSKWATCH,  which  is  avail¬ 
able  on  many  IBM  PC  bulletin 
boards.  It  appears  to  assemble  identi¬ 
cally  with  both  IBM  Macro  Assem¬ 
blers  2.0  and  1.0.  However,  with  Mi¬ 
crosoft  Macro  Assembler  1.27  and 
3.0,  it  will  not  assemble  cleanly,  and 
when  the  errors  are  corrected  by  a  tri¬ 
al  and  error  process,  the  resulting 
program  does  not  run.  Other  readers 
are  invited  to  send  in  their  own  expe¬ 
riences  and  comments. 

Detecting  Intel  Numeric 
Coprocessors 

When  writing  8086  applications  that 
perform  floating-point  arithmetic, 
you  should  test  for  the  presence  of  an 
Intel  8087  and  exploit  it  if  it  is  avail¬ 
able.  The  accepted  strategy  for  this 
test  has  been  to  initialize  the  proces¬ 
sor,  and  then  to  write  the  control 
word  into  a  previously  zeroed  memo¬ 
ry  location  and  examine  the  result: 

fninit 

mov  variable, 0 

fnstcw  variable 

cmp  variable, 03ffh 

jeq  np_present 
jmp  np_absent 

If  an  8087  is  present,  the  control 
word  will  be  stored  into  the  memory 
variable  as  03FFH;  if  it  is  not  present, 
the  contents  of  the  variable  will  not 
change.  It  is  important  not  to  use  any 
wait  instructions  during  this  test:  if 
the  8087  is  not  present,  the  wait  will 
cause  the  8086  simply  to  hang  (this  is 
why  the  fninit  and  fnstcw  variations 
of  the  Intel  mnemonics  are  used 
above,  instead  of  finit  and  fstcw). 

It  turns  out  that  programs  using 
this  strategy  will  fail  to  detect  the 
presence  of  an  80287  in  a  PC/AT  or 
other  80286-based  system.  Although 
the  80287  is  software  compatible 
with  the  8087  in  every  other  way,  it 
initializes  one  of  the  bits  in  the  lower 
byte  of  its  control  word  opposite  to 
the  8087.  So  the  compare  instruction 
in  the  above  code  sequence  should  be 
changed  to 

cmp  byte  ptr  variable+ 1,3 


Outstanding  Software 
Value 

I  have  been  using  a  program  called 
UNIFORM  by  Micro  Solutions  on  my 
Kaypro  IV  for  two  years  to  read  and 
write  disks  in  the  various  incompati¬ 
ble  CP/M  5'/4-inch  disk  formats  and 
have  been  very  happy  with  it.  It  has 
saved  me  untold  dollars  in  download¬ 
ing  fees.  Now  Micro  Solutions  has 
outdone  itself  with  a  release  of  UNI¬ 
FORM  for  the  IBM  PC  that  ought  to 
serve  as  a  standard  of  quality,  docu¬ 
mentation,  and  ease  of  use  for  other 
software  vendors. 

Basically,  UNIFORM  for  the  PC 
consists  of  an  installable  system  driv¬ 
er  (UNIFORM. SYS)  and  a  separate 
control  and  disk  initialization  pro¬ 
gram  (UNIFORM.EXE).  When  the 
driver  is  linked  into  your  operating 
system  by  including  its  name  in  the 
CONFIG.SYS  file  on  the  boot  disk,  it 
causes  your  second  floppy  disk  drive 
(B:)  to  double  as  a  logical  drive  (C:). 
When  the  drive  is  addressed  as  B:,  it 
appears  to  be  a  normal  MSDOS  disk 
drive.  But  when  it  is  addressed  as  C:, 
it  almost  magically  makes  disks  in 
CP/M  formats  appear  as  though  they 


were  MSDOS  disks! 

You  can  do  directories  on  CP/M 
disks,  copy  files  to  and  from  your 
other  MSDOS  format  drives,  erase  or 
rename  files,  and  in  general  complete¬ 
ly  ignore  the  fact  that  the  disk  in  phys¬ 
ical  drive  B:  is  formatted  and  struc¬ 
tured  in  a  way  that  is  totally  alien  to 
the  MSDOS  operating  system.  In  my 
opinion,  this  is  a  real  software  coup. 

The  separate  control  program  is 
menu  driven  and  allows  you  to  set  up 
the  logical  drive  C:  for  some  80  dif¬ 
ferent  soft-sectored  5'/4-inch  CP/M 
formats,  including  the  Epson  QX-10, 
the  Kaypro,  and  the  DEC  VT-180.  In 
addition,  if  your  system  has  a  special 
controller  for  80-track  5'/4-inch 
drives  or  8-inch  drives,  UNIFORM 
can  handle  that  too,  which  gives  you 
an  even  wider  selection  of  disk  for¬ 
mats  to  choose  from.  UNIFORM  can 
also  initialize  disks  in  all  of  these  dif¬ 
ferent  formats.  This  is  a  boon  to  small 
software  houses — it  allows  them  to 
centralize  their  distribution  files  on  a 
large  fixed  disk  hooked  to  an  MSDOS 
machine,  without  dropping  support 
for  their  CP/M  customers.  UNI¬ 
FORM  for  the  IBM  PC  costs  $69.95 
(it’s  easily  worth  ten  times  that)  and 
is  available  from  Micro  Solutions  at 
125  South  Fourth  Street,  DeKalb,  Il¬ 
linois  60115.  Phone  (815)756-3411. 

Breaking  with  MSDOS 

The  MSDOS  Control-Break  handler, 
whose  address  is  stored  in  the  INT 
23H  vector,  is  a  continual  thorn  in  the 
side  of  application  programmers. 
Whenever  MSDOS  detects  a  Control- 
C  in  a  character  stream  (especially  an 
unintentional  or  deliberate  Control-C 
entered  at  the  keyboard  by  the  opera¬ 
tor)  during  an  I/O  operation,  it  trans¬ 
fers  directly  to  this  handler.  The  de¬ 
fault  action  of  this  handler  is  simply  to 
blow  the  unsuspecting  application  out 
of  the  water  . . .  leaving  temporary 
files  on  the  disk,  critical  data  files  un¬ 
closed,  critical  interrupt  vectors  un¬ 
restored,  etc.  This,  of  course,  is  anti¬ 
thetical  to  the  concept  of  a  robust 
application,  which  should  always  be 
able  to  accomplish  a  graceful  exit. 

The  programmer  can  take  two  ap¬ 
proaches  to  the  Control-C  problem. 
The  first  is  to  disable  and/or  avoid 
Control-C  detection  by  a  variety  of 


116 


Dr.  Dobb’s  Journal,  September  1985 


methods,  such  as  using  only  those  1/ 
O  function  calls  that  are  not  Control- 
Break  sensitive;  disabling  Control-C 
detection  during  miscellaneous 
MSDOS  operations  with  the  MSDOS 
function  call  33H;  and  putting  all  of 
the  character  drivers  into  “raw  I/O” 
or  “uncooked”  mode. 

A  more  straightforward  way  to 
beat  the  Control-C  rap  is  simply  to 
replace  the  MSDOS  INT  23H  handler 
with  your  own.  On  the  IBM  PC,  you 
can  also  take  over  the  INT  1BH  vec¬ 
tor,  which  is  called  by  the  ROM  BIOS 
keyboard  driver  whenever  a  Control- 
Break  is  detected.  This  is  easy 
enough  for  applications  written  in  as¬ 
sembly  language  but  a  little  more 
tricky  for  those  using  high-level  lan¬ 
guages.  Note  that  the  INT  23H  han¬ 
dler  is  a  standard  MSDOS  feature, 
and  code  that  captures  this  interrupt 
is  portable  across  all  MSDOS  sys¬ 
tems.  INT  1BH,  however,  is  a  func¬ 
tion  of  the  IBM  ROM  BIOS  and  will 
not  be  valid  on  other  machines  unless 
they  are  a  very  close  IBM  compatible. 

Listing  Two  (page  119)  is  the 
source  code  for  assembly  language 
functions  that  can  be  linked  with  Lat¬ 
tice  C  applications  to  take  control  of 
the  Control-Break  interrupt  han¬ 
dlers.  This  code  should  be  readily 
portable  to  other  C  compilers  and  to 
assembly  language  applications.  The 
function  “capture”  is  called  with  the 
address  of  an  integer  variable  within 
the  C  program;  it  saves  the  address  of 
the  variable,  points  the  INT  1BH  and 
23 H  vectors  to  its  own  interrupt  han¬ 
dler,  and  returns.  When  a  Control-C 
or  Control-Break  is  detected,  the  in¬ 
terrupt  handler  sets  the  integer  vari¬ 
able  within  the  C  program  to  “True” 
and  returns — the  program  can  then 
poll  this  variable  at  its  leisure.  The 
function  “release”  simply  restores 
the  INT  1  BH  and  INT  23H  vectors  to 
their  original  values,  thereby  dis¬ 
abling  the  application’s  interrupt 
handler.  Listing  Three  (page  120)  is 
a  short  C  program  that  illustrates  the 
use  of  these  functions. 

It  is  a  good  idea  to  use  the  MSDOS 
Get  Interrupt  and  Set  Interrupt  func¬ 
tion  calls  to  inspect  and  modify  the 
contents  of  the  interrupt  vectors.  This 
will  keep  your  application  compatible 
with  multitasking  environments  such 


16-Bit 


(Text  begins  on  page  114) 


Listing  One 


LJ.C  —  A  printing  utility  for  the  HP  LaserJet 

This  program  prints  a  series  of  files  on  the  LaserJet 
printer.  The  files  are  printed  in  a  "landscape"  font  at 
17  characters  to  the  inch.  To  take  advantage  of  this 
density,  two  "pages"  of  information  from  the  file  are 
printed  on  each  piece  of  paper  (left  and  right  halves). 

Usage  is:  LJ  filel  file2  file3  ... 

Where  file#  is  a  valid  MS-DOS  filename,  included  on  the 
command  line.  This  program  is  compatible  with  Lattice  C 
on  the  IBM  PC  and  the  HP  Touchscreen  computers. 

Joe  Barnhart  original  version  May  5,  1985 

Ray  Duncan  date  and  time  stamping  May  22,  1985 

Joe  Barnhart  revised  date  stamping  June  6,  1985 


tinclude  <h\stdio.h> 


♦define  MAXLINE  56 
♦define  PAGE  '\f' 

♦define  TAB  8 

typedef  struct  { 

int  ax,  bx,  cx,  dx 
}  REGSET; 

main(argc,  argv) 
int  argc; 
char  *argv  [  ] ; 

I 

int  filenum; 

FILE  *fp,  *prn,  *fopen(); 


/*  maximum  lines  per  page  */ 

/*  for  compilers  without  '\f'  */ 
/*  width  of  one  tab  stop  */ 


si,  di; 


if (  (  prn  =  fopen(  " PRN : " ,  "w"  )  )  ==  NULL  ) 

printf(  "Error  opening  printer  as  file.\n"  )> 
else  { 

/*  initialize  the  LaserJet  for  landscape  printing  */ 
fprintf (  prn,  "\033E\033&llO\033 (sl7H\033&18d6E"  ); 
for(  filenum  =  1;  filenum  <  argc;  filenum++  )  { 
fp  =  fopen(  argv [filenum]  ,"r"  ); 
if  (  fp  ==  NULL  .) 

printf (  "File  %s  doesn't  exist. \n",  argv [filenum]  ); 
else  { 

printf (  "Now  printing  %s\n",  argv [filenum]  ); 
printfilef  fp,  prn,  argv [filenum]  ); 
f closet  fp  ); 


fprintf (  prn,  "\015\033E"  );  /*  clear  LaserJet  */ 


printf lie (fp, prn, filename) 
FILE  *fp,*prn; 
char  "filename; 

I 

int  pagenum  =  1; 


} 


while!  Ifeof(  fp  )  )  { 

fprintf (  prn,  "\033&a0r85m5L\015"  ); 
printpage(  fp,  prn  ); 
iff  Ifeof  (  fp  )  )  ( 

fprintf (  prn,  "\033sa0rl71m9lL"  ); 
^  printpagef  fp,  prn  ); 

stamp(  prn,  filename,  pagenum++  ); 
fputc(  PAGE,  prn  ); 


/*  set  left  half  */ 
f*  print  page  */ 

/*  if  more  . .  */ 

/*  set  right  half  */ 
/*  print  another  •/ 

/*  title  */ 

/*  kick  paper  */ 


printpage (fp,prn) 
FILE  *fp,*prn; 

char  c; 

int  line, col; 


(Continued  on  next  page ) 


Dr.  Dobb’~  Journal,  September  1985 


117 


725 


as  TopView  and  MS  Windows.  The 
interrupt  vector  table  belongs  to  the 
operating  system,  not  to  the  applica¬ 
tion,  and  it’s  not  nice  to  modify  mem¬ 
ory  directly  that  doesn’t  belong  to 
you.  If  the  fabled  “Big  DOS”  ever 
comes  along  that  runs  in  80286-pro- 
tected  mode,  your  application  will 
just  get  aborted  if  you  try  to  twiddle 
the  vector  table  without  using  the 
proper  operating  system  services  pro¬ 


vided  for  that  purpose. 

Although  in  this  little  example  pro¬ 
gram  the  INT  23H  vector  is  restored 
by  the  “release”  function,  MSDOS 
will  restore  this  vector  for  you  auto¬ 
matically  when  your  program  exits. 
However,  because  INT  1BH  is  an 
IBM  PC-specific  interrupt  handler 
and  so  is  not  known  to  MSDOS,  it  is 
absolutely  mandatory,  should  your 
program  modify  this  vector,  that  it  re¬ 


store  the  vector  properly  before  exit¬ 
ing.  Otherwise,  the  vector  will  be  left 
pointing  to  some  random  area  in  the 
next  program  that  runs — and  the  next 
time  you  press  Control- Break,  a  sys¬ 
tem  crash  is  the  best  you  can  hope  for. 

DDJ 

Reader  Ballot 

Vote  for  your  favorite  feature/article. 

Circle  Reader  Service  No.  200. 


16-Bit 


(Listing  Continued,  text  begins  on  page  114) 


Listing  One 


line  «  col  =  0; 

while (  line  <  MAXLINE  ) 

switchf  c  =  fgetc(fp)  )  { 
case  ' \n‘ : 
col  =  0; 
line++; 

fputc('\n' ,prn) ; 
break  > 
case  1 \t 1 : 
do 

fputc  ( '\040 1 ,prn) ; 
while  (  (++col  *  TAB)  1“  0  ); 
break ; 
case  PAGE: 
case  'MlTt 

line  -  MAXLINE ; 
break; 
default : 

fputc (c,prn)  ; 
col++; 
break  > 

) 

1 


/*  newline  found  */ 
/*  zero  column  */ 

/*  adv  line  cnt  */ 


/*  TAB  found  */ 


/*  Page  break  or  */ 

/*  EOF  found  */ 

/*  force  terminate  */ 

/*  no  special  case  •/ 
/*  print  character  */ 


stamp(  pm,  filename,  pagenum  ) 

PILE  *prn; 
char  ‘filename; 

int  pagenum; 

I 

char  datestr[10],  timestr[10); 

fprintf (  prn,  "\033&a5117lM"  );  /*  widen  margins  */ 

fprintf (  prn,  "\015\033&a58R"  );  /*  move  to  row  58  »/ 

fprintf (  prn,  "File:  %-133s",  filename  ); 

fprintf (  prn,  "Page  *-3d",  pagenum); 

tlmestampt  timestr  ); 

datestampt  datestr  ); 

fprintf (  prn,  "  Is  %s",  datestr,  timestr); 

} 


datestamp(  datestr  ) 
char  ‘datestr; 

I 

REGSET  regs; 

int  month,  day,  year; 

regs. ax  =  0x2a00; 
int86(  0x21,  sregs,  &regs  ); 
month  =  (  regs.dx  >>  8  )  &  255; 
day  ”  regs.dx  &  255; 

year  -  regs.cx  -  1900; 

sprintff  datestr,  "%02d/%02d/%02d" ,  month,  day,  year); 

I 

timestampf  timestr  ) 
char  ‘timestr; 

{ 

REGSET  regs; 
int  hours,  mins; 

regs. ax  =  0x2c00; 

int86 (  0x21,  &regs,  Sregs  ); 


118  Dr.  Dobb’s  Journal,  September  1985 

79R 


hours  =  (  regs.cx  >>  8  )  &  255; 
mins  =  regs.cx  S  255; 

sprintff  timestr,  "%02d:%02d",  hours,  mins  ); 

} 

End  Listing  One 

Listing  Two 

BREAK. ASM  Control-C  Handler  for  Lattice  C 


title  Control-Break  handler  for  Lattice  C  programs 
name  break 
include  dos.mac 


Control-Break  Interrupt  Handler  for  Lattice  C  programs 
running  on  IBM  PCs  (and  ROM  BIOS  compatibles) 

Ray  Duncan,  May  1985 

This  module  allows  C  programs  running  on  the  IBM  PC 
to  retain  control  when  the  user  enters  a  Control-Break 
or  Control-C.  This  is  accomplished  by  taking  over  the 
Int  23H  (MS-DOS  Control-Break)  and  Int  1BH  (IBM  PC 
ROM  BIOS  Keyboard  Driver  Control-Break)  interrupt 
vectors.  The  interrupt  handler  sets  an  internal 
flag  (which  must  be  declared  STATIC  INT)  to  TRUE  within 
the  C  program;  the  C  program  can  poll  or  ignore  this 
flag  as  it  wishes. 

The  module  follows  the  Lattice  C  parameter  passing 
conventions,  and  also  relies  on  the  Lattice  file  DOS.MAC 
for  the  definition  of  certain  constants  and  macros. 

The  Int  23H  Control-Break  handler  is  a  function  of  MS-DOS 
and  is  present  on  all  MS-DOS  machines,  however,  the  Int  1BH 
handler  is  a  function  of  the  IBM  PC  ROM  BIOS  and  will  not 
necessarily  be  present  on  other  machines. 


J 

if 

lprog 

args 

equ 

else 

6 

;offset  of  arguments.  Large 

models 

args 

equ 

endif 

4 

;offset  of  arguments.  Small 

models 

cr 

equ 

0dh 

;ASCII  carriage  return 

If 

equ 

0ah 

; ASCII  line  feed 

pseg 

public 

capture, release 

; function  names  for  C 

The  function  CAPTURE  is  called  by  the  C  program  to 
take  over  the  MS-DOS  and  keyboard  driver  Control- 
Break  interrupts  ( 1BH  and  23H)  .  It  is  passed  the 
address  of  a  flag  within  the  C  program  which  is  set 
to  TRUE  whenever  a  Control-Break  or  Control-C 
is  detected.  The  function  is  used  in  the  form; 


capture  proc 
push 
mov 
push 
mov 
mov 
mov 

mov 

int 

mov 

mov 

mov 

int 

mov 

mov 

push 

pop 

mov 

mov 

int 


static  int  flag; 
capture(Sflag) 


;take  over  Control-Break 
;  interrupt  vectors 


near 
bp 

bp,  sp 
ds 

ax, word  ptr  [bp+args] 

cs:flag,ax  ;save  address  of  integer 

cs:flag+2,ds  ;flag  variable  in  C  program 

;pick  up  original  vector  contents 
;for  interrupt  23H  (MS-DOS 
;Control-Break  handler) 


ax ,  3523h 
21h 

cs:int23,bx 
cs : int23+2,es 
ax, 351bh 
21h 

cs :  intlb,bx 
cs: intlb+2,es 
cs 
ds 

dx, offset  ctrlbrk 

ax,02523H  ;for  interrupt  23H 

21h 

( Continued  on  next  page) 


;and  interrupt  1BH 
; (IBM  PC  ROM  BIOS  keyboard  driver 
;Control-Break  interrupt  handler) 

;set  address  of  new  handler 


Dr.  Dobb’s  Journal,  September  1985 


119 

727 


76-Bit 


(Listing  Continued,  text  begins  on  page  114) 


Listing  Two 


mov 

ax, 0251bH 

/and  interrupt  1BH 

int 

21h 

pop 

ds 

/restore  registers  and 

pop 

bp 

/return  to  C  program 

ret 

capture  endp 


» 

;  The  function  RELEASE  is  called  by  the  C  program  to 
;  return  the  MS-DOS  and  keyboard  driver  Control-Break 
;  interrupt  vectors  to  their  original  state.  Int  23h  is 
;  also  automatically  restored  by  MS-DOS  upon  the  termination 
;  of  a  process,  however,  calling  RELEASE  allows  the  C 
;  program  to  restore  the  default  action  of  a  Control-C 
;  without  terminating.  The  function  is  used  in  the  form: 


t 

1 

release  ( ) 

1 

release  proc 

near 

/restore  Control-Break  interrupt 

/vectors  to  their  original  state 

push 

bp 

mov 

bp,  sp 

push 

ds 

mov 

dx, cs : intlb 

/set  interrupt  1BH 

mov 

ds , cs : intlb+2 

/ (MS-DOS  Control-Break 

mov 

ax, 251bh 

/interrupt  handler) 

int 

21h 

mov 

dx, cs : int23 

/set  interrupt  23H 

mov 

ds,cs : int23+2 

/(IBM  PC  ROM  BIOS  keyboard  driver 

mov 

ax, 2523h 

/Control-Break  interrupt  handler) 

int 

21h 

pop 

ds 

/restore  registers  and 

pop 

bp 

/return  to  C  program 

ret 

release  endp 

This  is  the  actual  interrupt  handler  which  is  called  by 
the  ROM  BIOS  keyboard  driver  or  by  MS-DOS  when  a  Control-C 
or  Control-Break  is  detected.  Since  the  interrupt  handler 
may  be  called  asynchronously  by  the  keyboard  driver,  it 
is  severely  restricted  in  what  it  may  do  without  crashing 
the  system  (e.g.  no  calls  on  DOS  allowed).  In  this 
version,  it  simply  sets  a  flag  within  the  C  program  to 
TRUE  to  indicate  that  a  Control-C  or  Control-Break  has 
been  detected;  the  address  of  this  flag  was  passed 


by  the  C  program  during  the 


ctrlbrk 

proc 

far 

push 

bx 

push 

ds 

mov 

bx,cs:flag 

mov 

ds,cs:f lag+2 

mov 

word  ptr  ds: 

pop 

ds 

pop 

iret 

bx 

ctrlbrk 

endp 

flag 

dw 

0,0 

int23 

dw 

0,0 

intlb 

dw 

endps 

end 

0,0 

call  to  the  CAPTURE  function. 

;Control-Break  interrupt  handler 
;save  affected  registers 

;set  flag  within  C  program 
;to  "True* 

,-l 

/restore  registers  and  exit 


/long  address  of  C  program's 
/Control-Break  detected  flag 
/original  contents  of  MS-DOS 
/Control-Break  Interrupt  23H 
/vector 

/original  contents  of  ROM  BIOS 
/keyboard  driver  Control-Break 
/Interrupt  1BH  vector 


TRYBREAK.C  Demo  of  Control-C  Handler 

/• 

Demonstrate  Control-Break  interrupt  handler  for  Lattice  C 
Ray  Duncan,  May  1985 

*/ 


120 

728 


Dr.  Dobb's  Journal,  September  1985 


♦include  <h\stdio.h> 


main(argc,  argv) 
int  argc; 
char  *argv[]» 

{  int  hit  ■=  0> 
int  c  =  0j 

static  int  flag  «  0; 
capture (Sf lag) ; 

put8(*\nTRY  has  CAPTURED  interrupt  vectors\n*)f 

while  (  (CS127)  1=  'Q') 

{  hit  =  kbhit ( ) ; 
if  (flag  1=  0) 

{  puts("\nControl-Break  detected\n") ; 
flag=0; 

} 

if  (hit  1=  0) 

{  c=getch(); 
putch(c) ; 

} 

} 

release ( ) » 

puts("\n\nTRY  has  RELEASEd  Interrupt  vectors\n"); 

)  End  Listings 


Dr.  Dobb’s  Journal,  September  1985 


121 

729 


OF  INTEREST 


by  Alex  Ragen 


IBM  PC 

Computers  like  the  Compaq  used  to 
be  called  “portables,”  but  that  mis¬ 
nomer  soon  gave  way  to  the  more  ac¬ 
curate  “transportable”  and  more  re¬ 
cently  to  “lugable,”  which  is  really 
the  only  way  to  describe  those  30 
pound  monsters.  There  are  now  avail¬ 
able,  however,  several  machines  that 
are  truly  portable. 

Morrow  introduced  its  Pivot  II  at 
Comdex  in  Atlanta.  The  original  Piv¬ 
ot  had  a  16-line  display,  but  the  new 
model  has  a  full  25-line  display. 
There  are  three  versions,  priced  be¬ 
tween  $  1 995  and  $3795  depending  on 
various  optional  features. 

The  big  problem  with  portables  of 
this  type  is  the  screen.  Data  General 
has  changed  screens  twice  on  its  im¬ 
pressive  (but  expensive)  DG/1  in  re¬ 
sponse  to  complaints  about  readabili¬ 
ty.  The  Pivot  II  uses  a  technology 
Morrow  calls  LumiCon,  which  com¬ 
bines  the  high  quality  of  electrolumi¬ 
nescent  displays  with  the  low  cost  and 
power  requirements  of  liquid  crystal 
displays  (LCDs).  Also  a  proprietary 
nonglare  material  makes  the  screen 
less  susceptible  to  glare  than  the  stan¬ 
dard  CRT  display,  Morrow  claims. 

The  CPU  is  the  80C88,  a  CMOS 
version  of  the  8088,  and  the  minimum 
memory  configuration  is  256K.  The 
computer’s  dimensions  are  1 3  X  6  X 
9.5  inches,  and  it  weighs  only  13 
pounds  with  two  5'/4-inch  drives.  MS- 
DOS  2.1 1  and  New  Word  (a  word  pro¬ 
cessing  program  strikingly  similar  to 
WordStar)  are  included.  The  power 
pack  and  internal  rechargeable  Nicad 
battery  provide  more  than  three  hours 
of  continuous  operation.  There  is  also 
a  32K  ROM,  which  includes  a  number 
of  utilities  like  a  calculator,  diary, 
phone  directory,  and  autodialer.  For 
additional  information,  contact  Mor¬ 
row  at  600  McCormick,  San  Leandro, 


California  94577  (415)  430-1970. 

Reader  Service  Number  101. 

Kaypro  too  has  introduced  an  1 1  - 
pound  portable,  the  Kaypro  2000, 
whose  price  also  begins  at  $1995.  It 
features  full  IBM  PC  compatibility, 
256K  of  RAM,  a  25-line  screen,  a  sin¬ 
gle  3'^-inch  720K  disk  drive  (in  the 
minimum  configuration),  and  Word¬ 
Star  and  CalcStar  bundled  in.  Its 
built-in  battery  pack  provides  for  four 
hours  of  continuous  operation.  The 
adjustable  LCD  behaves  like  an  IBM 
color  card  with  a  monochrome  adapt¬ 
er.  With  the  addition  of  an  optional 
base  unit  (which  contains  a  5'/i-inch 
drive),  the  machine  can  accept  stan¬ 
dard  IBM  expansion  cards.  Kaypro 
has  also  announced  the  286i  model  A, 
a  smaller  version  of  its  IBM  PC/AT 
compatible  286i,  priced  at  $2995  and 
including  512K  of  RAM  and  GW-BA- 
SIC.  For  further  information,  contact 
Kaypro  at  P.O.  Box  N,  Del  Mar,  Cali¬ 
fornia  92014  (619)  481-4300.  Reader 

Service  Number  103. 

Zenith’s  new  14.5-pound  Z- 171 
portable  PC  offers  full  IBM  PC  com¬ 
patibility,  256K  of  RAM  (expandable 
to  640K),  a  25-line  flat  panel  backlit 
LCD,  and  two  disk  drives.  The  price  is 
$2699.  The  company  has  also  intro¬ 
duced  the  Z-200  Advanced  PC,  a 
PC/AT  compatible  model  with  some 
extra  features,  which  comes  in  sever¬ 
al  configurations  priced  between 
$3999  for  the  single  floppy  version 
and  $5599  for  the  20  MB  hard  disk 
version.  Other  new  products  include 
the  Z-138,  a  24-pound  “transport¬ 
able”  with  a  7-inch  amber  display 
and  detachable  keyboard,  which 
starts  at  $2099,  and  two  additions  to 
the  Z-150  PC  family:  the  Z-148  and 
Z-158.  Contact  Zenith  Data  Systems 
at  1000  Milwaukee  Avenue,  Glen¬ 
view,  Illinois  60025  (312)  391-8949. 

Reader  Service  Number  105. 


You  don’t  have  to  trade  in  your  ag¬ 
ing  IBM  PC/XT  for  an  AT.  Seattle 
Telecom  &  Data  has  announced  the 
PC  286-MTM,  a  16-bit  iAPX286- 
based  accelerator  board  with  2.1  MB 
of  bank-switched  (paged)  memory 
for  the  XT.  It  can  support  nine  con¬ 
soles  and  is  supported  by  Xenix,  Pick, 
and  Concurrent  CP/M.  The  unit  can 
also  support  a  RAM  disk  driver,  al¬ 
lowing  the  user  to  access  1.5  Mb  of 
memory  in  RAM  disk.  The  price  is 
$1995.  Contact  the  company  at  2637 
151st  Place  NE,  Redmond,  Wash¬ 
ington  98052  (206)  883-8440.  Reader 

Service  Number  107. 

Pfaster  286,  a  80286-based  add-on 
board  that  upgrades  IBM  PCs  and 
PC/XTs  to  process  data  faster  than 
an  IBM  PC/AT  without  losing  the 
functionality  of  the  native  8088,  has 
been  introduced  by  Phoenix  Comput¬ 
er  Products.  The  board  is  fully  PC 
compatible,  runs  DOS  version  2.0  and 
higher,  and  uses  the  native  8088  as  a 
coprocessor  to  manage  input/output. 
The  price  is  $2395,  with  the  80287 
available  for  an  additional  $350. 
Contact  the  vendor  at  1420  Provi¬ 
dence  Highway,  Suite  1 1 5,  Norwood, 
Massachusetts  02062  (800)344-7200 
(in  Massachusetts  (617)  762-5030). 

Reader  Service  Number  109. 

A  dedicated  array  processor, 
which  plugs  into  a  single  expansion 
slot  on  the  IBM  PC,  PC/XT,  and  PC/ 
AT  and  performs  Fast  Fourier  Trans¬ 
forms  100  to  10,000  times  faster  than 
software-computed  FFTs,  has  been 
introduced  by  the  Ariel  Corporation. 
The  price  is  $1850  (quantity  one). 
Two  or  more  units  can  be  installed  in 
one  host  and  will  work  in  parallel, 
further  increasing  throughput.  Con¬ 
tact  the  vendor  at  600  West  1 1 6th 
Street,  Suite  84,  New  York,  New 
York  10027  (212)  662-7324.  Reader 

Service  Number  111. 


122 

730 


Dr.  Dobb’s  Journal.  September  1985 


Hallock  Systems,  a  supplier  of  co¬ 
processors  for  Z80  systems,  has  intro¬ 
duced  the  Pro68,  a  Motorola  68000- 
based  coprocessor  board  for  the  IBM 
PC.  The  board  can  be  configured 
with  between  256K  and  3072K  of 
RAM.  The  CPU  runs  at  10  MHz  with 
no  wait  states,  and  the  company  says 
it  runs  eight  times  faster  than  the 
IBM  PC  and  three  times  faster  than 
the  IBM  PC/AT.  Both  CPM68K  and 
OS9/68000  (a  Unix-like  operating 
system)  are  available,  along  with  an 
editor,  assembler,  linker,  debugger, 
and  full  K&R  standard  C  compiler. 
Third  party  software  includes  all  the 
popular  high-level  languages.  For 
further  information,  contact  the  ven¬ 
dor  at  267  North  Main  Street,  Herki¬ 
mer,  New  York  13350  (315)  866- 
7 1  25.  Reader  Service  Number  113. 

Micro/Sys  has  introduced  the 
BUS/BRIDGE  family,  which  consists 
of  14  low-cost  development  packages 
that  use  the  IBM  PC  as  the  host  envi¬ 
ronment  and  provide  for  six  target 
environments,  including  MULTIBUS 
(8085  and  8086),  VMEbus  (68000), 
STD  BUS  (Z80  and  8088),  and  MUL¬ 
TIBUS  II  (80286).  Three  software 
systems  are  supported:  the  target 
processor’s  assembly  language,  some 
of  the  IBM  PC’s  popular  compilers, 
and  a  special  customized  assembler 
and  compiler.  The  14  development 
packages  consist  of  various  combina¬ 
tions  of  the  target  hardware  environ¬ 
ments  and  software  systems.  Prices 
run  from  $1295  to  $5595.  Contact 
the  vendor  at  1011  Grand  Central 
Avenue,  Glendale,  California  91201. 

Reader  Service  Number  115. 

As  everyone  knows,  APL  on  the 
IBM  PC  requires  an  8087  or  80287, 
which  (though  they  have  come  down 
in  price  somewhat  in  the  last  year) 
are  still  not  exactly  cheap.  Also,  some 
machines,  like  the  PCjr,  don’t  sup¬ 
port  the  8087,  so  until  now  APL  has 
simply  not  been  available  for  them. 
Enter  the  8087  Eliminator,  a  soft¬ 
ware  package  from  Fort’s  Software 
that  functionally  replaces  the  math 
coprocessor  but  costs  only  $49  ($75 
for  the  PC/AT  version).  Contact  the 
vendor  at  P.O.  Box  396,  Manhattan, 
Kansas  66502  (913)  537-2897.  Read- 

er  Service  Number  117. 

If  the  PC/AT’s  20  Mb  disk  capaci¬ 


ty  leaves  you  unimpressed,  perhaps 
what  you  need  is  Reference  Technol¬ 
ogy’s  CLASIX  DataDrive  Series  500, 
a  laser  disk  drive  with  a  capacity  of 
550  Mb,  priced  at  $1535.  The  bad 
news  is  that  every  one  of  those  bytes 
is  read  only.  Just  the  thing  for  the 
Britannica  or  the  unabridged  Oxford 
English  Dictionary  but  not  for  every¬ 
one,  despite  the  very  reasonable 
price.  Contact  the  vendor  at  1832 
North  55th  Street,  Boulder,  Colora¬ 
do  80301  (303)  449-4157.  Reader  Ser- 

vice  Number  121. 

If  550  Mb  is  more  than  you  need, 
Optotech  has  a  400  Mb  WORM 
(Write  Once  Read  Many  times)  opti¬ 
cal  disk  cartridge  available.  Contact 
the  vendor  at  770  Wooten  Road, 
Suite  109,  Colorado  Springs,  Colora¬ 
do  80915  (303)  570-7500.  Reader  Ser- 

vice  Number  1 23. 

What’s  a  $45  billion  company  do¬ 
ing  in  the  personal  computer  market? 
Whatever  it  wants  to,  according  to 
some.  What  is  that  company  hoping 
to  achieve  with  TopView?  Maybe  all 
those  royalty  dollars  flowing  out  to 
Bellevue  every  quarter  are  distressing 
a  few  souls  in  Armonk  and  Boca  Ra¬ 
ton,  or  maybe  something  more  sinis¬ 
ter  is  afoot.  Who  knows?  But  soft¬ 
ware  developers  are  starting  to  bring 
out  products  with  TopView  in 
mind  although  at  this  point  Top- 
View  is  mostly  unfulfilled  promise. 
Lattice  has  introduced  the  Lattice 
TopView  Toolbasket  for  program¬ 
mers  writing  applications  to  take  ad¬ 
vantage  of  TopView’s  multitasking 
window  environment.  There  are  70  C 
functions  to  control  window,  cursor, 
and  pointer  operations,  as  well  as 
printer  control,  cut  and  paste,  and  de¬ 
bugging  functions.  Memory  require¬ 
ments  are  256K,  but  512K  are  rec¬ 
ommended.  The  price  is  $250  and  the 
source  code  is  available  for  $250 
more.  For  further  information,  con¬ 
tact  Lattice  at  RO.  Box  3072,  Glen 
Ellyn,  Ilinois  60138  (312)  858-7950. 

Reader  Service  Number  125. 

Matrix  Software  Technology  has 
introduced  Synergy,  a  1 2K  operating 
system  with  Macintosh-like  desktop 
and  windows,  which  runs  under  ei¬ 
ther  DOS  or  TopView  and  allows  up 
to  six  programs  to  run  concurrently 
in  windows  without  modification. 


Synergy  can  also  run  alone,  and  be¬ 
cause  it  requires  only  12K  of  RAM, 
it  will  run  on  any  PC  and  outperform 
TopView,  according  to  the  vendor, 
who  can  be  contacted  at  50  Milk 
Street,  Boston,  Massachusetts 
02 1 09.  Reader  Service  Number  127. 

Taking  another  approach.  Quar¬ 
terdeck  Office  Systems  has  intro¬ 
duced  DESQVIEW,  a  new  version  of 
its  floppy-based  DESQ  memory-resi¬ 
dent  multitasking  software  integra¬ 
tor.  Priced  at  $99.95,  it  supports  both 
keyboard  and  mouse.  Contact  the 
vendor  at  1918  Main  Street,  Santa 
Monica,  California  90405  (213)  392- 

985 1 .  Reader  Service  Number  129. 

The  New  York  Amateur  Comput¬ 
er  Club  has  published  a  catalog  of  its 
PC/Blue  Library:  over  560  public 
domain  programs  for  the  IBM  PC  and 
compatibles.  The  catalog  is  priced  at 
$5  ($7  for  overseas  airmail)  including 
postage.  The  diskettes  are  $7  post¬ 
paid  (North  America)  and  $10  over¬ 
seas  air.  Membership  dues  are  $15 
per  year.  Contact  the  club  at  P.O. 
Box  106,  Church  Street  Station,  New 
York,  New  York  10008. 

Flagstaff  Engineering  is  now  dis¬ 
tributing  the  Diskette  connec¬ 
tion  and  the  WORD  CONNEC¬ 
TION.  The  DISKETTE  CONNEC¬ 
TION  is  an  8-inch  stand-alone  drive 
system  with  controller  that  allows  a 
mini  or  mainframe  to  read  from  or 
write  to  a  PC  unit.  The  WORD  CON¬ 
NECTION  is  a  set  of  software  pro¬ 
grams  that  provide  the  ability  to  read 
and  write  text  documents  from  most 
word  processing  systems  using  an  IBM 
PC  equipped  with  the  DISKETTE 
CONNECTION.  All  of  the  WORD 
CONNECTION  programs  transfer 
documents  to  a  PCDOS  file  using 
IBM’s  Revisable  Form  Text  standards 
for  document  architecture  (DCA). 
For  more  information  write  Flagstaff 
Engineering,  Box  1970  Flagstaff,  AZ 
86002,  (602)  774-5188.  Reader  Service 

Number  135. 

Copy-protected  software  is  a  royal 
pain  in  the  ASCII,  but  with  the  help 
of  TranSec  Systems’  UN  lock,  you 
can  banish  the  pain  forever,  accord¬ 
ing  to  the  vendor.  The  program  “re¬ 
moves  copy  protection”  and  provides 
standard  nonprotected  DOS  copies, 
which  enable  the  user  conveniently  to 


Dr.  Dobb’s  Journal.  September  1985 


123 

731 


run  the  software  from  a  hard  disk  or 
RAM  disk.  The  original  disk  need  no 
longer  be  present  in  drive  A  when  the 
program  is  run.  The  price  is  $49.95. 
For  further  information,  contact  the 
vendor  at  701  East  Plantation  Circle, 
Plantation,  Florida  33324  (305)  474- 

7548.  Reader  Service  Number  137. 

KDS  Corporation  has  announced  its 
Knowledge  Delivery  System,  an  arti¬ 
ficial  intelligence  program  that  pro¬ 
duces  expert  systems  of  up  to  16,000 
production  rules  and  256,000  facts 
from  up  to  4,096  case  histories.  The 
fully  menu-driven  system  communi¬ 
cates  with  the  user  in  English.  The 
cost  is  $495-$795,  depending  on  op¬ 
tions.  Contact  KDS  at  934  Hunter 
Road,  Wilmette,  Illinois  60091  (312) 

25 1  -262 1 .  Reader  Service  Number  139. 

CP/M 

For  some  time,  DDJ  readers  have 
been  writing  to  express  concern  about 
Digital  Research’s  apparently  wan¬ 
ing  level  of  support  for  CP/M  80.  Co¬ 
lonial  Data  Systems  is  now  making 
available  CP/M  2.2  and  CP/M  Plus  in 
the  same  unconfigured  packages  that 
DR1  supplied  directly  in  the  past.  The 
prices  are  $75  for  CP/ M  2.2  and  $275 
for  CP/M  Plus.  Contact  the  vendor  at 
80  Picket  District  Road,  New  Mil¬ 
ford,  Connecticut  06776  (203)  355- 

3178.  Reader  Service  Number  163. 

A  hand-sized  computer  from  Brit¬ 
ain  with  CP/M  compatibility  and  up 
to  256K  of  RAM  has  been  announced 
by  AMS  Numerics.  The  LCD  screen 
can  display  two  or  four  lines  of  16 
characters,  and  the  keyboard  is  avail¬ 
able  with  either  20  or  42  keys.  Pro¬ 
grams  and  data  can  be  downloaded  to 
the  unit  via  its  built-in  RS232  port  at 
rates  up  to  9600  baud.  A  parallel  port 
allows  interfacing  with  other  electron¬ 
ic  units,  and  there  is  also  a  separate 
bar  code  reader  port.  The  unit  weighs 
29  ounces  and  operates  in  tempera¬ 
tures  from  0-40°C.  Power  is  sup¬ 
plied  by  a  rechargeable,  removeable 
battery  pack,  which  can  power  the 
unit  for  up  to  three  months  at  a  time. 
Contact  the  vendor  at  Wallstreams 
Lane,  Worsthorne  Village,  near  Bum- 
ley  in  Lancashire,  BB10  3PP,  En¬ 
gland.  Reader  Service  Number  165. 

Spectravideo  has  introduced  an  1 1  - 
pound,  battery  rechargeable,  CP/M 
2.2-based  lap-sized  portable  computer 


with  a  25-line  display,  a  built-in  3.5- 
inch  drive  (360K  formatted),  and  six 
pieces  of  bundled  software  (Word¬ 
Star,  ReportStar,  CalcStar,  Mail- 
Merge,  DataStar,  and  Scheduler 
Plus)  for  a  list  price  of  under  $1000. 
The  product  is  scheduled  for  delivery 
in  September  1985.  The  company  also 
will  offer  three  “transportable”  CP/M 
computers  and  two  MSDOS  desktop 
machines.  Contact  the  vendor  at  3300 
Seldon  Court  #10,  Fremont,  Califor¬ 
nia  94539  (415)490-4300.  Reader  S«r- 

vice  Number  167. 

The  Private  Line  is  a  software- 
based  DES  system  that  can  process 
any  CP/M  file  at  the  rate  of  10K  per 
minute  with  a  4  MHz  cpu.  It  will  also 
overwrite  a  file  with  binary  zeros  and 
purge  it  from  the  directory,  as  well  as 

display  a  file  in  hex  format.  Contact 
the  vendor,  Everett  Enterprises,  at 
P.O.  Box  193,  Bath,  NC  27808  (919) 

923-5621.  Reader  Service  Number  169. 

Note 

In  the  July  “Of  Interest”  column  we 
stated  that  the  EL286-88  Processor 
Converter  from  Edsun  Labs  allows 
you  to  replace  directly  an  8088  micro¬ 
processor  with  an  80286.  A  more  ac¬ 
curate  description  would  be  to  say 

that  the  EL286-88  is  a  VLSI  compo¬ 
nent  that  greatly  simplifies  the  hard¬ 
ware  design  of  an  80286  upgrade 
board  by  converting  8-bit  8088  bus 
signals  to  those  of  the  16-bit  80286 
while  accomodating  asynchronous 
clock  rates. 

DO( 


125 


Dr.  Dobb’s  Journal,  September  1985 


Dr.  Dobb's  Journal 

Editorial 

Editor-in-Chief  Michael  Swaine 
Managing  Editor  Frank  DeRose 
Technical  Editor  A  lex  Ragen 
Editorial  Assistant  Sara  Noah 

Contributing  Editors  Robert  Blum, 

Dave  Cortesi, 

Ray  Duncan, 

Allen  Holub 
Copy  Editor  Vince  Leone 
Typesetter  Jean  Aring 
Production 

Production  Manager  Bob  Wynne 

Art  Director  Shelley  Rae  Doeden 

Production  Assistant  Alida  Hinton 
Cover  Artist  Paul  Ambrose 

Circulation 

Sub.  Fulfillment  Mgr.  Stephanie  Barber 
Subscription  Mgr.  Maureen  Kaminski 
Book  Marketing  Mgr.  Jane  Sharninghouse 
Single  Copy  Sales  Mgr.  Stephanie  Barber 
Administration 
Finance  Manager  Sandra  Dunie 
Business  Manager  Betty  Trickett 
Accounts  Payable  Supv.  Mayda  Lopez -Quintana 
Accounts  Payable  Asst.  Denise  Giannini 
Billing  Coordinator  Laura  Di  Lazzaro 
Accountant  Marilyn  Brown 
Adm.  Coordinator  Kobi  Morgan 
Advertising 
Advertising  Director 
Shawn  Horst  (415)424-0600 
Systems  Manager  Ron  Copeland 
Administrative  Manager  Anna  Kittleson 
Advertising  Sales 

Walter  Andrzejewski  (617)  567-8361 
Lisa  Boudreau  (415)424-0600 
Beth  Dudas  (714)  643-9439 
Michele  Beaty  (31 7)  875-0557 


M&T  Publishing,  Inc. 

Chairman  of  the  Board  Olmar  Weber 
Director  C.  F.  von  Quad l 
President  Laird  Foshay 


Dr.  Dobb’s  Journal  (USPS  307690)  is  published 
monthly  by  M&T  Publishing,  Inc.,  2464  Embarcadero 
Way,  Palo  Alto,  CA  94303,  (415)  424-0600.  Second 
class  postage  paid  at  Palo  Alto  and  at  additional  entry 
points. 

Address  correction  requested.  Postmaster:  Send  Form 
3579  to  Dr.  Dobb's  Journal.  P.O.  Box  27809,  San 
Diego,  C A  92 1 28.  ISSN  0278-6508 

Subscription  Rates:  525  per  year  within  the  United 
States.  546  for  airmail  to  Canada,  562  for  airmail  to 
other  countries.  Foreign  subscriptions  must  be  pre-paid 
in  U.S.  Dollars,  drawn  on  a  US.  Bank.  For  subscription 
problems,  call:  outside  of  CA  800-321-3333;  within  CA 
619-485-9623  or  566-6947. 

Foreign  Distributor:  Worldwide  Media  Service,  Inc., 
386  Park  Ave.  South,  New  York,  NY  10016,  (212) 
686- 1 520  TELEX:  620430  ( WUI ) 

Entire  contents  copyright  ®  1985  by  M&T 
Publishing.  Inc.  unless  otherwise  noted  on 
specific  articles.  All  rights  reserved. 


People's  Computer  Company 

Dr.  Dobb’s  Journal  is  published  by  M&T  Publishing, 
Inc.  under  license  from  People’s  Computer  Company, 
2682  Bishop  Dr,  Suite  107,  San  Ramon,  CA  94583,  a 
non-profit,  educational  corporation. 


October  1985 
Volume  10,  Issue  10 


CONTENTS 


In  This  Issue 

Once  again  we  present  our  annual  Forth  issue.  Perhaps  the  most  significant 
development  in  Forth  over  the  past  year  has  been  the  direct  implementation 
of  Forth  engines  in  silicon  logic.  One  such  silicon  Forth  engine  is  the  Novix 
NC4000  chip.  In  our  feature  article  Leo  Brodie  describes  some  of  the 
revolutionary  features  of  this  chip  and  the  benefits  that  it  offers  to 
programmers. 

This  month  we  also  provide  a  solution  to  the  problems  of  developing 
tight,  rapidly  executing  code  for  PROM-based  applications  written  in 
Forth.  H.  Robinson,  P.D.  Morse  and  S.A.  Bowhill  of  the  Central  Illinois 
Chapter  of  the  Forth  Interest  Group  have  designed  a  post-compiler  that 
selects  from  compiled,  threaded  Forth  code  the  segments  necessary  for  the 
execution  of  the  PROM-based  application  and  redirects  to  the  RAM  of  the 
target  machine  any  code  that  must  be  resident  there. 

Our  review  section  this  month  also  focuses  on  Forth,  in  particular,  on 
Forth  and  the  Macintosh.  First  of  all,  Bruce  Horn  examines  Neon,  an  object- 
oriented  programming  environment  that  is  “a  cross  between  Smalltalk  and 
Forth.”  Then,  Alan  Clute  takes  a  look  at  MacFORTH,  a  development  pack¬ 
age  designed  to  ease  the  writing  of  programs  in  the  Mac  environment. 

Our  columnists  have  added  their  usual  monthly  goodies.  Dave  Cortesi 
serves  up  some  further  tidbits  from  his  study  of  MSDOS,  showing  how  to 
handle  ambiguous  pathnames  with  DOS  functions  4Eh  and  4Fh,  how  to 
define  MSDOS  diskette  formats  and  how  to  control  Control-Z. 

Allen  Holub  had  hoped  to  code  a  version  of  the  original  TgX  hyphen¬ 
ation  algorithm  in  C  for  last  month’s  issue,  in  which  we  reviewed  two 
implementations  of  TgX  for  the  IBM  PC.  Limitations  on  his  time  prevented 
him  from  realizing  his  intentions  before  deadline,  but  he  did  manage  to 
finish  off  the  project  for  this  month’s  C  Chest.  So,  if  you  want  to  find  out 
how  to  hyphenate  supercalifragilisticexpialidocious  . .  . 

Bob  Blum  seems  to  be  gleefully  drowning  in  the  deluge  of  new  CP/M 
products  that  developers  are  sending  to  him.  This  month  he  finishes  his 
discussion  of  the  AMPRO  Little  Board,  reviews  SLRMAC,  the  8080  assem¬ 
bler  from  SLR  Systems,  and  offers  some  enticing  glimpses  of  Date  Stamper 
from  Plu*Perfect  Systems,  DSD80,  the  debugger  from  Soft  Advances  and 
the  latest  single  board  computer  from  Micro  Mint. 

Finally,  we  continue  this  month  in  Mac  Toolbox  the  program  listings  for 
the  MacSCSI  hard-disk  interface  presented  by  John  Bass  last  month.  Look 
for  more  on  the  MacSCSI  next  month. 


4 

7-3/1 


Dr.  Dobb's  Journal,  October  1985 


Dr.  Dobb’s  Journal 


ARTICLES 


A  Threaded-Code  Microprocessor 

Bursts  Forth 

by  Leo  Brodie 

42 

A  discussion  of  the  hardware  features  of  the  Novix 
NC4000  Forth  chip  and  of  the  implications  for  software 
performance.  Reader  Ballot  No.  192. 

Design  of  a  Forth  Target  Compiler 

by  H.  Robinson,  P.D.  Morse  and  S.  A.  Bowhill 

52 

Members  of  CICFIG  unveil  a  recompiler  that  generates 
target  code  for  PROM  applications  from  a  host  Forth 
system.  Reader  Ballot  No.  1 93. 

COLUMNS 

Dr.  Dobb's  Clinic 

by  D.E.  Cortesi 

10 

MSDOS  and  PCDOS  programming  notes:  memory  access 
across  “chapter”  boundaries;  Control-Z;  disk  formats; 
ambiguous  pathnames  in  applications  programs.  Reader 
Ballot  No.  190. 

C  Chest 

by  Allen  Holub 

20 

A  bug  in  Lattice  C  Version  2. 1 5;  fixes  to  Is,  the  directory 
utility;  the  TgX  hyphenation  algorithm.  Reader  Ballot  No. 
191. 

Mac  Toolbox 

by  John  Bass 

106 

A  continuation  of  the  listings  for  the  MacSCSI  hard-disk 
interface.  Reader  Ballot  No.  1 94. 

CP/M  Exchange 

by  Bob  Blum 

114 

The  AMPRO  Little  Board  continued;  SLR  Systems’ 
SLRMAC;  Plu*Perfect  Systems’  Date  Stamper;  Soft 
Advances’  DSD80;  a  new  single  board  computer  from 
Micro  Mint.  Reader  Ballot  No.  195. 

DEPARTMENTS 

Editorial 

6 

Letters 

8 

Review 

96 

Kriya’s  Neon,  Creative  Solutions’  MacFORTH 

DDJ  Classifieds 

125 

Advertiser  Index 

128 

Dr.  Dobb’s  Journal.  October  1 985 


5 

735 


Charles  Moore  first  created  Forth  to  control  astronomical  instruments; 
lately  his  attention  has  been  focused  on  the  design  and  implementation 
of  a  microprocessor  that  uses  Forth  as  its  machine  language.  The  No- 
vix  chip  represents  only  one  recent  effort  to  put  Forth  on  silicon.  Rockwell  fit  a 
good  chunk  of  Forth  into  its  R65F1 1  chip,  along  with  the  6502  instruction  set. 
The  British  Metaforth  MF16LP  single-board  computer,  which  is  implement¬ 
ed  with  custom  bipolar  circuits,  uses  Forth  as  its  machine  language.  Bipolar 
technology  is  also  incorporated  in  the  H4TH/X  Forth  engines  from  Har- 
tronix,  which  provide  a  real-time  system  with  up  to  4,000  primitives  in 
firmware. 

None  of  these  products  (with  the  exception,  perhaps,  of  the  H4TF1/20)  is  of 
much  practical  significance  to  the  Forth  programmer  developing  commercial 
software  on  and  for  personal  computers.  They  are  more  likely  to  interest 
programmers  working  in  areas  similar  to  those  worked  on  by  the  first  Forth 
programmer:  device  control,  process  control,  robotics.  Forth  on  a  chip  can 
only  help  speed  developments  in  these  fields. 

There  are  grounds  for  wondering  whether  the  programmer  who  uses  Forth 
to  develop  personal  computer  applications  is  in  a  dwindling  minority.  The 
virtues  of  Forth  in  such  applications  are  similar  to  those  of  C.  Are  Forth’s 
additional  virtues  the  kind  that  will  secure  it  a  permanent  place  in  persona) 
computer  software  development?  Perhaps  they  are,  but  doubts  in  that  area 
may  have  been  one  of  the  reasons  why  the  language  Neon  was  developed  as  an 
extension  of  Forth.  Even  so,  extending  Forth  to  the  point  where  it  becomes  a 
different  language  is  not  a  new  idea. 

Forth  seems  in  excellent  health,  but  hardware  Forths  and  extended  Forths 
lead  one  to  wonder  in  just  what  area  Forth  will  see  its  greatest  use  in  the  next 
year,  and  even  whether  the  language  will  soon  cease  to  be  regarded  as  a  high- 
level  language.  One  wonders:  Where  is  Forth  bound? 

Where  DDJ  is  bound  is,  we  hope,  evident  from  its  ten-year  trajectory:  on  a 
path  of  providing  more  software  tools  for  advanced  programmers.  We  can  be 
more  specific.  Here  is  our  1986  editorial  calendar. 


January:  68000  Programming.  Article  deadline:  past. 

February:  Pascal,  Modula-2,  Ada.  Article  deadline:  now. 

March:  The  Future  of  Programming:  algorithms  for  parallel  processing.  Arti¬ 
cle  deadline:  12/1/85. 

April:  Artificial  Intelligence.  Article  deadline:  1  /I /86. 

May:  At  the  Human  Interface:  designing  usable  software.  Article  deadline:  2/ 

1/86. 

June:  Communications.  Article  deadline:  3/1/86. 

July:  Forth.  Article  deadline:  4/1  /86. 

August:  C.  Article  deadline:  5/1/86. 

September:  Algorithms.  Article  deadline:  6/ 1  /86. 

October:  80286/80386  Programming.  Article  deadline:  7/1  /86. 

November:  Graphics.  Article  deadline:  8/1/86. 

December:  Operating  Systems.  Article  deadline:  9/1  / 86. 


Plus,  of  course,  our  regular  columns  on 
and  Macintosh  software  development  tools. 


C,  Unix,  16-bit  software,  CP/M 


'^~jfhcLco\  ^UrcuHt 


Michael  Swaine 


Dr.  Dobb's  Journal,  October  1 985 


LETTERS 


Hardware 

Dear  DDJ , 

I  vote  for  more  and  continuing  hard¬ 
ware  coverage,  construction  and  oth¬ 
erwise.  Every  magazine  except  BYTE 
seems  to  be  doing  less  and  less  with  it. 
Yet,  over  time,  Steve  Ciarcia’s  hard¬ 
ware  features  are  consistently  among 
the  most  popular  BYTE  publishes. 

The  print  spooler  [in  the  July,  1985 
issue  of  DDJ ]  can  be  simplified  con¬ 
siderably  if  6264  or  similar  8K  X  8 
static  RAM  is  used  instead  of  4164s. 
Seven  6264s  would  give  56K  of  RAM, 
leaving  the  top  8K  of  the  64K  address 
space  for  ROM  and  the  6821  PIA.  A 
74LS138  3/»  decoder  could  break  the 
address  space  into  8K  chunks  and 
supply  the  enable  signals  for  individ¬ 
ual  devices.  Additional  decoding 
would  be  required  only  in  the  top  8K, 
containing  EPROM  and  the  PIA. 
That  could  be  done  with  a  single 
NOR  gate.  Two  74LS541  buffer 
packages  could  drive  A0-A1 2,  R/W, 
and  the  enable  (phase  2)  clock.  This 
buffering  is  desirable  due  to  the  load¬ 
ing  imposed  by  the  additional  3-state 
outputs  that  use  of  7  6264s  would  in¬ 
volve.  Software  could  be  much  sim¬ 
pler  because  of  elimination  of  the  re¬ 
fresh  routines.  System  clocks  would 
also  be  simplified.  Total  cost  of  con¬ 
struction  wouldn’t  be  much  more 
than  the  spooler  presented. 

The  S-100  real-time  clock  article 
was  interesting.  However,  there  were 
a  few  errors  and  omissions  that  de¬ 
serve  comment. 

The  use  of  the  MM58167A’s  RDY 
line  to  induce  WAIT  states  depends 
on  rather  critical  timing  relationships 
among  various  events.  These  timing 
considerations  can  be  greatly  reduced 
if  an  interface  adapter  such  as  the 
8255  is  used  to  control  the  clock  chip. 
The  only  critical  timing  relationships 
that  exist  then  are  those  involving  the 
8255’s  interface  with  the  bus.  Since  it 


is  much  faster  than  the  58167  and  its 
timing  requirements  more  closely 
matched  to  system  timing,  fewer 
problems  occur. 

The  POWER  DOWN  input  needs  to 
go  low  1  microsecond  before  the 
power  off  only  if  the  standby  inter¬ 
rupt  is  connected  to  something.  If  this 
particular  feature  of  the  MM58167A 
clock  chip  is  not  being  used  at  any 
time,  the  POWER  DOWN  input  can 
be  tied  directly  to  +  5  volt  power.  The 
2N2222  and  2N2907  transistors  and 
associated  components  can  then  be 
eliminated.  In  many  cases  the  power 
circuit  for  pin  24  of  the  clock  chip 
need  be  no  more  complex  than  two 
diodes,  a  resistor,  a  capacitor  and  a 
battery;  however,  if  the  clock  is  on  a 
board  that  tends  to  be  noisy,  a  more 
complex  zener  diode  and  transistor 
switch  can  be  used  there.  An  example 
of  this  can  be  seen  in  Steve  Ciarcia’s 
May  1 982  BYTE  article. 

While  the  fastest  fixed-interval  in¬ 
terrupt  frequency  specified  for  the 
58167  by  National  Semiconductor  is 
10  Hz,  a  100  Hz  interrupt  can  be  ob¬ 
tained  by  selecting  the  counter-latch 
match  (sometime  called  “alarm 
clock”)  interrupt  address,  then  writ¬ 
ing  “don’t  care”  states  into  the  sec- 
onds/tens-of-seconds  latches.  A  good 
deal  of  control  can  be  obtained  by 
writing  valid  comparison  values  into 
some  of  the  other  latches,  and  “don’t 
care”  into  others.  A  “4”  in  the  day- 
of-the-week  latch  and  “don’t  care” 
everywhere  else  would  get  you  100 
Hz  interrupts  all  day  every  Wednes¬ 
day.  A  “12”  in  the  hours/tens-of- 
hours  latches  and  “don’t  care”  else¬ 
where  gets  you  100  Hz  from  12:00:00 
through  1  2:59:59  every  day.  Add  the 
“4”  in  day-of-the-week  and  you  get 
the  12:00:00  through  12:59:59  100 
Hz  interrupts  every  Wednesday. 
Other  combinations  of  valid  and 
“don’t  care”  states  get  other  patterns. 


For  most  people’s  purposes,  this  ca¬ 
pability  is,  at  best,  somewhat  inter¬ 
esting,  but  otherwise  useless.  For 
automatic  data  logging,  however,  it 
could  be  quite  useful. 

The  GO  command  increments  the 
counter  if  the  seconds  count  is  greater 
than  39,  not  40,  as  stated  on  page  64. 

It  is  frequently  easier  and  more 
foolproof  to  use  the  seconds/tens-of- 
seconds  counter  reset  address  to  start 
the  clock  at  a  known  time.  I  think 
maybe  the  GO  command  was  either 
dreamed  up  because  there  was  an  ad¬ 
dress  that  wasn’t  otherwise  used,  or 
else  it  happened  by  accident.  A  goof 
in  a  mask,  perhaps?  A  bug  that  be¬ 
came  a  feature? 

On  page  62  it  is  stated:  “Each  Janu¬ 
ary  ...  the  clock  IC  does  not  automat¬ 
ically  change  to  a  new  year.”  While 
true,  it  is  also  misleading:  the  58167 
doesn’t  have  years/tens-of-years 
counters  (or  latches).  Any  changing 
of  the  year  gets  done  somewhere  other 
than  the  58167  clock  circuit. 

Someone  who  wanted  to  make  a 
clock  board  that  does  have  years 
counters  could  use  an  OKI 
MSM5832,  National  Semiconductor 
MM58274,  or  Motorola  MCM 146818 
or  MCM  1468 19  or  any  other  clock 
chip  that  tracks  years. 

Sincerely, 

Frank  Kuechman 
8113  NE  25th  Avenue 
Vancouver,  WA  98665 


DDJ 


Dr.  Dobb’s  Journal.  October  1985 


8 

737 


DR.  DOBB'S  CLINIC 


by  D.E.  Cortesi 


Here’s  a  batch  of  small  items  on 
MSDOS  and  PCDOS.  The  first  is 
based  on  a  note  from  Nick  Hammond 
of  Torrens,  Australia.  There,  in  a 
magazine  called  Your  Computer  and 
in  a  column  not  unlike  this  one,  ap¬ 
peared  the  plaint  of  a  puzzled  user. 
Sometimes,  he  said,  his  everyday  pro¬ 
grams  would  misbehave  strangely. 
DEBUG  would  bomb  or  fail  to  write  a 
good  file;  FORMAT  would  loop  or  re¬ 
port  track  zero  bad  when  it  wasn’t; 
and  so  forth.  The  only  common  fac¬ 
tors  were  that  the  failures  occurred 
when  there  were  resident  programs  in 
the  system  and  that  they  could  be 
fixed  by  booting  without  the  resident 
programs.  Sometimes  they  could  be 
fixed  merely  by  changing  the  order  in 
which  resident  programs  were 
loaded. 

Dismal  Memory  Access 

Hammond  wrote  to  explain  this  puz¬ 
zle.  When  IBM  designed  the  PC,  he 
tells  us,  they  fitted  a  Direct  Memory 
Access  (DMA)  chip  from  an  8-bit 
family.  It  can  address  only  64K.  To 
accommodate  the  1  Mb  address  space 
of  the  8088,  they  tacked  on  an  exter¬ 
nal  “page  register”  to  establish  which 
of  16,  64K  “pages”  the  DMA  chip 
would  use.  Because  an  8088  “para¬ 
graph”  is  16  bytes  and  a  “page”  is 
usually  256  bytes,  we  might  do  better 
to  call  these  64K  DMA  blocks, 
“chapters.” 

What  we  must  never  call  them, 
Hammond  says,  is  “segments.”  Un¬ 
like  the  65K  segments  the  8088  can 
address,  which  may  begin  at  any 
paragraph  boundary,  there  are  only 
16  DMA  chapters,  and  they  fall  ex¬ 
actly  on  the  64K  boundaries  of 
storage. 

As  a  result,  the  DMA  chip  is  physi¬ 
cally  incapable  of  transferring  a 
block  of  data  that  crosses  a  chapter 
boundary  in  a  single  operation.  “You 


have  to  transfer  as  far  as  the  bound¬ 
ary,  change  the  page  register,  then 
transfer  the  rest,”  says  Hammond. 
“This  would  be  fine  if  the  firmware  in 
the  BIOS  ROM  handled  the  house¬ 
keeping,  but  it  doesn’t.” 

For  example,  if  you  use  BIOS  inter¬ 
rupt  1 3h  to  access  a  floppy  disk  and 
your  data  area  crosses  a  chapter 
boundary — if  any  byte  in  your  buffer 
except  the  first  has  an  address  that  is 
divisible  by  65,536 — the  BIOS  will 
return  an  error.  Programs  that  don’t 
specifically  check  for  this  condition 
will  fail  intermittently,  depending  on 
their  load  address,  and  that  depends 
on  what  resident  programs  were  load¬ 
ed  ahead  of  time. 

It’s  our  impression  that  I/O  done 
through  MSDOS  does  not  suffer  this 
constraint — just  one  more  reason  to 
avoid  ROM  calls. 

Control  That  Zee! 

MSDOS  function  40h  writes  up  to  CX 
bytes  to  the  file  named  by  the  “han¬ 
dle”  in  BX  and  returns  in  AX  the 
number  of  bytes  written.  Ordinarily, 
all  the  bytes  will  be  written  and  AX 
will  equal  CX  afterward. 

Usually  the  reason  DOS  will  report 
writing  fewer  than  CX  bytes  is  that 
the  output  disk  is  full  and  there  is  no 
place  to  put  them.  That’s  a  disastrous 
error  from  which  a  program  has  few 
courses  of  recovery. 

We  recently  discovered  another 
cause — one  that  isn’t  documented  so 
far  as  we  are  aware.  If  the  output 
data  include  a  logical  end  of  file  byte 
(Control  Z  ),  and  if  BX  names  the 
standard  output  handle  (0001),  and 
if  standard  output  has  not  been  redi¬ 
rected  to  a  file,  DOS  will  stop  writing 
at  the  Control-Z  and  return  the  count 
of  bytes  that  preceded  it. 

This  behavior  makes  sense  in  a 
way;  it  makes  it  easy  to  copy  a  disk 
file  to  the  screen  without  displaying 


the  superfluous  garbage  that  may  fol¬ 
low  logical  end  of  file.  On  that  ac¬ 
count,  we’d  expect  DOS  to  behave  the 
same  whenever  the  target  file  was  im¬ 
plemented  by  a  character  device  driv¬ 
er,  whether  it  was  named  by  a  stan¬ 
dard  handle  number  or  not.  We 
didn’t  test  to  see  if  it  was  that  consis¬ 
tent,  or  whether  it  only  applied  the 
rule  to  the  handles  for  the  standard 
output,  print,  and  auxiliary  files,  or 
whether  it  was  just  a  glitch  on  handle 
0001,  or  whether  it  varied  with  the 
device  driver. 

The  more  consistent  DOS  is,  the 
worse  the  problems  are.  How  is  a  pro¬ 
gram  supposed  to  distinguish  a  full 
disk  from  an  incidental  Control-Z? 
The  only  way  we  can  think  of  is  to  use 
the  ioctl  function  to  get  the  attributes 
of  the  file's  device  driver,  and  see  if 
it’s  a  block  device  or  a  character  one. 
Wonderful. 

One  program  that  doesn’t  check  is 
the  MS  Pascal  3.0  runtime  library.  If 
a  Pascal  program  copies  from  stan¬ 
dard  input  to  standard  output  and  if 
input  is  an  edited  keyboard  line  in¬ 
cluding  Control-Z  and  output  is  the 
screen,  it  will  abort  with  a  disk-full 
error. 

Medium  Smart 

Further  gleanings  from  our  study  of 
MSDOS  can  be  found  in  the  Table 
(page  12),  a  compilation  of  the  num¬ 
bers  that  define  MSDOS  diskette  for¬ 
mats.  We  pieced  it  together  from 
IBM  and  Microsoft  manuals. 

The  second  line  of  the  table  is 
headed  “format  flag-byte.”  That’s 
what  we  call  the  first  byte  of  the  FAT, 
the  same  byte  that  Microsoft  and  the 
illiterati  of  Boca  Raton  persist  in  call¬ 
ing  the  “media  descriptor,”  thereby 
driving  us  straight  up  the  wall.  The 
word  “media”  is  plural,  dammit,  and 
is  in  no  way  a  synonym  for  “disk.” 
The  recording  medium  used  with  dis- 


10 

738 


Dr.  Dobb’s  Journal,  October  1 985 


kettes  is  an  iron  or  chromium  oxide  in 
a  plastic  binder,  but  the  named  byte 
doesn’t  describe  it. 

Whatever  you  call  it,  that  byte  is  a 
poor  guide  to  disk  layout.  The  same 
flags  can  be  found  on  5-  and  8-inch 
disks  of  very  different  formats,  and 
the  flag  F9h  appears  on  both  80- 
track  diskettes  and  fixed  disks.  There 
is  a  better  indicator:  DOS  functions 
ICh  and  36h  both  return  in  register 
DX  the  total  number  of  allocation 
units  on  a  disk.  Read  across  the  third- 
from-last  row  of  Table  1;  you’ll  see 
that  each  format  has  a  unique  num¬ 
ber  of  allocation  units. 

Hear  No  Evil .  .  . 

...  is  the  policy  of  Microsoft  regard¬ 
ing  MSDOS.  We  found  a  bug  in  DOS 
and  wrote  it  up  in  these  words:  “DOS 
function  43h,  AL=00  (return  file  at¬ 
tributes  of  path  *DS:DX)  returns  im¬ 
possible  values  and  no  error  in  the 
carry  flag  when  given  the  path  ‘C:\.’ 
Under  DOS  3.0,  despite  absence  of  a 
carry  error  signal,  function  59h  re¬ 
turns  partly-correct,  partly-contra- 
dictory  information,  suggesting  an 
error  was  recognized.”  We  attached 
debug  output  to  prove  these  points 
and  mailed  it  all  to  Seattle. 

It  was  a  waste  of  time.  Microsoft  is 
not  interested  in  trouble  reports  from 
civilians;  they  only  talk  to  companies. 
Or  so  said  Doug  Boa  of  Microsoft  in 
an  earnest  reply.  “Microsoft  does  not 
support  either  of  our  operating  sys¬ 
tems  (MSDOS  and  Xenix)  directly,” 
he  wrote.  “We  do  not  sell  them  to  the 
public,  but  only  to  the  equipment 
manufacturers  .  .  .  [who]  are  respon¬ 
sible  for  all  support  functions.” 
Therefore,  Boa  said,  “even  though 
you  are  reporting  a  problem  with  a 
function  call  general  to  all  MSDOS 
implementations,  you  still  need  to  di¬ 
rect  the  report  to  the  equipment 
manufacturer.” 

We  like  to  think  we  can  read  a  tone 
of  honest  regret  in  those  lines.  If  we 
wrote  an  OS,  it  would  just  kill  us  to 
have  to  turn  back  trouble  reports  be¬ 
cause  we’d  know  that,  first,  the  peo¬ 
ple  who  report  them  are  more  likely 
to  give  up  than  to  submit  them  a  sec¬ 
ond  time  to  their  OEMs,  and  second, 
the  OEM  is  all  too  likely  to  ignore  the 
reports,  lose  them,  reject  them  out  of 


ignorance  (or  habit),  or  just  sit  on 
them  for  a  few  months  before  passing 
them  on. 

So  let’s  give  a  moment’s  sympa¬ 
thetic  thought  to  the  Microsoft  devel¬ 
opers,  sitting  around  wistfully  hoping 
that  somebody  will  write  them  a  let¬ 
ter  they’ll  be  allowed  to  read.  But  if 
you  find  a  bug  in  MSDOS  or  Xenix, 
save  your  time  and  postage.  We 
might  add  that  the  sysops  of  the  Mi¬ 
crosoft  forum  on  CompuServe  will 
also  reject  any  technical  queries  or 
trouble  reports  on  MSDOS  or  Xenix. 

Narrow  Paths 

MSDOS  permits  a  program  to  control 
files  entirely  in  terms  of  compact 
strings  of  characters  called  path¬ 
names.  Having  to  convert  the  name 
of  a  file  from  a  compact,  punctuated 
string  like  C: DROSS. DAT  to  the  fixed 
fields  of  a  file  control  block  and  back 
again  was  a  major  irritation  of  file 
work  in  CP/M  and  early  MSDOS. 
With  pathnames,  programs  talk  to 
DOS  in  the  same  terms  with  which 
users  talk  to  programs.  That  should 
make  things  easier. 

And  it  does,  so  long  as  you  work 
only  with  single  files  with  explicit 
names.  Get  what  should  be  a  path¬ 
name  from  the  user  and  terminate 
the  input  string  with  a  null  byte;  point 
to  the  string  and  tell  DOS,  “Open  it.” 
DOS  takes  care  of  case  conversion 
and  of  parsing  the  string  into  its 
many  parts:  drive  letter,  directory 


names,  filename,  extension.  If  the 
path  describes  anything  that  DOS  can 
accept  as  a  file,  your  program  gets 
back  a  numeric  “handle”  and  is  off  to 
the  races.  If  it  doesn’t,  DOS  returns 
either  error  2,  “file  not  found,”  or  er¬ 
ror  3,  “path  not  found.”  The  first 
means  that  the  disk  letter  (if  any) 
and  directory  names  (if  any)  were 
valid  but  the  filename  and  extension 
couldn’t  be  found  in  the  (last-named 
or  default)  directory.  Error  3  means 
that  something  went  wrong  before 
the  filename  and  extension  could  be 
checked.  We  should  be  delighted  that 
DOS  can  handle  all  this  complexity 
for  us,  because  it  would  be  a  real 
punctuation  (a  real  #$?!%$*&!)  to 
have  to  program  it. 

It’s  as  easy  to  erase,  create,  or  re¬ 
name  any  single  file  from  its  path¬ 
name,  or  set  its  attributes  or  time- 
stamp,  as  it  is  to  open  one. 

Winding  Paths 

Complications  arise  when  you  want 
to  handle  ambiguous  pathnames 
(ones  in  which  wild  cards  are  used  to 
represent  multiple  files),  especially  if 
you  want  your  program  to  be  as  flexi¬ 
ble  as  DIR  or  COPY. 

Let’s  create  an  example.  Suppose 
that  on  drive  B  you  have  a  diskette 
with  a  small  hierarchy  of  files: 

top.dat 
mydir 
innerl  ,dat 
inner2.dat 


5  1 /4-inch . 8-inch  -- 


Nominal  capacity  1 60K 

Format  flag-byte  FEh 

Entries  in  root  dir  64 

Bytes  per  sector  512 

Sectors  per  cylinder  8 

Cylinders  per  disk  40 

Total  sectors  320 

Reserved  sectors 
for  bootstrap  1 

for  FAT  copies  2 

for  root  dir  4 

total  reserved:  7 

Available  sectors  3 1 3 

Sectors  per  unit  1 

Number  of  units  313 

Bytes  per  unit  512 

Data  capacity  (Kb)  156.5 


180K 

320K 

360K 

FCh 

FFh 

FDh 

64 

112 

112 

512 

512 

512 

9 

16 

18 

40 

40 

40 

360 

640 

720 

1 

1 

1 

4 

2 

4 

4 

7 

7 

9 

10 

10 

351 

630 

708 

1 

2 

2 

351 

315 

354 

512 

1024 

1024 

175.5 

315.0 

354.0 

1.2M 

250K 

1.2M 

F9h 

FDh 

FEh 

224 

68 

192 

512 

128 

1024 

30 

26 

16 

80 

77 

77 

2400 

2002 

1232 

1 

4 

1 

14 

12 

4 

14 

17 

6 

29 

33 

11 

2371 

1969 

1221 

1 

4 

1 

2371 

492 

1221 

512 

512 

1024 

1185.5 

246.5 

1221.0 

Table 

Format  data  for  a  number  of  MSDOS  diskette  layouts.  The  "number  of  units," 
returned  in  DX  by  DOS  function  1  Ch,  can  be  used  to  distinguish  between 

layouts. 


Dr.  Dobb's  Journal,  October  1985 


13 

739 


And  suppose  that  at  some  time  you 
moved  to  drive  B  and  made  MYDIR 
the  current  directory: 

A>b: 

B>cd  mydir 
B>a: 

That,  by  the  way,  is  a  point  that’s 
poorly  explained  by  the  DOS  manuals 
we’ve  seen.  They  don’t  make  it  clear 
that  each  drive  has  its  own  current 
directory.  A  reference  to  a  file  by 
drive,  name  and  extension,  as  in  the 
example 

A>type  b:innerl.dat 

means  “look  up  the  file  named  IN¬ 
NER  1. DAT  in  the  current  directory 
of  drive  B.”  It  doesn’t  mean  the  root 
directory  of  drive  B;  that  would  be 

A>type  b:\innerl.dat 

Nor  does  it  necessarily  mean  in  the 
directory  last  named  to  CD;  that 
might  have  been  a  new  current  direc¬ 
tory  for  another  drive. 

Now  consider  all  the  ways  that  you 
can  refer  to  the  files  on  drive  B  using 
DIR.  You  can  ask  about  all  files  by 
not  supplying  any  operand, 

A>b: 

B>dir 

which  will  list  all  files  in  the  current 
directory.  You  can  do  the  same,  when 
another  drive’s  the  default,  by  nam¬ 
ing  the  drive. 

A>dir  b: 

You  can  ask  for  all  the  files  in  the 
root  directory  of  the  disk  by  specify¬ 
ing  “root”  with  a  backslash: 

A>dir  b:\ 

By  convention  of  DIR  and  COPY  such 
a  reference  to  a  directory  is  implicitly 
a  reference  to  all  the  files  in  that  di¬ 
rectory.  It  saves  your  having  to  type 
*.*,  as  in 

A>dir  b:\*.* 

or  the  equivalent  double-dot  back- 
reference, 

A>dir  b:..\*.* 

You  may  refer  to  files  in  any  directo¬ 
ry  by  naming  the  directories  along 

14 

740 


the  path  to  the  files, 

A>dir  b:.. \mydir\..\top.dat 

including  redundant  ones,  as  in  that 
example. 

Primrose  Paths 

There  are  a  lot  of  useful  programs 
with  specifications  that  start  off  like 
this:  “for  every  file  that  matches  the 
first  operand,  the  program  will  .  .  .” 
For  consistency,  such  programs 
ought  to  interpret  their  command 
operands  just  as  generally  as  DIR 
does.  Anything  less  will  just  confuse 
the  users:  they’ll  come  back  com¬ 
plaining  “DIR  finds  this  file,  why 
doesn’t  your  program?” 

The  key  to  handling  ambiguous 
pathnames  lies  in  the  DOS  functions 
4Eh  and  4Fh,  search  for  first  and 
next  match  to  path,  respectively. 
Point  to  a  pathname  string  that  may 
or  may  not  be  ambiguous  and  call 
DOS;  if  there’s  (another)  file  that 
matches  the  pathname,  DOS  drops 
information  about  it  into  the  current 
data  transfer  area.  One  returned  item 
is  the  filename  and  extension,  for¬ 
matted  as  a  string. 

Unfortunately,  functions  4E/4F 
aren’t  as  general  as  DIR  is.  They  do 
not  assume  *.*  after  a  directory  or 
drive  letter,  for  instance. 

Even  when  they  are  successful, 
they  leave  you  with  a  riddle:  how  can 
you  open  the  file  you’ve  discovered? 
You  have  the  pathname  you  used  for 
the  search,  a  user-entered  string  like 
b:\mydir\*.dat,  for  example.  You 
have  the  filename  and  extension  re¬ 
turned  by  DOS,  INNER2.DAT,  for  in¬ 
stance.  Now  you’d  like  to  do  some¬ 
thing  to  the  file  you’ve  found — open 
it  or  erase  it  or  something  but  you 
don’t  have  enough  information.  You 
can’t  just  point  to  the  filename  string 
and  request  an  open.  The  file  may  re¬ 
side  in  a  disk  or  directory  other  than 
the  current  default,  and  DOS  won’t 
find  it.  The  necessary  qualifiers  are  in 
the  search  string,  but  you  can’t  get  at 
them  without  parsing  the  string. 
Parsing  pathnames  was  the  job  that 
we  so  gladly  turned  over  to  DOS. 

As  you  already  suspect,  we 
wouldn’t  be  going  on  about  this  if  we 
didn’t  have  a  solution.  We  found  that 


we  could  use  DOS  function  4300h 
(return  attributes  of  file  using  path¬ 
name)  to  tell  the  type  of  path  a  user 
has  provided.  With  fair  reliability, 
the  user’s  path  specification  can  be 
processed  into  two  versions:  a  search 
path  for  use  with  functions  4E/4F, 
and  a  lead-in  path  to  be  prefixed  to 
the  filename  string  that  those  func¬ 
tions  return. 

The  logic  of  it  is  explained  at  te¬ 
dious  length  in  the  comments  of  List¬ 
ing  One  (page  16),  the  source  of  a  C 
function  that  does  the  job.  This  func¬ 
tion,  fixpath(),  would  be  called  once 
to  initialize  a  path  provided  by  the 
user.  If  it  returns  a  zero,  there  are  no 
files  to  be  found  down  that  path.  Oth¬ 
erwise  it  has  made  two  new  paths,  a 
search  path  and  a  prefix  path.  If  the 
input  path  was,  say, 

b:..\ 

(“all  the  files  in  the  parent  of  the  de¬ 
fault  directory  of  drive  B”),  fixpathf) 
will  return  a  search  path  of 

b:..\*.* 

and  a  prefix,  or  lead-in,  path  of 
b:..\ 

However,  if  the  input  string  was 
b:\mydir 

it  would  return  a  search  path  of 
b:\mydir\*.* 
and  a  lead-in  path  of 
b:\mydir\ 

The  search  path  is  for  input  to  func¬ 
tions  4E/4F.  If  function  4E  returns 
an  error,  the  original  path  was  un¬ 
grammatical  somehow.  Otherwise, 
the  search  path  will  return  the  same 
filenames  that  DIR  would  print  if  it 
were  given  the  same  input  path  you 
gave  to  fixpath().  When  you  append 
one  of  these  returned  filename  strings 
to  the  prefix  path  set  up  by  fixpath(), 
you  have  a  complete  pathname  to 
give  to  DOS  functions  3Dh  (open), 
4 1  h  (erase),  43h  (get/set  attributes) 
or  56h  (rename). 

DDl 

Dr.  Dobb's  Journal,  October  1985 


Dr.  Dobb's  Clinic  Listing 


(Text  begins  on  page  10) 


fixpathO  processes  a  DOS  pathname  for  two  different  uses. 

The  input  path,  *ip,  is  presumably  a  command  operand  like  the 
first  operand  of  DIR.  One  output,  the  search  path  *sp,  is 
the  input  possibly  augmented  with  or  "\*.*"  so  that  it 

will  work  reliably  with  DOS  functions  4E/4F.  The  other  output 
is  a  lead-in  path,  *lip,  which  can  be  prefixed  to  the  simple 
filename. ext  returned  by  functions  4E/4F  to  make  a  complete 
path  for  opening  or  erasing  a  file. 

The  function  returns  1  if  it  is  successful,  but  0  if  the 
input  path  can't  be  processed  and  should  not  be  used. 

Some  input  paths  can  be  processed  here  yet  be  invalid  or 
useless.  The  search  path  produced  from  such  an  input  will 
cause  an  error  return  from  function  4E  (search  first  match). 

int  fixpath(ip,sp,lip) 
register  char 

*ip,  /*  the  input  path  */ 

*sp,  /*  the  search  path  */ 

*lip;  /*  the  lead-in  or  prefix  path  */ 

{ 

char  *cp;  /*  work  pointer  for  scanning  paths  */ 

Pick  off  4  special  cases: 

(1)  the  null  string,  which  we  take  to  mean 

(2)  the  simple  "d:"  reference  to  a  drive,  which  we 
also  take  to  mean  "d:*.*" 

(3)  the  root-dir  reference  "d:\"  which  is  mishandled 
by  function  4300  of  both  DOS  2.1  and  3.0. 

(4)  any  reference  that  ends  in  "\" 

In  all  cases,  the  input  path  is  ok  as  a  lead-in,  but  it  needs 
the  global  qualifier  *.*  added  for  searching. 

if  (  (strlen (ip) ==0)  /*  null  string  */ 

I  I (strcmp(ip+l, " : " ) ==0)  /*  d:  only  */ 

I  I (iptstrlen (ip) -1] =='\\' )  )  /*  ends  in  bkslsh  */ 

{ 

strcpy (lip, ip) ;  /*  input  is  ok  prefix  */ 

strcpy (sp, ip) ; 

strcat (sp, "*.*") ;  /*  add  *.*  for  search  */ 

return  ( 1 )  ; 

/*  1 

Ok,  we  have  a  non-null  string  not  ending  in  \  and  not  a  lone 
drive-letter.  Four  possibilities  remain: 

(1)  an  explicit  file  reference,  b:\mydir\mydat 

(2)  an  explicit  directory  reference,  \mydir 

(3)  an  ambiguous  file  reference,  *.*  or  b :\mydir\* .dat 

(4)  an  unknown  name,  a:noway  or  b:\mydir\nonesuch.dat 
We  can  separate  this  with  fair  reliability  using  DOS  function 
4300h,  get  attributes  from  path. 

The  DOS  operations  used  here  are  based  on  the  MS-C  3.0 
library  functions,  but  all  C  compilers  have  similar  library 
routines  and  header  files. 

regs.x.ax  =  0x4300;  /*  AH=43h,  AL=00h  */ 

(char  *)regs.x.dx  =  ip;  /*  DS:DX  ->  input  path  */ 
regs.x.cx  =  0;  /*  clear  CX  */ 

intdos(&regs,&regs) ;  /*  call  DOS  */ 

if  (  (regs.x.cflag)  &&  (regs. x.ax==0x0002)  ) 

{ 

/*  =  =  =  =  =  =  =  =  =  =  =  =  ==  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =sr=  =  ==  =  =  =  ==  = 

The  path  is  valid,  in  that  all  directories  and  drives 
named  in  it  are  valid,  but  the  final  name  is  unknown.  No 
files  will  be  found  in  a  search,  so  quit  now. 

return(0) ; 

) 

if  (  (( 0 1 =regs . x. cf lag) && (regs. x. ax==0x0003 ) ) 

I  I  ( ( 0==regs. x.cf lag) && ( 0== (reqs. x.cx  &  0x0010)))  ) 

{ 


(Continued  on  page  18) 


Dr.  Dobb’s  Journal,  October  1985 


16 

741 


Dr.  Dobb's  Clinic  Listing 


(Listing  continued,  text  begins  on  page  10) 


/*  ================================„===========„====„ 

Error  3,  path  not  found,  could  mean  total  junk  or  a 
misspelt  directory  name,  but  most  likely  it  just  means 
the  path  ends  in  an  ambiguous  name.  If  there's  an  error 
the  initial  search  (function  4Eh)  will  fail. 

With  no  error  and  reg  cx  NOT  showing  the  directory 
attribute  flags  0010,  we  have  a  normal,  unambiguous  file 
pathname  like  "b:\mydir\mydat"  or  just  "myprog.com". 

In  either  case  the  search  path  is  the  whole  input 
path.  The  lead-in  is  that  shorn  of  whatever  follows  the 
rightmost  \  or  :,  whichever  comes  last  —  or  just  a  null 
string  if  there  is  no  \  or  :. 

strcpy (sp, ip) ;  /*  working  copy  of  ip  in  sp  */ 
cp  =  sp+strlen(sp)-l; 
fort;  cp  >  sp;  — cp) 

if  ( ( ' \V ==*cp)  I  I  ( 1  : ' ==*cp) )  break; 
if  (cp>sp)  ++cp;  /*  retain  colon/slash  */ 

*cp='\0';  /*  may  make  a  null  string  */ 
strcpy (lip, sp) ; 

strcpy (sp, ip) ;  /*  whole  input  for  search  */ 
return (1)  ; 

} 

if  (  ( 0=~regs . x . cf lag ) && ( regs . x. cx  &  0x0010)) 

{ 

/*  ==================„==„==========================,===== 

No  error  and  the  directory  attribute  returned  in  cx 
shows  an  unambiguous  path  that  happens  to  end  in  the 
name  of  a  directory,  for  instance  ,\bin"  or 

"b:\mydir"  Applying  the  rules  of  DIR  or  COPY,  we 
have  to  treat  these  as  global  refs  to  all  files  named 
in  the  directory.  The  search  path  is  the  input  with 

"\*.*"  tacked  onto  it.  The  lead-in  path  is  just  the 
input  plus  a  backslash. 

==============*,======================================  */ 

strcpy (sp, ip) ; 
strcpy (lip, ip) ; 
st r cat (sp, "\\*. *") ; 
strcat(lip,"\\") ; 
return (1) ; 

} 

else 

{  /*  unexpected  events  make  me  nervous,  give  up  */ 
return ( 0 ) ; 

}  End  Listing 


18 

7A9 


Dr.  Dobb’s  Journal,  October  1 985 


This  month  we’re  going  to  look  at  an 
implementation  of  Knuth's  hyphen¬ 
ation  algorithm.  This  code  is  yet  an¬ 
other  part  of  my  version  of  the  Unix 
text  formatter,  nroff. 

Bugs  and  Fixes 

Before  getting  started,  here’s  a  little 
miscellany.  First,  while  bringing  up  a 
version  of  printf( )  the  other  day,  I 
found  a  bug  in  Version  2.15  of  Lat¬ 
tice  C  (though  the  developer  would 
probably  argue  that  it’s  a  feature). 

When  writing  a  subroutine  with  a 
varying  number  of  arguments  of 
varying  types  (like  printf),  arguments 
are  fetched  from  the  stack  frame  by 
casting  a  “generic”  pointer  to  point 
at  an  individual  object  on  the  stack. 
After  fetching  the  argument,  the 
pointer  is  advanced  past  it.  In  other 
words,  we  want  to  cast  a  character 
pointer  into  a  long  pointer,  fetch  a 
long  object  off  the  stack,  and  then  ad¬ 
vance  the  character  pointer  by  the 
size  of  a  long.  The  first  thing  1  tried 
was: 

char  *p ; 

long  x  ; 

x  =  *(long  *)p+  +  ; 

but  this  won’t  work  correctly.  Here, 
the  problem  is  order  of  precedence.  *, 
(long  *),  and  +  +  are  all  at  the  same 
precedence  level,  but  they  associate 
right  to  left.  When  fully  parenthe¬ 
sized,  the  associativity  gives  us: 

x  =  *((long  *)(p  +  +  )) 

Because  the  +  +  binds  more  tightly 
to  p  than  the  cast,  the  compiler  will 
assume  that  p  is  a  character  pointer 
(rather  than  a  long  pointer)  for  the 
sake  of  the  increment,  though  it  will 
fetch  the  long  object  correctly.  In 
other  words,  the  expression  evaluates 


to  a  long,  but  1  (rather  than  4)  is  add¬ 
ed  to  the  pointer.  I  tried  to  force  the 
precedence  to  behave  by  doing: 

x  =  *(  (long  *)p  )+  +  ; 

but  now  Lattice  gives  me  an  error  25 
(lvalue  required)  and  won’t  compile 
it.  By  itself,  ((long  *)p)  +  +  doesn’t 
compile  either.  Because  this  state¬ 
ment  indeed  has  an  lvalue  (the  x),  1 
was  rather  nonplused.  I  think  that 
the  compiler  was  confused  by  the 
cast,  thinking  that  I  was  trying  to  do 
something  such  as  x  +  +  =  y  or: 

int  foo; 

(long)foo  =  5L; 

which  won’t  work  because  this  would 
force  foo  to  overflow  (only  an  inte¬ 
ger’s  worth  of  space  is  reserved  for 
foo,  and  we’re  trying  to  squeeze  a 
long  object  into  it).  However,  ((long 
*)p)+  +  should  work  because  we’re 
just  modifying  the  way  that  the 
pointer  arithmetic  works,  not  over¬ 
flowing  the  target.  A  more  clever  er¬ 
ror  detection  algorithm  would  take 
care  of  this  exception.  The  following, 
though  inelegant,  does  work: 

char  *p; 

x  =  *(long  *)p  ; 

p  +  =  sizeof(  long  ); 

While  we’re  on  the  subject  of  bugs, 
several  people  have  written  about 
what  we  thought  was  a  bug  in  Is,  the 
directory  utility  printed  a  couple 
months  back.  The  problem  here  is  un¬ 
derlining.  On  a  Compaq,  directory 
names  comes  out  at  half  intensity  in¬ 
stead  of  underlined.  The  problem 
here  is  the  hardware  (and  I  contend 
that  this  is  a  bug,  not  a  feature).  The 
Compaq  emulates  an  IBM  color  card 
running  a  monochrome  monitor.  The 


color  card  doesn’t  support  underlin¬ 
ing  (1  think  that  it  should)  but  prints 
text  in  a  different  color  rather  than 
underlining  it.  On  a  monochrome  dis¬ 
play,  this  second  color  comes  out  as 
reduced  intensity.  All  the  IBM  cards, 
color  or  otherwise,  should  support  the 
attributes  specified  in  the  ANSI. SYS 
documentation.  Anyway,  the  Para¬ 
dise  Modular  Graphics  card  has  the 
same  problem  as  the  Compaq.  On  the 
other  hand,  the  Hercules  Graphics 
card  does  underlining  correctly. 

There’s  a  second  bug  in  Is  that’s  a 
little  more  serious.  Ls,  as  it  stands, 
runs  fine  under  DOS  Version  3.0. 
However,  when  running  under  Ver¬ 
sion  2.x,  it  couldn’t  find  the  root  di¬ 
rectory  (\)  when  it  was  explicitly  re¬ 
quested  by  an  Is  \  command.  A 
version-independent  fix  for  the  prob¬ 
lem  is  given  in  Listing  One  (page  26). 

Hyphenation 

Moving  on,  the  capability  to  hyphen¬ 
ate  words  is  desirable  in  any  text  for¬ 
matter  or  word  processor.  A  lot  of 
needless  white  space  between  words 
can  be  eliminated  by  hyphenation. 
Narrow  columns  look  better,  and  we 
can  get  more  words  per  page.  Look¬ 
ing  at  the  output  of  several  commer¬ 
cially  available  word  processors,  I 
suspect  that  most  of  them  derive  their 
algorithms  from  the  one  given  by 
Knuth  in  T gX  and  Metafont ,  and  I’ve 
used  his  algorithm  here.1 

Hyphenation  is  a  harder  problem 
than  you  would  think  at  first.  To  use 
Knuth’s  examples,  why  is  record  hy¬ 
phenated  rec-ord  when  used  as  a 
noun  and  re-cord  when  as  a  verb? 
Consequently,  it’s  impractical  to  at¬ 
tempt  a  truly  universal  algorithm — 
there  are  just  too  many  exceptions. 
Knuth’s  algorithm  is  intentionally 
conservative — if  it  isn’t  absolutely 
sure  how  to  split  two  syllables,  it 
doesn’t  split  them. 


Dr.  Dobb’s  Journal,  October  1985 


20 

743 


Algorithm  Description 

Knuth  attacks  the  problem  by  break¬ 
ing  it  into  several  parts,  the  first  of 
which  is  exception  removal.  He  keeps 
a  dictionary  of  words  that  are  known 
exceptions  to  his  other  rules  and  then 
sees  if  a  word  is  an  exception  before 
processing  it  further.  Strictly  speak¬ 
ing,  we  could  use  a  huge  dictionary 
and  dispense  with  the  rest  of  the  algo¬ 
rithm,  but  this  would  be  too  cumber¬ 
some  to  be  practical.  Knuth  includes 
a  dictionary  of  about  300  words  in  his 
book,  some  of  which  are  rather  use¬ 
less.  (How  many  times  do  you  use 
“conefiower”  in  an  average  docu¬ 
ment?)  There  just  wasn’t  room  to 
print  the  dictionary  look-up  part  of 
the  hyphenator  this  month.  It’s  a 
straightforward  program — it  reads  a 
sorted  list  of  exceptions  and  then  does 
a  simple  binary  search  in  a  table. 

The  second  part  of  the  algorithm 
does  suffix  removal.  The  following 
suffixes  are  recognized: 


-able,  -ary,  -cal,  -cate,  -cial, 
-cious,  -cient,  -dent,  -ful, 

-ize,  -late,  -less,  -ly,  -ment, 
-ness,  -nary,  -ogy,  -rapher, 
-raphy,  -scious,  -scope, 
-scopic,  -sion,  -sphere,  -tal, 
-tial,  -tion,  -tional,  -live, 
and  -lure. 

Some  of  these  are  qualified 

-able  must  be  preceded  by  e , 
h,  i,  k,  l,  o,  u,  v,  w,  x,  y.  nt ,  or 
rt;  -ary  must  be  preceded  by 
ion  or  en;  -cate  and  -late 
must  be  preceded  by  a  vowel 
( a ,  e,  /,  o,  u,  ory’);  -nary  may 
not  be  preceded  by  e  or  10; 
-cious  may  not  be  preceded 
by  s\  and  -ize  must  be  pre¬ 
ceded  by  /. 

Another  complication  is  -ing. 

Knuth  says  if  -ing  is  preceded  by  few¬ 


Prefixes: 

0 

1-68 

69 

70 
71-80 

81 

82 

83 

84 

85 

86 
87 


Failure,  return  original  pointer 

Get  another  character  and  branch  to  next  state  as  indicated  in  table. 
Get  a  character,  hyphenate  *p,  branch  to  next  state  using  *p  as  the 
current  character. 

hyphenate  *(p-1 )  and  branch  as  above, 
don’t  exist. 

hyphenate  *(p-1 )  and  return  (p-1 ). 
hyphenate  *p  and  *(p — 1 ),  return  p. 
hyphenate  *p  and  *(p— 2),  return  p. 
hyphenate  *p  and  *(p-3),  return  p. 
hyphenate  *(-p),  don't  return, 
hyphenate  *p,  dont’t  return, 
hyphenate  *p  and  return  p. 


Suffixes: 

0  Failure,  if  the  word  ends  in  s,  e  or  ed  strip  it  off  and  try  again,  else  return 
original  string  pointer. 

1  -57  Get  another  character  and  branch  to  next  state  as  indicated  in  table 
58-79  Don’t  exist. 

80  p +  =  1,  hyphenate  *(p+1). 

81  p  +=  2,  hyphenate  *(p+1). 

82  p +  =  3,  hyphenate  *(p+1). 

83  hyphenate  *<p+1). 

84  Failure,  return  original  pointer. 

85  Doesn't  exist. 

86  process  an -ing. 

87  hyphenate  *(p  + 1 )  and  return  p. 

88  hyphenate  *(p  +  2)  and  return  (p  + 1 ). 

89  hyphenate  *(p  +  5)  and  *(p  + 1 ),  return  p. 


In  both  tables,  unspecified  transitions  go  to  state  O.  Unmarked  transitions  always  go  to 
the  indicated  state,  regardless  of  input  character.  P  points  at  the  character  being  pro¬ 
cessed  at  present. 

Figure  1 

States  and  Associated  Actions 


er  than  four  letters,  don’t  insert  a  hy¬ 
phen.  If  -ing  is  preceded  by  two  iden¬ 
tical  consonants  (other  than /,  l,  s,  or 
z)  put  the  hyphen  between  the  conso¬ 
nants,  and  if  it’s  preceded  by  any  let¬ 
ter  but  /,  break  as  -ing.  If  the  letter 
before  -ling  is  b,  c,  d,f \  g,  k,  p,  t ,  or  z, 
break  before  this  letter  (but  do  not 
break  a  ck  combination  before  -ling)-, 
otherwise  break  -ing. 

If  an  - able ,  -ary,  -ful,  -ize,  -less, 
-ly,  -ment,  or  -ness  is  recognized, 
then  the  algorithm  is  applied  again  to 
the  residual  word  (the  word  less  the 
suffix  we  just  found).  Similarly,  if  we 
fail  to  find  a  suffix  and  the  word  ends 
in  s,  e,  or  ed,  we  try  again  with  the  s,  e, 
or  ed  removed. 

Having  removed  suffixes,  we  now 
try  to  remove  potential  prefixes, 
working  on  the  word  without  all  the 
suffixes  just  recognized.  The  follow¬ 
ing  prefixes  will  be  recognized: 

be-,  com-,  con-,  dis-,  equi-, 
equiv-,  ex-,  hand-,  horse-, 
hy-per-,  im-,  in-,  in-ter, 
in-tro-,  lex-i-,  mac-ro-, 
math-e-,  max-i-,  min-i-, 
mul-ti-,  non-,  out-,  over-, 
pseu-do-,  quad-,  semi-, 
some-,  sub-,  su-per-,  there-, 
trans-,  tri-,  un-der,  and  un-. 

The  intermediate  hyphens  aren’t  in¬ 
serted  unless  the  entire  prefix  is 
found  (e.g.,  hy-per-bole  vs.  hyp-no- 
tic).  There  are  also  exceptions  here: 

Be-  must  be  followed  by  c,  h, 
s,  or  w;  dis-  may  not  be  fol¬ 
lowed  by  h  or  y,  equi-  may 
not  be  followed  by  v;  trans- 
must  be  followed  by  a,f,  g,  l, 
or  m\  tri-  must  be  followed 
by  a,f  or  u;  un-  may  not  be 
followed  by  der  or  i. 

As  with  suffixes,  the  algorithm  is  ap¬ 
plied  repeatedly  after  dis-,  im-,  in-, 
non-,  over-,  or  un-  is  recognized. 

At  this  juncture,  the  word  has  been 
partitioned  into  three  parts:  prefix, 
root,  and  suffix.  We  now  apply  sever¬ 
al  consonant-pair  rules  to  the  root 
portion  of  the  word.  Here,  vowels  are 
a,  e,  i,  o,  u,  and  y,  and  consonants  are 
all  other  single  letters.  Moreover,  the 
pairs  ch,  gh,  ph,  sh,  and  th  are  treated 
as  single  consonants.  If  a  vowel-con- 


22 


Dr.  Dobb's  Journal,  October  1985 


sonant-consonant  (VCC)  pattern  is 
found  and  both  consonants  are  the 
same  and  neither  consonant  is  an  /  or 
s,  the  break  will  be  between  the  con¬ 
sonants.  In  the  case  of  a  vowel-ll  or 
vowel-ss  pattern,  there  will  be  a 
break  only  if  the  following  letter  isn’t 
a  vowel  and  the  word  doesn’t  end 
VCCer  or  VCC ers.  If  the  pattern  is 
vowel-ck,  the  break  will  be  after  the 
ck.  If  the  pattern  is  vowel-qu ,  the 
break  will  be  before  the  qu. 

If  a  VCCV  combination  is  found, 
the  break  will  be  between  the  conso¬ 
nants  unless  they  are  one  of  the  fol¬ 
lowing  combinations: 

bl ,  br ,  cl,  cr,  chi,  chr,  dg,  dr, 
ft,  fr,  ght,  gl,  gr,  kn,  Ik,  Iq, 
nch,  nk,  nx,  phr,  pi,  pr,  rk, 
sp,  sq,  tch,  tr,  thr,  wh,  wl, 
wn,  or  wr. 

Finally,  don’t  break  between  conso¬ 
nants  if  the  word  ends  with  VCCer, 
VCCers,  VCC  age,  VCC  ages,  VCC  est 
and  the  two  consonants  are  either  ft,  Id, 
mp,  nd,  ng,  ns,  nt,  rg,  rm,  rn,  rt,  or  st. 

Once  the  word  is  hyphenated, 
Knuth  goes  back  and  fixes  obvious 
mistakes.  He  takes  back  very  short 
syllables  and  syllables  ending  with  a 
silent  e.  I  decided  to  use  the  exception 
dictionary  to  do  this  rather  than  add¬ 
ing  more  code  to  the  algorithm  prop¬ 
er.  I  should  say  that  Knuth  is  not  his 
usual  lucid  self  when  describing  this 
part  of  the  algorithm.  A  major  factor 
in  my  not  implementing  the  “short 
end”  retention  is  that  I  can’t,  for  the 
life  of  me,  figure  out  what  he  was  say¬ 
ing.  If  anybody  can  decipher  this  part 
of  the  algorithm  description,  please 
enlighten  me. 

Implementation 

The  code  this  month  (Listing  Two, 
page  26)  supports  the  suffix  removal, 
prefix  removal,  and  consonant-pairs 
part  of  the  algorithm.  The  Lattice 
compiler  produces  a  9063-byte  object 
module  (which  seems  a  little  bloated 
to  me,  though  the  code  could  be  opti¬ 
mized  a  bit  more  if  space  is  a  prob¬ 
lem).  Hyphens  are  marked  by  setting 
the  high  bit  of  the  character  that  will 
follow  the  hyphen  (the  first  letter  in 
the  syllable).  Only  unhyphenated 
words  composed  of  lower  case  letters 


will  be  processed.  Among  other 
things,  this  lets  us  avoid  the  problems 
associated  with  proper  nouns,  which 
shouldn’t  be  hyphenated. 

All  global  variables  and  all  subrou¬ 
tines  that  don’t  need  to  be  accessed 
from  another  module  are  declared 
static.  The  one  externally  accessible 
routine  is  the  driver,  hyphen( )  (on  line 
484),  which  calls  the  various  other 
processing  routines. 

The  suffix/prefix  removal  routines 
use  table-driven  state  machines  for 
pattern  recognition.  As  usual,  there  is 
a  trade-off  between  space  and  execu¬ 
tion  time.  The  fastest  state  machines 
use  a  large  two-dimensional  array  in 
which  one  axis  is  the  current  state  and 
the  other  the  current  input  character. 
The  table  itself  holds  the  next  state. 
As  most  of  the  states  have  only  one 
legal  transition,  this  method  uses  an 
unnecessarily  large  amount  of  memo¬ 
ry.  At  the  other  extreme,  we  can  keep 
a  list  associated  with  each  state  that 
contains  all  legal  input  characters  and 
the  state  to  go  to  when  that  character 
is  encountered.  This  method  usually 
takes  less  space,  but  we  have  to  search 
the  list  every  time  we  process  a  char¬ 
acter  so  execution  time  isn’t  great 
when  the  lists  are  too  long. 

The  tables  used  here  are  a  compro¬ 
mise  between  these  two  methods. 
Each  table  is  an  array  of  pointers  to 
character  arrays.  The  tables  are  in¬ 
dexed  by  current  state.  If  there’s  only 
one  legal  transition  out  of  a  given 
state,  the  first  byte  of  the  associated 
array  is  1,  the  next  byte  is  the  one 
legal  input  character,  and  the  third 
byte  is  the  state  to  go  to  when  that 
character  is  found.  All  other  input 
characters  will  cause  a  transition  to 
state  0.  The  second  type  of  array  is 
used  when  there  is  more  than  one  le¬ 
gal  transition  out  of  a  state.  In  this 
case,  the  first  byte  of  the  array  is  0 
and  the  next  26  bytes  are  an  array  of 
next  states  indexed  by  current  input 
character.  The  state  tables  begin  on 
line  605  of  the  listing. 

The  state  diagrams  for  these  tables 
are  shown  in  Figure  2  (page  24)  and 
Figure  3  (page  25).  In  both  ma¬ 
chines,  state  0  is  a  failure  state. 
States  with  numbers  greater  than  or 
equal  to  80  are  success  states.  These 
are  the  only  states  in  which  any  sort 


of  action  is  performed.  The  actions 
themselves  are  spelled  out  in  Figure  1 
(page  22).  Note  that  the  suffix  recog¬ 
nition  code  goes  through  the  word 
from  back  to  front.  Consequently,  the 
state  table  for  suffixes  is  a  little  weird 
looking  (all  the  suffixes  are 
backwards). 

The  table-processing  routines  start 
on  lines  1 10  (suffix)  and  221  (prefix). 
They  are  essentially  the  same,  though 
different  tables  are  used  to  do  the 
processing.  The  subroutine  next( ), 
when  given  the  current  state  and  in¬ 
put  character,  will  return  the  next 
state.  All  possible  action  states  are 
taken  care  of  in  the  switches  on  lines 
155  and  235. 

Consonant-pair  recognition  is  done 
by  brute  force  rather  than  with  a  ma¬ 
chine.  (I  tried  to  do  a  state  machine, 
but  it  got  too  complicated).  One  of 
the  places  where  the  code  size  could 
be  reduced  is  here.  Nextchf )  (on  line 
318)  gets  the  next  input  character 
and  advances  the  string  pointer  past 
the  character.  If  a  consonant  pair  (th, 
sh,  ph,  ch,  or  gh)  is  found,  both  char¬ 
acters  are  skipped  and  an  integer  val¬ 
ue  (#defined  on  line  31  f.)  is  returned. 
The  actual  consonant-pair  processing 
routine  starts  on  line  393. 

That’s  hyphenation.  The  algorithm 
works  correctly  with  most  of  the 
words  I’ve  tried  it  on.  My  version  suc¬ 
cessfully  hyphenates  su-per-califragi- 
lis-ticex-pialido-cious,  just  like 
Knuth’s  does.  (Note  that  the  algo¬ 
rithm  doesn’t  insert  every  possible 
hyphen,  but  it  doesn’t  put  in  any  in¬ 
correct  ones  either.) 

If  anyone  doesn’t  feel  like  typing  in 
this  month’s  program,  machine  read¬ 
able  copies  of  all  source  code  from  C 
Chest  are  now  available  on  5 '/4-inch 
IBM  PC  compatible  disks  from  Soft¬ 
ware  Engineering  Consultants,  P.O. 
Box  5679,  Berkeley,  CA  94705.  The 
cost  is  $25  per  disk  (one  disk  is  one 
month’s  column).  In  addition,  my  ver¬ 
sion  of  grep,  the  Unix  generalized  reg¬ 
ular  expression  parser  presented  in 
Doctor  Dobb’s  Journal  #96,  is  still 
available  for  $35  from  the  same 
address. 

Notes 

1  Donald  E.  Knuth,  TEX  and  Meta¬ 
font  ,  New  Directions  in  Typeset- 
f/rtg(Digital  Press),pp.l 80— 1 86.  DDJ 


Dr.  Dobb’s  Journal,  October  1985 


23 

745 


TART 


^  e.lyJQ,o,u.\^w.)^.y 


'^HjsVK?)-*^) 


-^2Tyi*(22)--W86) 


-H^23Vi«(24V*-(83) 


(- >  (25W/26Vh^/87^ 


-TION-AL 


-V/27V-*Y28V*Y29V<^/89) 


-^-/^oV»^^V-W3?]rc^ 


'  S~\  e  STS  h  /->.  P  /~\  a  ^-v  r 


[35)-*-i 

(36) 

e 

39) 

[S3) 

on  1 

3 

o 

®— 

© 

-H5}, 

<v 

© 

s 

O, 

e 

46) 

(63) 

(48) 

-►© 

From  all 

unspecified  transitions 


s,d,e  >— <  e  &  d 


This  transition 
is  not  always 
done.  See 
hyphenc 
line  132. 


[84)  FAIL 
's — '  (Return  pointer 
to  original  end 
of  string) 


H*/49V-i-(^oVW87) 


'TiV^(52)-»,^53YT^^  j 

s~/ 

188)  -NARY 


Figure  1  suffixes 


24 

746 


Dr.  Dobb’s  Journal,  October  1985 


^  t^/fC 73 1  (Text  begins  on  page  20) 

Listing  One 


Changes  to  ls.c  to  fix  the  root  directory  problem. 


1)  Add  the  subroutine  isrootdir(): 


isrootdir(  name  ) 
char  ‘name; 

{ 

if(  ‘name  (.&  name[l)  =«  ) 

name  +=  2; 

return (  (‘name  ==  '\\'  ||  ‘name  ==  '/')  &&  Inamefl]  ); 


2)  Modify  the  first  if  statement  of  the  subroutine  f ixup_name ( ) 
(line  184  of  the  original  listing)  to  read: 

if(  ! isrootdir (name)  &&  !f ind_f irst (name,  ALL,  reys)  ) 


End  Listing  One 


Listing  Two 


1:  tinclude  <stdio.h> 
2:  tdefine  DEBUG 
3: 

4:  /*- . - . . 

5:  * 

6: 

7: 

8: 

9: 

10: 

11: 

12: 

13: 

14: 

15: 

16: 

17: 

18: 

19: 

20: 

21: 

22: 

23: 

24  : 

25: 

26: 

27: 

28: 

29: 

30: 


HYPHEN. C  -  An  impl ementat ion  of  the  Knuth  hyphenation  algorithm  * 

«  * 

*  Copyright  (c)  1985,  Allen  I.  Holub.  All  rights  reserved.  * 

*  - - - * 

V 

/*  Various  psuedo-subroutines.  HYPHEN  defines  a  bit  to  set  when  a  hyphen 

*  is  inserted.  HYPHENATE  sets  the  bit,  UNHYPHENATE  clears  it, 

*  HAS_HYPHEN  tests  for  it.  The  ER  macro  checks  for  an  "er"  at  the  end 

*  of  a  word,  it's  used  by  the  consonant  pair  checking  routine. 

*  Is  consonant  returns  true  if  c  is  a  consonant. 

V 


♦define 

HYPHEN 

0x80 

♦define 

HYPHENATE (c) 

(  (c) 

I-  HYPHEN  ) 

♦define 

UN HYPHEN ATE ( C ) 

(  (c) 

“HYPHEN  ) 

♦define 

HAS_HYPHEN ( C ) 

(  (c) 

&  HYPHEN  ) 

♦def ine 

ER (p,end) 

(*p  - 

=  '  e  '  £.&  *  (p+1 ) 

♦define 

isconsonant (c) 

((c) 

&&  l isvowel (c) ) 

/* 

The  dipthongs 

ch,  gh, 

ph,  sh  and  th 

(pfl) 


consonants.  The  subroutine  nextchO  will  map  these  two 
characters  into  a  single  character  as  follows: 


end ) 


3l! 

♦define 

CH 

fz' 

+ 

1  ) 

/*  ( 

0x7b 

\173 

*/ 

32: 

♦define 

GH 

('  z' 

+ 

2  ) 

/*  1 

0x7c 

\17  4 

V 

33: 

♦define 

PH 

<  •  Z  • 

+ 

3  ) 

/*  ) 

0x7d 

\175 

*/ 

34: 

♦define 

SH 

('z' 

+ 

4  ) 

/*  ‘ 

0x7e 

\176 

V 

35: 

♦define 

TH 

('*' 

+ 

5  ) 

/*  DEL 

0x7f 

\177 

*/ 

36: 

37: 

38: 

39: 

40: 

41: 

42: 

43: 

44: 

45: 

46: 

47:  ♦define  LATTICE  1 
48: 

49:  /* - 

50:  *  GLOBAL  VARIABLES: 

51:  V 
52: 

53: 

54: 


/*  S(x)  is  used  to  initialize  the  state  tables.  It  turned  out  to 

*  be  easier  to  use  a  define  than  a  typedef.  ATYPE  is  used  store 

*  strings  in  something  other  than  chars  (useful  if  we're  keeping 

*  attributes  around).  I  haven't  tried  compiling  with  ATYPE  set  to 

*  anything  except  char  so  be  careful  if  you  do. 

V 

♦define  S(x)  static  char  x[J 
typedef  char  ATYPE; 


/*  Compile  for  Lattice  C  compiler 


55: 

56: 

57: 

58: 

59: 

60: 

61: 

62: 

63: 

64: 


static  char  “States;  /*  Points  to  table  for  current  state  machine  */ 
extern  char  ‘Suffixes!);  /*  Suffix  state  table  (at  end  of  this  module)  */ 
extern  char  ‘Prefixes!!;  /*  Prefix  state  table  "  */ 


♦ifdef  DEBUG 
static  int  Debug 
fendif 


/*  True  if  debug  diagnostics  are  to  be  printed  */ 


/* 


♦ifdef  LATTICE 


(Continued  on  page  28) 


26 

748 


Dr.  Dobb’s  Journal.  October  1 985 


C  Chest  (Listing  continued,  text  begins  on  page  20) 

Listing  Two 


65: 
66: 
67: 
68: 
69: 
70: 
71: 
72: 
73: 
74: 
75: 
76: 
77: 
78: 
79: 
80: 
81: 
82: 
83: 
84: 
85: 
86: 
87: 
88: 
89: 
90: 
91: 
92: 
93: 
94: 
95: 
96: 
97: 
98: 
99: 
100: 
101: 
102: 
103: 
104: 
105: 
106: 
107: 
108: 
109: 
110: 
111: 
112: 
113: 
114: 
115: 
116: 
117: 
118: 
119: 
120: 
121: 
122: 
123: 
124: 
125: 
126: 
127: 
128: 
129: 
130: 
131: 
132: 
133: 
134  : 
135: 
136: 
137: 
138: 
139: 
140: 
141: 
142: 
143: 
144: 
145: 
146: 
147: 
148: 
149: 
150: 
151: 
152: 
153: 
154: 
155: 
156: 


extern  char  *stpchr(); 

♦define  index(s,c)  stpchr(s,c)  /*  For  some  reason  the  unix  indexO 

*  function  is  called  stpchrO  by 

*  Lattice. 

V 

♦define  register  static  /*  This  will  give  more  efficient  code 

*  since  Lattice  doesn't  support  any 

*  register  variables.  DON'T  EXPLICITLY 

*  INITIALIZE  AUTOMATIC  VARIABLES  IF 

*  THIS  IDEFINE  IS  ACTIVE. 

V 


♦  else  /*- 

char  *  index  (s,c) 

char  *s ; 

{ 

/*  Return  a  pointer  to  the  left-most  occurance  of  the 

*  character  c  in  the  string  s  or  NULL  if  the  character 

*  isn't  found. 

V 


for(;  *s  ;  s++  ) 

if(  *s  ==  c  ) 
return 


NULL; 


s; 


♦endif 

/• - V 

isvowel (c) 

ATYPE  c  ? 

{ 

/*  We  check  for  a  vowel  in  the  order  in  which  it  is  likely  to 
*  appear  in  English  (ETAOIN  SHRDLU).  Return  true  if  c  is  a  vowel. 

V 

c  &=  0x7f ; 

return  (  c== '  e  '  I  I  c== ' a  '  I  I  c== ' o '  I  I  c== '  i  '  I  I  c== '  u  '  I  I  c=«= ' y 1 ) ; 

} 


/* - V 

static  ATYPE  *suffix(  beg,  end  ) 

ATYPE  *beg ,  *end  ; 

{ 

/*  Split  off  any  suffixes,  using  the  "Suffixes"  state  table 

*  (defined  below)  to  recognize  them. 

V 


register  char  *p; 

register  int  state,  c,  c*2,  times  ; 

state  =  1; 
times  =  0; 

States  =  Suffixes; 

f or (  p  =  end;  p  >=  beg  ;) 

f 

state  =  next(  state,  c  =  *p —  &  0x7f  ); 

if(  !('a'  <«  c  &&  c  <=  'z')  )  /*  Words  containing  non¬ 
return  end;  /*  lower-case  characters  *. 

/*  won't  be  hyphenated.  */ 

/*  Test  for  state  0  with  an  if  instead  of  in  the  switch. 

*  Lattice  C  will  make  the  switch  very  inefficient  if  the 

*  range  of  case  values  is  too  high.  State  0  is  wierd; 

*  we  use  it  to  test  for  trailing  e  or  ed  on  failure. 

*  The  first  time  through  we  set  p  to  end  so  we  can  check 

*  the  end  of  the  word.  The  third  time  through  we  abort. 

*  This  means  that  we'll  retry  if  the  word  ends  -s  -e  -d 

*  -ed  -de  -dd  -ee.  The  extra  endings  shouldn't  matter. 

V 


iff  state  ==  0  ) 

{ 

if(  times  ==  0  ) 

p  =  end  ;  /*  Strip  trailing  e  or  d  */ 


else 

else 


if(  times  --!)/*  Strip  trailing  ed, 
p  =  end-1  ;  /*  ee,  or  dd 

return  end; 


de  V 

V 


times++; 

) 

switch(  state  ) 

{ 


(Continued  on  page  30) 


(Listing  continued,  text  begins  on  page  20) 

Listing  Two 


157: 

158: 

159: 

160: 

161: 

162: 

163: 

164: 

165: 

166: 

167: 

168: 

169: 

170: 

171: 

172: 

173: 

174: 

175: 

176: 

177: 

178: 

179: 

180: 

181: 

182: 

183: 

184: 

185: 

186: 

187: 

188: 

189: 

190: 

191: 

192: 

193: 

194: 

195: 

196: 

197: 

198: 

199: 

200: 


case 


/* 

*  Process  -ing.  This 

*  more  states  but  it 

V 


could  have  been  done  with 
seemed  easier  to  do  it  here. 


c  =  *p  &  0x7f  ; 
c2  =  *(p-l)  &  0x7f  ; 

if!  p  -  beg  <  3  ) 

^  return  end;  /*  FAIL  */ 

else  if (  (  c  ==  c2  )  &&  (  lisvowel(c)  ) 

&&  (  c  !=  'f ' )  &&  (  c  !=  's'  ) 

Si  (  c  1=  '1')  &  &  (  c  !  =  'z'  )  ) 

{ 

}  P# 

else  if(  c  =-  '1'  &&  index ( "bcdfghkptz" ,  c2)  ) 

p  -=  (c2  ==  'k*  &&  ( * ( p-2 )  b  0x7f )  ==  'c') 


/*  fall  through  to  state  87  */ 


case  87: 

HYPHENATE  (  Mp+1)  )? 
return  p; 

case  88: 

HYPHENATE (  *(p+2)  ); 
return (  p  +  1  ) ; 

case  89: 

HYPHENATE (  *(p+5)  ); 
HYPHENATE (  *(p+l)  ); 
return  p; 

case  82:  p++;  /*  p  +=  3  */ 

case  81:  p++;  /*  p  +=  2  */ 

case  80:  p++j  /*  P  +=  1  */ 

case  83: 


201: 

end  =  p  ; 

202: 

HYPHENATE! 

203: 

state  =  1; 

204  : 

break ; 

205: 

206  : 

case  84: 

207  : 

return  end; 

208  : 

} 

209: 

) 

210: 

211: 

• ifdef 

DEBUG 

212: 

if!  Debug  ) 

213: 

printf ("\n") ; 

214: 

tendif 

215: 

216: 

return  end; 

217: 

} 

218: 

219: 

/• 

220: 

221: 

static 

ATYPE  *prefix(  beg,  end  ) 

222: 

ATYPE 

♦beg,  #end; 

223: 

{ 

224  : 

/*  Split  off  any  prefi 

225: 

V 

226: 

227  : 

register  ATYPE  *p; 

228: 

register  int  state,  c  ; 

(P  +  1)  ); 

/*  FAILURE  STATE  */ 

- V 

sf  using  the  "Prefixes"  state  table 


229: 
230: 
231: 
232: 
233: 
234: 
235: 
236  : 
237: 
238: 
239: 
240: 
241: 
242: 

243  : 

244  : 

245  : 

246  : 
247: 
248: 


state  *  1; 

States  =  Prefixes; 


for (  p  =  beg  ;  p  <  end  ;) 

{ 

switch(  state  =  next(state,  c  =  *p++  &  0x7f)  ) 

{ 

case  82:  HYPHENATE (  *p  );  HYPHENATE (  *{p-l)  )  ;  return  p; 

case  83:  HYPHENATE  (  *p  );  HYPHENATE  (  Mp-2)  )  ;  return 

case  84:  HYPHENATE  (  *p  );  HYPHENATE!  Mp-3)  )  ;  return  p 

case  81:  — p; 
case  87:  HYPHENATE!  *p  ); 
return  p; 


case  85:  — p; 
case  86:  beg  =  p; 

state  =  1; 

case  69:  HYPHENATE!  *p  ); 


30 

750 


Dr.  Dobb’s  Journal,  October  1985 


break; 


case  70:  HYPHENATE (  *(p-l)  ); 
break  / 

/*  The  following  states  don't  actually  exist. 

*  Putting  them  in  the  table  will  cause  Lattice  to 

*  compile  the  switch  in  a  more  efficient  manner. 

*  (see  p  4-33  of  the  1985  vintage  manual). 

V 

case  71:  case  72:  case  73:  case  74:  case  75: 
case  76;  case  77:  case  78:  case  79: 
break ; 


lifdef  DEBUG 

if(  Debug  ) 

printf ("\n") j 

#endif 


return  beg; 


/*  Failure  V 


This  table  is  used  to  find  exceptions  to  the  VCCV  rule.  It  is 
indexed  by  the  first  of  the  two  consonents  &  contains  pointers 
to  strings,  any  character  of  which  will  form  an  exception  if 
it's  the  second  consonant. 


static  char 
{ 


*vccv_except ( ) 


283: 

/*  a 

*/ 

"" 

284  : 

/*  b 

*/ 

"1  r" 

285  : 

/*  c 

*/ 

"lr" 

286: 

/•  d 

V 

"gr" 

287: 

/*  e 

V 

288: 

/*  £ 

V 

"lr" 

289: 

/*  9 

V 

"lr" 

290: 

/*  h 

*/ 

■  N 

291: 

/*  1 

*/ 

R  N 

292: 

/*  j 

V 

n  " 

293: 

/*  k 

*/ 

"n" 

294: 

/*  1 

V 

"kq" 

295: 

/*  m 

*/ 

N  R 

296: 

/*  n 

*/ 

"{kx" 

297: 

/*  o 

V 

n  R 

298: 

/*  P 

*/ 

"lr" 

299: 

/*  q 

V 

n  n 

300: 

/*  r 

*/ 

"k" 

301: 

/*  S 

*/ 

"pq" 

302: 

/*  t 

V 

"(r" 

303: 

/*  u 

*/ 

"  " 

304  : 

/*  v 

*/ 

" " 

305: 

/*  w 

*/ 

"hlnr " 

306: 

/*  X 

*/ 

N  n 

307: 

/*  y 

*/ 

308: 

/*  z 

*/ 

309: 

/*  CH 

*/ 

"lr" 

310: 

/*  GH 

V 

"t" 

311: 

/*  PH 

*/ 

R^R 

312: 

/*  SH 

V 

*  II 

313: 

/*  TH 

*/ 

R^R 

/*  CH,  k,  x  V 


/*  CH,  r 


static 

ATYPE 

( 


PE  nextch(  pp,  endp  ) 
p,  *endp; 

Advance  *pp  (one  for  a  vowel  or  consonant,  2  for  a  dipthong) 
and  return  the  character  we've  just  skipped.  Dipthongs  are 
mapped  to  the  single  characters:  TH,  SH,  PH,  CH  or  GH. 

0  is  returned  once  *pp  reaches  or  passes  endp. 


register  ATYPE  rval,  *p; 

if (  (p  =  *pp)  >  endp  ) 

return  (ATYPE)  0; 

rval  =  *p++  &  0x7f  ; 

i f  (  (  *p  S.  0x7f  )  =«  'h' 

switch(  rval  ) 

{ 


case 

'  t  *  : 

rval 

=  TH; 

p++. 

break ; 

case 

's': 

rval 

*  SH; 

P++; 

break ; 

case 

'p'  : 

rval 

»  PH; 

p++; 

break ; 

case 

'c '  : 

rval 

=  CH; 

P++; 

break  ; 

case 

•g*  : 

rval 

»  GH; 

p++j 

break ; 

(Continued  on  next  page) 


Or.  Dobb’s  Journal,  October  1985 


l^UCbl  (Listing  continued,  text  begins  on  page  20) 

Listing  Two 


344  : 

345: 

346:  # ifdef 
347: 

348: 

349:  lendif 
350: 

351: 

352: 

353:  } 

354  : 

355: 

356:  /* - 

357: 

358:  static 
359:  ATYPE 
360:  { 

361: 

362: 

363: 

364: 

365: 

366: 

367: 

368: 

369: 

370: 

371: 

372: 

373: 

374: 

375: 

376: 

377: 

378: 

379: 

380: 

381: 

382: 

383: 

384: 

385: 

386: 

387: 

388: 

389:  } 

390: 

391:  /* - 

392: 

393:  static 
394:  ATYPE 
395:  { 

396: 

397: 

398: 

399: 

400  : 

401: 

402  : 

403  : 

404  : 

405: 

406  : 

407: 

408  : 

4  09: 

410: 

411: 

412: 

413: 

414  : 

415: 

416: 

417: 

418: 

419: 

420  : 

421: 

422: 

423: 

424: 

4  25: 

426: 

427: 

428: 

429: 

430: 

431: 

432: 

433  : 

4  34  : 

435: 


DEBUG 

if(  Debug  ) 

pr intf  (  "nextchO,  got  <%c>\n",  rval  )? 


*PP  =  p; 
return  rval; 


iswierd(  xr  y,  p  ) 

*p; 

/*  Return  true  if  the  string  pointed  to  by  p  ends  in: 
X Yer  XYers  XYage  XYages  XYest 

*  where  XY  is  one  of  the  pairs: 

*  ft  Id  mp  nd  ng  ns  nt  rg  rm  rn  rt  st 


register 

int 

cl,  c2, 

c3  ? 

cl  =  *p++ 
c2  =  *p++ 
c3  =  *p 

; 

! 

! 

return ( 

( 

1 1 

(  cl  == 

'e  * 

&& 

c2  ==  ' 

r  1 

(  cl  == 

'a' 

&& 

c2  ==  • 

g '  66 

c  3  =  = 

) 

1 1 

(  cl  == 

'e' 

&& 

c2  ==  ' 

s'  66 

c3  *« 

66 

( 

1  1 

(  X  =-  = 

*f  • 

&& 

y  ==  '  t 

'  ) 

(  x 

•  1 

&& 

y  “=  1  d 

’  ) 

1  1 

(  X  =■  = 

'  m ' 

&& 

y  ==  'p 

'  ) 

1  1 

(  X  ■« 

'  s ' 

&& 

y  «==  't 

1  ) 

1  1 

(  X  =« 

'n ' 

&& 

index ( 

"dgst" 

y)  ) 

1  1 

(  X  =  = 

'  r ' 

&& 

index ( 

"gmnt" 

y)  ) 

)? 


V 


consonants (  beg,  end  ) 
*beg,  *end; 


/ 


Hyphenate  consonant  pairs. 

Look  for  a  VCC  pattern:  use  the  state  machine: 


V  C  C 

Start - >  1 - >  2 - >  3 - >  4 

|C  *  I  V  |  V  I  (no  fetch  ) 

I  I  I  v  |  | 

I  < — +  + - < - +  I 

I  I 

+ - + 


*  The  machine  is  implemente  explicitly  (with  whiles  and  ifs 

*  ifs  and  such)  rather  than  with  a  table  driven  engine. 

*  A  new  character  is  fetched  in  states  1,  2,  &  3  (but  not  4). 

*  When  you  exit  reach  state  4  cl  and  c2  will  hold  the  two 

*  consonants,  cp  will  point  to  the  beginning  of  the  second 

*  consonant,  beg  will  point  just  past  the  second  consonant. 

*/ 

register  ATYPE  cl,  c2,  *cp; 
register  char  *p; 


while(  1  ) 

{ 

statel : 


do  { 

c2  =  nextch(  ibeg,  end  ); 

}  while(  isconsonant (c2)  ); 

do  { 

for(  cl  =  c2;  isvowel(cl)  ;  ) 

( 

cp  =  beg? 

cl  =  nextch(  fcbeg,  end  ); 

1 


if(cl  ==  'q'  &&  *beg  ==  'u')  /*  Vqu  */ 

{ 

HYPHENATE (  *cp  );  /*  V-qu  */ 


( Continued  on  page  34) 


32 


7W 


Dr.  Dobb's  Journal,  October  1985 


C  Chest  (Listing  continued,  text  begins  on  page  20) 

Listing  Two 


436  : 
437: 
438  : 
439: 
448: 
441: 
442: 

443  : 

444  : 

445  : 

446  : 

447  : 

448  : 

449  : 
450: 
451 : 
452  : 
453: 
454  : 
455: 
456  : 
457: 
458: 
459: 
460: 
461: 
462: 
463: 
464  : 
465: 
466: 
467: 
468: 
469: 
470: 
471: 
472: 
473: 
474: 
475: 
476: 
477: 
478: 
479: 
480: 
481: 
482: 

483  : 

484  : 
485: 
486  : 
487: 
488  : 
489: 
490  : 
491: 
492  : 
493: 

494  : 

495  : 

496  : 
497: 

498  : 

499  : 
500: 
501: 
502: 
503: 
504: 
505: 
506  : 
507: 

508  : 

509  : 
510: 
511: 
512: 
513: 
514: 
515: 
516: 
517: 
518: 
519: 
520: 
521: 
522: 
523: 
524: 
525: 
526: 
527: 
528: 
529: 
530: 


nextch (  tbeg,  end  );  /*  skip  the  u  V 

goto  statel; 

} 

cp  =  beg  ; 

c2  *  nextch  Ubeg,  end)  ; 

)  while(  isvowel(c2)  )  ; 

if (  lei  I  I  lc2  ) 
break ; 

/*  At  this  point  we  have  found  a  Vowel -Cons-Cons 

*  sequence.  Cl  and  c2  will  hold  the  left  and  right 

*  consonant  respectively.  Beg  will  point  at  the 

*  character  following  the  second  consonant.  Cp 

*  will  point  at  the  second  consant. 

V 

if (  cl  ==  'c'  &S.  c2  ==  'k'  )  /*  Vck  */ 

{ 

if(  *beg  ) 

HYPHENATE (  *beg  );  /*  Vck-  * 

} 

else  if(  cl  ==  c2  ) 

{ 

if  (  (cl  !=  '1'  &&  cl  !=  's' )  II 

(isvowel (*beg)  &&  IER (beg, end) )  ) 
HYPHENATE (  *cp  )? 

} 

else  if(  isvowel (  *beg  )  )  /*  VCCV  */ 

{ 

if(  I  iswierd(cl,  c2,  beg)  ) 

{ 

if(  l  ( (p  =  vccv_except[  (int.)cl  -  'a'  )) 
&&  index(p,  c2)  )) 
HYPHENATE (  *cp  ); 

) 

} 


} 


if (  HAS_HYPHEN(*end)  ) 

UNHYPHENATE (  *end  ); 


/* 


*/ 


hyphen ( 
ATYPE 
( 


beg,  end  ) 

*beg,  *end; 

/*  Hyphenate  the  word  deliniated  by  beg  and  end:  First 

*  strip  suffixes,  then  strip  a  trailing  s  e  or  ed, 

*  then  strip  prefixes.  Only  words  with  more  than  four 

*  letters  and  which  consist  of  lower  case  letters  only 

*  (no  MULI.s)  will  be  hyphenated.  0  is  returned  if  no 

*  attempt  was  made  to  hyphenate  the  word,  one  otherwise. 

*  If  the  word  has  already  been  hyphenated,  1  is  returned 

*  but  the  word  is  not  modified. 


register  ATYPE  *prefixp,  *suffixp; 

i f (  end-beg  <=  4  ) 
return  0; 


for  ( 

f 


} 


prefixp  =  beg?  prefixp  <=  end  ;  prefixp++  ) 

i f (  HAS_HYPHEN(*pref ixp)  ) 
return  1; 

if(  1 islower ( *pref ixp  &  0x7f)  ) 
return  0  ? 


suffixp  =  suffix (beg, end) ;  /*  Hyphenate  and  remove  anv  */ 

/*  suffixes 

if(  suffixp  ==  end  ) 

{ 

/*  If  root  has  a  trailing  s  e  or  ed  remove  it 

*  before  applying  any  other  rules 

V 

if(*end  ==  's'  II  *end  =='e') 
suffixp  **  end  -  1  ? 

else  if(  *(end-l)  =*  'e'  &&  *end  ==  'd'  ) 

suffixp  =  end  -  2  ; 


pref ixp 
lifdef  DEBUG 


prefix(  beg,  suffixp);  /*  Hyphenate  and  remove  any  */ 
/*  prefixes  */ 


V 


34 

753 


531: 

532: 

533: 

534: 

535: 

536: 

537: 

538: 

539: 

540: 

541: 

542: 

543  : 

544  : 
545: 
546  : 
547: 
548: 
549  : 
550: 
551: 
552: 
553: 
554: 
555: 
556: 
557: 
558: 
559: 
560: 
561: 
562: 
563: 
564  : 
565: 
566: 
567: 
568: 
569: 
570: 
571: 
572: 
573: 
574: 
575: 
576: 
577: 
578: 
579: 
580: 
581: 
582: 
583: 
584  : 
585: 
586: 
587: 
588  : 
589: 
590: 
591: 
592: 
593: 
594  : 
595: 
596: 
597  : 
598: 
599: 
600: 
601: 
602: 
603: 

604  : 

605  : 

606  : 
607: 
608: 
609  : 
610: 
611: 
612: 
613: 
614: 
615: 
616: 
617: 
618: 
619: 
620: 
621: 
622: 
623: 
624  : 
625: 
626: 
627: 
628: 
629: 
630: 


#endif 


/*  Print  the  word  in  the  form: 
/* 

bprint(  beg,  prefixp-1  ); 
putchar ('/')? 

bprint(  prefixp,  suffixp  )? 
putchar ('/'); 

bprint(  suffixp+1,  end  ); 

/* - 


prefixes/root/suffixes  */ 

,  V 

/*  Print  prefixes  */ 

/*  Print  middle  */ 

/*  Print  suffixes  */ 

- */ 


i f (  (suffixp  -  prefixp)  >=  3  ) 

/*  Apply  the  consonant  pairs  rules  only  if  the  word 

*  has  at  least  4  letters  in  it  (after  prefixes 

*  and  suffixes  have  been  removed 

V 


consonants(  prefixp,  suffixp  ); 


return  1; 

} 

/* - 


V 


static  next(  cur_state,  cur_char  ) 

ATYPE  cur_char  ; 
int  cur_state  ? 

{ 

/*  Given  the  current  state  and  the  current  input  character 

*  return  the  next  state.  The  global  variable  States  must 

*  point  at  the  correct  state  table  (Suffixes  or  Prefixes). 


char  *p  =  States!  cur_state  ]  ; 
#ifndef  DEBUG 


lelse 


if(  *p  ) 

return  (int) (  (cur_char  ==  p [ 1 ] )  V  p[2]  :  0  ); 

else 

return  (int.)(  p!(cur_char  -  'a')  +  1]  )? 


int  rval; 

if (  Debug  ) 

printf("%s:  Current  state  =  %d,  Input  char  =  <%c>,  ", 

States  ==  Prefixes  ?  "PREFIX"  :  "SUFFIX", 
cur_state,  cur_char  ) ; 


if (  *P  ) 

rval  =  (int)  (  (cur_char  ==  p [ 1 ) )  ?  pf2]  :  0  ); 

else 

rval  =  (int) (  p((cur„char  -  'a')  +  1]  ); 
if(  Debug  ) 

printf("Next  state  =  %d\n",  rval  ); 
return  (  rval  ) ; 

fendif 

} 


/* - 

*  State  machine  tables: 


*  There  are  two  types  of  states.  If  the  first  entry  is  0  then  the  state 

*  is  an  array  holding  the  next  state,  indexed  by  the  current  input 

*  character.  If  the  first  entry  is  1  then  all  transitions  but  one  are  to 

*  state  0,  the  next  character  is  the  one  exception  and  the  transition 

*  state  follows. 


*/ 

S(s0  >-( 

0, 

84  , 

84,84,1, 

S(sl  >  =  { 

0/ 

0,0 

,2, B, 7, 0 

S(s2  )-{ 

1, 

•  i  ' 

,3  ); 

S  (s3  )  =  [ 

1, 

'P' 

,4  ); 

S(s4  )  =  [ 

1, 

'  o ' 

,5  1; 

S(s5  )={ 

1, 

'  c ' 

,6  ) ; 

S(s6  )={ 

1  r 

's' 

,  87  ); 

S(s7  )={ 

0r 

0,0 

,  0, 0, 0, 0 

S(b8  )=( 

1, 

•b' 

,9  ), 

S(s9  )=( 

1, 

'  a ' 

,  55  ); 

S  <sl  0  )  =  { 

0r 

0,0 

,0,0,11, 

S(sll)  =  l 

1, 

•h’ 

,  12  )  , 

S  (si  2  )  =  f 

1, 

•p* 

,6  }; 

S  ( si  3  )  =  { 

0, 

0,0 

,0,0, 0,0 

S(b14)={ 

1, 

•t ' 

,  87  ); 

S(sl5)={ 

1, 

'i  1 

,  14 

S(sl6)=( 

1, 

1  a ' 

,  17  }; 

S(sl7)=( 

0 , 

0,0 

,18,0,0, 

S (si  8 )  =  { 

0, 

88, 

0,0,0,88 

S (sl9 ) = ( 

1, 

*  ' 

,  20  ); 

S (s20) = ( 

1, 

'1  ' 

,  80  }; 

S  (s 2 1 )  =  ( 

1, 

'  n ' 

,  22  ); 

S (s22 ) = ( 

1, 

’  • 

,  86  }; 

S (s23 ) = { 

0, 

25, 

0,0, 0,0, 

84,84,84,84,84,84  }; 

3, 23, 0,30, 0,0, 0,33, 38, 45, 0,0, 0,0, 49,0}; 


(Continued  on  next  page ) 


35 

7RA 


Dr.  Dobb  s  Journal,  October  1985 


C  Chest  (Listing  continued,  text  begins  on  page  20) 

Listing  Two 


631 

S(s24)=( 

i,  'f ■ , 

83  }; 

632 

S ( s2  5 ) = ( 

0,  0,0, 

87, 0, 0, 0, 0, 0,26, 0,0, 0,0, 27, 0,0, 0,0,0, 87, 0,C 

633 

S ( S26 ) = { 

P,  0,0, 

87, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 87, 0,0, 

634 

S ( s27 ) = { 

1,  'o'. 

28  }, 

635 

S(s28)={ 

1,  ' i  ' , 

29  }; 

636 

S (s29 ) = { 

1,  'f. 

89  ),• 

637 

S (s30 ) = { 

1,  'o', 

31  }; 

638 

S ( s3 1 ) = { 

1,  '1', 

32  ); 

639 

S (632) = ( 

0,  0,0, 

6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,87,0,0,0 

640 

S(s33)={ 

1,  'o'. 

34  }; 

641 

S (s34 ) = { 

1,  'h', 

35  ); 

642 

S(s35)=f 

1,  V, 

36  }; 

643 

S  ( 8  3  6  )  =  { 

1,  'a'. 

37  }; 

644 

S ( s37 )  =  f 

1  '  r  ' 

87  ); 

645 

S  ( s  3  8 )  =  ( 

0,  0,0, 

0, 0,0, 0,0, 0,0, 0,0,0, 0,0, 0,0, 0,0, 39,0, 41, 0,f 

646 

S(s39)={ 

1,  'e\ 

40  ); 

647 

S ( s4  0 )  =  { 

0,  0,0, 

0,0,0,0,0,0,0,0,0,83,0,83,0,0,0,0,0,0,0,0, 

648 

S (s41 ) = { 

1,  'o', 

42  ), 

649 

S (842) = { 

1, 

43  ); 

650 

S (s43) = ( 

1,  'c\ 

44  )j 

651 

S (s44 ) = { 

0,  88,80,88,88,88,88,88,88,88,88,88,88,88,88,88,8 

652 

88,88,88 

653 

S (b4  5 ) c | 

1,  'n', 

46  }; 

654 

S (s46 ) = | 

1,  'e'. 

47  }; 

655 

S (s47) = { 

0,  0,0, 

0,87, 0,0, 0,0, 48, 0,0, 0,83, 0,0, 0,0, 0,0, 0,0,0 

656 

S(b48)=| 

1,  'o', 

87  }, 

657 

S (b4  9 )  =  | 

0,  0,0, 

0,0,0,0,50,35,0,0,0,83,0,0,0,0,0,51,0,0,0, 

658 

S(s50)=l 

1,  'o', 

87  }, 

659 

S(s51)=l 

1,  'a'. 

52  }, 

660 

S { s52 ) H 

1,  'n\ 

53  ),• 

661 

S(b53)=| 

0,  88,88,88,88,81,88,88,88,88,88,88,88,88,88,54,8 

662 

88,88,88 

663 

S(b54)=| 

1,  '  i  '  . 

82  ); 

664 

S(s55)=I 

0,  0,0, 

0,0,80,0,0,80,80,0,80,80,0,0,80,0,0,0,0,13 

665 

666 

667 

static  char 

*Suffixes[]  «* 

668 

{ 

669 

60  ,  S  1  r 

s2,  s3,  64,  s5,  s6,  s7,  s8,  s9, 

670 

b10,  sll 

,  sl2,  sl3,  sl4,  sl5,  g16,  s17,  s18,  sl9, 

671 

s20 ,  s21 

,  s22,  s23,  s24 ,  b25,  s26,  s27,  s28,  s29, 

672 

s30,  s31 

,  s32,  s33,  634,  s35,  s36,  s37,  s38,  s39, 

673 

s40,  s41 

,  s42,  s43,  s44,  s45,  546,  s47,  s48,  s49. 

674 

s5  0 ,  s5 1 

,  s52,  s53,  s54 ,  655 

675 

i) 

676 

/* _ 

676 

679 

S(pB  )= 

a,  0,  0 

) ; 

680 

S(pl  )= 

(0,  0,2, 

4,6,9,0,0,13,22,0,0,26,29,39,41,45,50,0,53 

681 

O# 

682 

S(p2  )- 

fl,  'e', 

3  )  1 

683 

S(p3  )= 

(0,  0,0, 

81,0,0,0,0,81,0,0,0,0,0,0,0,0,0,0,81,0,0,0 

684 

S(p4  )= 

(1,  ’o'. 

5  ); 

685 

S (p5  )= 

(0,  0,0, 

0,0,0,0,0,0,0,0,0,0,87,87,0,0,0,0,0,0,0,0, 

686 

S (p6  )= 

(1,  'i\ 

7  1 ; 

687 

S (p7  )= 

U,  's'. 

8  }> 

688 

S(p8  )- 

(0,  85,85,85,85,85,85,85,  0,85,85,85,85,85,85,85,8 

689 

85,85,85 

690 

S(p9  )= 

(0,  0,0, 

0,0,0,0,0,0,0,0,0,0,0,0,0,0,10,0,0,0,0,0,8 

691 

S(pl0)= 

fl,  'u\ 

11  ) ; 

692 

S(pll)= 

(1,  '  i  ' , 

12  b 

693 

S (pl2) - 

{0,  81,81,81,81,81,81,81,81,81,81,81,81,81,81.81.8 

694 

81,  0,81 

695 

S (pl3 ) = 

f0,  14,0 

,0,0,0,0,0,0,0,0,0,0,0,0,16,0,0,0,0,0,0,0, 

696 

S(pl4)= 

U,  ’n\ 

15  ); 

697 

S(pl5)-= 

fl,  'd'. 

87  }; 

698 

S(pl6)- 

(1,  ' r  • , 

17  ); 

699 

S(pl7)« 

(1,  '6', 

18  ); 

700 

S(pl8)= 

(1,  ’e\ 

87  ), 

701 

S (pl9) * 

fl,  'P', 

20  ), 

702 

S (p20) = 

(1,  'e', 

21  b 

703 

S  ( p2 1 )  = 

fl,  'r\ 

84  }; 

704 

S (p22 ) = 

(0,  0,0, 

0,0,0,0,0,0,0,0,0,0,86,69,0,0,0,0,0,0,0,0, 

705 

S  (p23 )  *= 

{0,  81,0 

,0,0,0,81,81,0,0,0,0,81,81,0,0,0,0,0,0,0,0 

706 

S (p24 ) “ 

(0,  0,0, 

0,0,21,0,0,0,0,0,0,0,0,0,0,0,0,25,0,0,0,0, 

707 

S (p25 ) = 

fl,  'o'. 

87  ); 

708 

S (p26 ) = 

(1,  'e'. 

27  b 

709 

S (p27 ) = 

fl, 

35  ); 

710 

S (p28 ) = 

(1,  ' i  ' , 

87  }; 

711 

S(p29)= 

{0,  30,0 

,0,0,0,0,0,0,36,0,0,0,0,0,0,0,0,0,0,0,37,0 

712 

S (p30 ) = 

(0,  0,0, 

31,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,33,0,0, 

713 

S(p31)= 

(1,  'r'. 

32  ), 

714 

S(p32)= 

fl,  'o', 

83  b 

715 

S (p33) = 

U,  'h\ 

34  }; 

716 

S (p34 ) = 

(1,  'e'. 

82  }, 

717 

S (p35 ) * 

(1,  '  1  ' , 

02  }, 

718 

S (p36 ) = 

fl#  'n ' , 

35  1, 

719 

S(p37)= 

(i,  'i', 

38  ), 

720 

S (p38) * 

a,  't\ 

65  ), 

721 

S(p39)= 

fl,  'o', 

40  b 

722 

S  (p40 )  *= 

fl,  'n\ 

86  }, 

80,0} ) 


)( 


(Continued  on  page  38) 


Dr.  Dobb's  Journal,  October  1985 


36 

755 


C  Chest 

Listing  Two 

(Listing  co 

723s 

S  (p41 )  = 

(0, 

0,0 

,  0, 0, 0, 0 

724: 

S (p42) = 

u. 

't' 

,  87  }  ; 

725: 

S (p43) = 

u. 

'e' 

,  44  }; 

726: 

S (p44  )  = 

fl, 

•r ' 

,  86  )  , 

727: 

S (p45) * 

fi, 

's' 

,  46  }) 

728: 

S(p46)= 

(1, 

'e' 

,  47  >) 

729: 

S (p47) = 

(1, 

'u' 

,  48  )  ; 

730: 

S (p4  8 )  = 

(1, 

•d' 

,  49  }: 

731: 

S (p49 ) - 

(1, 

'o' 

,  83  ); 

732: 

S (p50) = 

(1, 

'u' 

,  51  }, 

733: 

S  (p51 )  “ 

(1, 

'a' 

,  52  ), 

734: 

S(p52)  = 

{1, 

'd' 

,  87  ); 

735: 

S  (p53)“ 

(0, 

0,0 

,0,0,54, 

736: 

S(p54)= 

(1, 

'm ' 

,  28  ), 

737: 

S  (p55 )  “ 

(1, 

•m' 

,  18  1; 

738: 

S  (p56)  = 

(0, 

0,87,0,0,0, 

739: 

S  (p57)  = 

(0, 

0,0 

,  0, 0, 0, 0 

740: 

S(p58)= 

(1, 

'e' 

,  59  ); 

741: 

S  (p59  J  ■= 

fl. 

■r' 

,  60  1| 

742: 

S  (p60 )  = 

fl, 

'e' 

,  87  ): 

743: 

S(p61)“ 

{0. 

62, 

0,0, 0,0, 

744  : 

S  { p62  )  = 

fl, 

■n' 

,  63  ], 

745: 

S  ( p6  3  )  * 

fl, 

's' 

,  23  }) 

746: 

S  (p64 )  * 

(1, 

■n’ 

,  66  ], 

747: 

S(p65)- 

(1, 

•  i  • 

,  83  ); 

748: 

749: 

S  (p66)  = 

(0, 

85, 

85,85,70 

750: 

S  ( p6  7 )  = 

fl. 

•r' 

,  87  ); 

751: 

S  ( p6  8  )  = 

10, 

81, 

0,0, 0,0, 

752: 

753: 

S  (p69 )  “ 

(0, 

B5, 

85,85,85 

754: 

755: 

756: 

S(p70)- 

fl, 

'e' 

,  67  ); 

757: 

static  char 

•Prefix 

758 


85,85,85,85,05,85,85,85  }; 


85,24,85,85,85,85,85,85  }? 


759  t 

p0, 

pi. 

p2, 

pi, 

P<, 

P5, 

p6 , 

Pl, 

p8. 

P9, 

760* 

pl0. 

pn. 

pl2, 

pi  3 , 

pl  4 , 

pl  5 , 

pl  6 , 

pl  7 , 

pl  8 , 

P19 

761* 

p20. 

p2 1 , 

p22, 

p23. 

p24, 

p25 , 

p26 , 

p27 , 

p2  8 , 

P29 

762: 

p30 , 

p31 , 

p32. 

p33, 

p34. 

p35. 

p36, 

p37, 

p3R, 

p39 

763* 

p40. 

p41, 

p42. 

P<3, 

p4  4 , 

p45, 

p46 , 

p47 , 

p48. 

p49 

764* 

p5  0  , 

P5 1 , 

p52. 

p53. 

p5  4 , 

p55 , 

p5  6  , 

p57. 

p58 , 

p59 

765* 

p60 , 

p61 , 

p6  2 , 

p6  3. 

p6  4 , 

p6  5  , 

P6  6, 

p67. 

p68. 

p69 

766* 

p70 

767 

768 

769 

770 

771 

772 

773 

774 

775 

776 

777 

778 

779 

780 

781 

782 

783 

784 

785 

786 

787 
788: 
789: 
790: 
791: 
792: 
793: 
794  : 
795: 

796  : 

797  : 
798: 
799: 
800: 
801: 
802: 
803: 
804: 
805: 
806: 
807: 
808: 
809: 
810: 
811: 
812: 
813: 
814: 
815: 
816: 


}} 

/* 


V 


MSC  DEBUGGING  ROUTINES  (INCLUDING  A  MAIN  MODULE): 


fifdef  DEBUG 
bprint(  b  ,  e  ) 

ATYPE  *b,  *ej 

{ 

/*  Print  all  characters  between  b  and  e  inclusive  */ 


while(  b  <»  e  ii  *b  ) 

putchar(  *b++  &  0x7f  )j 


char 

char 

{ 


*sgets(  prompt,  buf,  maxch  ) 

♦prompt,  *buf; 

/*  Print  prompt  to  stdout,  then  fill  buf  fm  stdin.  Get  at  most 

*  maxch  characters.  Return  pointer  to  string  terminator  (ie.  the 

*  null  at  the  end  of  the  string)  or  0  on  end  of  file. 

V 

register  int  cj 

register  char  *start; 

start  =  buf; 
pr intf (  prompt  ) ; 

whilet  (c  =  getchar(J) 

*buf++  *  c; 


EOF  &&  c  1=  '\n'  --maxch  >  0  ) 


*buf  ■»  0; 
return  (c 


EOF  &&  start 


buf)  ?  (char  *)0  :  buf  ; 


-v 


phyphen (  p  ) 

ATYPE  *pi 

{ 

/*  Print  a  word  with  all  the  hyphens  visible 

*/ 

printf ("<*) ) 


38 


Dr.  Dobb's  Journal,  October  1985 


817: 
818: 
819: 
820: 
821: 
822: 
823: 
824: 
825: 
826: 
827: 
828: 
829: 
830: 
831: 
832: 
833: 
834  : 
835: 
836: 
8  37: 
838: 

839 

840 

841 

842 

843 

844 

845 

846 

847 

848 

849 
850: 


for (  ;  *p  7  putchar (*p++  &  0x7f)  ) 
if (  HAS_HYPHEN ( *p)  ) 
putchar 

printf  ("An")  ; 


/* 


main(argc,  argv) 


int 

char 


argc ; 

*  *argv ; 


char 


buf [ 1 32 ] ,  *p; 


if  (  argc  >  1  ) 
Debug  = 


1  7 


while(  p  ■  sgets(  "word:  ",  buf,  132  )  ) 

{ 

i f (  I *buf  ) 

printf ("\n") 7 

else  if(  p —  >  buf  ) 


{ 


hyphen (  buf,  p  )? 
putchar ( * \t • ) 7 
phyphen(  buf  ): 


#endif 


End  Listing  Two 


Listing  Three 


I  Diagnostic  output  front  hyphen. c  when  compiled  with  the  DEBUG  Idefined:  I 
+ - - - + 


word:  be/cxxxe/able 
word:  be/hxxxh/able 
word:  be/sxxxi/able 
word:  be/wxxxk/able 
word:  com/xxxl/able 
word:  con/xxxo/able 
word:  dis/xxxu/able 
word:  equi/xxv/able 
word:  ex/xxxxw/able 
word:  hand/xxxxxx/able 
word:  hor se/xxxxy/able 
word:  hyper/xxxnt/able 
word:  im/xxxxxxrt/able 
word:  in/xxxxxxion/ary 
word:  inter/xxxxen/a ry 
word:  int ro/xxxxx/na ry 
word:  lexi/xxxxxxx/cal 
word:  macro/xxxxa/cate 
word:  mathe/xxxxe/cate 
word:  maxi/xxxxxi/cate 
word:  mini/xxxxxo/cate 
word:  mult i/xxxxu/cate 
word:  non/xxxxxxy/cate 
word:  out/xxxxxxx/cial 
word:  over/xxxxx/cious 
word:  pseudo/xx/scious 
word:  quad/xxxxx/cient 
word:  semi/xxxxxx/dent 
word:  some/xxxxxxx/f ul 
word:  sub/xxxxxxxl/ize 
word:  super/xxxxa/late 
word:  there/xxxxe/la te 
word:  trans/axxxi/late 
word:  t rans/f xxxo/late 
word:  t rans/gxxxu/late 
word:  t rans/1 xxxy/late 
word:  t rans/mxxxx/less 
word:  t r i/axxxxxxxx/ly 
word:  t r i/f xxxxxxxx/ly 
word:  tr i/uxxxxxxxx/ly 
word:  under/xxxxx/ment 
word:  un/xxxxxxxx/ness 
word:  /xxxxx/ogy 
word:  /xx/rapher 
word:  /xxx/raphy 
word:  /xxx/scope 
word:  /xx/scopic 
word:  /xxx/scion 
word:  /xx/sphere 
word:  /xxxxx/tal 
word:  /xxxx/tial 
word:  /xxxx/tion 
word:  /xx/tional 
word:  /xxxx/tive 
word:  /xxxx/ture 
word:  /xxxxxl/ing 
word:  /xxxxl/ing 
word:  /xxxg/gling 
word:  /xxxa/bling 


<be-cxxxe-able> 

<be-hxxxh-able> 

<be-sxxxi-able> 

<be-wxxxk-able> 

<com-xxxl-able> 

<con-xxxo-able> 

<dis-xxxu-able> 

<equi-xxv-able> 

<ex-xxxxw-able> 

<hand-xxxxxx-able> 

<horse-xxxxy-able> 

<hy-per-xxxnt-able> 

<im-xxxxxxrt-able> 

<in-xxxxxxion-ary> 

<in-ter-xxxxen-ary> 

<in-tro-xxxxx-nary> 

<lex-i-xxxxxxx-cal> 

<mac-ro-xxxxa-cat.e> 

<math-e-xxxxe-cate> 

<max-i-xxxxxi-cate> 

<min-i-xxxxxo-cate> 

<mul-t i-xxxxu-cate> 

<non-xxxxxxy-cate> 

<out-xxxxxxx-cial> 

<over-xxxxx-cious> 

<p8eu-do-xx-scious> 

<quad-xxxxx-cient> 

<semi-xxxxxx-dent> 

<some-xxxxxxx-f ui > 

<sub-xxxxxxxl-ize> 

<su-per-xxxxa-late> 

<there-xxxxe-late> 

<trans-ax-xxi-late> 

<trans-fxxxo-late> 

<trans-gxxxu-late> 

<trans-lxxxy-late> 

<t rans-mxxxx-less> 

<tr i-ax-xxxxxxx-ly> 

<tri-fxxxxxxxx-ly> 

<t r i-ux-xxxxxxx-ly> 

<un-der -xxxxx-ment > 

<un-xxxxxxxx-ness> 

<xxxxx-ogy> 

<xx-rapher> 

<xxx-raphy> 

<xxx-scope> 

<xx-scopic> 

<xxx-scion> 

<xx-spher  e> 

<xxxxx-tal > 

<xxxx-t ial> 

<xxxx-t ion> 

<xx-t ion-al > 

<xxxx-tive> 

<xxxx-ture> 

<xxxxxl-ing> 

<xxxxl-ing> 

<xxxg-gl ing> 
<xxxa-bl ing> 


(Continued  on  next  page) 


Dr.  Dobb's  Journal,  October  1 985 


39 

757 


L  x^flCSI  (Listing  continued,  text  begins  on  page  20) 

Listing  Three 


word : 
word: 
word : 
word: 
word : 
word : 
word : 
word : 
word : 
word : 
word : 
word : 
word : 
word : 
word : 
word: 
word : 
word : 
word : 
word : 
word : 
word : 
word : 
word : 
word : 
word: 
word : 
word: 
word : 
word : 
word : 
word : 
word : 

word : 
word: 

word : 
word : 
word : 
word : 
word : 
word : 
word : 
word : 
word : 
word : 
word : 
word : 
word : 
word : 
word : 
word : 
word : 
word : 
word : 
word : 
word : 
word : 
word : 
word : 
word : 
word : 
word : 
word : 
word : 
word : 
word : 
word : 
word : 
word : 
word : 
word : 
word : 
word : 
word : 
word : 
word: 
word: 
word : 
word : 
word : 
word : 
word : 
word : 
word : 
word : 
word : 
word : 
word : 
word : 
word : 


/xxxck/ling 

/xxxg/kling 

/duck/ing 

/fit/ting 

/hiss/ing 

/swell/ing 

/f ic/t ion 

/f ic/t ional 

/f ic/tionalize 

/f ic/t ional i zed 

/f ic/t ional izes 

/uuuccuuu/ 

/uuuquuuu/ 

/uuuckuuu/ 

/uuuccuuu/ 

/uuuchchuu/ 

/uuughghuu/ 

/uuuphphuu/ 

/uuushshuu/ 

/ uuuththuu/ 

/uuuxxxxxx/ 

/uuullaxxxx/ 

/uuussexxxx/ 

/uuufteee/e 

/uuuxxer/ 

/uuuxxer/s 

/uuuxxag/e 

/uuuxxage/s 

/uuuxxest/ 


<xxxck-ling> 
<xxxg-kl i ng> 
<duck-ing> 

<f it-ting> 
<hiss-ing> 
<swell-ing> 

<f ic-t ion> 

<f ic-t ion-al> 

<fic-tion-al-ize> 

<f ic-t ion-al-ized> 

<f ic-t ion-al-izes> 

<uuuc-cuuu> 

<uuu-quuuu> 

<uuuck-uuu> 

<uuuc-cuuu> 

<uuuch-chuu> 

<uuugh-ghuu> 

<uuuph-phuu> 

<uuush-shuu> 

<uuuth-thuu> 

<uuux-xxxxx> 

<uuul-lax-xxx> 

<uuus-sex-xxx> 

<uuuf-teeee> 

<uuux-xer> 

<uuux-xer s> 

<uuux-xage> 

<uuux-xages> 

<uuux-xest> 


<Test  repetetive  suffix  and  prefix  removal :> 

disiminnonoverun/en/aryf ulizelessl ymentableness 

<di s-im-in-non-over-un-en-ary-f ul-i ze-1 ess-ly-ment-able-ness> 

super /call f ragilisticexpi al ido/c ious 

<su-per-calif ragil is-t icex-pial ido-cious> 

<A1 1  words  following  this  point  test  exceptions  to> 

<the  hyphenation  rules  and  shouldn't  be  hyphenated. > 


/uuussx/ 

<uuussx> 

/uuull x/ 

<uuullx> 

/uuuller/ 

<uuul 1 er> 

/uuull er/s 

<uuullers> 

/uuubluu/ 

<uuubluu> 

/uuubruu/ 

<uuubruu> 

/uuucluu/ 

<uuucluu> 

/uuucruu/ 

<uuucruu> 

/uuuchluu/ 

<uuuchl uu> 

/uuuchruu/ 

Cuuuchr uu> 

/uuudguu/ 

<uuudguu> 

/uuudruu/ 

<uuudruu> 

/uuuf luu/ 

<uuuf 1 uu> 

/uuuf  r uu/ 

<uuuf ruu> 

/uuughtuu/ 

<uuughtuu> 

/uuugluu/ 

<uuugluu> 

/uuugruu/ 

<uuugruu> 

/uuuknuu/ 

<uuuknuu> 

/uuulkuu/ 

<uuulkuu> 

/uuulquu/ 

<uuulquu> 

/uuunchuu/ 

<uuunchuu> 

/uuunkuu/ 

<uuunkuu> 

/uuunxuu/ 

<uuunxuu> 

/uuuphruu/ 

<uuuphruu> 

/uuupl uu/ 

<uuupluu> 

/uuupruu/ 

<uuupruu> 

/uuurkuu/ 

<uuurkuu> 

/uuuspuu/ 

<uuuspuu> 

/uuusquu/ 

<uuusquu> 

/uuutchuu/ 

<uuutchuu> 

/uuut ruu/ 

<uuut ruu> 

/uuuthruu/ 

<uuuthruu> 

/ uuuwhuu/ 

<uuuwhuu> 

/uuuwluu/ 

<uuuwluu> 

/uuuwnuu/ 

<uuuwnuu> 

/uuuwruu/ 

<uuuwruu> 

/uuuf ter/ 

<uuuf ter> 

/uuuf ter/s 

<uuuf ters> 

/uuuf tag/e 

<uuuf tage> 

/uuuf tage/s 

<uuuf tages> 

/uuuf test/ 

Cuuuf test > 

/uuulder/ 

<uuulder  > 

/uuumper/ 

<uuumper> 

/uuunder/ 

<uuunder > 

/uuunger/ 

<uuunger> 

/uuunser/ 

<uuunser > 

/uuunter/ 

<uuunter > 

/  mi'i  roor  / 

<uuurqer> 

/uuurner/ 

<uuurner> 

/uuurter/ 

<uuurt er> 

/uuuster/ 

<uuuster> 

End  Listings 


40 

758 


Dr.  Dobb's  Journal,  October  1985 


A  Threaded-Code 
Microprocessor 
Bursts  Forth 

Subroutines  Without 
Performance  Anxiety 

by  Leo  Brodie 

Subroutine  calls  in  one  clock  cycle  are 
one  advantage  as  Forth  goes  to  silicon. 


The  Novix  NC4000  processor  has  arrived,  and  it  I 
threatens  to  shake  most  of  our  beliefs  about  micro¬ 
processor  design.  What  implications  does  this  new 
technology  have  for  the  art  of  programming?  This  article 
will  explain  the  techniques  used  by  Forth  and  by  the 
NC4000  microprocessor,  and  explore  the  impact  of  this 
technology  on  software  design. 

Subroutines:  The  More  the  Better 

Most  programmers  recognize  the  importance  of  dividing 
a  large,  complex  program  into  a  group  of  smaller,  man¬ 
ageable  pieces.  The  use  of  smaller  “modules”  is  advanta¬ 
geous  for  the  following  reasons: 

•  smaller  pieces  of  code  are  easier  to  think  about,  and 
therefore  easier  to  design  and  to  write; 

•  smaller  pieces  of  code  are  easier  to  debug;  the  likelihood 
of  introducing  a  bug  is  reduced,  and,  if  one  does  appear,  it 
will  be  easier  to  find; 

•  different  parts  of  a  program  may  be  assigned  to  different 
programmers; 

•  subroutines  can  be  used  to  hide  information  about  things 
that  might  change  in  subsequent  revisions;  if  a  change 
must  be  made  to  a  program,  only  the  subroutine  has  to  be 
changed. 

Unfortunately,  most  conventional  high-level  languages 
(HLLs)  are  not  optimized  for  the  invocation  of  subrou¬ 
tines.  One  problem  is  the  loss  of  time  caused  by  saving 
registers  before  a  jump  to  a  subroutine  and  restoring  them 
afterwards.  This  has  been  estimated  as  40%  of  program 
execution  time.  Even  more  time-consuming  is  the  invisi¬ 
ble  but  significant  code  needed  to  pass  arguments  to  and 
from  the  subroutine.  At  the  machine  level  as  well,  proces¬ 
sors  are  not  generally  optimized  for  invoking  subroutine 
calls. 

For  these  reasons,  although  the  use  of  many,  small  sub¬ 
routines  is  an  ideal,  nevertheless,  in  practice,  program¬ 
mers  tend  to  writer  fewer,  larger  subroutines.  When  effi¬ 
ciency  is  at  stake,  it’s  hard  to  appreciate  the  elegance  of 
subroutines. 

There  is  a  high-level  programming  language  optimized 
for  subroutines:  Forth.  And  now,  a  single-chip  micropro¬ 
cessor  is  available  that  applies  Forth’s  optimization  tech¬ 
niques  directly  to  a  silicon  architecture.  The  result  is  a 
machine  that  can  execute  high-level  subroutine  calls  and 
returns  in  a  single  clock  cycle! 

Threaded  Code 

Let’s  begin  by  examining  what  the  inherent  architecture 
of  Forth  is,  regardless  of  which  processor  it  runs  on.  Forth 
is  implemented  using  a  technique  called  “threaded  code.” 
Suppose  we  define  a  word  in  Forth  called  PROCESS,  con¬ 
sisting  of  three  operations. 

:  PROCESS  STEP1  STEP2  STEP3  ; 

In  this  definition,  the  name  of  the  word  (subroutine)  is 
PROCESS.  It  consists  of  three  lower-level  subroutines.  The 


Dr.  Dobb's  Journal,  October  1985 


42 

759 


PROCESS 

link 

code  pointer 

code  pointer  field 

adr  of  STEP  1 

adr  of  STEP  2 

adr  of  STEP  3 

adr  of  EXIT 

Figure  1 

A  compiled  definition  in  the  dictionary  of  an  emulated  Forth 
system 


"colon  definition"  machine-code 


definition 

Figure  2 

The  code  pointer  of  an  assembler  definition  (e.g..  STEP1) 
points  to  its  own  definition.  The  code  pointer  of  a  high-level 
definition  (e.g.,  PROCESS)  points  to  a  prologue  routine  called 
CALL. 


return  stack 


Figure  3 

When  STEP2's  CALL  routine  is  invoked,  it  saves  I  (the  inter¬ 
preter  pointer)  on  the  return  stack,  puts  the  address  of  the 
body  of  STEP2's  definition  in  I,  then  invokes  NEXT. 


colon  begins  the  definition,  while  the  semicolon  ends  it. 

Figure  1  (page  44)  shows  what  this  definition  looks  like 
when  compiled  into  memory  on  an  emulated  Forth  sys¬ 
tem.  Such  a  definition  consists  of  the  following  parts: 

•The  name  field  contains  an  ASCII  representation  of  the 
word’s  name,  so  that  the  word  can  subsequently  be  found 
and  executed. 

•The  link  field  points  to  the  previous  definition  in  the 
linked  list  of  dictionary  entries.  (A  search  through  the 
dictionary  for  a  given  word  begins  with  the  most  recently 
defined  word,  and,  using  the  link  field  for  each  word,  pro¬ 
ceeds  backwards  through  the  chain  or  chains  of  words.) 

•  The  code  pointer  field  points  to  machine-executable 
code  that  is  generic  to  each  “family”  of  definitions.  As  yet 
we  have  only  discussed  colon  definitions;  we  will  intro¬ 
duce  other  types  later. 

•  The  remainder  of  the  definition  is  a  list  of  addresses  of 
other  routines.  Depending  on  the  processor,  these  ad¬ 
dresses  are  usually  16-  or  32-bit  numbers.  In  this  exam¬ 
ple,  the  addresses  of  the  routines  STEP1,  STEP2,  and 
STEP 3  are  listed.  At  the  end  of  the  list  is  the  address  of 
another  routine  called  EXIT,  which  is  compiled  by  the 
semicolon  and  which  terminates  the  definition  when  it  is 
executed. 

The  unusual  thing  about  threaded  code  is  the  absence  of 
any  CALL  instructions — everything  is  a  subroutine.  Defi¬ 
nitions  consist  simply  of  addresses  of  other  definitions.  As 
we’ll  see,  even  data  structures  are  defined  as  dictionary 
entries,  so  that  their  addresses  appear  in  definitions  when 
referenced. 

If  you  expand  your  imagination  a  bit,  you  can  view 
these  addresses  as  actual  instructions,  since  each  address 
is  unique.  This  is  exactly  the  way  they  are  viewed  by  the 
Forth  machine,  and  in  particular,  by  the  routine  called 
the  “address  interpreter,”  also  known  as  NEXT. 

It  is  the  function  of  the  address  interpreter  to  execute 
each  “Forth  instruction”  in  turn.  This  function  is  analo¬ 
gous  to  the  operation  of  a  machine  processor  wherein  each 
opcode  is  interpreted  and  the  program  counter  advanced. 
An  “interpreter  pointer,”  called  I  (usually  a  register  in 
the  real  processor)  operates  analogously  to  the  processor’s 
program  counter:  during  execution  of  any  Forth  instruc¬ 
tion,  I  points  to  the  next  instruction  to  be  executed;  it  is 
incremented  by  the  address  interpreter. 

To  begin  with  the  simplest  case  possible,  let’s  assume 
that  each  of  the  definitions  STEP1,  STEP2,  and  STEP3  is 
defined  in  machine  code — they  are  not  high  level  Forth 
definitions.  (Forth  allows  routines  to  be  coded  in  high- 
level  code  or  in  machine  code  without  distinction;  in  prac¬ 
tice,  however,  only  a  handful  of  time-critical  or  hardware- 
dependent  routines  must  be  machine-coded.)  Here’s  what 
happens,  in  somewhat  simplified  form  for  now.  I  is  point¬ 
ing  to  the  first  address  in  the  definition  of  PROCESS;  this 
is  the  address  of  STEP1.  The  address  interpreter,  NEXT, 
first  advances  I  to  the  next  cell  (the  one  containing  the 
address  of  STEP2),  then  jumps  to  STEP1.  STEP1  is  a  ma¬ 
chine  code  definition,  executed  in  the  normal  way  by  the 


44 

760 


Dr.  Dobb's  Journal,  October  1985 


underlying  processor.  The  final  piece  of  code  in  STEP1  is 
NEXT  once  again  (either  in-line  or  as  a  jump  instruction, 
depending  on  whether  performance  is  a  more  or  less  im¬ 
portant  consideration  than  memory  usage  in  the  particu¬ 
lar  hardware  configuration).  This  second  NEXT  incre¬ 
ments  I  to  the  third  cell,  then  jumps  to  STEP2,  and  so  on. 

Each  machine-coded  definition  (called  a  primitive) 
must  end  by  executing  the  code  for  NEXT  to  keep  the 
Forth  machine  running. 

In  the  preceding  discussion,  we  omitted  one  important 
detail.  The  address  interpreter  does  not  jump  to  the  ad¬ 
dress  of  each  new  instruction,  but  rather  to  the  code 
pointed  to  by  the  code  field  of  each  new  instruction — one 
added  level  of  indirection.  Figure  2  (at  left)  shows  this 
relationship. 

In  the  case  of  a  machine  code  definition,  the  code  field 
points  to  the  body  of  the  definition  itself,  where  the  actual 
machine  instructions  have  been  compiled.  In  the  case  of 
all  colon  (high-level)  definitions,  however,  the  code  field 
points  to  a  single  routine  in  memory  that  we  will  name 
CALL.  Essentially  this  routine  handles  the  job  of  nesting 
definitions,  so  that  one  high-level  word  may  invoke  anoth¬ 
er  word,  which  in  turn  invokes  another,  and  so  on. 

Here  is  what  CALL  does,  step  by  step.  Using  our  previ¬ 
ous  example,  let’s  now  suppose  that  STEP2  has  been  de¬ 
fined  as  a  colon,  or  high-level,  definition.  We  are  execut¬ 
ing  PROCESS,  and  have  just  completed  STEP1.  I  (the 
interpreter  pointer)  is  pointing  to  the  address  of  STEP2. 
The  address  interpreter  advances  the  pointer  in  anticipa¬ 
tion  of  the  return,  then  gets  the  address  of  STEP2.  Using 
this  address,  it  jumps  to  the  address  pointed  to  by  the  code 
pointer  field  of  STEP2.  Since  STEP2  is  a  high-level  defini¬ 
tion,  the  routine  at  this  address  is  CALL.  This  routine 
saves  I  on  a  special  stack  for  return  addresses  called  the 
return  stack.  It  then  places  into  I  the  address  of  the  begin¬ 
ning  of  the  instruction  list  in  STEP2  (see  Figure  3,  at  left). 
This  done,  CALL  executes  NEXT.  Thus  the  Forth  ma¬ 
chine  continues  as  before,  but  has  nested  in  a  level. 

The  opposite  of  the  CALL  function  is  EXIT,  which  ter¬ 
minates  each  high-level  definition.  When  the  EXIT  in 
STEP2  is  encountered,  it  pops  the  top  of  the  return  stack 
back  into  the  interpreter  pointer,  thus  resuming  execution 
of  PROCESS  at  the  address  of  STEP3. 

Because  of  the  extra  level  of  indirection  offered  by  the 
code  pointer  field,  the  architecture  we’ve  described  has 
been  called  indirect  threaded  code  (ITC). 

The  value  of  the  code  pointer  field  is  that  each  word 
knows  what  kind  of  procedure  it  is,  and  how  to  invoke 
itself.  This  is  what  makes  it  possible  in  Forth  to  design  a 
routine  first  in  high-level  code,  then  to  rewrite  it  in  ma¬ 
chine  code  without  changing  any  code  elsewhere  that  in¬ 
vokes  the  routine. 

Each  family  of  data  structures  in  Forth  uses  a  unique 
prologue  routine,  pointed  to  by  the  code  fields  of  all  mem¬ 
bers  of  that  family.  A  simple  example  is  the  constant,  as 
illustrated  by  Figure  4  (page  45).  A  Forth  constant  is  a 
dictionary  entry,  possessing  the  same  type  of  header 
structure  as  all  dictionary  entries.  Its  body  consists  of  a 
code  pointer  for  all  constants,  along  with  the  value  of  the 


HUNDRED 

link 

code  pointer 
100 


the 

"do-constant" 

routine 


Figure  4 

A  Forth  constant  is  a  normal  dictionary  entry  whose  code 
pointer  points  to  a  routine  common  to  all  constants. 


i  i 


Forth  on 

conventional 

processors 


NEXT 


^ _ 

r  i 

Instruction 

Instruc¬ 

tion 

Instruction 

Instruction 

(sfower  than  conventional  machine  code) 


Forth 

engine 


NEXT  I 

P ! 

(faster  than  conventional  machine  code) 


Figure  5 

Comparison  of  overhead  for  NEXT 


name  field 


last  operation  including  return 


Figure  6 

A  definition  list  on  the  NC4000. 


SAMPLE 

_ [ink _ 

1  opcode 
0  address 
0  address 
0  address 
1  opcode  40 


Dr.  Dobb's  Journal.  October  1985 


45 

761 


constant.  The  code  pointer  points  to  a  routine,  which  we 
will  call  “do-constant,”  whose  job  it  is  to  fetch  the  value 
from  the  body  of  the  constant,  then  invoke  NEXT. 

The  Forth  Engine  in  Silicon 

In  indirect  threaded  code  implementations  of  Forth  as  just 
described,  the  invoking  of  words  is  elegant  and  more  effi¬ 
cient  than  the  calling  of  subroutines  in  other  high-level 
languages.  However,  because  conventional  microproces¬ 
sors  are  not  optimized  for  ITC,  running  the  Forth  machine 
involves  some  performance  overhead.  This  overhead  con¬ 
sists  of  the  time  spent  executing  NEXT  after  each  machine 
instruction,  plus  the  time  spent  for  each  call  and  return. 
Running  high-level  Forth  versus  straight  machine  code  can 
increase  execution  time  by  as  much  as  100  percent  (which 
is  still  better  than  other  high-level  languages). 

A  primary  goal  of  the  NC4000  architecture  is  to  allow 
the  execution  of  threaded  code  with  as  little  overhead  as 
possible.  This  goal  is  achieved  first  by  eliminating  NEXT 
as  a  software  routine.  Instead,  NEXT  has  become  the 
background  to  the  chip’s  operation.  The  overhead  of 
NEXT  is  reduced  from  many  clock  cycles  to  less  than  one 
(see  Figure  5,  at  right). 

Secondly,  the  NC4000  reduces  the  normal  overhead  of 
the  CALL  operation.  In  fact,  it  achieves  this  goal  to  the 
utmost  degree  possible:  a  subroutine  call  executes  in  a 
single  clock  cycle.  Compare  this  with  the  15  clocks  re¬ 
quired  by  the  8086  to  effect  a  machine-level  subroutine 
call.  A  high-level  language  such  as  C  will  require  still 
more  instructions  to  effect  a  jump  to  subroutine. 

How  does  the  NC4000  achieve  these  results?  One  of 
the  bits  (the  MSB)  of  the  16-bit  opcode  is  reserved  exclu¬ 
sively  to  indicate  a  subroutine  call.  For  all  ordinary  ma¬ 
chine  instructions,  this  bit  is  one;  but  if  this  bit  is  zero,  the 
NC4000  effects  a  call  to  the  subroutine  whose  address  is 
specified  by  the  remaining  15  bits.  Thus,  colon  definitions 
appear  as  compiled  object  code  as  shown  in  Figure  6  (at 
right). 

As  you  can  see,  compiled  object  code  for  the  NC4000 
consists  of  a  mixture  of  actual  machine  instructions  and 
single-instruction  subroutine  calls  to  definitions.  In  other 
words,  the  NC4000  executes  high-level  Forth  threaded 
code  as  its  native  machine  language. 

Of  actual  machine  instructions  the  NC4000P  boasts 
over  170  combinations,  not  including  permutations  of 
register  addressing.  These  include  the  usual  Forth  primi¬ 
tives  for  data-stack  manipulation,  arithmetic,  memory, 
register,  and  I/O  fetches  and  stores,  jumps  and  loops 
even  multiply,  divide,  and  square  root  steps. 

The  use  of  the  high-order  bit  enables  the  chip’s  logic  to 
decode  each  instruction  early  in  the  clock  cycle.  If  the 
instruction  is  a  CALL,  then  the  address  represented  by  the 
remaining  15  bits  will  appear  on  the  address  bus  in  time 
for  the  appropriate  instruction  (in  the  called  routine)  to 
be  latched  and  executed  on  the  next  cycle. 

The  NC4000  does  even  better  with  the  EXIT  routine. 
Another  of  the  1 6  bits  in  the  opcode  (octal  40)  is  reserved 
exclusively  to  indicate  the  return  operation.  The  return 
can  occur  simultaneously  with  other  operations  of  the 


ALU.  In  a  single  clock  cycle,  a  word  can  perform  its  last 
operation  and  return  to  the  word  that  invoked  it.  Thus,  in 
most  cases,  there  is  no  overhead  for  the  EXIT  operation. 

One  consequence  is  that  program  space  is  limited  to 
32K  addresses.  Since  the  NC4000  uses  word  addresses , 
not  byte  addresses,  the  program  may  occupy  64K  bytes  of 
memory.  Given  the  extreme  compactness  of  threaded- 
code  systems  in  general,  and  of  this  instruction  set  in  par¬ 
ticular  (each  instruction  can  perform  up  to  five  operations 
simultaneously),  64K  of  program  memory  will  contain  a 
considerable  amount  of  code  compared  to  traditional  sys¬ 
tems.  Since  memory  operators  use  full  16-bit  addresses, 
program  size  may  be  further  compacted  by  placing  data 
buffers  and  tables  above  the  32K  word  boundary  instead 
of  in  the  program  region.  For  those  who  still  aren’t  satis¬ 
fied,  Novix  is  currently  considering  development  of  a  32- 
bit  version  of  the  NC4000  architecture. 

A  necessary  ingredient  in  this  design  is  the  implemen¬ 
tation  of  a  return  stack  in  hardware.  The  return  stack  can 
be  expressly  manipulated  by  the  programmer,  but  it  is 
also  controlled  automatically  by  the  CALL  and  EXIT  op¬ 
erations  of  the  processor.  The  NC4000  features  a  unique 
parallel  architecture  that  allows  the  return  stack  to  oper¬ 
ate  simultaneously  with  other  CPU  operations. 

Notice  in  Figure  6  that  the  code  field  is  no  longer  need¬ 
ed,  because  Forth  is  the  native  machine  code  of  the 
NC4000.  The  CALL  routine,  for  instance,  which  serves  as 
the  prologue  for  colon  definitions,  now  exists  in  processor 
logic,  and  is  invoked  whenever  bit  1 5  is  zero.  In  the  case  of 
constants,  the  pointer  to  the  old  “do-constant”  routine 
can  now  be  replaced  by  a  literal-fetch  instruction. 

In  fact,  each  dictionary  entry,  whether  a  definition,  a 
constant,  variable,  array,  or  what-have-you,  is  most  effi¬ 
ciently  compiled  as  a  definition  and  directly  executed. 
Thus,  the  Novix  processor  uses  “direct-threaded-code” 
(DTC),  not  ITC. 

To  summarize,  the  NC4000  realizes  the  Forth  machine 
directly  in  silicon  logic,  without  even  resorting  to  micro¬ 
code.  As  a  result,  it  supports  a  subroutine  call  and  return 
with  an  overhead  of  one  clock  cycle  in  most  instances,  and 
never  more  than  two.  Comparable  CALL  and  RET  in¬ 
structions  on  the  8086  require  a  total  of  34  clock  cycles 
(and  each  8086  cycle  is  two  to  five  times  slower  than  that 
of  the  Novix  prototype). 

The  Software  Implications 

What  does  all  of  this  mean  to  the  programmer?  It  is  easily 
demonstrated  that  the  NC4000  runs  faster  than  conven¬ 
tional  micros.  Because  Forth  instructions  execute  in  a  sin¬ 
gle  clock  cycle,  the  chip  runs  Forth  code  about  100  times 
faster  than  Forth  running  on  a  conventional  processor.  A 
benchmark  using  the  Sieve  of  Eratosthenes  reveals  that  the 
NC4000  runs  Forth  over  10  times  faster  than  the  68000 
runs  its  own  machine  code.  (This  benchmark  was  made  on 
the  NC4000P,  the  initial  CMOS  gate-array  prototype;  fu¬ 
ture  revisions  will  increase  the  speed  considerably.) 

But  these  benchmarks  only  begin  to  show  the  potential 
power  of  the  NC4000  architecture.  A  benchmark  such  as 
the  Sieve  represents  a  straightforward  algorithm,  which  is 
|  best  coded  in  the  linear  terms  of  conventional  processors. 


46 


Dr.  Dobb’s  Journal,  October  198. 


There  is  nothing  to  be  gained  by  decomposing  such  an 
algorithm  into  smaller  modules.  Although  a  10-times 
speed  improvement  is  nothing  to  be  ashamed  of,  the  Sieve 
benchmark  doesn’t  let  the  NC4000  show  its  true  stuff. 

The  real  wonder  of  the  Novix  chip  is  that  it  allows 
execution  of  an  elegant,  high-level,  modular  language  di¬ 
rectly  in  the  logic  of  the  CPU. 

Forth  and  the  NC4000  offer  three  distinct  benefits  in 
terms  of  programming  methodology.  The  first  is  the  inter¬ 
changeability  of  a  word  without  affecting  any  code  that 
invokes  the  word.  The  second  is  the  ability  to  use  words, 
which  tend  to  be  shorter  and  simpler,  instead  of  subrou¬ 
tines  which  tend  to  be  longer.  The  third  is  that  more  effi¬ 
cient  subroutines  encourage  greater  freedom  in  the  use  of 
modular  programming.  Let’s  examine  these  points  more 
closely. 

Interchangeability 

In  the  world  of  software  development,  it’s  no  longer  enough 
just  to  get  a  program  running.  The  program  must  also  be 
able  to  be  maintained  and  easy  to  change.  Why  would  a 
program  ever  need  to  change,  once  it’s  running?  You  may 
need  to  upgrade  the  program  to  run  on  newer  equipment. 
You  may  need  to  add  more  sophisticated  features  to  keep 
up  with  the  competition.  In  fact,  most  software  groups  find 
themselves  writing  families  of  programs;  that  is,  they  write 
many  versions  of  related  programs  in  their  application 
field,  each  a  variant  on  an  earlier  program. 

To  be  maintainable,  a  program  must  be  easy  to  read, 
easy  to  understand,  and,  most  importantly,  structurally 
durable.  By  this  I  mean  that  you  can  rewrite  a  section  of 
the  program  without  having  the  whole  thing  collapse. 

Here  is  an  example  of  how  Forth  encourages  structur¬ 
ally  durable  code.  Suppose  we  have  a  variable  called  STA¬ 
TUS.  In  Forth  this  is  defined  as 

VARIABLE  STATUS 

One  component  of  our  application,  a  device  driver,  for 
example,  writes  into  STATUS,  another  component  reads 
STATUS.  References  to  STATUS  are  present  throughout 
the  program. 

In  Forth,  variables  are  locations  that  can  be  acted  up¬ 
on.  For  instance,  in  the  phrase 

STATUS  @ 

the  word  @  (pronounced  “fetch”)  fetches  the  value  of 
STATUS.  In  the  phrase 

STATUS  ! 

the  word  !  (pronounced  “store”)  stores  a  given  value  into 
STATUS.  In  either  phrase,  the  execution  of  the  word  STA¬ 
TUS  is  the  same:  it  merely  returns  the  address  of  the  loca¬ 
tion  of  its  data.  This  address  is  then  used  by  the  @  or  ! 
operators  (or  by  any  other  memory  manipulation 
operation). 

Now  suppose  we  must  write  the  next  generation  of  the 
program.  This  time  we  are  monitoring  ten  devices  instead 
of  just  one  and  we  must  handle  a  STATUS  variable  for 
each  of  them.  What  we  need  is  a  ten-cell  array  and  a 
pointer  to  the  cell  in  the  array  for  the  current  device.  In 


Forth  we  merely  define: 

CREATE  STATUSES  20  ALLOT  (10  ceils) 
VARIABLE  DEVICE  (  number  of  current  device) 

:  STATUS  (  -  -  adr) 

DEVICE  @  2*  STATUSES  +  ; 

On  the  first  line  we  have  defined  a  10-cell  array  (20  bytes) 
called  STATUSES.  On  the  second  line  we’ve  created  a 
variable  that  will  contain  the  number  of  the  current  de¬ 
vice  (between  0  and  9).  On  the  third  line  we’ve  defined  the 
word  STATUS  to  fetch  the  number  of  the  current  device, 
multiply  this  by  two  (to  compute  the  offset  into  the  array 
in  bytes),  then  add  it  to  the  address  of  the  top  of  the  array. 
The  result  is  the  address  of  the  cell  containing  the  status 
for  the  current  device. 

Now  the  other  components  of  the  application  may 
write 

STATUS  @ 
and 

STATUS  ! 

as  before,  even  though  we  have  changed  the  definition  of 
STATUS  from  that  of  a  variable  to  a  colon  definition  — 
from  a  data  structure  to  a  procedure.  Forth  allows  us  to 
hide  the  details  of  how  STATUS  is  defined  from  the  code 
that  uses  it.  What  appears  to  be  a  thing  (a  variable)  to  the 
original  code  is  actually  defined  now  as  an  action. 

Words,  Not  Subroutines 

Strictly  speaking,  Forth  words  are  no  different  than  ordi¬ 
nary  subroutines.  However,  because  they  are  optimized, 
and  because  they  require  no  special  calling  command  in 
the  code  that  invokes  them,  Forth  encourages  a  finer 
granularity  in  the  decomposition  of  an  application.  In 
other  words,  Forth  words  tend  to  be  much  shorter  and 
more  numerous  than  typical  subroutines  in  other  lan¬ 
guages.  This  characteristic  imparts  a  style  that  is  unique 
to  Forth,  and  it  is  one  of  its  most  difficult  characteristics 
to  describe. 

Here  is  an  example  of  a  collection  of  subroutines  that 
might  be  written  in  a  conventional  language: 

ENABLE-LEFT  MOTOR 

ENABLE-RIGHT-MOTOR 

DISABLE-LEFT-MOTOR 

DISABLE-RIGHT-MOTOR 

EN  A  BLE-LEFT-SOLENOI D 

ENABLE-RIGHT-SOLENOID 

DISABLE-LEFT-SOLENOID 

DISABLE-RIGHT-SOLENOID 

In  Forth  one  might  replace  these  eight  subroutines  with 
these  six  words: 

ENABLE 

DISABLE 

LEFT 

RIGHT 

MOTOR 

SOLENOID 


Dr.  Dobb's  Journal,  October  1985 


~7  CO 


Now  our  syntax  reduces  to  simple,  English  words,  com¬ 
bined  in  useful  phrases: 

ENABLE  LEFT  MOTOR 

ENABLE  RIGHT  MOTOR 

Etc. 

Not  only  do  we  reduce  the  number  of  routines  from  eight 
to  six,  we  also  make  each  word’s  definition  much  shorter 
and  simpler.  The  following  listing  shows  how  little  code 
we  might  need  to  define  the  syntax: 

-1  CONSTANT  ENABLE  (  all  “one”s) 

0  CONSTANT  DISABLE  (  all  zeros) 

1  CONSTANT  LEFT  (bit  mask) 

2  CONSTANT  RIGHT  (bit  mask) 

:  MOTOR  (action  side  -  -)  ’MOTOR  SET-BIT  ; 

:  SOLENOID  (action  side  -  -)  ’SOLENOID  SET-BIT  ; 
We  have  defined  the  word  SET-BIT  to  require  three  argu¬ 
ments:  a  flag,  either  all  ones  or  all  zeros;  a  bit-mask,  with 
a  “1”  indicating  the  desired  bit;  and  an  address.  SET-BIT 
sets  the  bit  indicated  by  the  mask  at  the  given  address 
according  to  the  given  flag.  The  words  ’MOTOR  and  ’SO¬ 
LENOID  provide  the  hardware  addresses  of  the  ports  that 
communicate  to  the  motor  and  solenoid  respectively;  bits 
zero  and  one  of  these  addresses  control  the  left  and  right 
devices,  respectively. 

In  well-written  Forth  code,  the  average  definition  con¬ 
sists  of  only  seven  words,  and  fits  within  a  single  line  of 
code  (or  two  lines  if  a  long  comment  is  used).  Few  other 
languages  encourage  you  to  create  subroutines  called 
LEFT  and  RIGHT,  merely  to  act  as  “adjectives.  ”  Nor 
would  you  find  routines  such  as  MOTOR  that  consist  of 
only  two  other  words. 

In  conventional  programming  languages,  subroutines 
tend  to  be  longer  and  more  complex,  covering  many  possi¬ 
ble  options.  A  conventional  subroutine  will  contain  a 
number  of  conditional  branches,  so  that  it  can  decide  how 
to  behave  depending  on  the  desired  function.  Typically, 
many  of  these  decisions  shouldn’t  have  to  be  made  at  run¬ 
time;  they  arise  only  because  the  subroutine  is  too  gener¬ 
al.  It  has  not  been  decomposed  sufficiently. 

The  product  of  good  decomposition  is  simplicity.  Sim¬ 
plicity  is  the  key  not  only  to  efficient,  compact  programs, 
but  also  to  programs  that  are  easy  to  write  and,  therefore, 
easy  to  test  and  debug.  They  are  also  easy  to  understand 
and,  therefore,  easy  to  maintain. 

Modularization 

The  third  way  that  optimized  subroutines  can  enhance  pro¬ 
gram  maintainability  is  by  allowing  the  program  designer 
to  break  the  application  into  very  small,  useful  modules. 
Modularization  is  the  key  to  well-written  Forth  code. 

Modularization  is  more  than  merely  chopping  code  up 
into  small  pieces.  There  is  no  way  to  automate  good  modu¬ 
larization.  It  is  something  that  arises  from  the  program¬ 
mer’s  intimate  understanding  of  the  problem  being  solved. 
Allowing  the  problem  to  decompose  itself  naturally  is  a 
critical  part  of  the  early  design  phase — one  that  requires 
the  programmer’s  experience,  sensitivity,  and  artistry. 

The  best  use  of  modularization  is  to  decompose  the  ap- 

48 

7R4 


plication  into  components.  A  component  is  a  resource, 
such  as  a  UART  driver,  a  queue,  a  linked  list,  a  stack,  etc. 
Each  resource  will  typically  consist  of  a  number  of  func¬ 
tions  and  data  structures. 

The  idea  of  component  programming  is  to  define  all 
these  functions  and  data  structures  in  the  same  place,  not 
scattered  throughout  the  program  as  they  are  needed. 
The  primary  advantage  is  the  ability  to  change  the  com¬ 
ponent  if  necessary  by  editing  code  in  only  one  location. 

In  Forth,  component  programming  means  creating  a 
set  of  names  (words)  to  represent  the  functions  and  struc¬ 
tures  of  each  component.  The  set  of  words  veil  the  inter¬ 
nal  algorithms  and  structures,  the  “how  it  works.’’  They 
present  only  a  conceptual  model  of  the  compenent  de¬ 
scribed  in  natural  language,  the  “what  it  does.” 

These  words  then  become  the  language  for  describing 
the  data  structures  and  algorithms  of  components  written 
at  a  higher  level.  The  “what”  of  one  component  becomes 
the  “how”  of  a  higher-level  component.  A  Forth  applica¬ 
tion  is  nothing  but  a  collection  of  components. 

Thus,  Forth  programming  consists  of  extending  the 
root  language  toward  the  application,  providing  new  com¬ 
mands  that  can  be  used  to  describe  the  problem  at  hand. 
Forth  is  an  environment  for  creating  “application-orient¬ 
ed  languages,”  i.e. ,  programming  languages  designed  for 
particular  applications.  Examples  include  robotics  lan¬ 
guages,  control  languages,  data  acquisition  languages, 
and  music  languages. 

Conclusion 

Charles  Moore,  the  creator  of  Forth  and  chief  architect  of 
the  NC4000  processor,  has  claimed  that  the  Forth  chip 
represents  “a  landmark  in  the  evolution  of  hardware  and 
software.  This  article  has  only  tapped  the  surface  of  the 
many  revolutionary  hardware  features  of  the  NC4000.  Its 
high-speed  computation  and  I/O  characteristics  have 
made  the  processor  immediately  interesting  to  engineers 
concerned  with  bandwidth  in  applications  such  as  tele¬ 
communications  and  signal  processing. 

Its  virtues  are  not  limited  to  hardware  features  and 
real-time  applications,  however.  I  hope  that  this  article 
has  at  least  suggested  some  of  the  elegance  of  the  proces¬ 
sor’s  native  language,  and  that  you  will  have  a  chance  to 
experience  its  unique  style  on  your  own. 

Note 

For  more  information  on  threaded  code  languages,  refer 
to:  “An  Architectural  Trail  to  Threaded-Code  Systems,” 
by  Peter  M.  Kogge,  in  Computer ,  March  1982;  also  to 
Threaded  Interpretive  Languages  by  Ronald  Loeliger, 
McGraw-Hill/Byte  books.  For  further  reading  on  the 
Novix  processor,  see  “Fast  processor  chip  takes  its  infrac¬ 
tions  directly  from  Forth,”  Electronic  Design ,  March  21, 
1985,  or  Programmer’s  Introduction  to  the  Novix 
NC4000P  Microprocessor  available  from  Novix,  10590 
N.  Tantau  Ave.  ,  Cupertino,  CA  95014.  For  a  discussion 
of  Forth  methodology,  see  Thinking  Forth  by  Leo  Brodie 
(Prentice-Hall,  1984). 


Dr.  Dobb's  Journal,  October  1985 


Design  of  a  Forth  Target  Compiler 


by  Howard  H.  Robinson, 
Philip  D.  Morse  II  and 
Sidney  A.  Bowhill 


Forth  is  a  programming  language 
that  is  both  flexible  and  extensi¬ 
ble.  It  also  generates  threaded 
code  that  is  extremely  compact  and 
executes  rapidly.  Forth  also  allows  the 
programmer  to  stay  within  a  higher- 
level  programming  environment, 
while  at  the  same  time  making  use  of 
all  of  the  low-level  capabilities  of  the 
computer.  These  are  ideal  character¬ 
istics  for  a  language  to  be  used  in  the 
development  of  programs  for  ma¬ 
chines  with  their  code  in  PROM. 

We  had  been  developing  code  for 
PROM-based  applications  for  several 
years.  Primarily  we  used  assemblers. 
After  we  became  acquainted  with 
FIG-Forth,  we  realized  that  it  had  the 
potential  to  be  an  excellent  program¬ 
ming  environment  for  the  develop¬ 
ment  of  code  for  PROM  applications. 
There  were,  however,  two  serious 
drawbacks.  Firstly,  there  are  large 


essary  for  the  execution  of  the  appli¬ 
cation  program.  It  also  directs  any 
code  that  must  be  resident  in  RAM  to 
the  RAM  area  of  the  target  machine. 

The  General  Design  of  the 
CICFIG  Recompiler 

The  program  described  in  this  article 
is  a  recompiler  of  the  type  specified 
above.  It  allows  programs  to  be  devel¬ 
oped  in  Forth  on  a  host  machine  and 
recompiled  to  produce  the  target  ap¬ 
plication  code.  It  was  developed  by 
the  Central  Illinois  Chapter  of  the 
Forth  Interest  Group  (CICFIG)  and 
works  in  conjunction  with  the  native 
host  Forth  compiler. 

To  produce  the  machine  execut¬ 
able  target  code,  source  code  for  the 
application  program  is  compiled  by 
the  standard  Forth  compiler  within 
the  host  system.  The  recompiler  (it¬ 
self  already  compiled  into  the  host 


By  recompiling  Forth  programs ,  you  can  develop 
tight,  PROMable  code  that  executes  at  the  top 
speed  the  target  microprocessor  will  allow. 


sections  of  the  compiled  threaded 
Forth  code  that  are  not  needed  for 
the  functioning  of  the  PROM-based 
application,  but  are  difficult  to  ex¬ 
clude  from  the  code  (such  as  the 
Forth  compiler  itself).  Secondly,  sec¬ 
tions  of  the  code  must  be  in  RAM 
(such  as  self-modifying  code,  vari¬ 
ables  and  pseudo-registers). 

What  was  needed  to  rectify  these 
problems  was  a  post-compiler.  A 
post-compiler,  or  recompiler,  selects 
from  the  compiled  threaded  Forth 
code  just  those  segments  that  are  nec- 


H.  Robinson  et  al..  University  of  Illi¬ 
nois,  1406  W.  Green  St.,  Urbana,  IL 
61801 


Forth  system)  selects  parts  of  the 
host  system  along  with  the  compiled 
application  code  to  be  included  in  the 
target  code.  The  recompiler  relocates 
all  the  addresses  within  this  code  to 
conform  to  the  memory  map  of  the 
target  machine,  supplied  by  the  ap¬ 
plication  programmer.  The  resulting 
minimum  length  code  can  be  execut¬ 
ed  from  PROM,  and  potentially  can 
perform  tasks  at  the  maximum  speed 
of  the  microprocessor.  The  code  also 
conforms  to  the  memory  map  of  the 
target  machine,  and  can  stand  alone 
(i.e.  does  not  necessarily  require  an 
operating  system  or  monitor). 

One  of  Forth’s  greatest  advantages 
is  that  its  interpreter  can  execute  new- 

52 


Dr  Dobb's  Journal,  October  1985 


765 


ly  compiled  code.  This  property  great¬ 
ly  simplifies  debugging:  compiled 
parts  of  the  program  can  be  tested  and 
debugged  individually  with  the  inter¬ 
preter.  To  extend  this  advantage  to 
the  process  of  debugging  the  target 
application,  we  have  written  the  re¬ 
compiler  so  that  the  target  machine 
code  can  optionally  include  the  inter¬ 
preter.  When  the  program  is  fully  op¬ 
erational,  the  minimum  code  can  be 
generated  by  excluding  the  interpreter 
(and  the  verb  headers  required  for  the 
interpreter  to  operate).  In  addition, 
once  the  interpreter  has  been  exclud¬ 
ed,  the  user  has  access  only  to  those 
parts  of  the  program  allowed  by  the 
application  programmer;  the  fact  that 
the  program  is  compiled  Forth  code 
should  be  completely  transparent  to 
the  user. 

Compiled  Forth  Code 

The  recompiler  has  as  input  the  ap¬ 
plication  program  already  compiled 
by  the  host  Forth  system.  To  under¬ 
stand  what  the  recompiler  must  do,  it 
is  necessary  to  understand  the  struc¬ 
ture  of  compiled  FIG-Forth-model 
code.  The  model  includes  three  parts: 
the  start-up  code,  an  inner  interpret¬ 
er,  and  the  dictionary.  The  start-up 
code  and  the  inner  interpreter  are 
contiguous  in  low  memory  in  the  host 
Forth  system  and  thus  can  be  treated 
as  one  block  that  must  be  included  in 
the  target  machine  code. 

The  start-up  code  initializes  the 
registers  required  for  the  operation  of 
the  Forth  inner  interpreter  and  then 
jumps  to  the  inner  interpreter.  The 
inner  interpreter  uses  these  registers 
to  arrange  the  sequential  execution  of 
verbs  from  the  dictionary. 

The  dictionary  is  a  set  of  verbs  in 
one  linked  list.  Each  verb  defines  a 
sequence  of  operations  to  be  executed 
by  the  machine.  Each  verb  has  three 
parts:  a  header,  a  code  field,  and  a 
parameter  field.  The  header  specifies 
the  name  of  the  verb  (so  the  verb  can 
be  identified  within  the  dictionary),  a 
link  address  (so  the  dictionary  forms 
one  linked  list  of  verbs),  and  certain 
special  bits  that  are  signals  to  the 
compiler  of  the  host  Forth  system. 

The  code  field  contains  the  address 
of  the  machine  executable  code  to 
which  the  inner  interpreter  will  cause 

54 


the  processor  to  jump  when  that  verb 
is  executed  at  run-time.  The  parame¬ 
ter  field  contains  the  information 
that  is  necessary  for  the  function  of 
the  verb  to  be  accomplished.  This 
may  be  data,  as  in  the  case  of  vari¬ 
ables  (and  arrays  and  constants),  or  a 
list  of  addresses  of  other  verbs  that 
are  to  be  performed  sequentially  (e.g. 
a  colon  definition).  Alternatively,  the 
parameter  field  may  contain  machine 
executable  code  that  performs  the 
function  of  the  verb  directly.  In  this 
case,  the  code  field  contains  the  ad¬ 
dress  of  the  parameter  field. 

Since  Forth  code  consists  largely  of 
lists  of  addresses,  we  have  devised  a 
novel  algorithm  for  identifying  all  ad¬ 
dresses  in  the  host  Forth  system  that 
may  need  to  be  redirected  for  inclu¬ 
sion  in  the  target  machine  code.  The 
procedure  calls  for  two  complete 
copies  of  the  host  Forth  system,  in¬ 
cluding  the  compiled  application  pro¬ 
gram.  These  two  Forth  systems  are 
identical  except  that  one  Forth  sys¬ 
tem  has  its  origin  exactly  201  Hex  lo¬ 
cations  higher  in  memory  than  the 
other  (i.e.  one  has  its  origin  at  100H 
and  the  other  at  301 H).  When  equiv¬ 
alent  locations  in  both  images  are 
compared,  all  numbers  (pairs  of 
bytes)  differing  by  201  H  must  be  ad¬ 
dresses.  This  algorithm  identifies  all 
absolute  addresses,  whether  they  are 
embedded  in  machine  executable 
code,  or  used  as  data  (such  as  the  list 
of  code  field  addresses  in  the  parame¬ 
ter  field  of  a  colon  definition). 

Forth  Systems  for  Target 
Recompilation 

The  recompiler  described  here  is  a 
program  written  entirely  in  high  level 
Forth  (MVP-Forth).  The  recompiler 
is  itself  compiled  into  an  existing 
Forth  system.  The  application  pro¬ 
gram  is  also  compiled  into  the  same 
host  Forth  system.  The  recompiler 
examines  the  compiled  application 
code  and  determines  all  code  seg¬ 
ments  necessary  for  the  proper  func¬ 
tioning  of  the  application  program  in 
the  target  machine.  A  file  is  generat¬ 
ed  that  contains  a  copy  of  those  nec¬ 
essary  code  segments.  All  addresses 
in  the  code  segments  in  that  file  have 
been  corrected  to  reflect  the  squeez¬ 
ing  and  the  relocation  of  the  host 


Forth  system  to  generate  the  target 
code  image.  This  file  then  contains 
the  code  for  the  application  program, 
which  can  be  loaded  into  the  target 
machine  and  executed. 

The  code  generated  by  the  recom¬ 
piler  is  intended  to  be  executed  on  a 
target  machine  with  a  microprocessor 
that  is  code-compatible  with  the  host 
microprocessor;  thus,  the  recompiler 
does  not  provide  the  function  of  a 
cross-compiler.  At  present,  the  recom¬ 
piler  can  only  generate  code  to  be  exe¬ 
cuted  on  either  an  8080/Z80  compati¬ 
ble  or  an  8086/88  microprocessor 
based  target  machine.  Development 
machines  for  these  two  families  of  mi¬ 
croprocessors  are  common  and  can  be 
quite  modest  (for  instance,  only  one 
floppy  disk  drive  is  required). 

We  have  considered  adapting 
other  Forths  to  allow  the  use  of  other 
microprocessors  (such  as  the  6502). 
Time  limitations,  however,  have  pre¬ 
cluded  this.  In  addition,  not  all  Forth 
systems  are  easily  adapted  for  recom¬ 
pilation  of  this  kind.  The  code  to  be 
included  by  the  recompiler  in  the  tar¬ 
get  machine  code  must  comply  with 
the  following  three  rules. 

Rule  #  1 :  All  non-addresses  in  the  two 
compiled  Forth  images  discussed 
above  must  be  equal.  Thus,  data  stor¬ 
age  areas,  such  as  arrays,  must  be  ini¬ 
tialized.  This  rule  is  only  relaxed  in 
the  case  of  variables,  due  to  the  possi¬ 
ble  use  of  certain  system  variables  in 
both  the  host  Forth  system  and  in  the 
target  machine  code  (such  as  PREV, 
USE  and  SEC). 

Rule  #2;  All  inter-verb  relative  ad¬ 
dressing  mode  jumps  are  forbidden 
(intra-verb  relative  jumps  are  al¬ 
lowed).  This  rule  is  necessary  be¬ 
cause  the  algorithm  for  detecting  ad¬ 
dresses  does  not  detect  relative  jump 
addresses.  The  versions  of  the  target 
compiler  available  from  CICFIG  (see 
the  section  on  Availability,  page  68) 
have  all  the  inter-verb  relative  jumps 
in  their  kernels  removed. 

Rule  #3:  Both  the  host  and  the  appli¬ 
cation  compiled  code  must  not  con¬ 
tain  machine  code  jumps  and  calls 
from  one  code  level  verb  to  another 
code  level  verb.  This  practice,  com- 


7RC 


Dr.  Dobb's  Journal,  October  1985 


mon  in  FIG-Forth  model  systems,  can 
be  eliminated  by  recoding  certain 
parts  of  the  kernel. 

MVP-Forth  implemented  on  CP/ 
M-80  and  MSDOS  machines  seemed 
to  conform  most  nearly  to  all  our  re¬ 
quirements.  In  order  to  make  it  com¬ 
ply  with  the  above  stated  rules,  we 
changed  the  kernel  of  the  MSDOS 
version  of  MVP-Forth  to  replace  all 
relative  jumps  to  the  inner  interpreter 
with  absolute  jumps.  The  verbs 
OBRANCH,  </LOOP>  and 
<LOOP>  were  recoded  to  remove 
jumps  to  BRANCH.  A  relative  jump 
in  <  +  LOOP>  to  <LOOP>  was 
changed  to  an  absolute  jump.  Both 
< VOCABULARY79>  and  <VO- 
CABULARYFIG>  were  changed  to 
contain  an  absolute  jump  to  DO¬ 
DOES.  DOES>  was  changed  to  put 
in  an  absolute  jump  to  DODOES. 

The  kernel  of  the  CP/M-80  version 
of  MVP-Forth  was  changed  to  com¬ 
plete  the  following  verbs  without  in- 
ter-verb  jumps:  — ,  OBRANCH,  <, 
</LOOP>,  <?TERMINAL>, 
<CR>,  <KEY>,  <LOOP>,  SEC- 
READ,  and  U*.  Two  instructions 
were  added  to  the  beginning  of  the 
start-up  code  that  read  and  load  an 
initial  value  into  the  cell  that  is  to 
function  as  the  pseudo-register  for 
the  return-stack  pointer.  This  was 
necessary  to  allow  this  cell  to  be  ini¬ 
tialized  in  RAM  when  the  start-up 
code  segment  is  in  PROM. 

In  both  versions,  the  verb 
<  +  LOOP>  has  an  inter-verb  jump 
to  BRANCH.  This  case  is  handled  as 
an  exception  by  the  recompiler.  We 
intended  that  the  compiler  of  Forth 
should  not  be  included  in  the  resul¬ 
tant  target  machine  code.  Attempts 
to  use  the  compiler  in  the  target  ma¬ 
chine  will  have  unpredictable  results. 

User  Interface 

Generation  of  the  target  machine 
code  with  this  recompiler  is  a  two- 
step  process.  The  two  steps  are  per¬ 
formed  by  the  two  host  Forth  systems 
TARG1  and  TARG2.  These  two 
Forth  systems  have  the  recompiler  al¬ 
ready  compiled  into  them  and  are 
identical  except  that  they  have  their 
origins  20 1H  locations  apart. 

The  application  programmer  speci¬ 
fies  the  memory  map  of  the  target  ma¬ 


chine  by  loading  the  constants  indicat¬ 
ed  in  Figure  1  (page  57).  These 
constants  indicate  where  the  origin  of 
the  target  machine  code  is  and  to  what 
value  pointers  to  any  desired  RAM  ar¬ 
eas  of  the  target  machine  are  initial¬ 
ized.  For  simplicity,  this  memory  map 
conforms  to  the  memory  map  of 
MVP-Forth.  The  application  pro¬ 
grammer  may  rearrange  this  map  to 
suit  the  nature  of  the  particular  prob¬ 
lem. 

The  application  programmer  must 
specify  the  verb  in  the  host  dictionary 
that  is  to  serve  as  the  cold  start  verb 
of  the  target  machine  code.  This  is 
done  by  loading  the  constant 
TCOLDCFA  with  the  code  field  ad¬ 
dress  (cfa)  of  the  target  machine  cold 
start  verb.  This  verb  should  perform 
any  initialization  of  the  Forth  system 
that  may  be  required  by  the  applica¬ 
tion  program  (for  example,  the  “user 
area”  in  RAM  may  need  to  be  initial¬ 
ized).  The  examples  given  in  the  fol¬ 
lowing  sections  explain  other  initiali¬ 
zations  that  may  be  required, 
depending  upon  the  nature  of  the  ap¬ 
plication  program.  The  cold  start 
verb  must  invoke  the  application  pro¬ 
gram.  In  this  way  the  recompiler  can 
detect  and  include  in  the  target  code 
all  verbs  necessary  for  the  operation 
of  the  cold  start  verb  and  the  applica¬ 
tion  program. 

Upon  start-up  of  the  target  ma¬ 
chine  code,  the  start-up  routine  will 
first  be  performed.  This  will  start  the 
inner  interpreter,  which  executes  the 
cold  start  verb  and  then  the  applica¬ 
tion  program. 

The  procedure  for  the  generation 
of  target  code  is  outlined  on  page  76. 
The  recompiler  is  initiated  with  a 
zero  on  the  parameter  stack.  This  sig¬ 
nals  to  the  recompiler  that  headers 
should  be  removed  from  all  verbs  to 
be  included  in  the  target  machine 
code.  Alternatively,  if  the  parameter 
field  address  (pfa)  of  the  root  verb  of 
the  application  is  on  top  of  the  stack, 
then  headers  will  be  included  in  the 
target  machine  code.  This  allows 
verbs  in  the  target  machine  code  to  be 
“found”  (by  -FIND).  In  this  case  the 
cold  start  verb  usually  invokes  an  in¬ 
terpreter  such  as  the  verb  QUIT  (see 
example  #3). 

In  example  #1  (see  screens 


Dr.  Dobb’s  Journal,  October  1985 


200-201,  page  76)  the  recompiler  is 
used  to  generate  headerless  code  with 
the  Forth  outer  interpreter  excluded 
for  a  disk-based  task.  This  program 
accepts  HEX  numbers  from  the  key¬ 
board  and  sends  them  to  the  MSDOS 
device  PRN  (usually  the  printer). 
This  is  useful  for  setting  up  dot-ma¬ 
trix  printers  in  various  modes. 

Screen  #200  loads  a  1  into  the  con¬ 
stant  MAPFILE  to  signal  the  genera¬ 
tion  of  a  DOS  file,  <codefile>.MAP, 
which  will  contain  (in  ASCII)  the  ad¬ 
dresses  and  names  of  all  verbs  in  the 
target  machine  code.  This  screen  also 
loads  the  constants  that  define  the 
memory  map  of  the  target  machine 
code  (see  Figure  1).  Loading  these 
constants  is  required  prior  to  the  ini¬ 
tiation  of  the  recompiler. 

Screen  #201  contains  the  applica¬ 
tion  program  for  example  #1.  The 
verb  TINPUT  is  for  numerical  input 
from  the  keyboard.  This  avoids  the 
Forth  verb  ABORT”  that  threads  to 
the  outer  interpreter  (which  we  are 
trying  to  exclude).  LP  sends  a  byte  to 
the  PRN  device  and  APP  is  the  router 
and  root  verb  of  the  application  pro¬ 
gram.  Screen  #201  also  has  the  verb 
TCOLD  which  performs  the  initializa¬ 
tion  required  to  load  the  “user  area” 
of  RAM.  The  constant  TCOLDCFA 
has  been  loaded  with  the  code  field 
address  of  the  verb  to  be  executed 
upon  start-up.  The  code  generated  by 
this  program  is  less  than  2K. 

Example  #2  (screens  202-203,  see 
page  76)  demonstrates  the  minimum 
code  length  that  can  be  generated  by 
the  MSDOS  MVP-Forth  version  of  the 
recompiler.  Screen  #202  loads  the 
memory  map  for  the  target  machine. 
Screen  #203  has  the  verb  TCOLD, 
which  simply  performs  a  return  to  the 
operating  system.  The  code  generated 
is  412  bytes,  and  includes  the  Forth 
start-up  code,  the  inner  interpreter,  a 
group  of  mandatory  verbs  (which  in¬ 
cludes  the  run-time  routine  DOCOL, 
for  instance),  and  INTCALL.  Eleven 
verbs  are  included  in  the  target  ma¬ 
chine  code  in  this  example. 

Example  #3  (screens  204-207,  see 
page  76)  demonstrates  that  the  Forth 
interpreter  can  be  included  in  the  tar¬ 
get  machine  code.  The  verbs  in  the 
target  code  must  have  headers  so  that 
they  can  be  found  by  the  outer  inter- 

55 

7C7 


Constants 

High  Memory 

Allocation 

(HEX  bytes) 

TVRAM _ 

TEM 

A 

Target  Variable 

RAM  Area  (opt.) 

TBUF1 

Block  Buffers 
(opt.) 

T#BUFF  *404 

TRO 

User  Area 
(opt.) 

52 

\ 

t 

Return  Stack 

AO 

TSPO 

A 

Term  in  Buffer 

S 

,  1 

Parameter  Stack  -- 

s' 

s' 

TARGORG - 

Target 

Code 

Low  Memory 


FIGURE  1 

Constants  Loaded  by  Application  Programmer 
to  Specify  Memory  Map 


Constants 


S' 


NLIST . 


TLIST . 


High  Memory 

(Low  Stack) 


HERE 


nfa  of 
last  verb 


nfa  2 


nfal 


FORTHORG  or 
FFFF  (HEX) 


FFFF  (HEX) 


FORTHORG-TARGORG 


HERE 


Last  Verb 
Offset 


Last  Verb 
nfa  or  cfa 


First  Verb 
Offset 


First  Verb 
nfa  or  cfa 


FORTHORG-TARGORG 


FORTHORG 


Low  Memory 
(High  Stack) 


Allocation 


VERBS  #  +  2 
BYTES 


(TVERBS  #  +  3)  *  2 
BYTES 


FIGURE  2 

nfa  and  Target  Offsets  List 


preter.  Care  must  be  taken  to  ensure 
that  the  Forth  virtual  memory  block 
disk  verbs  are  not  included.  Screens 
#205-206  have  the  Forth  outer  inter¬ 
preter  tailored  to  exclude  references 
to  the  disk  block  verbs.  Screen  #207 
has  a  test  application  program  (APP) 
and  the  cold  start  verb  TCOLD.  The 
vocabulary  link  in  Forth  must  also  be 
initialized.  The  recompiler  is  started 
by  typing  ’  APP  TARGET,  rather 
than  0  TARGET,  as  in  a  headerless 
case.  The  cfa  of  the  root  verb  of  the 
application  program  is  on  the  param¬ 
eter  stack;  knowledge  of  it  is  neces¬ 
sary  to  the  recompiler  since  the  verb 
TCOLD  pointed  to  by  the  constant 
TCOLDCFA  does  not  thread  to  APP. 

Example  #4  (screens  208-212, 
page  78)  shows  that  the  verbs  of  the 
Forth  virtual  memory  disk  block  han¬ 
dlers  can  be  included  without  the  out¬ 
er  interpreter  (disconnecting  certain 
groups  of  routines  within  the  host 
Forth  system  can  be  a  bit  tricky). 
This  program  reads  in  a  number  of 
Forth  blocks  (in  ASCII)  and  writes 
their  contents  to  a  DOS  file  (in  AS¬ 
CII).  This  is  done  so  that  the  file  can 
be  manipulated  by  your  favorite  word 
processor  (such  as  WordStar;  you 
must  set  the  page  length  to  27  to  ma¬ 
nipulate  16  line  Forth  screens  in  doc¬ 
ument  mode  from  the  DOS  file  gener¬ 
ated  from  this  program). 

In  screen  #208,  the  target  memory 
map  is  loaded  to  indicate  that  2  disk 
block  buffers  are  to  be  allotted. 
Screens  #209-210  set  up  the  DOS 
file  and  transfer  the  information 
from  the  blocks  to  the  DOS  file.  Trail¬ 
ing  spaces  are  calculated  by  CHARS 
and  removed  by  BLOCK>FOUT. 
Screen  #21 1  has  the  verb  TCOLD, 
which  initializes  the  program.  Sever¬ 
al  constants  are  loaded  in  TCOLD.  It 
is  important  to  be  sure  that  the  verbs 
referenced  indirectly  in  TCOLD  as  li¬ 
terals  (FIRST,  LIMIT,  #BUFF,  and 
C/L)  are  included  in  the  target  ma¬ 
chine  code  by  direct  references  else¬ 
where  in  the  program. 

The  Forth  verb  <R/W>  has 
ABORT”  in  its  definition.  Conse¬ 
quently,  ABORT,  QUIT,  etc.  would 
all  be  included,  although  they  would 
be  quite  useless,  since  the  code  gener¬ 
ated  is  headerless.  Thus,  <R/W> 
should  be  revectored  to  a  new  verb 


58 


Dr.  Dobb's  Journal,  October  1985 


which  does  not  have  this  problem. 
Screen  #212  has  the  verb  <TR/ 
W>,  which  is  identical  to  the  normal 
<R/W>  except  that  the  ABORT” 
construction  has  been  removed. 
<TR/W>  is  re-vectored  through 
'R/W  so  that  <TR/W>  will  be  in¬ 
cluded  by  the  recompiler  rather  than 
<R/W>  (and  the  entire  Forth  out¬ 
er  interpreter). 

The  program  in  example  #5 
(screens  212-217,  page  78)  provides 
the  inverse  function  of  the  program  in 
example  #4.  In  the  DOS-ASCII  files 
the  <cr>  <lf>  pair  is  present  at  the 
end  of  each  line  of  text,  and  unneces¬ 
sary  spaces  are  not  present.  These  pro¬ 
grams  allow  interchange  between 
Forth  blocks  and  DOS  files.  Example 
#5  removes  the  <cr>  <lf>  pairs 
and  inserts  spaces  at  the  end  of  each 
line  to  fill-out  each  line  of  the  Forth 
block.  In  screen  #215,  the  verb 
TVARIABLE  demonstrates  how  the 
RAM  variable  storage  area  can  be 
used. 

It  may  be  necessary  to  generate 
code  for  a  low-memory  jump  table  to 
handle  a  start-up  jump  and  interrupt 
jumps.  In  simple  cases,  this  can  be 
handled  by  hand  patching.  In  more 
complex  cases  the  recompiler  can  be 
instructed  to  generate  a  file  (by  set¬ 
ting  the  constant  MAPFILE,  I  '  MAP- 
FILE  !)  containing  the  memory  map 
(in  ASCII)  of  all  verbs  in  the  target 
machine  code.  The  code  for  the  low- 
memory  jump  table  can  be  generated 
by  a  program  that  queries  the  memo¬ 
ry  map  file  for  addresses. 

Error  Messages  and  Warnings 

During  the  process  of  target  compila¬ 
tion,  if  the  recompiler  finds  numbers 
that  are  not  equivalent  in  the  two 
Forth  images  and  are  not  identifiable 
as  addresses  (or  the  two  bytes  of  the 
data  area  of  a  variable),  then  an  error 
is  indicated  (“Images  not  matched  at 
<address>”).  A  brief  dump  is  then 
produced,  the  dictionary  is  “un¬ 
smudged”,  and  control  is  returned  to 
the  interpreter.  This  error  is  general¬ 
ly  the  result  of  an  uninitialized  data 
area  within  a  verb.  The  application 
programmer  must  then  inspect  the 
dump  to  determine  the  offending 
verb  and  recode  or  initialize  the  verb. 
After  repairing  the  problem,  the  tar¬ 


get  compilation  process  must  be  re¬ 
initiated  from  the  beginning. 

A  temporary  DOS  file  (TAR- 
GET.TMP)  is  generated  on  the  de¬ 
fault  drive  by  TARG1  to  hold  an  im¬ 
age  of  itself.  TARG2  expects  to  find 
this  file  on  the  default  drive.  If  this 
file  is  missing,  TARG2  requests  that 
it  be  replaced  (“TARGET.TMP  not 
found  on  default  drive.”). 

A  warning  (“<address>  <ID.> 
— Code  verb  assumed.”)  is  issued  by 
both  TARG1  and  TARG2  if  there  is  a 
verb  with  an  address  in  its  code  field 
that  the  recompiler  cannot  recognize. 
The  offending  verb  will  be  included 
in  the  target  machine  code  and  the 
recompiler  will  continue. 

The  most  common  problem  we 
have  encountered  while  using  this 
target  compiler  has  been  caused  by  a 
verb  missing  from  the  application 
code  that  seemingly  should  have  been 
included.  This  situation  arises  be¬ 
cause  the  missing  verb  is  referenced 
only  indirectly  (as  a  literal  for  in¬ 
stance).  That  is,  the  missing  verb’s 
pfa  is  compiled  into  a  verb  as  a  literal 
by  the  verb  ’  at  compile-time.  If  all 
references  to  a  verb  are  of  this  type 
then  the  recompiler  will  not  include 
this  verb  in  the  application  code.  The 
recompiler  was  designed  this  way  be¬ 
cause  there  is  no  way  to  anticipate 
the  intention  of  the  application  pro¬ 
grammer  for  the  literals  compiled 
into  the  definition  of  a  verb. 

If  the  application  program  crashes, 
the  programmer  should  first  inspect 
the  source  code  to  see  if  there  are  any 
missing  verbs.  This  can  be  done  by 
checking  all  verbs  referenced  by  liter¬ 
als  (from  ’)  against  the  verbs  includ¬ 
ed  in  the  application  code  listed  in  the 
DOS  application  code  map  file.  If  a 
verb  is  missing,  then  the  application 
code  must  be  changed  to  force  inclu¬ 
sion  of  the  missing  verb  by  explicitly 
referencing  that  verb. 

It  is  our  intention  that  verbs  not  re¬ 
quired  for  the  run-time  activity  of  the 
application  program  not  be  included 
in  the  target  machine  code.  We  have 
found  that  the  outer  interpreter  verbs 
or  the  disk  block  virtual  memory  verbs 
are  sometimes  included  in  the  code 
when  they  have  no  intended  use  in  the 
application.  The  most  common  source 
of  this  problem  is  run-time  error  han¬ 


dling  of  Forth  through  the  verb 
<ABORT”>.  This  verb  leads  the  re- 
compiler  far  into  the  outer  interpreter 
and  BLOCK  verbs  of  the  host  Forth 
system.  To  avoid  including  unwanted 
verbs,  the  application  programmer 
needs  to  identify  an  unwanted  verb 
(e.g.  QUIT  or  R/W  etc.).  A  program 
has  been  devised  (to  be  described  else¬ 
where)  to  determine  the  threads  that 
lead  to  the  unwanted  verbs.  The  appli¬ 
cation  programmer  can  redefine  the 
particular  verb  that  leads  to  the  un¬ 
wanted  verbs.  Example  #3  demon¬ 
strates  this  situation.  In  this  example 
some  of  the  verbs  of  the  interpreter 
have  been  redefined  to  exclude  any 
reference  to  the  disk  block  virtual 
memory  verbs.  This  is  quite  a  general 
procedure  and  demonstrates  the  great 
flexibility  of  Forth  in  allowing  proce¬ 
dures  that  are  central  to  the  function¬ 
ing  of  the  host  system  to  be  redefined 
for  inclusion  in  the  target  code. 

Inside  the  Target  Compiler 

The  verb  TARGET  sequentially  in¬ 
vokes  the  major  tasks  required  by  the 
recompilation  process.  The  first  of 
these  tasks,  APPTAG,  tags  (sets  bit  5 
of  the  header,  the  SMUDGE  bit)  all 
verbs  in  the  host  that  are  to  be  includ¬ 
ed  in  the  application  code. 

The  constant  HEADERS,  if  it  is 
non-zero  (that  is,  the  cfa  of  the  appli¬ 
cation  root  verb),  signals  that  all 
verbs  necessary  for  the  functioning  of 
both  the  application  root  verb  and  the 
cold  start  verb  (referenced  by  the 
constant  TCOLDCFA)  are  to  be 
tagged.  In  this  case,  headers  will  be 
included  in  the  target  code.  The  cold 
start  routine  must  lead  to  an  inter¬ 
preter  (such  as  QUIT). 

If  the  constant  HEADERS  is  zero, 
then  only  those  verbs  needed  for  the 
functioning  of  the  cold  start  verb  are 
tagged  and  the  cold  start  verb  must 
thread  to  the  application  program.  In 
this  case,  headers  will  not  be  included 
in  the  target  code.  If  the  interpreter 
or  other  unwanted  verbs  are  uninten¬ 
tionally  tagged,  the  source  code  must 
be  modified  to  exclude  references  to 
unwanted  verbs. 

To  search  the  tree  structure  defined 
by  the  application  program,  the  recur¬ 
sive  verb  TREETAG  is  initiated  with 
the  cfa  of  the  cold  start  of  the  applica- 


Dr.  Dobb’s  Journal,  October  1985 


61 

769 


tion  on  the  stack.  TREETAG  deter¬ 
mines  the  type  of  the  verb  being  ex¬ 
amined  at  that  node  in  the  tree. 
Variables,  constants,  and  machine- 
code  verbs  are  simply  tagged  and 
TREETAG  terminates  at  that  level  of 
recursion.  If  TREETAG  is  examining 
a  colon  verb,  then  each  entry  in  the 
parameter  field  is  the  cfa  of  another 
verb  that  must  be  searched.  TREE- 
TAG  puts  a  cfa  on  the  stack  and  then 
calls  itself.  This  process  is  repeated  for 
all  entries  in  the  parameter  field.  The 
verb  PFSKIPS  handles  entries  in  the 
parameter  field  that  are  exceptions 
(entries  that  are  to  be  skipped,  such  as 
branches  and  character  strings). 

Verbs  that  are  “CREATE 
DOES>”  defining  verbs  are  tagged 
by  having  VERBTAG  find  their  name 
field  addresses  with  the  verb 
MAXNFA.  MAXNFA  recursively 
searches  the  entire  host  dictionary 
(when  initiated  properly;  see  the  glos¬ 
sary,  page  69)  and  returns  the  maxi¬ 
mum  nfa  less  than  the  address  of  the 
DOES>  part  of  the  “CREATE 
DOES>”  construction.  Further 
searching  of  the  DOES>  portion  of 
the  defining  verb  is  performed  by  the 
verb  CREATE- DOES >.  This  verb 
functions  similarly  to  TREETAG  and 
recursively  calls  TREETAG  by  the 
forward-reference-executing  verb 
>TTAG.  At  present,  the  CREATE 
portion  of  the  CREATE-DOES>  con¬ 
struction  will  be  included  (wasteful- 
ly)  in  the  target  code;  however,  no 
searching  or  tagging  of  the  verbs  ref¬ 
erenced  in  this  portion  is  performed. 
The  exact  form  of  “CREATE 
DOES>”  defining  verbs  varies  with 
different  Forth  implementations; 
some  adjustments  to  the  recompiler 
may  be  necessary  with  other  FIG- 
Forth  model  systems. 

APPTAG  directs  TFIX;CODE  and 
TFIXNULL  to  tag  certain  verbs  that 
are  required  in  the  target  code;  also 
the  root  verb  of  the  application  is 
searched  if  headers  are  requested. 

VERBLISTS  directs  the  creation  of 
two  lists  on  the  parameter  stack.  The 
first  list  (proceeding  down  in  memo¬ 
ry)  is  a  numerically  sorted  list  of  all 
name  field  addresses  in  the  host  dic¬ 
tionary.  This  list  reflects  the  physical 
order  of  the  verbs  in  memory  rather 
than  the  order  defined  by  the  links 
within  headers  of  the  verbs.  This 


physical  ordering  is  useful  during  the 
elimination  of  unwanted  verbs.  The 
second  verb  list  contains  pairs  of 
numbers.  There  is  one  pair  for  each 
tagged  verb  in  the  host.  The  first 
number  is  the  address  of  the  begin¬ 
ning  of  the  memory  segment  for  that 
tagged  verb  in  the  host  system.  This 
is  either  a  cfa  or  an  nfa,  depending  on 
whether  or  not  headers  are  to  be  in¬ 
cluded  in  the  target  code.  The  second 
number  of  each  pair  is  the  numerical 
difference  between  the  first  number 
of  the  pair  and  the  address  of  the 
start  of  that  verb  in  the  target  ma¬ 
chine  code.  This  information  will  be 
used  to  recalculate  all  of  the  address¬ 
es  in  the  target  code  to  reflect  the 
squeezing  out  of  the  unnecessary 
verbs  from  the  host  Forth  system. 

The  verb  FRAMEIN  sets  aside  the 
space  (see  Figure  2,  page  57)  on  the 
stack  for  the  nfa  list  and  the  target 
offsets  list.  The  size  of  this  space  is 
determined  by  the  verb  COUNT- 
VERBS  which  recursively  searches  all 
vocabulary  branches  of  the  host  dic¬ 
tionary,  determining  the  total  number 
of  verbs  in  the  host  and  how  many  of 
those  verbs  are  tagged  (the  variables 
VERBS#  and  TVERBS#).  FRAMEIN 
initializes  certain  pointers  to  these 
lists  and  also  initializes  the  beginning 
and  ending  entries  that  are  used  as 
signals  for  the  list  searching  verbs. 

The  verb  OFFSETS  enters  into  the 
target  offsets  list  the  starting  address 
of  the  verbs  to  be  in  the  target  code. 
A  running  count  of  the  verb  locations 
to  be  in  the  target  code  is  calculated. 
This  number  is  used  to  calculate  the 
difference  between  the  host  address 
and  the  target  code  address  of  each 
verb.  For  each  verb  that  will  be  in  the 
target,  the  host  code  address,  paired 
with  the  offset  (the  difference  calcu¬ 
lated  above),  is  entered  into  the  tar¬ 
get  offsets  list. 

The  name  field  addresses  of  all 
verbs  in  the  host  are  entered  into  the 
nfa  list  by  the  verb  NFALISTFILL. 
NFALISTF1LL  fills  this  list  in  the  se¬ 
quence  followed  by  the  recursive- 
branch-searching  routine  when 
searching  the  host  dictionary  (starting 
at  the  verb  pointed  to  by  FORTH 
CONTEXT  @  @).  This  list  is  not  nec¬ 
essarily  in  the  same  sequence  as  the 
physical  order  of  the  dictionary,  and 
thus  must  be  sorted.  Since  the  list  usu¬ 


ally  has  large  areas  that  are  already  in 
sequence  with  just  the  order  of  these 
blocks  out-of-sequence,  the  sort  can 
be  performed  rapidly  by  the  verb 
WINDOWSORT.  This  routine  detects 
the  limits  of  the  out-of-sequence 
blocks  with  WINDOWTOP,  FRAME- 
BOTTOM,  and  WIN  DOW  BOTTOM. 
The  blocks  are  then  moved  into  se¬ 
quence  by  OPENMOVE. 

The  relocation  routines  compare 
the  host  Forth  image  generated  by 
TARG1  (in  the  DOS  file  TAR- 
GET.TMP)  with  the  current  resident 
image  (TARG2).  These  images  should 
differ  only  for  addresses,  and  the  ad¬ 
dresses  should  be  different  by  exactly 
201 H.  All  addresses  within  the  verbs 
being  included  in  the  target  machine 
code  must  be  recalculated  to  account 
for  the  squeezing  out  of  unwanted 
verbs  in  the  host.  Certain  addresses  at 
the  beginning  of  the  target  code  must 
also  be  patched  to  reflect  the  memory 
map  of  the  target  machine. 

Either  of  two  pairs  of  routines  di¬ 
rect  this  relocation  operation,  de¬ 
pending  on  whether  headers  are  to  be 
included  in  the  target  code  (either 
RELOCATE  and  VERB>TARG,  for 
headers,  or  RELOCATENH  and 
VERB>TARGNH,  for  no  headers). 
RELOCATE  directs  DISCARD  to  ad¬ 
vance  the  file  containing  the  image  of 
TARG1  past  areas  of  the  file  that  are 
to  be  discarded.  The  verb  VERB> 
TARG  then  copies  the  verb  currently 
being  examined  by  RELOCATE  to 
the  target  image  file.  All  addresses 
detected  by  VERB>TARG  are  exam¬ 
ined  by  REPAIRADD,  which  returns 
the  address  reflecting  the  relocation 
of  the  current  verb.  If  a  link  address 
is  being  adjusted,  REPAIRADD 
directs  NEWLINK  to  determine  a 
new  link  address  to  account  for  inter¬ 
vening  verbs  in  the  host  that  are  not 
to  be  included  in  the  target  code.  In 
this  process,  the  host  dictionary 
structure  is  lost  and  the  target  dictio¬ 
nary  links  will  reflect  the  physical  or¬ 
der  of  its  verbs.  REPAIRADD  detects 
whether  the  addresses  need  to  be 
patched  by  PATCHADD  to  take  into 
account  the  memory  map  of  the  tar¬ 
get  machine.  REPAIRADD  then  con¬ 
sults  the  target  offsets  list  to  adjust 
the  addresses  to  reflect  the  relocation 
of  code. 

If  numbers  in  the  two  images  are 


66 

770 


Dr.  Dobb's  Journal,  October  1 985 


not  equal,  the  verb  VAR  examines  the 
code  to  determine  if  the  number  is  in 
the  data  area  of  a  variable.  If  this  is 
not  true,  an  error  is  issued  by  VAR 
and  a  dump  produced  by  DUMP/  =  . 
In  this  case  the  tagged  verbs  are  all 
untagged  to  allow  examination  of  the 
host  system  from  the  outer  interpret¬ 
er  by  the  application  programmer. 

If  no  headers  were  to  be  included, 
the  same  steps  as  above  occur  except 
that  RELOCATENH  and  VERB> 
TARGNH  are  used.  Because  headers 
are  not  to  be  included  in  the  target 
code,  the  vocabulary  links  do  not 
need  to  be  recalculated. 

After  all  tagged  verbs  have  been 
relocated  and  the  application  code 
file  closed,  a  map  of  the  memory  allo¬ 
cation  of  the  target  machine  is  dis¬ 
played  by  TARGETM EMORY.  Dur¬ 
ing  the  relocation  process,  each  verb 
to  be  included  in  the  target  code  is 
displayed  on  the  screen  along  with  its 
address  in  the  target  machine.  This 
information  is  also  copied  to  a  file  if 
requested  ( 1  ’  MAPFILE  !)  by  the  ap¬ 
plication  programmer. 

The  DOS  file  interface  is  identical 
for  both  the  CP/M-80  and  MSDOS 
versions.  The  primary  verbs  are  FC@ 
and  FC!.  These  verbs  fetch  or  store  a 
byte  from  the  sequential  DOS  file 
specified  by  the  file  specification  ad¬ 
dress  on  the  stack.  The  defining  verb 
FILE  creates  a  verb  that  functions 
like  a  verb  created  by  VARIABLE. 
The  verb  created  by  the  defining 
FILE  has  three  parts:  first,  a  count 
index  byte;  second,  a  disk  memory 
area  (128  bytes);  and  third,  a  DOS 
filename  specification  area  (file  con¬ 
trol  block).  When  the  verb  is  execut¬ 
ed  at  run-time,  the  address  of  the 
count  byte  is  left  on  the  stack.  The 
verb  DMA  will  advance  this  address 
to  the  disk  memory  area,  and  the  verb 
FCB  will  advance  the  count  byte  ad¬ 
dress  to  the  DOS  filename  specifica¬ 
tion  area  (file  control  block).  The 
verbs  FILE@  and  FILE!  are  used  by 
FC@  and  FC!  to  fetch  or  store  the 
next  sequential  segment  to  or  from 
the  disk  memory  area.  Note  that  the 
file  areas  can  be  reused  as  often  as 
desired.  The  verbs  SPACEFILE, 
SPACESFILE,  CRFILE,  TYPEFILE 
and  U.RFILE  perform  output  func¬ 
tions  to  a  file  that  are  the  same  as 
analogous  screen  display  verbs. 


There  are  other  verbs  involved  in 
maintaining  the  DOS  file  directory  and 
in  opening  and  closing  DOS  files.  These 
verbs  all  return  status  flags  except 
SETDMA.  The  verb  FILENAME?  que¬ 
ries  a  DOS  filename  from  the  keyboard 
and  then  sets  up  the  filename  specifica¬ 
tion  area  to  be  ready  to  search  for  or 
open  a  DOS  file.  File  specification  can 
also  be  loaded  by  the  string-handling 
verb  PUTS  with  string  verbs  created  by 
the  defining  verb  STRING. 

Complex  Forth  Constructions 
Forth  allows  great  flexibility  in  gen¬ 
erating  new  programming  construc¬ 
tions.  Rather  than  trying  to  antici¬ 
pate  all  possible  new  constructions, 
we  decided  to  have  the  recompiler 
handle  simple  cases,  but  to  allow  the 
application  programmer  easy  access 
to  change  the  recompiler  to  handle 
more  complex  cases.  Examples  of 
cases  that  the  recompiler  does  not 
currently  handle  are:  1.)  a  new  verb 
with  the  function  of  colon;  2.)  verbs 
that  have  an  unusual  effect  on  the  pa¬ 
rameter  field  of  a  verb  being  com¬ 
piled  (such  as  branches  and  literals). 

The  recompiler  does  properly  han¬ 
dle  simple  CREATE-DOES>  con¬ 
structions  (such  as  the  defining  verb 
STRING,  screen  #125).  A  verb  such 
as  F$  (screen  #128)  would  be  includ¬ 
ed  in  the  application  code  if  it  had 
been  referenced.  The  application 
code  would  also  include  the  defining 
verb  (STRING)  since  this  verb  con¬ 
tains  the  run-time  action  of  the  de¬ 
fined  verb  (F$).  All  other  verbs  nec¬ 
essary  for  the  function  of  the  DOFS> 
portion  of  the  defining  verb  would 
also  be  included. 

If  the  recompiler  finds  a  value  in  a 
code  field  of  a  verb  that  it  does  not 
recognize,  then  that  verb  will  be  in¬ 
cluded  in  the  application  code;  how¬ 
ever,  no  further  searching  of  this  verb 
occurs.  If  this  verb  was  the  result  of  a 
new  colon-type  definition,  then  not 
searching  this  verb  may  result  in 
some  verbs  that  are  needed  not  being 
included  in  the  application  code. 

The  recompiler  may  not  generate 
the  proper  application  code  if  there 
are  bytes  in  the  parameter  field  of  a 
colon-defined  verb  that  are  not  a  code 
field  address  (or  special  instances  that 
the  recompiler  has  been  instructed  to 
search  for,  for  example,  branches  and 
literals). 


<  The  recompiler  has  been  designed 
to  allow  cases  such  as  those  described 
above  to  be  easily  included  in  the  re¬ 
compiler  by  the  application  program¬ 
mer.  The  verb  PFSKIPS  on  screen 
#137  must  be  augmented  to  handle 
special  bytes  within  the  parameter 
field  of  colon-defined  verbs.  The  verb 
TREETAG  on  screen  #139  can  be 
augmented  to  handle  cases  such  as 
newly  defined  colon-type  definitions. 

The  verb  EXIT  must  not  be  used 
since  the  verb  tagging  routines  as¬ 
sume  that  the  cfa  of  the  verb  EXIT 
terminates  each  colon  defined  verb. 
The  cfa  of  EXIT  must  be  the  last  cfa 
in  the  parameter  field  of  a  colon  verb 
and  must  not  appear  anywhere  else  in 
the  definition. 

Availability  of  the  Ready-to-Run 
Target  Compiler  from  CICFIG 

This  project  grew  out  of  the  needs  of 
several  of  the  CICFIG  members  for  a 
target  compiler  to  use  to  generate 
PROMable  code  for  our  work  on  vari¬ 
ous  instrumentation  control  applica¬ 
tions.  We  are  placing  the  result  of  our 
efforts  in  the  public  domain  because 
we  believe  there  is  a  broad-based 
need  for  such  a  package.  This  target 
compiler  fulfills  a  large  part  of  our 
initial  desires.  It  has  not  been  ported 
to  as  large  a  variety  of  microproces¬ 
sors  as  we  had  initially  hoped,  but 
this  may  change  after  the  release  of 
this  code. 

The  address  relocation  process  is 
not  as  fast  as  we  had  hoped;  however, 
the  modular  design  of  the  recompiler 
will  allow  us  easily  to  replace  these 
routines  with  more  efficient  ones  in  a 
later  release. 

Since  the  generation  of  TARG1. 
COM  and  TARG2.COM  may  be  a  bit 
complicated  due  to  the  recoding  of 
certain  parts  of  the  kernel  of  the  host 
Forth  system,  we  have  decided  to  dis¬ 
tribute  (at  a  cost  of  $30)  a  package 
containing  the  target  compiler  and  all 
the  parts  of  the  target  compiler  gen¬ 
erator.  This  package  contains:  the 
ready-to-run  target  compiler 
(TARG1  and  TARG2),  the  verb  tag 
tracer,  and  a  program  to  filter  the 
CP/M-80  target  code  to  move  the  re¬ 
turn  stack  pointer  to  RAM.  Also  in¬ 
cluded  are  the  sources  (Forth  blocks 
and  DOS  files)  for  the  recompiler,  the 
examples,  and  the  kernels.  Documen¬ 
tation  on  the  generation  of  the  target 


Dr.  Dobb’s  Journal,  October  1985 


67 

771 


compiler  and  its  use  to  target-compile 
application  code  are  also  included. 

At  present  we  are  supplying  this 
material  for  use  on  MSDOS  compati¬ 
ble  computers  and  on  CP/M-80  com¬ 
patible  systems.  Other  systems  may 
become  available  later  and  we  should 
be  contacted.  Disk  formats  which  are 
available  are  MSDOS  Version  2  IBM- 
PC  DSDD  5 '/4-inch  8-sector,  CP/M-80 
Version  2.2  standard  8-inch  and  CP/ 
M-80  NorthStar  Horizon  SSDD  514- 
inch. 

Address  of  CICFIG 

CICFIG 

Dept,  of  Electrical  and  Computer 

Engineering 

University  of  Illinois 

1406  W.  Green  St. 

Urbana,  Illinois  61801 

Acknowledgements 

We  thank  the  members  of  the  Target 
Compiler  Group  and  the  other  mem¬ 
bers  of  CICFIG  for  their  input  into 
the  design  and  testing  of  this  target 
compiler.  Everett  Dade  provided  in¬ 
valuable  assistance  by  rigorously  ex¬ 
amining  the  source  code.  This  work 
was  supported  in  part  by:  a  grant 
from  the  U.S.  Department  of  Energy 
(DOE  DEAC02  80ER 10701)  to  Dr. 
Antony  Crofts,  a  grant  from  the  Na¬ 
tional  Foundation  for  Cancer  Re¬ 
search  to  Dr.  Harold  Swartz,  and  by 
a  grant  from  the  National  Aeronau¬ 
tics  and  Space  Administration  under 
grant  NsG-7506. 

Glossary  for  the  CICFIG 
Recompiler  Source  Code 

The  following  abbreviations  are  used 
in  the  stack  pictures, 
a  address  ( 1 6  bit  addressing) 
b  byte 

cfa  code  field  address 
f  flag  (0  =  false) 
fa  file  address  (the  address  of  the 
first  byte  of  a  data  area  created 
by  the  defining  word  FILE) 
n  number  (16  bit  signed) 
nfa  name  field  address 
pfa  parameter  field  address 
1FIX  (  —  a  )  scr#  122  A  variable 
used  to  avoid  patching  the  link  field 
of  the  verb  !  when  headers  are  to  be 
included  in  the  target  code. 


0BRANCHCFA  (  —  cfa  )  scr#  124 
A  constant  that  leaves  the  cfa  of 
0BRANCH. 

;S  (  —  )  scr#  126  Put  a  zero  (null) 
at  the  next  byte  of  the  input  stream  to 
terminate  interpretation  of  a  screen. 

<  +  LOOP>CFA  (  —  cfa  )  scr# 
1 24  A  constant  that  leaves  the  cfa  of 

<  +  LOOP>. 

<.”>CFA  (  —  cfa  )  scr#  124  A 
constant  that  leaves  the  cfa  of  <.”>. 
</LOOP>CFA  (  —  cfa  )  scr#  124 
A  constant  that  leaves  the  cfa  of 
</LOOP>. 

<;CODE>CFA  (  —  cfa  )  scr#  124 
A  constant  that  leaves  the  cfa  of 
<;CODE>. 

<ABORT”>CFA  (  --  cfa  )  scr# 
124  A  constant  that  leaves  the  cfa  of 
<ABORT”>. 

<LOOP>CFA  (  —  cfa  )  scr#  124 
A  constant  that  leaves  the  cfa  of 
<LOOP>. 

>TTAG  (  —  )  scr#  1 36  Execute  the 
verb  TREETAG.  This  allows  a  for¬ 
ward  reference  to  TREETAG  during 
compilation  of  the  recompiler. 
APPTAG  (  —  )  scr#  140  This  verb 
routes  the  verb  tagging  routines. 
ASCII  (  —  b  )  scr#  127  Leave  the 
number  that  is  the  ASCII  code  for 
the  next  character  after  space  in  the 
input  stream. 

BRANCHCFA  (  —  cfa  )  scr#  124  A 
constant  that  leaves  the  cfa  of 
BRANCH. 

CASE  (  —  )  scr#  127  Defined  in 
“Eaker  Case  Statement”  FORTH 
Dimensions  1 1/3  p  37,  9/80. 
CLEANUPDOS  (  —  )  scr#  155 
Flushes  and  closes  the  DOS  files  used 
by  the  relocation  routines. 
COUNTVERBS  (  nfa  —  )  scr#  1 34 
Search  the  dictionary  of  the  host 
Forth  system  from  the  verb  with 
name  field  address  nfa  to  the  begin¬ 
ning  of  the  dictionary.  Keep  a  run¬ 
ning  count  of  the  number  of  verbs  en¬ 
countered  in  the  variable  VERBS#, 
and  the  number  of  tagged  verbs  in  the 
variable  TVERBS#.  This  verb  is  re¬ 
cursive  and  is  started  by  leaving  the 
nfa  obtained  from  FORTH  CON¬ 
TEXT  @  @.  The  entire  host  Forth 
system  is  searched  through  recursive 
calls  to  COUNTVERBS  to  include 
all  vocabularies. 

CREATE-DOES>  (  a  ---  )  scr#  I  38 


Tag  the  verb  with  the  largest  nfa  less 
than  the  address  a.  If  a  points  to  the 
DOES>  portion  of  a  verb  that  is  a 
CREATE-DOES>  construction, 
continue  to  TREETAG  all  verbs 
which  the  DOES>  portion  of  this 
verb  requires.  Otherwise,  indicate  an 
exception  (“—  Code  verb  assumed”) 
and  continue  without  searching  the 
verb  that  the  address  a  points  into. 
CRFILE  (  fa  —  )  scr#  1 34  Append  a 
<cr>  to  the  DOS  file  specified  by 
the  file  address  fa. 

DIJRIVE  (  — )scr#  141  Prompt  the 
operator,  and  pause  until  a  key  is 
pressed. 

DISCARD  (  n  —  )  scr#  152  Discard 
n  bytes  from  the  file  IMG 
(TARGET.TMP). 

DJMPLEN  (  —  n  )  scr#  1 20  An  im¬ 
plementation  specific  constant  that 
leaves  the  number  of  bytes  of  the  ma¬ 
chine  code  sequence  that  is  a  jump  to 
DODOES  at  the  start  of  the 
DOES>  portion  of  a  “CREATE 
DOES>”  defining  verb. 

DMA  (  fa  —  a  )  scr#  128  Leave  the 
address  of  the  disk  transfer  area  of 
the  file  specified  by  the  file  address 
fa. 

DOCODE  (  —  a  )  scr#  122  A  vari¬ 
able  used  in  TREETAG  to  determine 
if  the  verb  being  currently  examined 
is  a  code  level  verb. 

DOCOL  (  —  a  )  scr#  124  A  con¬ 
stant  that  leaves  the  address  of 
DOCOL. 

DOCON  (  —  a  )  scr#  124  A  con¬ 
stant  that  leaves  the  address  of 
DOCON. 

DOESJMP  (  —  b  )  scr#  1 20  An  im¬ 
plementation  specific  constant  that 
leaves  the  first  byte  of  the  machine 
code  sequence  that  is  a  jump  to  DO¬ 
DOES  at  the  start  of  the  DOES> 
portion  of  a  “CREATE  DOES>” 
defining  verb. 

DOUSE  (  —  a  )  scr#  1 24  A  constant 
that  leaves  the  address  of  DOUSE. 
DOVAR  (  —  a  )  scr#  124  A  con¬ 
stant  that  leaves  the  address  of 
DOVAR. 

DOVAR?  (  —  a  )  scr#  1 22  A  vari¬ 
able  used  as  a  flag  by  the  code  reloca¬ 
tion  routines  to  signal  that  a  variable 
is  currently  being  relocated. 

DOVOC  (  —  a  )  scr#  124  A  con¬ 
stant  that  leaves  the  address  of 
DOVOC. 


69 

772 


Dr.  Dobb's  Journal,  October  1985 


DOWN  (  —  a  )  scr#  122  A  variable 
whose  value  is  used  as  a  pointer  to 
within  the  name  field  address  list. 
DUMP  (an  —  )  (defined  in  MVP- 
FORTH  Utilities)  Display  the  con¬ 
tents  of  the  n  bytes  of  memory  start¬ 
ing  at  address  a. 

DUMP/=  (  a  —  )  scr#  148  Display 
the  contents  of  memory  in  the  vicini¬ 
ty  of  the  address  a.  This  will  help  to 
identify  the  section  of  the  Forth  sys¬ 
tem  that  was  not  equivalent  to  the 
image  in  the  DOS  file  TAR- 
GET.TMP.  In  addition,  the  DOS 
files  are  closed,  all  tagged  verbs  are 
untagged,  and  then  the  program 
aborts. 

ENDCASE  (  —  )  scr#  127  See 
CASE. 

ENDOF  (  —  )  scr#  127  See  CASE. 
EXITCFA  (  —  cfa  )  scr#  1 24  A  con¬ 
stant  that  leaves  the  cfa  of  the  verb 
EXIT. 

E$  (  —  an)  scr#  128  A  string  cre¬ 
ated  by  the  defining  verb  STRING. 
There  are  n  bytes  currently  in  F$, 
starting  at  address  a.  This  string 
holds  the  name  of  the  DOS  file 
TARGET.TMP. 

EC!  (  b  fa  —  )  scr#  1 33  Append  the 
byte  b  to  the  file  specified  by  the  file 
address  fa. 

FC@  (  fa  —  b  )  scr#  133  Get  the 
next  byte  b  from  the  file  specified  by 
the  file  address  fa. 

FCB  (  fa  —  a  )  scr#  128  Leave  on 
the  stack  the  address  of  the  file  con¬ 
trol  block  area  specified  by  the  file 
address  fa. 

FCLOSE  (  fa  —  f  )  scr#  131  Close 
the  DOS  file  specified  by  the  file  ad¬ 
dress  fa.  Leave  the  flag  FFFF  hex  if 
the  file  cannot  be  found,  0  if 
successful. 

FCREATE  (  fa  —  f )  scr#  1 30  Make 
(and  open)  a  new  DOS  file  with  the 
name  specified  by  the  contents  of  the 
FCB  area  of  the  file  specified  by  the 
file  address  fa.  Leave  FFFF  hex  if  no 
more  directory  space  is  available,  0  if 
successful. 

FDELETE  (  fa  —  f  )  scr#  1 30  De¬ 
lete  the  DOS  file  with  the  name  spec¬ 
ified  by  the  contents  of  the  FCB  area 
of  the  file  specified  by  the  file  address 
fa.  Leave  FFFF  hex  if  the  file  was  not 
found,  0  is  successful. 

FILE  (  —  )  scr#  128  A  defining 
word  that  creates  a  verb  that  func¬ 


tions  like  a  variable  with  162  bytes. 
Use  like  VARIABLE:  FILE  <file- 
name>.  In  the  parameter  field  of 
<filename>,  the  first  byte  is  used  to 
keep  a  count  on  the  disk  memory 
transfer  area  from  DOS  (the  128 
bytes  where  data  transfers  from  DOS 
are  stored),  and  the  last  33  bytes  are 
used  as  the  DOS  file  control  block  for 
the  DOS  filename. 

FILE!  (  fa  —  f  )  scr#  132  Append 
the  128  bytes  in  the  file  transfer  area 
of  the  file  specified  by  the  file  address 
fa.  Leave  FFFF  hex  if  the  disk  was 
full,  0  if  the  write  was  successful. 
FILE@  (  fa  —  f  )  scr#  1 32  Read  the 
next  128  bytes  into  the  DMA  area 
from  the  DOS  file  named  in  the  FCB 
area  of  the  file  specified  by  the  file 
address  fa.  Leave  FFFF  hex  if  no 
more  data  can  be  read,  0  if  the  read 
was  successful. 

FILECREATE  (  fa  —  f  )  scr#  132 
Make  (and  open)  a  new  DOS  file  de¬ 
leting  if  necessary  an  existing  file 
with  the  same  name.  The  DOS  file  is 
named  by  the  contents  of  the  FCB 
area  of  the  file  specified  by  the  file 
address  fa.  Leave  FFFF  hex  if  no  di¬ 
rectory  space  was  available,  0  if 
successful. 

FILEEOF  (  fa  —  f  )  scr#  1 32  Leave 
FFFF  hex  if  the  last  allocated  byte  of 
the  DOS  file  specified  by  the  file  ad¬ 
dress  fa  has  been  read;  0  otherwise. 
FILENAME?  (  fa  —  )  scr#  129 
Prompt  the  keyboard  for  a  DOS  fi¬ 
lename.  The  filename  is  loaded  into 
the  FCB  area  of  the  file  specified  by 
the  file  address  fa. 

FINDTAG  (  al  —  a2  )  scr#  146 
Search  the  name  field  address  list 
starting  at  address  al  and  leave  the 
the  address  a2  of  the  next  location  in 
the  list  that  has  a  value  that  points  to 
a  tagged  verb. 

FIXLINK?  (  —  a  )  scr#  122  A  vari¬ 
able  used  by  the  relocation  routines 
when  the  target  code  is  to  contain 
headers.  A  non-zero  value  indicates 
that  the  next  encountered  address  is  a 
link  and  must  be  patched  to  correct 
for  the  possible  absence  in  the  target 
code  of  the  verb  to  which  the  link 
used  to  point  to. 

FIXSMUDGE  (  —  )  scr#  147  This 
verb  sets  to  zero  the  smudge  bit  of  all 
verbs  which  have  been  tagged  by  the 
tagging  routines. 


FLIP  (  n  1  —  n2  )  scr#  1 26  Swap  the 
high  and  low  bytes  of  the  word  on  the 
stack. 

FNSEARCH  (  fa  —  )  scr#  130 
Have  DOS  search  for  the  next  file 
that  matches  the  file  named  (wild¬ 
cards  are  usually  used)  in  the  FCB 
area  specified  by  the  file  address  fa. 
The  matching  DOS  filename  is  re¬ 
turned  in  the  disk  memory  transfer 
area  last  assigned  by  the  verb 
SETDMA.  Leaves  FFFF  hex  if  no 
match  is  found,  0  if  a  match  is  found. 
FOPEN  (  fa  —  f  )  scr#  131  Open  a 
DOS  file  with  the  name  contained  in 
the  FCB  area  of  the  file  specified  by 
the  file  address  fa.  Leaves  FFFF  hex 
if  the  file  cannot  be  found,  0  if  the  file 
was  successfully  found. 

FORTHORG  (  —  a  )  scr#  120  An 
implementation  specific  constant 
that  leaves  the  address  of  the  origin 
of  the  current  host  Forth  system. 
FRAMEBOTTOM  (  al  n  —  a2  ) 
scr#  144  Search  down  the  name  field 
address  list  from  the  pointer  al  until 
the  value  in  the  list  is  less  than  the 
number  n.  Leave  the  pointer  a2  for 
this  entry. 

FRAMEIN  (  —  nl...nj  )  scr#  142 
Construct  the  framework  for  the  verb 
lists  on  the  stack  by  leaving  zeros  for 
the  j  cells  required.  Certain  locations 
within  the  lists  and  certain  pointers  to 
the  lists  are  initialized.  The  verb 
RAZE  will  return  the  stack  to  its 
depth  prior  to  FRAMEIN.  The  num¬ 
ber  j  is  calculated  from  the  variables 
VERBS#  and  TVERBS#. 

FREAD  (  fa  —  f  )  scr#  131  Read 
the  next  1 28  bytes  from  the  file  speci¬ 
fied  by  the  file  address  fa.  These 
bytes  are  transferred  to  the  disk 
transfer  area  specified  by  the  last  call 
to  the  operating  system  (SETDMA). 
Leave  FFFF  hex  if  there  is  no  more 
data  available,  0  if  the  read  was 
successful. 

FSEARCH  (  fa  —  f  )  scr #  1  30  Have 
DOS  search  for  the  file  named  in  the 
FCB  area  of  the  file  specified  by  the 
file  address  fa.  Leave  FFFF  hex  if  no 
match  is  found,  0  otherwise. 

FWRITE  (  fa  —  f  )  scr#  131  Ap¬ 
pend  128  bytes  from  the  last  disk 
transfer  area  set  (SETDMA),  to  the 
file  named  in  the  FCB  area  specified 
the  file  address  fa.  Leave  FFFF  hex  if 
the  disk  was  full,  0  if  the  write  was 
successful. 


Dr.  Dobb’s  Journal,  October  1985 


71 

773 


HEADERS  (  —  n  )  scr#  123  A  con¬ 
stant  that  leaves  either  0  if  the  recom¬ 
piler  is  to  generate  headerless  code, 
or  the  pfa  of  the  root  verb  of  the  ap¬ 
plication  program  if  headers  are  to  be 
included  in  the  target  code. 

HITKEY  (  —  )  scr#  141  Pause  until 
a  key  is  depressed  on  the  console. 
ID.FILE  (  nfa  fa  —  )  scr#  1 35  Copy 
a  verb  name  to  the  tile  specified  by 
the  file  address  fa. 

IMAGESAVE  (  —  )  scr#  141  Copy 
an  image  of  the  current  Forth  system 
from  FORTHORG  to  HERE  to  the 
DOS  file  TARGET.TMP. 

IMG  (  —  fa  )  scr#  1 28  A  file  specifi¬ 
er  created  by  the  defining  verb  FILE. 
Leaves  the  address  fa  of  the  disk 
transfer  area  count  byte. 

ISFF  (  fl  —  f2  )  scr#  130  Leaves 
FFFF  hex  if  f  1  is  FF  hex,  0  otherwise. 
ISZERO  (  fl  —  f2  )  scr#  130 
Leaves  0  if  fl  is  0,  FFFF  hex 
otherwise. 

LB  (  n  —  b  )  scr#  1 28  Leave  the  low 
byte  of  the  number  n. 

LITCFA  (  —  cfa  )  scr#  1 24  Leave 
the  cfa  of  the  verb  LIT. 

LOADFCB  (  al  n  a2  —  )  scr#  141 
Zero  33  bytes  starting  at  address  a2 
(an  FCB  area),  and  move  n  bytes 
from  address  a  1  to  address  a2+l. 
LTIB  (  —  a  )  scr#  120  A  constant 
that  leaves  the  address  of  a  cell  in  low 
memory  that  contains  a  pointer  to  the 
terminal-input-bufTer. 

M$  (  —  an)  scr#  1 28  A  string  cre¬ 
ated  by  the  defining  verb  STRING. 
There  are  n  bytes  currently  in  M$, 
starting  at  address  a.  This  string 
holds  the  DOS  filename  attribute 
MAP. 

MAP  (  —  fa  )  scr#  128  A  file  speci¬ 
fier  created  by  the  defining  verb 
FILE.  Leaves  the  address  of  the  disk 
transfer  area  count  byte. 

MAPFILE  (  —  f  )  scr#  123  A  con¬ 
stant  that  leaves  a  zero  if  no  map  file 
is  to  be  generated;  otherwise,  signal 
the  generation  of  a  DOS  ASCII  file 
<filename>.MAP  with  the  names 
and  addresses  of  the  verbs  included  in 
the  target  code. 

MAXNFA  (  nfal  a  nfa2  —  nfa3  a  ) 
scr#  135  This  verb  recursively 
searches  the  entire  host  dictionary 
starting  at  nfa2  (FORTH  CON¬ 
TEXT  @  @)  and  returns  nfa3  which 
is  the  maximum  nfa  that  is  less  than 
or  equal  to  a.  When  started,  nfa3 


should  be  0. 

NEWLINK  (  nfal  —  nfa2  )  scr# 
148  Find  the  host  name  field  address 
nfa2  of  the  verb  that  when  moved  to 
the  target  code,  should  be  pointed  to 
by  the  link  field  of  the  verb  with  the 
host  name  field  address  nfal. 
NEXTNFA  (  nfal  —  nfa2  )  scr# 
150  Leave  the  name  field  address 
nfa2  of  the  next  verb  in  physical  or¬ 
der  in  the  host  Forth  system  after  the 
verb  with  the  name  field  address 
nfal.  If  the  verb  with  name  field  ad¬ 
dress  nfal  is  the  last  verb  in  the  dic¬ 
tionary  (in  physical  order),  then  leave 
HERE. 

NFALISTFILL  (  nfa  —  )  scr#  143 
Search  the  dictionary  of  the  host 
Forth  system  from  the  verb  with 
name  field  address  nfa,  to  the  begin¬ 
ning  of  the  dictionary.  Enter  the 
name  field  address  of  all  verbs  en¬ 
countered  in  the  name  field  address 
list.  This  verb  is  recursive  and  is  start¬ 
ed  by  leaving  the  nfa  obtained  from 
FORTH  CONTEXT  @  @.  The  en¬ 
tire  host  Forth  system  is  searched 
through  recursive  calls  to  NFALIST¬ 
FILL  to  include  all  vocabularies. 
NFTAG  (  nfa  —  )  scr#  136  Set  the 
smudge  bit  of  the  verb  with  name 
field  address  nfa  to  1. 

NLIST  (  —  a  )  scr#  123  A  constant 
that  leaves  the  address  of  the  first  en¬ 
try  in  the  name  field  address  list. 

OF  (  —  )  scr#  1 27  See  CASE. 
OFFSETS  (  —  )  scr#  146  Enter  into 
the  tagged  verb  offsets  list  the  name 
field  addresses  (or  code  field  address¬ 
es)  of  all  tagged  verbs  in  the  host 
Forth  system.  With  each  entry,  store 
the  difference  between  this  address  in 
host  Forth  system  and  the  address 
that  verb  will  be  at  in  the  target  code. 
OPENMOVE  (  al  a2  a3  —  )  scr# 
145  Insert  at  list  pointer  a2,  the  sec¬ 
tion  of  the  name  field  address  list  that 
starts  at  pointer  al  and  ends  at  point¬ 
er  a3.  See  WINDOWSORT. 
PATCHADD  (  al  —  a2  f  )  scr#  149 
If  the  flag  f  is  1,  then  the  address  a2 
found  at  the  address  a  1  should  be  cor¬ 
rected  to  a  new  address  for  the  target 
code.  If  the  flag  f  is  0,  then  the  ad¬ 
dress  a2  should  not  be  corrected.  This 
is  used  to  allow  loading  of  target  code 
RAM  addresses. 

PATCHMAX  (  —  a  )  scr#  120  An 
implementation  specific  constant 


72 


Dr.  Dobb's  Journal,  October  1985 


that  leaves  the  address  above  which 
in  the  host  Forth  system  no  addresses 
will  need  to  be  patched  by  PATCH- 
ADD  for  inclusion  in  the  target 
code. 

PFSKIPS  (  al  —  al  or  a2  0  )  scr# 
137  Check  to  see  if  the  cfa  (which  is 
an  entry  in  the  parameter  field  of  a 
colon  verb)  pointed  to  by  a  1  is  in  need 
of  special  handling,  such  as  branches, 
loops  and  text.  Leave  the  address  al 
if  this  cfa  is  not  an  exception.  Leave 
the  address  a2  and  a  0  flag  to  bypass 
searching  the  verb  referenced  by  al, 
and  continue  searching  at  the  pointer 
a2  +  2. 

PUTS  (an  —  )  scr#  1 25  Move  the 
bytes  (string)  in  the  input  stream 
starting  after  the  next  space  charac¬ 
ter  to  the  next  $  character  (exclusive) 
to  the  address  a.  The  byte  before  the 
address  a  stores  the  current  number 
of  bytes  in  the  string  and  the  byte 
below  that  holds  the  maximum  num¬ 
ber  of  bytes  in  the  string  (see 
STRING).  QUIT  if  the  string  is  too 
long.  The  number  n  is  ignored. 

RAZE  (  nl...nj  —  )  scr#  142  Re¬ 
move  the  verb  lists  (j  cells)  from  the 
parameter  stack  (see  FRAME1N). 
The  number  j  is  calculated  from  the 
variables  VERBS#  and  TVERBS#. 
RELOCATE  (  —  n  )  scr#  1 52  Used 
when  headers  are  to  be  included  in 
the  target  code.  This  word  organizes 
the  relocation  of  the  necessary  sec¬ 
tions  of  the  host  Forth  system  to  gen¬ 
erate  the  target  code.  The  total  num¬ 
ber  of  bytes  n  in  the  target  code  is  left 
on  the  stack. 

RELOCATENH  (—  n  )  scr#  154 
Used  when  headers  are  not  to  be  in¬ 
cluded  in  the  target  code.  This  word 
organizes  the  relocation  of  the  neces¬ 
sary  sections  of  the  host  Forth  system 
to  generate  the  target  code.  The  total 
number  of  bytes  n  in  the  target  code 
is  left  on  the  stack. 

REPAIR  ADD  (  al  -  a2  )  scr#  150 
Leave  the  address  a2  which  is  the  val¬ 
ue  at  the  host  Forth  address  al,  cor¬ 
rected  to  reflect  the  address  reloca¬ 
tion  of  the  code  to  be  included  in  the 
target  code. 

ROOF  (  —  a  )  scr#  123  A  constant 
that  leaves  the  address  of  last  name 
field  address  in  the  name  field  ad¬ 
dress  list. 

SETDMA  (  fa  —  )  scr#  131  In¬ 


struct  DOS  that  the  disk  transfer 
area  is  at  memory  location  fa+ 1. 
SETUPDOS  (  —  )  scr#  1 55  Setup 
the  DOS  files  for  the  relocation  rou¬ 
tines  of  the  recompiler. 

SFLAG  (  f  1  n  —  f2  n  )  scr#  1 36  This 
verb  changes  the  flag  fl  to  f2 
(f2  =  f  1  —  1 ),  This  is  used  to  determine 
the  none-of-the-above  case  of  the 
CASE  construction  in  the  verb 
TREETAG. 

SPACEFILE  (  fa  —  )  scr#  134  Ap¬ 
pend  a  space  character  to  the  file 
specified  by  the  file  address  fa. 
SPACESFILE  (  n  fa  —  )  scr#  134 
Append  n  space  characters  to  the  file 
specified  by  the  file  address  fa. 
STRING  (  n  —  )  scr#  125  A  defin¬ 
ing  word  used  to  create  a  dictionary 
entry  for  the  verb  with  the  name  of 
the  next  character  string  in  the  input 
stream.  The  new  verb  will  be  allotted 
n  +  2  bytes.  The  first  byte  is  the  maxi¬ 
mum  allotted  space  for  the  string,  the 
next  byte  is  the  current  length  of  the 
string.  At  run-time  the  new  verb  will 
leave  on  the  stack  the  address  of  the 
first  byte  of  the  string  and  the  num¬ 
ber  of  bytes  in  the  string. 


T'STREAM  (  —  a  )  scr#  129  Func¬ 
tions  as  ’STREAM  except  that  the 
verb  BLOCK  has  been  removed 
T#BUFF  (  —  n  )  scr#  123  A  con¬ 
stant  that  leaves  the  number  of  disk 
block  buffers  required  by  the  applica¬ 
tion  program. 

TARG  (  —  fa  )  scr#  128  A  file  spec¬ 
ifier  created  by  the  defining  verb 
FILE.  Leaves  the  address  of  the  disk 
transfer  area  count  byte. 

TARGET  (  pfa  or  0  —  )  scr#  157 
This  verb  routes  the  operation  of  the 
target  compiler.  If  the  top  of  the 
stack  has  a  zero,  then  no  headers  will 
be  included  in  the  target  code;  other¬ 
wise,  this  value  is  the  parameter  field 
address  of  the  root  verb  of  the  appli¬ 
cation  program  and  headers  will  also 
be  included  in  the  target  code. 
TARGETMEMORY  (  n  —  )  scr# 
156  Display  a  memory  map  of  the 
target  code  that  has  n  bytes. 
TARGMAP  (  n  nfa  —  )  scr#  148 
Display  the  verb  name  with  name 
field  address  nfa  and  display  the  tar¬ 
get  code  address  (n  +  TARGORG) 
of  that  verb.  Copy  this  same  informa¬ 
tion  to  a  DOS  file  if  a  map  file  has 


Dr.  Dobb’s  Journal,  October  1985 


77*> 


been  requested. 

TARGORG  (  —  a  )  scr#  1 23  A  con¬ 
stant  that  leaves  the  address  of  the 
origin  of  the  target  code. 

TBUF1  (  —  a  )  scr#  1 23  A  constant 
that  leaves  the  address  of  the  start  of 
the  disk  block  buffer  area  of  the  tar¬ 
get  code. 

TCOLD1  (  —  a  )  scr#  120  An  im¬ 
plementation  specific  constant  that 
leaves  the  address  of  the  cell  in  low 
memory  that  contains  the  address  of 
the  first  verb  to  be  executed  upon 
start-up  of  the  target  code. 
TCOI.DCFA  (  —  cfa  )  scr#  123  A 
constant  that  leaves  the  cfa  of  the 
first  verb  to  be  executed  upon  start¬ 
up  of  the  target  code. 

TEM  (  —  a  )  scr#  123  A  constant 
that  leaves  the  address  of  the  top  of 
the  disk  block  buffer  area  in  the  tar¬ 
get  code. 

TFIX;CODE  (  —  )  scr#  140  This 
verb  tags  certain  ;CODE  verbs  that 
are  mandatory  in  the  target  code. 
TF1XNULL  (  —  )  scr#  140  This 
verb  tags  the  verb  X  (null)  and  then 
patches  the  header  for  this  verb. 
TLIST  (  —  a  )  scr#  1 23  A  constant 
that  leaves  the  address  of  the  start  of 
the  target  verb  offset  list. 

TPICK  (  nl  —  n2  )  scr#  126  Func¬ 
tions  as  PICK  except  that  ABORT” 
has  been  removed. 

TRO  (  —  a  )  scr#  123  A  constant 
that  leaves  the  address  of  the  start  of 
the  target  code  return  stack  pointer 
for  initialization  of  the  inner-inter¬ 
preter  of  the  target  machine. 

TROLL  (  n  —  )  scr#  126  Functions 
as  ROLL  except  that  ABORT"  has 
been  removed. 

TREETAG  (  cfa  —  )  scr#  139  This 
recursive  verb  tags  (sets  the  smudge 
bit  to  1 )  all  verbs  in  the  host  Forth 
system  that  are  required  for  the  func¬ 
tion  of  the  verb  whose  code  field  ad¬ 
dress  is  cfa. 

TREETAGCFA  (  —  cfa  )  scr#  1 23 
A  constant  that  leaves  the  cfa  of 
TRHETAG.  Used  to  allow  forward 
referencing  at  compilation  time  with¬ 
in  TARGET. 

TSPO  (  —  a  )  scr#  123  A  constant 
that  leaves  the  address  of  the  start  of 
the  target  code  parameter  stack 
pointer  for  initialization  of  the  inner- 
interpreter  of  the  target  machine. 
TVARIABLE  (  —  )  scr#  126  A  de¬ 


fining  word  used  in  the  form:  TVAR¬ 
IABLE  <varname>.  When  TVAR¬ 
IABLE  is  executed,  it  creates  the 
verb  <varname>  in  the  host  dictio¬ 
nary  with  its  parameter  field  initial¬ 
ized  to  the  next  allotted  cell  in  the 
variable  RAM  area  of  the  target  ma¬ 
chine.  This  allows  variables  to  be  im¬ 
plemented  in  RAM  when  the  target 
code  is  in  ROM.  The  application  pro¬ 
grammer  must  set  up  the  target  vari¬ 
able  RAM  area  by  loading  the  con¬ 
stant  TVRAM. 

TVERBS#  (  —  a  )  scr#  122  A  vari¬ 
able  used  to  hold  the  number  of  verbs 
that  have  been  tagged  by  the  tagging 
routines. 

TVRALLOT  (  n  —  )  scr#  1 26  Add 
the  number  n  to  the  variable 
TVRAMOFF.  This  allows  target 
variable  RAM  bytes  to  be  allotted. 
TVRAM  (  —  a  )  scr#  123  A  con¬ 
stant  that  leaves  the  address  of  the 
start  of  the  target  variable  RAM 
area.  This  value  must  be  loaded  by 
the  application  programmer  if 
needed. 

TVRAMOFF  (  —  a  )  scr#  122  A 
variable  that  contains  the  current 
number  of  bytes  of  the  target  variable 
RAM  area  that  have  been  allotted. 
TWORD  (  b  —  a  )  scr#  129  Func¬ 
tions  as  <WORD>  except  that 
ABORT”  has  been  removed. 
TYPEFILE  (  a  n  fa  —  )  scr#  134 
Copy  n  characters  starting  at  address 
a  to  the  file  specified  by  the  file  ad¬ 
dress  fa. 

U.RFILE  (  nl  n2  fa  —  )  scr#  134 
Convert  the  number  nl  to  an  un¬ 
signed  ASCII  number  right  justified 
n2  spaces.  Append  these  ASCII  char¬ 
acters  to  the  file  specified  by  the  file 
address  fa. 

U8  (  —  a  )  scr#  1 20  A  constant  that 
leaves  the  address  of  the  cell  in  low 
memory  that  contains  the  value  to 
initialize  the  eight  user  variable. 

VAR  (a  —  )  scr#  149  If  the  address 
a  is  the  pfa  of  a  VARIABLE  defined 
verb  (or  the  return  stack  pointer  cell 
address)  then  copy  the  contents  of 
that  cell  to  the  DOS  file  specified  by 
TARG.  Otherwise,  dump  the  con¬ 
tents  of  the  host  Forth  system  in  vi¬ 
cinity  of  the  address  a  and  indicate  an 
error. 

VERB>TARG  (  nfa  —  )  scr#  151 
The  host  Forth  verb  (including  the 


header)  with  name  field  address  nfa 
is  copied  to  the  DOS  file  specified  by 
TARG.  Address  relocation  and 
patches  are  performed. 
VERB>TARGNH  (  cfa  —  )  scr# 
153  The  host  Forth  verb  (excluding 
the  header)  with  code  field  address 
cfa  is  copied  to  the  DOS  file  specified 
by  TARG.  Address  relocation  and 
patches  are  performed. 

VERBLISTS  (  —  )  scr#  147  This 
routine  routes  the  construction  and 
filling  in  of  the  verb  lists  on  the  pa¬ 
rameter  stack. 

VERBS#  (  —  a  )  scr#  122  A  vari¬ 
able  used  to  hold  the  number  of  verbs 
that  are  in  the  host  Forth  system 
dictionary. 

VERBTAG  (  a  —  )  scr#  136  Tag 
(set  the  smudge  bit  to  1 )  the  verb 
whose  code  field  address  or  DOES> 
jump  address  is  a. 

WIN  DOW  BOTTOM  (  al  a2  —  a3  ) 

scr#  144  Examine  the  values  in  the 
name  field  address  list  starting  with 
the  element  pointed  to  by  a  1 .  Contin¬ 
ue  so  long  as  they  are  decreasing  and 
greater  than  the  contents  of  the  ele¬ 
ment  of  the  list  pointed  to  by  a2. 
When  this  is  not  true,  leave  a3,  which 
points  to  the  element  just  beyond  this 
point. 

WINDOWSORT  (  a  -  )  scr#  145 
Sort  the  list  of  unsigned  numbers 
(name  field  addresses)  between  the 
high  memory  address  a  and  low 
memory  location  specified  by  the  val¬ 
ue  FFFF  hex  in  the  list.  At  the  end  of 
the  sort,  change  the  entry  at  the  bot¬ 
tom  of  the  list  to  FORTHORG. 
WINDOWTOP  (  al  --  a2  )  scr# 
144  Index  down  the  list  of  unsigned 
numbers  starting  at  pointer  al  until 
the  values  in  the  list  are  no  longer 
monotonically  decreasing.  Leave  the 
pointer  a2  to  where  this  occurs.  See 
WINDOWSORT. 

X  (  —  )  scr#  140  This  is  a  pseud¬ 
onym  for  null  that  does  not  have  the 
compile-time  parts  of  the  host  system 
Forth  verb  null.  This  version  of  null 
will  be  present  in  the  target  code. 

DDJ 


74 


Dr.  Dobb’s  Journal,  October  1 985 


Procedure 

TARG1 

(A)  load  screen  with  memory  map 

(B)  load  screen(s)  with  application 

(C)  load  screen  with  cold  start 

(from  TARG1.COM  DOS) 
(scr#  200,  example  #1) 
(scr#  201,  example  #  1 ) 
(scr#  20 1 ,  example  #  1 ) 

0  TARGET 

(control  will  be  returned  to  the  operating  system) 

(0  =  >  no  headers) 

TARG2 

(fromTARG2.COM  DOS) 

(A-C)  exactly  as  above 

(200  201  THRU) 

0  TARGET 

(upon  request,  supply  the  DOS  filename  for  the  target  code) 

(control  will  be  returned  to  the  interpreter  when  complete) 

(0  =>  no  headers) 

Forth  Compiler  Listing  (Text  begins  on  page  52) 


SCR  1200 

1  (  No  headers,  No  block  verbs;  Target  Heiory  Hap  Constants)  HEX 

2  (  This  progra*  outputs  to  printer,  ASCII  chars  typed  in  in  hex) 
J 

4  1  ’  HAPFILE  ! 

5 

6  6000  '  TVRAH  ! 

7  6000  ’  TEN  ! 

8  TEH  ’  TBUF1  ! 

9  T8UE1  52  -  ’  TR0  ! 

10  TR0  A0  -  '  TSPO  ! 

11  100  '  TARG0RG  ! 

12  DECIHAL 

13 

14 

15 

16 


SCR  1201 

1  (  IP  TINPUT  APP  TC0LD)  HEX 

2 

3:1  P  (  b  —  )  5  SNAP  STSCALL  ; 

4 

5  :  TINPUT  (  —  n  )  0  0  '  OUERT  1  TNORD  CONVERT  DROP  DROP  ; 

6 

7  :  APP  (  —  )  HEX 

8  .‘To  send  an  ASCII  character  to  the  printer,"  CR 

9  type  a  HEX  nuiber  and  (cr)  after  ’?'  {FF  to  teriinate).'  CR 

10  BEGIN  TINPUT  DUP  FF  =  NOT  NHILE  SPACE  LP  REPEAT  0  0  STSCALL  ; 

11 

12  :  TCOLD  (  —  )  0  EPRINT  ! 

13  INIT-USER  UP  B  6  r  30  CHOVE  PAGE  APP  ; 

14 

15  ’  TCOLD  CFA  ’  T COL DCF A  ! 

16  DECIHAL 

SCR  1202 

1  (  No  headers,  No  block  verbs;  HINIHUH  Test;  Target  Hetory  Hap) 

2  HEX 

3  0  ’  HAPFILE  ! 

4 

5  6000  ’  TVRAH  ! 

6  6000  ’  TEH  ! 

7  TEH  ’  TBUF1  ! 

8  TBUF1  ’  TRO  ! 

9  TRO  A  -  ’  TSPO  ! 


10  100  ’  TARGORG  ! 

11  DECIHAL 

12 

13 

14 

15 

16 


SCR  1203 

1  (  TCOLD)  HEX 

2 

3  :  TCOLD  0  0  0  0  21  1NTCALL  ; 

4 

5  '  TCOLD  CFA  ’  TCOLDCFA  ! 

6  DECIHAL 

7 

8 

9 

10 
11 
12 

13 

14 

15 

16 


SCR  1204 

1  (  Headers,  No  block  verbs;  Target  Heiory  Hap  Constants)  HEX 

2 

3  1  ’  HAPFILE  ! 

4 

5  6000  ’  TVRAH  ! 

6  6000  ’  TEH  ! 

7  TEH  '  TBUF1  ! 

8  TBUF1  52  -  ’  TRO  ! 

9  TRO  AO  -  ’  TSPO  ! 

10  100  ’  TARGORG  ! 

11  DECIHAL 

12 

13 

14 

15 

16 


Dr.  Dobb's  Journal,  October  1985 


76 

777 


Forth  Compiler  Listing 


(Listing  continued,  text  begins  on  page  52) 


SCR  1205 

1  (  TABORTCFA  THABORT  TNUH8ER  T-FIND) 

2 

3  0  CONSTANT  TABORTCFA 

4 

5  :  TNABORT  NOT  RECOGNIZED'  TABORTCFA  EXECUTE  ; 

6 

7  :  TNUHBER  (a  —  d  ) 

8  0  0  ROT  DUP  It  CB  ASCII  -  =  DUP  )R  t  -1  DPI  ! 

9  CONVERT  DUP  CB  BL  ) 

10  IF  DUP  Ct  ASCII  .  =  NOT  IF  TNABORT  THEN  0  DPL  ! 

11  CONVERT  DUP  CB  BL  )  IF  TNABORT  THEN 

12  THEN  DROP  R>  IF  NEGATE  THEN  ; 

13 

14  :  T-FIND  BL  TWORD  CONTEXT  B  B  (FIND)  ; 

15 

16 


SCR  1206 

1  (  TINTERPRET  TQUIT  TABORT) 

2 

3  :  TINTERPRET  (  —  ) 

4  BEGIN  T-FIND 

5  IF  DROP  CFA  EXECUTE 

6  ELSE  HERE  TNUHBER  DPL  B  It  NOT  IF  DROP  THEN 

7  THEN 

8  AGAIN  ; 

9 

10  :  TOUIT  (  —  ) 

11  BEGIN  CR  RP!  OUERX  TINTERPRET  .*  OX’  AGAIN  ; 

12 

13  :  TABORT  (  —  ) 

14  SP!  (COMPILE]  FORTH  TOUIT  ; 

15 

16  ’  TABORT  CFA  ’  TABORTCFA  ! 


SCR  1207 

1  (  APP  TCOLD) 

2  HEX 

3  :  APP  CR  HELLO  FROH  APP-  BEGIN  2TERHINAL  ?DUP  UNTIL  ; 

4 

5  :  T.R  (  nl  n2  —  )  >R  0  SNAP  OVER  DUP  Dt-  (I  IS  ROT  SIGN  l> 

6  R>  OVER  TYPE  ; 

7 

8  :  TCOLD  (  —  )  0  EPRINT  ! 

9  INIT-USER  UP  I  6  t  30  CHOVE 

10  PAGE  QUIT  Of  CR 

11  INI1-F0RTH  B  ’  FORTH  2t  ! 

12  DECIMAL  TABORT  HEX  T.R  ; 

13  DECIMAL 

14 

15  ’  TCOLD  CFA  ’  TCOLDCFA  ! 

16 


SCR  1208 

1  (  Block  verbs,  No  headers;  8L0CKASC;  Target  Heoory  Hap)  HEX 

2  (  Generates  DOS-ASCII  files  of  Forth  disk  block  areas) 

3 

4  1  ’  MAPFILE  ! 

5 

6  2  ’  TIBUFF  ! 

7 

8  6000  ’  TVRAH  ! 

9  6000  ’  TEH  ! 

10  TEH  404  MUFF  *  -  ’  TBUF1  ! 

11  TBUF1  52  -  ’  TRO  ! 

12  TRO  AO  -  '  TSPO  ! 

13  100  ’  TARGORG  ! 


14  DECIMAL 

15 

16 


SCR  1209 

1  (  CHARS  BLOCK >F0UT)  HEX 

2 

3  :  CHARS  (  a  —  n  ) 

4  DUP  C/L  ♦  1- 

5  BEGIN  DUP  CB  20  =  OVER  4  TPICK  )  AND 

6  WHILE  1- 

7  REPEAT  SNAP  -  It  ; 

8 

9  :  BLOCK >FOUT  (  ba  fa  —  ) 

10  SNAP  DUP  400  t  SNAP 

11  DO  I  CHARS  I  t  I  DO  I  CB  OVER  FC!  1  /LOOP 

12  D  OVER  FC!  A  OVER  FC!  C/L 

13  /LOOP  DROP  ; 

14  DECIMAL 

15 

16 

SCR  1210 

1  (  FOUT  TINPUT  BLOCKASC)  HEX 

2 

3  FILE  FOUT 

4 

5  :  TINPUT  (  —  n  )  0  0  -  QUERY  I  TNORD  CONVERT  DROP  DROP  ; 

6 

7  :  BLOCKASC  (  —  ) 

8  CR  Starting  block  I  (DEC)-  TINPUT 

9  I  of  blocks-  TINPUT 

10  DRIVE  (0,1)-  TINPUT  0  MAX  1  MIN  IF  DR1  ELSE  DRO  THEN 

11  CR  .*  DOS  file  out  naie-  FOUT  FILENAME?  FOUT  FILECREATE  DROP 

12  OVER  t  SNAP 

13  DO  I  BLOCK  FOUT  BLOCK )FOUT  LOOP 

14  1A  FOUT  FC!  FOUT  FILE!  DROP  FOUT  FCLOSE  DROP 

15  CR  .*  Task  coiplete.-  CR  BYE  ; 

16  DECIMAL 


SCR  1211 

1  (  TCOLD)  HEX 

2 

3  :  TCOLD  (  —  )  0  EPRINI  ! 

4  INIT-USER  UP  B  6  t  30  CMOVE  PAGE 

5  TBUF1  ’  FIRST  !  TEN  ’  LTH1T  !  TIBUFF  '  IBUFF  !  ENPTY-BUFFERS 

6  CR  1  DENSITY  !  FIRST  USE  !  FIRST  PREV  ! 

7  DRO  40  ’  C/L  ! 

8  DECIMAL  BLOCKASC  ; 

9 

10  ’  TCOLD  CFA  ’  TCOLDCFA  ! 

11  DECIMAL 

12 

13 

14 

15 

16 


SCR  1212 

1  (  <TR/N» 

2 

3  :  (TR/N)  (  n  —  )  USE  B  >R  ROT  USE  !  SNAP  HAX  DRV  0 

4  DO  I  DR-DEN  DENSITY  1  DUP  BPDRV  -  1  V 

j  iF  aPONV  -lit  MA/-JKV  -  ;F  d.E  THEN 

6  ELSE  I  DRIVE  i  LEAVE 

7  THEN 

8  LOOP  SPBLK  ♦  SPBLK  0 

9  DO  DDUP  T&SCALC 

(Continued  on  page  81) 


78 

778 


Dr.  Dobb's  Journal,  October  1 985 


Forth  Compiler  Listing  (Listing  continued,  text  begins  on  page  52) 


10  IF  SEC-READ  ELSE  SEC-HRITE  THEN 

11  U  1024  SPBLK  /  USE  ♦! 

12  LOOP  DDROP  R>  USE  !  ; 

13 

14  ’  <TR/H>  CFA  ’ R/H  ! 

15 

16 


SCR  1213 
1 
2 

3 

4 
j 
6 

7 

8 

9 

10 
11 
12 

13 

14 

15 

16 

SCR  1214 

1  (  Block  verbs,  No  headers;  ASCBLOCK;  Target  Heiory  Map)  HEX 

2  (  Generates  Forth  disk  block  areas  fori  DOS-ASCII  files) 

3 

4  1  ’  HAPFILE  ! 

5 

6  2  ’  TI6UFF  ! 

7 

0  6000  ’  TVRAH  1 
9  6000  ’  TEH  ! 

10  TEH  404  TI8UFF  *  -  ’  I6UF1  ! 

11  TBUF1  52  -  ’  TRO  ! 

12  TRO  AO  -  ’  ISPO  ! 

13  100  ’  1ARG0RG  ! 

14  DECIMAL 

15 

16 


SCR  1215 

1  (  CURBLOCK  BCOUNT  FIN  BC!  LINE > BLOCK )  HEX 

2 

3  TVARIABLE  CURBLOCX  TVARIAB1E  BCOUNT 

4 

5  FILE  FIN 

6 

7  :  BC!  (  b  —  )  BCOUNT  DUP  I  400  - 

8  IF  1  CURBLOCX  ♦  !  UPDATE  CURBLOCX  8  BUFFER  DROP  0  OVER  !  THEN 

9  DUP  I  DUP  1»  ROT  !  PREV  I  2*  ♦  C!  ; 

10 

11  :  LINE1BL0CX  (  b  —  )  DUP  D  - 

12  IF  0  ELSE  7F  AND  BC!-  1 

13  BEGIN  FIN  FCI  7F  AND  DUP  D  :  NOT 

14  WHILE  BC!  1* 

15  REPEAT  DROP  FIN  FOB  DROP  C/L  SNAP  - 

16  THEN  BEGIN  DUP  WHILE  1-  20  BC!  REPEAT  DROP  ;  DECIMAL 


SCR  1216 

1  (  TYPEFILENAME  TINPUT  ASCBLOCX)  HEX 

2  :  TYPEFILENAME  (  fa  —  ) 

3  DUP  FC8  1*  8  TYPE  .’  FC8  9+3  TYPE  ; 

4 

5  :  TINPUT  (  —  n  )  0  0.*?*  QUERY  1  TWORD  CONVERT  DROP  DROP  ; 


6 

7  :  ASCBLOCX  (  —  ) 

8  CR  DOS  in  file  naie’  FIN  FILENAME?  FIN  FOPEN 

9  IF  CR  FIN  TYPEFILENAME  .*  can’t  be  opened.’  BYE  THEN 

10  CR  Starting  block  I  (DEC)’  TINPUT  .’  Drive  I  (0,1)’  TINPUT 

11  0  MAX  1  MIN  IF  DR1  ELSE  DRO  THEN 

12  DUP  CURBLOCX  !  0  BCOUNT  !  BUFFER  DROP 

13  BEGIN  FIN  FCI  DUP  1A  =  NOI  WHILE  LINEiBLOCX  REPEAT  DROP 

14  BEGIN  BCOUNT  I  400  <  WHILE  20  BC!  REPEAT 

15  UPDATE  SAVE-BUFFERS  FIN  FCLOSE  DROP 

16  CR  .*  Task  coiplete.’  CR  BYE  ;  DECIMAL 


SCR  1217 

1  (  TCOLD)  HEX 

2 

3  :  TCOLD  (  —  )  0  EPRINT  ! 

4  INIT-USER  UP  I  6  r  30  CHOVE  PAGE 

5  TBUF1  '  FIRST  !  TEM  ’  LIMIT  !  TIBUFF  ’  IBUFF  !  EMPTY-BUFFERS 

6  CR  1  DENSITY  !  FIRST  USE  !  FIRST  PREV  ! 

7  DRO  40  ’  C/L  ! 

8  DECIHAL  ASCBLOCX  ; 

9 

10  ’  TCOLD  CFA  ’  TCOLDCFA  ! 

11  DECIMAL 

12 

13 

14 

15 

16 


SCR  1120 

1  (  Iipleientation  specific  constants  for  CP/H)  HEX 

2 

3  INIT-FORTH  12  -  CONSTANT  FORTHORG  (  Host  origin) 

4  148  CONSTANT  PATCHMAX  (  end  of  patch  area) 

5 

6 
7 


110  CONSTANT  TC0LD1 
116  CONSTANT  U8 
118  CONSTANT  LTIB 
CD  CONSTANT  DOESJHP 
3  CONSTANT  DJMPLEN 
DECIMAL 


(  address  for  patch  for  juip  to  cold) 

(  address  for  patch  for  user  18) 

(  address  for  patch  for  Ter»-in-buffer) 
(  first  byte  of  run-tiie  create-does) ) 

(  I  of  bytes  for  run-tiie  c-d  jip) 


SCR  1121 

1  (  MS-DOS  Iipleientation  specific  addresses)  HEX 

2 

3  INIT-FORTH  IB  -  ’  FORTHORG  ! 


4  151  ’  PATCHMAX  !  (  MS-DOS) 

5  119  ’  TC0LD1  !  (  MS-DOS) 

6  11F  ’  U8  !  (  MS-DOS) 

7  121  ’  LTIB  !  (  MS-DOS) 

8  B8  ’  DOESJHP  !  (  MS-DOS) 

9  5  ’  DJMPLEN  !  (  HS-DOS) 


10  DECIMAL 

11 
12 

13 

14 

15 

16 


Dr.  Dobb's  Journal,  October  1985 


81 

779 


Forth  Compiler  Listing  (Listing  continued,  text  begins  on  page  52) 


oCR  1122 

1  (  VARIABLES)  HEX 

0 

L 

3  VARIABLE  IVRAMOFF 

0  IVRAMOFF 

4  VARIABLE  DOCODE 

0  DOCODE 

5  VARIABLE  VERBSI 

0  VERBSI 

6  VARIABLE  I VERBSI 

0  TVER8SI 

7  VARIABLE  DONN 

0  DONN 

8  VARIABLE  FIXL1NX? 

0  FIXLINX? 

9  VARIABLE  !FIX 

0  ! F IX 

10  VARIABLE  DOVAR? 

0  DOVAR? 

11  DECIMAL 

12 

13 

14 

15 

16 


SCR  1123 

1  (  CONSTANTS  see  Examples  and  Glossary)  HEX 

2  0  CONSTANT  HAPFILE  (  These  constants  are  loaded  by  the  user) 

3  0  CONSTANT  TIBUFF 

4  0  CONSTANT  TVRAN 

5  0  CONSTANT  TEN 

6  0  CONSTANT  TBUF1 

7  0  CONSTANT  TRO 

8  0  CONSTANT  TSPO 

9  0  CONSTANT  TARGORG 

10  0  CONSTANT  TCOLDCFA 

11 

12  0  CONSTANT  ROOF  (  These  constants  are  initialized  by  TARGET) 

13  0  CONSTANT  NL 1ST 

14  0  CONSTANT  TLIST 

15  0  CONSTANT  HEADERS 

16  0  CONSTANT  TREETAGCFA  DECIMAL 


SCR  1124 

1  (  CONSTANTS  for  detection  of  special  cases) 


2  ’ 

(tLOOP) 

CFA  CONSTANT 

(tLOOP)CFA 

3  ' 

LIT 

CFA  CONSTANT 

LITCFA 

4  ’ 

(.*> 

CFA  CONSTANI 

(,’)CFA 

5  ’ 

(ABORT’ 

)  CFA  CONSTANT 

( ABORT’ >CFA 

6  ’ 

EXIT 

CFA  CONSTANT 

EXITCFA 

7  ’ 

OBRANCH 

CFA  CONSTANI 

OBRANCHCFA 

8  ’ 

(LOOP) 

CFA  CONSTANT 

(LOOP)CFA 

9  ’ 

(/LOOP) 

CFA  CONSTANI 

(/LOOPJCFA 

10  ’ 

(;CODE> 

CFA  CONSTANT 

(;COOE)CFA 

11  ’ 

BRANCH 

CFA  CONSTANT 

BRANCHCFA 

12  ’ 

CFA  1  CONSTANT 

DOCOL 

13  ’ 

SPO 

CFA  1  CONSTANT 

DOUSE 

14  ’ 

0 

CFA  1  CONSTANT 

DOCON 

15  ’ 

PREV 

CFA  1  CONSTANT 

DOVAR 

16  ’ 

FORTH 

CFA  1  CONSTANI 

DOVOC 

SCR  1125 

1  (  STRING  PUTt  froi  A.Hinfield  ’The  Complete  FORTH’  Sigia  1983) 

2  HEX 

3  :  STRING  (  n  —  ) 

4  CREATE  DUP  C,  0  C,  ALLOT 

5  DOES)  2*  DUP  1-  Cl  ; 

6 

7  :  riii  $  (an-—) 

8  DROP  1- 

9  DUP  1-  Ct 

10  24  WORD  DROP 

11  HERE  Cl  ( 

12  IF  String  too  big’  DROP  QUIT  THEN 

13  HERE  DUP  Cl  1* 

14  ROT  SNAP  CHOVE  ; 

15  DECINAL 

16 


SCR  1126 

1  (  TVRALLOT  TVARIABLE  TPICX  TROLL  ;S  FLIP)  HEX 

2 

3  :  TVRALLOT  (  n  —  )  TVRAHOFF  ♦!  ; 

4 

5  :  TVARIABLE  (  —  )  TVRAHOFF  DUP  I  2  ROT  ♦!  TVRAH  t  CONSTANT  ; 

6 

7  :  TPICX  (  nl  —  n2  )  2»  SPI  t  I  ; 

8 

9  :  TROLL  (  n  —  )  I*  DUP  TPICX  SWAP  2»  SPI  t 

10  BEGIN  DUP  2-  I  OVER  !  2-  SPI  OVER  U<  NOT  UNTIL  DDROP  ; 

11 

12  :  ;S  (  —  )  0  ’STREAM  !  ;  DECIMAL 

13 

14  ;S  HEX 

15  :  FLIP  (  n  —  n  ) 

16  0  100  U/NOD  SNAP  100  *  ♦  ;  DECIHAL 

SCR  1127 

1  (  EAXER  CASE  STATEHENT  FROH  CEE  9/80  FORTH  DIMENSIONS  11/3  P  37) 

2 

3  :  CASE  ?COHP  CSP  I  SPI  CSP  !  4  ;  IMMEDIATE 

4 

5  :  OF  4  7PAIRS  COMPILE  OVER  COMPILE  =  COMPILE  OBRANCH  HERE  0 

6  ,  COMPILE  DROP  5  ;  IMMEDIATE 

7 

8  :  ENDOF  5  ’PAIRS  COMPILE  BRANCH  HERE  0  ,  SNAP  2 

9  [COMPILE]  THEN  4  ;  IMMEDIATE 

10 

11  :  ENDCASE  4  ’PAIRS  COMPILE  DROP  BEGIN  SPI  CSP  I  =  0= 

12  WHILE  2  [COMPILE]  THEN  REPEAT  CSP  !  ;  IMMEDIATE 

13 

14  :  ASCII  BL  NORD  1*  Cl  [COMPILE]  LITERAL  ;  IMMEDIATE 

15 

16 


SCR  *128 

1  (  Ft  H$  FILE  IMG  TARG  MAP  DMA  FCB  LB)  HEX 

2 

3  B  STRING  Ft  Ft  PUTt  TARGET  THPt 

4  3  STRING  Nt  Ht  PUTt  MAPt 

5 

6  :  FILE  (  —  )  CREATE  HERE  A2  ALLOT  A2  0  FILL  ; 

7 

8  FILE  IMG 

9  FILE  TARG 

10  FILE  MAP 

11 

12  :  DMA  (  fa  —  a  )  1*  ; 

13  :  FCB  (  fa  —  a  )  81  ♦  ; 

14  :  LB  (  n  —  b  )  FF  AND  ; 

15  DECIMAL 

16 


SCR  1129 

1  (  T’STREAM  TNORD  FILENAME? )  HEX 

2  :  T’STREAM  (  —  a  )  TIB  I  )IN  I  *  ; 

3 

4  :  TNORD  (  b  —  a  )  T'STREAM  SNAP  ENCLOSE  DDUP  ) 

5  IF  DDROP  DDRUP  0  HERE  ! 

6  ELSE  > IN  ♦!  OVER  -  DUP  >R  HERE  C!  ♦  HERE  1*  R>  It  CHOVE 

7  THEN  HERE  ; 

8 

9  :  FILENAME?  (  fa  --  ) 

10  .’  ?  ’  OUERY 

11  TIB  I  It  Cl  ASCII  :  =  (  default  or  select?) 

12  IF  3A  TNORD  It  Cl  40  -  0  MAX  OF  MIN  OVER  FCB  C!  (  select  disk) 

13  ELSE  0  OVER  FCB  C!  THEN  (  default  disk) 

14  DUP  FCB  OC  t  is  0  FILL  OUP  FCB  1+  OB  BLANX  (  clear  FCB) 

15  2E  TNORD  COUNT  8  MIN  3  TPICX  FCB  It  SNAP  CMOVE  (  load  FCB) 

16  BL  TNORD  COUNT  3  MIN  3  TROLL  FCB  9  t  SNAP  CMOVE  ;  DECIMAL 


84 


Dr.  Dobb's  Journal,  October  1985 


SCR  1130 

1  (  ISFF  FSEARCH  FNSEARCH  FDFLEFE  FCREATE)  HEX 

2 

3  :  ISFF  (  f  —  f  )  FF  -  IF  FFFF  ELSE  0  I HEN  ; 

4 

5  :  ISZERO  (  f  —  f  )  0-  IF  0  ELSE  FFFF  THEN  ; 

6 

7  :  FSEARCH  (  fa  —  f  )  FCB  11  SNAP  SYSCALL  L8  ISFF  ; 

8 

9  :  FNSEARCH  (  fa  —  f  )  FC8  12  SNAP  SYSCALL  LB  ISFF  ; 

10 

11  :  FOELETE  (  fa  —  f  )  FCB  13  SNAP  SYSCALL  LB  ISFF  ; 

12 

13  :  FCREATE  (  fa  —  f  ) 

14  DUP  81  0  FILL  FCB  16  SNAP  SYSCAll  LB  ISFF  ; 

15  OECIHAL 

16 


SCR  1131 

1  (  FOPEN  F CLOSE  SETOHA  FREAD  FNRITE)  HEX 

2 

3  :  FOPEN  (  fa  —  f  ) 

4  DUP  80  SNAP  C!  FCB  F  SNAP  SYSCALL  LB  ISFF  ; 

5 

6  :  FCLOSF.  (  fa  —  f  ) 

7  FCB  10  SNAP  SYSCALL  LB  ISFF  ; 

8 

9  :  SE1DMA  (  fa  —  ) 

10  DMA  1A  SNAP  SYSCALL  DROP  ; 

11 

12  :  FREAD  (  fa  —  f  ) 

13  FCB  14  SNAP  SYSCALL  LB  ISZERO  ; 

14 

15  :  FNRITE  (  fa  —  f  ) 

16  FCB  15  SNAP  SYSCALL  LB  ISZERO  ;  DECIHAL 

SCR  1132 

1  (  SETDHA  FREAD  FNRITE  FILE#  FILE!  FILEEOF)  HEX 

2 

3  :  FILE!  (  fa  —  f  ) 

4  DUP  SETDHA  DUP  FREAD  IF  FFFF  FF  ELSE  0  0  THEN  ROT  C!  ; 

5 

6  :  FILE!  (  fa  —  f  )  DUP  C« 

7  IF  DUP  SETDHA  DUP  FNRITE  SNAP  81  0  fILL  ELSE  DROP  0  THEN  ; 

8 

9  :  FILEEOF  (  fa  —  f  ) 

10  C8  FF  --  IF  FFFF  ELSE  0  THEN  ; 

11 

12  :  FILECREATE  (  fa  —  f  ) 

13  DUP  FSEARCH  NOT  IF  DUP  F DELETE  DROP  THEN  FCREATE  ; 

14  DECIHAL 

15 

16 


SCR  1133 

1  (  FCB  FC!  IHG  TARG  HAP  ft  NS)  HEX 

2 

3  :  FCB  (  fa  —  b  ) 

4  DUP  CB  80  =  IF  DUP  FILES  DROP  THEN 

5  DUP  CB  !♦  DUP  3  TPICX  C!  4  CB  ; 

6 

7  :  FC!  (  b  fa  —  ) 

8  DUP  CB  30  :  IF  DUP  FILE!  DROP  THEN 

9  DUP  CB  1+  DUP  3  TPICX  C!  +  C!  ; 

10  DECIHAL 

11 
12 

13 

14 

15 

16 


SCR  1134 

1  (  SPACEFILE  SPACESFILE  TYPEFILE  U.RFILE)  HEX 

2  :  SPACEFILE  (  fa  —  )  20  SNAP  FC!  ; 

3 

4  :  SPACESFILE  (  n  fa  —  )  SNAP  0  HAX  ?DUP 

5  IF  0  DO  DUP  SPACEFILE  LOOP  THEN  DROP  ; 

6 

7  :  CRFILE  (  fa  —  )  D  OVER  FC!  A  SNAP  FC!  ; 

8 

9  :  TYPEFILE  (  a  n  fa  —  )  ROT  ROT  DUP  0) 

10  IF  OVER  ♦  SNAP  DO  1  CB  7F  AND  OVER  FC!  1  /LOOP 

11  ELSE  DDROP  THEN  DROP  ; 

12 

13  :  U.RFILE  (  »  r  fa  —  )  ROT  ROT 

14  0  SNAP  >R  SNAP  OVER  DUP  D*~ 

15  0  IS  ROT  SIGN  I)  R)  OVER  - 

16  4  ROLL  DUP  ROT  SNAP  SPACESFILE  TYPEFILE  ;  DECIHAL 


SCR  1135 

1  (  ID. FILE  HAXHFA)  HEX 

2 

3  :  ID. FILE  (  nfa  fa  —  )  SNAP  COUNT  IF  AND  ROT  TYPEFILE  ; 

4 

5  :  HAXHFA  (  nfal  a  nfa2  —  nfa3  a  )  [  SHUDGE  1 

6  BEGIN  DUP  DUP  0=  SNAP  B  A081  =  OR  NOT 

7  NHILE  DUP  PFA  DUP  CFA  B  OOVOC  =  SNAP  4  *  B  0=  NOT  AND 

8  IF  ROT  ROT  3  PICX  PFA  2<  B  HAXNFA  ROT  THEN 

9  OVER  OVER  U<  NOT 

10  IF  ROT  DDUP  U<  NOT  If  DROP  DUP  THEN  ROT  ROT  THEN 

11  PFA  4  -  B 

12  REPEAT  DROP  [  SHUDGE  ]  ; 

13  DECIHAL 

14 

15 

16 


SCR  1136 

1  (  NFTAG  VER8TAG  SFLAG  1TTAG)  HEX 

2 

3  :  NFTAG  (  nfa  —  ) 

4  DUP  CB  20  OR  SNAP  C!  ; 

5 

6  :  VERB  TAG  (  cfa  —  ) 

7  DUP  2-  B  (;CODE)CFA  = 

8  IF  0  SNAP  [COMPILE]  FORTH  CONTEXT  B  B  HAXNFA  DROP 

9  ELSE  2*  NFA 

10  THEN  NFTAG  ; 

11 

12  :  SFLAG  (  f  —  f  )  SNAP  1-  SNAP  ; 

13 

14  :  JTTAG  (  —  )  TREE TAGCFA  EXECUTE  ; 

15  DECIHAL 

16 


SCR  1137 

1  (  PFSXIPS) 

2 

3  :  PFSXIPS  (  al  —  al  /  a2  0  ) 

4  DUP  B 

5  CASE  LITCFA  OF  LITCFA  1TTAG  2*  0  ENDOF 

6  <.‘)CFA  OF  < . * >CFA  HTAG  2*  COUNT  +  2-  0  ENDOF 

7  (ABORDCFA  OF  (ABORT* )CFA  HTAG  2*  COUNT  *  2-  0  ENDOF 

8  OBRANCHCFA  OF  OBRANCHCfA  HTAG  2*  0  ENDOF 

9  BRANCHCFA  OF  BRANCHCFA  HTAG  2*  0  ENDOF 

10  (LOOP)CFA  OF  (LOOP)CFA  HTAG  2*  0  ENDOF 

11  (/L00P1CFA  OF  (/LOOP) CFA  HTAG  2+  0  ENDOF 

12  (FLOOP)CFA  OF  <  +LOOP >CFA  > TTAG  (LOOP)CFA  HTAG  2*  0  ENDOF 

13  ENDCASE  ; 

14 

15 

16 


Dr.  Dobb’s  Journal,  October  1985 


Forth  Compiler  Listing  (Listing  continued,  text  begins  on  page  52) 


SCR  1138 

1  (  CREATEDOES))  HEX 

2 

3  :  CREATE-DOES)  (  cfa  —  ) 

4  I  DUP  VERB  TAG  DUP  Cl  OOESJHP 

5  IF  ’  DOES)  CFA  VERBTAG  DJHPLEN  2-  * 

6  BEGIN  2+  DUP  8  EXITCFA  =  NOT 

7  WHILE  PFSKIPS  ?DUP  IF  DUP  8  > TTAG  THEN 

8  REPEAT 

9  ELSE  DUP  DUP  HEX  CR  U.  2+  NFA  ID. 

10  —Code  verb  assuaed."  CR  DECIHAL 

11  THEN  DROP  ; 

12  DECIHAL 

13 

14 

15 

16 


SCR  1139 

1  (  TREETAG)  HEX 

2  :  TREETAG  (  cfa  —  )  [  SHUDGE  ] 

3  DUP  2*  NFA  C8  20  AND  NOT 

4  IF  0  SNAP  DUP  2+  DOCODE  !  DUP  VERBTAG  DUP  8 

5  CASE  DOCODE  8  OF  SFLAG  ENDOF 

6  DOCOL  OF 

7  BEGIN  2*  DUP  8  DUP  EXITCFA  -  SNAP  (;CODE)CFA  =  OR  NOT 

8  NHILE  PFSKIPS  ?DUP  IF  DUP  8  TREETAG  THEN 

9  REPEAT  SFLAG  ENDOF 

10  DOUSE  OF  DUP  2*  8  DUP  14  >  SNAP  36  (  AND 

11  IF  DUP  EXECUTE  8  TREETAG  THEN  SFLAG  ENDOF 

12  DOVAR  OF  SFLAG  ENDOF 

13  DOCON  OF  SFLAG  ENDOF 

14  ENDCASE  SNAP  NOT 

15  IF  DUP  CREATE-DOES)  THEN 

16  THEN  DROP  [  SHUDGE  ]  ;  DECIHAL 


SCR  1140 

1  (  TF IX ; CODE  X  TFIXNUL  APPTAG)  HEX 

2  :  TFIX;CODE  (  —  ) 

3  ’  CREATE  CFA  VERBTAG  ’  CONSTANT  CFA  VERBTAG 

4  ’  :  CFA  VERBTAG  '  USER  CFA  VERBTAG  1  EXIT  CFA  VERBTAG 

5  HEADERS  IF  ’  BYE  CFA  TREETAG  THEN  ; 

6 

7  :  X  R)  DROP  ; 

8  :  TFIXNULL  (  —  ) 

9  '  X  CFA  TREETAG  80E0  ’  X  NFA  !  ;  (  patch  for  null) 

10 

11  :  APPTAG  {  —  )  CR  CR  Start  tagging." 

12  TFIX;CODE  TCOLDCFA  TREETAG 

13  HEADERS  ?DUP  IF  TREETAG  TFIXNULL  THEN 

14  Finished  tagging."  ;  DECIHAL 

15 

16  '  TREETAG  CFA  ’  TREETAGCFA  ! 


SCR  1141 

1  (  HITKEY  DDR1VE  LOADFCB  IHAGESAVE)  HEX 

2 

3  :  HIIKEY  (  —  )  and  hit  any  key.  —  -  KEY  DROP  ; 

4 

5  :  DDR1VE  (  —  ) 

6  CR  CR  Insert  OOS  disk  in  default  drive,"  HITKEY  ; 

7 

3  :  LOADFCB  (  a  n  fcba  —  ) 

9  DUP  21  0  FILL  1+  SNAP  CHOVE  ; 

10 

11  :  IHAGESAVE  (  —  )  DDRIVE 

12  Ft  IHG  FCB  LOADFCB 

13  IHG  F1LECREATE  DROP 


14  HERE  FORTHORG  DO  I  1-  SETDHA  IHG  FNRITE  DROP  80  /LOOP 

15  IHG  FCLOSE  DROP  CR  CR  ."  Step  1  coaplete."  DDRIVE  BYE  ; 

16  DECIHAL 


SCR  1142 

1  (  FRAHEIN  RAZE)  HEX 

2 

3  :  FRAHEIN  (  —  ) 

4  SP8  2-  2-  ’  ROOF  ! 

5  VERBS!  8  2*  0  DO  0  LOOP 

6  SP8  '  NLIST  ! 

7  HERE  ROOF  2*  !  FFFF  NLIST  ! 

8  TVERBS8  I  3  ♦  2  «  0  DO  0  LOOP 

9  SPI  ’  TLIST  ! 

10  FORTHORG  DUP  TLIST  ! 

11  TARGORG  -  TLIST  2*  ! 

12  HERE  NLIST  8  -  ! 

13  FFFF  NLIST  4  -  !  ; 

14 

15  :  RAZE  (  —  ) 

16  VERBSI  I  2*  TVERBSI  8  3  *  2  *  ♦  0  D0  DROP  LOOP  ;  DECIHAL 


SCR  1143 

1  (  COUNTVERBS  NFALISTFILL)  HEX 

2 

3  :  COUNTVERBS  (  nfa  —  )  [  SHUDGE  ] 

4  BEGIN  DUP  DUP  0;  SNAP  8  A081  =  OR  NOT 

5  NHILE  DUP  PFA  DUP  CFA  8  DOVOC  =  SNAP  4*10=  NOT  AND 

6  IF  DUP  PFA  2+  8  COUNTVERBS  THEN 

7  DUP  C8  20  AND  IF  1  TVERBSI  ♦!  THEN  PFA  4  -  8  1  VERBSI  H 

8  REPEAT  DROP  (  SHUDGE  )  ; 

9 

10  :  NFALISTFILL  (  nfa  —  )  (  SHUDGE  ] 

11  BEGIN  DUP  DUP  0;  SNAP  8  A081  =  OR  NOT 

12  NHILE  DUP  PFA  DUP  CFA  I  DOVOC  =  SNAP  4*80=  NOT  AND 

13  IF  DUP  PFA  2*  8  NFALISTFILL  THEN 

14  DUP  PFA  4  -  8  SNAP  DONN  8  !  -2  DONN  ♦! 

15  REPEAT  DROP  (  SHUDGE  )  ; 

16  DECIHAL 


SCR  1144 

1  (  NINDONTOP  FRAHEBOTTOH)  HEX 

2  :  NINDONTOP  (  al  —  a2  ) 

3  BEGIN  DUP  DUP  I  SNAP  2-  8  U<  NOT 

4  NHILE  2- 

5  REPEAT  2-  ; 

6 

7  :  FRAHEBOTTOH  (  al  n  —  a2  )  SNAP 

8  BEGIN  DUP  8  3  PICK  U<  NOT 

9  NHILE  2- 

10  REPEAT  SNAP  DROP  ; 

11 

12  :  NIND0N80TT0H  (  al  a2  —  a3  )  8  OVER 

13  BEGIN  DUP  8  DUP  4  PICK  U<  NOT  SNAP  FFFF  =  NOT  AND 

14  NHILE  2- 

15  REPEAT  SNAP  DROP  SNAP  NINDONTOP  DDUP  U< 

16  IF  SNAP  THEN  DROP  ;  OECIHAL 


SCR  1145 

1  (  OPENHOVE  NINDONSORT)  HEX 

2 

3  :  OPENHOVE  (  al  a2  a3  —  ) 

4  DUP  DUP  2*  SNAP  5  PICK  SNAP  -  DUP  >R  PREV  8  SNAP  BHOVE 

5  3  PICK  2*  SNAP  2+  3  PICK  5  ROLL  -  BHOVE 


87 


Dr.  Dobb  s  Journal,  October  1985 


6  Rg  -  2*  PREV  g  SWAP  R)  BHOVE  ; 

7 

8  :  UINOOWSOR  T  (a  —  ) 

9  BEGIN  DUP  UINDOUTOP  DUP  I  FFFF  =  NOT 

10  WHILE  OVER  OVER  g  F RAMEBO f TOM 

11  DDUP  HINDOHBOT TOM  OPENHOVE 

12  REPEAI  DROP  DROP  FORIHORG  NLISI  !  ; 

13  DECIMAL 

14 

15 

16 


SCR  1146 

1  (  F [NOTAG  OFFSETS)  HEX 

2 

3  :  FINDTAG  (  al  —  a2  ) 

4  BEGIN  2*  DUP  g  C8  20  AND  UNTIL  ; 

5 

6  :  OFFSETS  (  —  )  CR  CR  Calculating  offsets.’ 

7  NLISI  2*  8  ILISf  NLISI  TVERBS*  8  0 

8  DO  FINDTAG  DUP  >R  g  HEADERS  NOT  IF  PFA  CFA  THEN 

9  ROT  -  SWAP  4  ♦  SWAP  OVER  2-  8  ♦  OVER 

10  2*  !  R8  8  HEADERS  N01  IF  PFA  CFA  THEN  OVER  1 

11  R8  24  8  SWAP  R) 

12  LOOP 

13  DROP  DUP  24  8  SWAP  6  4  !  DROP 

14  Offsets  calculated.’  ; 

15  DECIHAL 

16 


SCR  1147 

1  (  VERBSLIST  FIXSHUDGE)  HEX 

2 

a  :  VERBL1STS  (  —  )  0  VERBSI  !  0  (VERBS!  ! 

4  CR  CR  .’  Creating  verb  lists.' 

5  [COMPILE]  FORTH  CONTEXT  8  8  COUNTVERBS  FRAHEIN 

6  ROOF  DOWN  1  CONTEXT  8  8  NFALIS1FILL 

7  1  BUFFEU  DROP  .’  Sorting.’  ROOF  WINDOWSORT 

8  DECIHAL  CR  CR  VERBSI  8  U.  .’  (DEC)  Verbs  in  host  Forth. ’ 

9  CR  TVERBS!  8  U.  .’  (DEC)  Verbs  in  target  code.’ 

10  OFFSETS  ; 

11 

12  :  FIXSHUDGE  (  —  ) 

13  TLIST  4  4  TVERBS!  8  0 

14  DO  DUP  I  4  «  4  8  HEADERS  NOT  IF  24  NFA  THEN 

15  DUP  C8  DF  AND  SWAP  C! 

16  100P  DROP  ;  DECIHAL 


SCR  1148 

1  (  NEWLINX  TARGHAP  DUMP/;)  HEX 

2 

3  :  NEWLINX  (  nfal  —  nfa2  ) 

4  TLISI  2-  2- 

5  BEGIN  24  2*  DDUP  8  ;  UNTIL 

6  2-2-8  SWAP  DROP  0  FIXLINX?  !  ; 

7 

8  :  TARGHAP  (  n  nfa  —  ) 

9  SWAP  TARGORG  4  DUP  4  HEX  U.R  SPACE  HAPFILE 

10  IF  4  HAP  U.RFILE  HAP  SPACEFILE  DUP  HAP  ID. FILE  HAP  CRFILE 

11  ELSE  DROP  THEN  OUT  8  SWAP  ID.  OUT  8  -  A  *  DECIMAL 

12  BEGIN  1-  DUP  0<  NOT  WHILE  SPACE  REPEAT  DROP 

13  50  OUT  8  F  4  <  IF  CR  0  OUT  !  THEN  ; 

14 

15  :  DUHP/=  (  a  —  )  CR  20  -  50  DUMP  TARG  FILE!  DROP 

16  TARG  FCLOSE  DROP  IMG  FCLOSE  DROP  FIXSHUDGE  ABORT  ;  DECIHAL 

SCR  1149 

1  (  PATCHADD  VAR)  HEX 

2 

3  :  PATCHADD  (  al  —  a2  f  )  DUP 


4 

CASE  TC0LD1 

OF 

TOOL  DCF A  1  ENDOF 

5 

INIT-USER 

Of 

TSPO 

0  ENDOF 

6 

U8 

OF 

TRO 

0  ENDOF 

7 

L1IB 

OF 

TSPO 

0  ENDOF 

8 

UP 

OF 

TRO 

0  ENOOF 

9 

RPP 

OF 

TRO 

0  ENDOF 

10 

DUP 

Of 

DUP  8 

1  ENDOF 

11  ENDCASE  ROT  DROP  ; 

12 

13  :  VAR  (a  —  )  DOVAR?  8  NOT  OVER  RPP  =  NOI  AND 

14  IF  CR  .’  Iiages  not  latched  at  *  DUP  HEX  4  U.R  DECIMAL  DUMP/ 

15  THEN  PATCHADD  DROP  DUP  FF  AND  TARG  FC!  FLIP  FF  AND  TARG  FC! 

16  DECIHAL 


SCR  1150 

1  (  NEXTNFA  REPAIRADD)  HEX 

2 

3  :  NEXTNFA  (  nfal  —  nf>2  ) 

4  NLIST  2-  BEGIN  24  DDUP  8  U(  UNTIL  8  SWAP  DROP  ; 

5 

6  :  REPAIRADD  (  al  —  a2  ) 

7  DUP  8  DOVAR  :  IF  2  DOVAR?  !  THEN 

8  DUP  PATCHMAX  (  IF  PATCHADD  ELSE  8  1  I HEN 

9  IF  FIXLINX?  8  ?DUP  IF  SWAP  DROP  NEWLINX  THEN 

10  TLIST 

11  BEGIN  DDUP  8  U<  NOT 

12  WHILE  24  24 

13  REPEAT  2-  8  - 

14  THEN  ; 

15  DECIMAL 

16 


SCR  1151 

1  (  VERB) TARG)  HEX 

2 

3  :  VERB) TARG  (  nfa  —  ) 

4  DUP  NEXTNFA  SWAP 

5  DO  IHG  FC8  DUP  I  C8  : 

6  IF  I  FIXLINX?  8  = 

7  IF  DF  AND  !FIX  8  IF  0  FIXLINX?  !  0  !FIX  !  THEN 

8  THEN  TARG  FC!  1 

9  ELSE  IHG  FC8  100  *  +  201  -  I  8  - 

10  IF  I  REPAIRADD  DUP  FF  AND  TARG  FC! 

11  FLIP  FF  AND  TARG  FC!  2 

12  ELSE  I  VAR  2 

13  THEN 

14  THEN  DOVAR?  8  IF  -1  DOVAR?  4(  THEN 

15  /LOOP  ; 

16  DECIHAL 


SCR  1152 

1  (  DISCARD  RELOCATE)  HEX 

2 

3  :  DISCARD  (  n  —  ) 

4  ?DUP  IF  IHG  SWAP  0  DO  DUP  FC8  DROP  LOOP  DROP  THEN  ; 

5 

6  :  RELOCATE  (  —  n  )  0  DOVAR?  ! 

7  0  OUT  !  0  !FIX  !  0  FIXLINX?  ! 

8  CR  CR  TARGORG  4  HEX  U.R  .’  HEX  (target  origin)’  CR  CR 

9  HAPFILE  IF  TARGORG  4  HAP  U.RFILE  HAP  CRFILE  THEN 

10  FORTHORG  0  NLIST  3  -  TLIST 

11  DO  I  TLIST  :  NOT  IF  DUP  I  8  TARGHAP  I  8  FIXLINX?  !  THEN 

12  I  8  [  ’  !  NFA  ]  LITERAL  :  IF  1  !FIX  !  THEN 

13  SWAP  I  8  SWAP  -  DISCARD 

14  I  8  VERB >  TARG  I  8  DUP  NEXTNFA  DUP  ROT  -  ROT  ♦  4 

15  /LOOP  SWAP  DROP  DECIMAL  ; 

16  DECIHAL 


Dr.  Dobb' s  Journal,  October  1985 


(Listing  continued,  text  begins  on  page  52) 


SCR  1153 

1  (  VERB1TARGNH)  HEX 

2 

3  :  VE RB > TAR6NH  (  cfa  —  ) 

4  DUP  HEXTNEA  SWAP 

5  DO  IHG  FCt  DUP  1  Cl  : 
i  IE  TARG  EC!  1 

7  ELSE  IHG  ECI  100  *  ♦  201  -18  = 

8  IF  I  REPAIRADO  DUP  EE  AND  TARG  EC! 

9  FLIP  EE  AND  TARG  EC!  2 

10  ELSE  I  VAR  2 

11  THEN 

12  THEN  OOVAR?  (  IE  -1  DOVAR?  ♦!  THEN 

13  /LOOP  ; 

14  DECIHAL 

15 

16 

SCR  1154 

1  (  RELOCATENH)  HEX 

2 

3  :  RELOCATENH  (  —  n  ) 

4  0  OUT  !  0  EIXLINK?  !  0  DOVAR?  ! 

5  CR  CR  TARGORG  4  HEX  U.R  HEX  (target  origin)'  CR  CR 

6  HAPEILE  IE  TARGORG  4  HAP  U.REILE  HAP  CREILE  THEN 

7  FORTHORG  0  HEIST  8  -  TLIST 

8  DO  I  TLIST  -  NOT  IF  DUP  I  «  2*  NFA  TARGHAP  THEN 

9  SNAP  I  I  SNAP  -  DISCARD 

10  1  «  VERB ) TARGNH 

11  I  t  DUP  NEXTNFA 

12  DUP  ROI  -  ROT  4  4 

13  /LOOP  SNAP  DROP  DECIHAL  ; 

14  DECIHAL 

15 

16 


SCR  1155 

1  (  SETUPDOS  CI.EANUPDOS)  HEX 

2 

3  :  SETUPDOS  (  —  ) 

4  DDRIVE  Ft  IHG  FC8  LOADFCB 

5  BEGIN  IHG  F SEARCH 

6  NH1LE  CR  TARGET. THP  not  found  on  default  drive'  DDRIVE 

7  REPEAT  IHG  FOPEN  DROP 

8  CR  CR  Target  code  filena«e’ 

9  TARG  EILENAHE?  TARG  FILECREATE  DROP  HAPEILE 

10  IE  TARG  ECB  HAP  ECB  21  CHOVE 

11  Ht  HAP  ECB  9  *  SNAP  CHOVE  HAP  FILECREATE  DROP 

12  THEN  ; 


13 

14  :  CLEANUPDOS  (  —  )  IHG  FCLOSE  DROP 

15  IHG  FDELETE  DROP  TARG  FILE!  DROP  TARG  FCLOSE  DROP  HAPEILE 

16  IF  1A  HAP  EC!  HAP  FILE!  DROP  HAP  FCLOSE  DROP  THEN  ;  DECIHAL 


SCR  1156 

1  (  TARGETHEHORY ) 

2 

3  :  TARGETHEHORY  (  n  —  )  CR  CR 

4  .'  (HEX)  Target  Heiory  Hap'  HEX 

5  CR  TVRAH  T VRAHOFE  B  ♦  1-  4  U.R  .'  Last  needed  byte  of  RAH.' 

6  CR  TVRAH  4  U.R  .'  Target  variable  RAH  start  (TVRAH).' 

7  CR  TEH  4  U.R  .'  Target  end-of-ie»ory  (TEH).’ 

8  CR  T8UF1  4  U.R  .'  Start  of  block  buffer  area  (TBUF 1) . * 

9  CR  TRO  4  U.R  .'  Initial  return  stack  pointer  (TRO).' 

10  CR  ISPO  4  U.R  .'  Initial  stack  pointer  ( TSPO) . ' 

11  CR  TARGORG  4  1-4  U.R  .'  Target  code  end  address  (last  used).' 

12  CR  TARGORG  4  U.R  .'  Target  origin  (TARGORG).' 

13  DECIHAL  ; 

14 

15 

16 


SCR  1157 

1  (  TARGET)  HEX 

2 

3  :  TARGET  (  pfa  or  0  —  ) 

4  DUP  If  CFA  THEN  ’  HEADERS  ! 

5  TCOLDCEA  2*  NFA  FENCE  !  FREEZE 

6  APPTAG 

7  FORTHORG  301  :  IF  IHAGFSAVf  THEN 

8  VERBLISTS 

9  SEIUPDus 

10  HEADERS  IF  RELOCATE  ELSE  RELOCATENH  THEN 

11  CR  CR  HEX  DUP  U.  DECIHAL  .'  (HEX)  Bytes  in  target.  ' 

12  TARGETHEHORY  CLEANUPDOS  FIXSHUDGE  RAZE 

13  CR  CR  .'  Target  coipilation  complete."  ; 

14  DECIHAL 

15 

16 

End  Listing 


90 


Dr.  Dobb's  Journal,  October  1985 


Neon,  Version  1.0 

Company:  Kriya  Systems,  Inc. 
505  INI.  Lake  Shore  Drive, 
Suite  5510,  Chicago,  IL 
60611,(312)822-0624 
Computer:  Apple  Macintosh 
(1 28K) 

Price:  $155.00 

Circle  Reader  Service  No.  101 
Reviewed  by  Bruce  Horn 

This  review  is  reprinted  with  the  per¬ 
mission  of  the  author,  Bruce  Horn, 
and  The  Club  Mac  News,  in  which  it 
originally  appeared  (Vol.  I,  No.  12, 
May  1985). 

I  have  not  written  many  programs  in 
Forth.  I’ve  always  considered  Forth 
to  be  a  sort  of  high-level  assembly 
language,  fun  for  hacking  around  in, 
but  not  suited  to  development  of 
large  programs.  I  have  written  Small¬ 
talk  programs,  though,  and  when  I 
heard  that  Charles  Duff  and  the  folks 
at  Kriya  had  developed  Neon,  an  ob¬ 
ject-oriented  programming  environ¬ 
ment  that  was  said  to  be  a  cross  be¬ 
tween  Smalltalk  and  Forth,  I  was 
intrigued;  is  this  the  long-awaited  fa¬ 
vorite  programming  environment  for 
the  Macintosh? 

History  of  Object-Oriented 
Programming 

Object-oriented  programming  began 
in  the  early  1960’s  with  the  develop¬ 
ment  of  Simula  by  the  Norwegian 
Computation  Center  in  Oslo,  Nor¬ 
way.  Simula  was  the  first  language  to 
implement  the  Class  construct.  In  the 
early  1970’s,  the  Learning  Research 
Group  at  Xerox  Palo  Alto  Research 
Center  began  implementation  of  the 
Smalltalk  programming  environ¬ 
ment.  Smalltalk  was  the  first  system 
to  be  designed  completely  around  the 
Class/Object  concept.  Many  other 
languages  have  since  provided  sup¬ 


port  for  classes,  objects,  and  subclass¬ 
ing,  including  CLU,  Ada,  C+  +  (an 
extended  version  of  C),  and  several 
versions  of  LISP,  though  objects  and 
classes  are  not  as  well-integrated  into 
these  languages  as  they  are  in  Small¬ 
talk.  The  ease  of  experimentation 
with  objects  was  one  of  the  key  rea¬ 
sons  that  much  of  the  interesting  user 
interface  designs  borrowed  for  Mac¬ 
intosh  and  Lisa  were  created  in  the 
Smalltalk  system.  (See  Smalltalk- 
80:  The  Language  and  its  Implemen¬ 
tation,  by  Adele  Goldberg  and  Dave 
Robson,  for  more  details.)  The  latest 
version  of  Smalltalk  has  been  li¬ 
censed  to  several  universities  and 
computer  companies;  Tektronix  mar¬ 
kets  a  68000-based  Smalltalk  ma¬ 
chine  for  around  $14,000,  and  a  ver¬ 
sion  of  Smalltalk-80  will  soon  be 
available  for  the  Macintosh  XL. 

Classes  and  Objects — a  Quick 
Overview 

In  Neon,  classes  define  objects,  a 
convenient  packaging  of  1)  the  data 
that  defines  the  object  and  2)  the  ob¬ 
ject’s  actions.  For  example,  Class  Pen 
defines  what  a  pen  object  looks  like 
(as  far  as  its  internal  state)  and  acts 
like  (the  messages  it  responds  to). 
The  internal  state,  or  instance  vari¬ 
ables  of  a  Pen  object,  might  include 
location,  direction,  and  pen  size;  the 
messages  that  pens  respond  to  would 
include  move:,  turn:,  goto:,  and  home:. 
(The  Neon  tutorial  has  an  excellent 
discussion  of  the  implementation  of  a 
Pen  class,  with  examples  and  line-by- 
line  explanation  of  the  source  code). 
Class  definitions  are  bracketed  by 
:Class  and  ;Class  words,  and  include 
a  list  of  instance  variables  (which 
may  themselves  be  objects)  and 
methods  (the  procedures  run  when  a 
message  is  sent  to  the  object). 

Defining  a  class  is  like  designing  a 
little  computer,  with  its  own  memory 


(the  instance  variables)  and  instruc¬ 
tion  set  (the  messages  it  can  receive). 
In  using  a  computer,  usually  you 
don’t  concern  yourself  with  how  the 
computer  executes  the  instructions 
you  give  it.  If  you  did,  and  you  count¬ 
ed  on  the  various  nuances  of  the  im¬ 
plementation  of  each  instruction, 
programming  the  computer  would  be 
much  more  complicated.  Classes  pro¬ 
tect  you  from  the  need  to  know  about 
the  implementation  of  the  object 
(computer)  by  allowing  you  to  talk  to 
the  object  through  a  set  of  messages. 

Responses  to  messages  include  re¬ 
turning  a  result  on  the  stack,  chang¬ 
ing  the  internal  state  of  the  object, 
sending  messages  to  other  objects,  or 
just  producing  a  side-effect  (such  as 
drawing  a  line  on  the  screen).  10 
move:  Bic  sends  the  message  move:  to 
the  pen  object  named  Bic,  changing 
its  location  instance  variable  and  pos¬ 
sibly  drawing  a  line  on  the  screen;  45 
turn:  Bic  turns  the  pen  45  degrees  to 
the  right.  One  advantage  of  having  a 
pen  object  do  your  line  drawing  on 
the  screen  instead  of  just  calling 
Quickdraw  directly  is  that  you  can 
have  many  separate  pens,  each  with 
its  own  state,  and  you  can  send  mes¬ 
sages  to  them  independently.  To 
paraphrase  Dan  Ingalls,  one  of  the 
developers  of  Smalltalk,  program¬ 
ming  with  objects  is  like  working 
with  trained  animals  instead  of  push¬ 
ing  data  around  with  a  broom. 

Classes  may  be  defined  that  are 
subclasses  of  another  class.  This 
causes  objects  of  that  class  to  inherit 
both  the  instance  variables  and  ac¬ 
tions  of  the  superclass.  Subclassing 
provides  the  capability  of  saying,  “I 
want  something  just  like  that,  except 
a  little  different.”  For  example,  you 
might  want  to  have  a  Pen  that  re¬ 
corded  its  movements.  Since  the  cur¬ 
rent  class  Pen  handles  everything  ex¬ 
cept  the  recording,  subclassing  Pen 


Dr.  Dobb's  Journal,  October  1985 


96 

7ft  ft 


and  creating  a  RecordingPen  that 
provides  the  capability  would  make 
the  most  sense. 

Since  class  RecordingPen  is  a  sub¬ 
class  of  Pen,  RecordingPen  objects 
will  have  both  Pen  and  RecordingPen 
instance  variables,  and  will  respond 
to  the  union  of  all  Pen  and  Recor¬ 
dingPen  messages.  In  general,  if  the 
message  is  not  defined  in  the  class 
that  the  object  belongs  to,  Neon  will 
search  up  the  superclass  chain  to  find 
a  class  that  can  respond  to  the  mes¬ 
sage,  and  runs  the  appropriate  meth¬ 
od.  You  may  also  add  new  messages 
and  instance  variables  to  distinguish 
your  class  from  the  superclass,  and 
you  can  override  superclass  messages 
by  providing  them  in  your  subclass. 

To  create  the  new  RecordingPen 
class  all  you  would  need  to  do  is  add 
startRecording:,  stopRecording:,  and 
playBack:  messages,  and  a  new  in¬ 
stance  variable  penRecord,  which 
keeps  the  list  of  messages  sent  to  the 
object  since  recording  started.  By 
overriding  each  message  that  the  ob¬ 
ject  wants  to  record,  it  can  remember 
the  message  in  penRecord  and  send 
the  appropriate  message  to  its  super¬ 
class,  Pen,  to  do  the  actual  drawing. 

Building  a  system  from  scratch 
based  on  classes  and  objects  provides 
an  extremely  flexible,  compact  and 
modifiable  system.  Since  the  code 
that  defines  the  system  itself  is  acces¬ 
sible,  extensions  to  the  system  may  be 
made  quickly  and  easily  at  even  the 
lowest  level. 

(For  an  excellent  discussion  of 
classes,  messages  and  object  oriented 
programming,  see  Brad  Cox’s  article 
“Message/Object  Programming:  An 
Evolutionary  Change  in  Program¬ 
ming  Technology”  in  the  January 
1984  issue  of  IEEE  Software.) 

Built-in  Classes 

Since  Neon,  like  Smalltalk,  was  writ¬ 
ten  in  itself,  using  its  own  object  and 
class  concepts,  the  groundwork  for 
building  applications  is  already  there 
and  fully  accessible  to  the  program¬ 
mer.  The  basic  classes  provided  in 
Neon  make  it  easy  to  support  the 
Macintosh  User  Interface  standard 
with  minimal  effort.  In  Pascal,  a  Mac 
program  typically  has  several  large 
case  statements  at  the  very  highest 


level  of  the  main  program  to  classify 
events  received  and  to  dispatch  them 
to  handlers.  Neon  helps  to  take  care 
of  this  by  encapsulating  most  of  the 
standard  event  handling  in  class 
Event,  menu  selection  and  dispatch¬ 
ing  in  class  Menu,  and  standard  win¬ 
dow  actions  in  class  (you  guessed  it) 
Window.  Through  these  classes, 
Neon  shields  the  programmer  from 
the  standard  necessities  and  deals 
with  most  of  the  housekeeping  that  is 
required  in  a  Macintosh  application 
behind  the  scenes. 

There  are  many  other  classes  writ¬ 
ten  to  simplify  interaction  with  the 
User  Interface  Toolbox.  Class  File 
provides  access  to  the  Toolbox  File 
I/O  and  Save/Open  dialogs,  and 
class  String  defines  messages  for 
printing,  insertion,  and  pattern 
matching  of  text  strings  (a  nice  inter¬ 
face  to  Munger,  the  Toolbox  string 
handler).  In  addition,  there  is  a  set  of 
nine  classes  to  support  Quickdraw, 
including  Rect,  Oval,  Picture,  and 
GrafPort.  All  Toolbox  routines  are 
directly  accessible. 

Using  Neon 

Neon  is  started  by  double-clicking  on 
neon.com.  Neon.com  is  a  prebuilt  en¬ 
vironment  in  which  a  basic  set  of 
classes,  messages,  and  objects  have 
been  installed.  As  you  work  in  this 
environment,  you  will  be  creating 
classes,  objects  and  messages,  as  well 
as  new  Neon  words.  Once  you  have 
modified  the  default  environment 
you  may  save  it  on  the  disk.  In  this 
way  you  can  build  your  own  custom 
Neon  system. 

When  Neon  starts  up,  it  comes  up 
with  a  single  teletype  window,  where 
you  can  type  Neon  commands.  Out¬ 
put  from  some  of  the  utilities  appears 
there  also.  The  Neon  window  does 
not  update  when  windows  are  moved 
around  above  it,  which  is  a  minor  nui¬ 
sance.  There  is  no  text  editing  al¬ 
lowed  in  the  Neon  window  other  than 
backspacing. 

It  is  fairly  easy  to  create  new  win¬ 
dows  and  menus  while  in  Neon  and  to 
attach  Neon  words  to  each  menu 
item.  For  example,  sending  the  mes¬ 
sage  Example:  to  a  new  window  cre¬ 
ates  a  Neon  window  that  will  inter¬ 
pret  any  text  typed  to  it.  There  are 


also  example  messages  in  several 
other  classes,  including  Control. 

The  standard  Neon.com  •  ystem,  as 
it  comes  from  Kriya,  has  five  menus: 

1 )  The  Apple  menu  has  the  standard 
list  of  desk  accessories,  an  “About 
Neon  .  . .”  item,  and  an  extra  acces¬ 
sory  called  “Editor.”  This  accessory 
provides  the  text-editing  facilities 
necessary  for  editing  class  and  object 
definitions  within  Neon. 

2)  The  File  menu  consists  of  five 
items: 

Load...  loads  a  text  source  file  into 
the  current  environment,  defining 
new  classes,  objects,  and  messages. 
You  would  load  grdemo.load  to  de¬ 
fine  the  special  classes  for  the  graph¬ 
ics  demonstration  program. 

Save  saves  your  current  environment 
onto  the  disk.  As  you  modify  classes 
and  customize  the  system  to  meet 
your  needs,  you  can  save  the  com¬ 
plete  system  and  return  to  it  by  sim¬ 
ply  opening  the  appropriate  Neon 
document.  This  is  often  called  “tak¬ 
ing  a  snapshot.” 

Save  As...  allows  you  to  save  your 
current  environment  under  a  new 
name. 

Print...  prints  a  specified  text  file  to 
the  printer.  The  print  command  pro¬ 
duces  a  formatted  (“prettyprinted”) 
listing  of  the  source  code  in  the  file. 
Quit  closes  all  files  and  returns  to  the 
Finder.  All  changes  made  to  the 
Neon  environment  are  lost  (unless 
they  had  been  previously  Saved.) 

3)  The  Edit  menu  provides  the  stan¬ 
dard  Undo,  Cut,  Paste,  Copy,  and 
Clear  items,  as  well  as  a  special  Edi¬ 
tor  item  to  start  up  the  editor  desk 
accessory. 

4)  The  Utilities  menu  has  six  items: 
List  Words  lists  all  words  currently 
defined  in  the  dictionary. 

List  Objects  lists  all  objects  of  a  given 
type  that  currently  exist  in  the 
environment. 

List  Classes  lists  all  defined  classes  in 
the  current  environment,  indenting  to 
show  subclass  relationships. 

Examine  Memory  is  a  feature  that  al¬ 
lows  you  to  examine  and  search  the 
Macintosh  memory. 


98 


Dr.  Dobb’s  Journal,  October  1985 


Grep...  searches  all  text  files  on  the 
disk  for  a  given  string.  The  results  of 
the  search  (file  name,  line  number, 
and  text  line)  are  printed  in  the  Neon 
window. 

Install  lets  you  change  the  sizes  of  the 
stack,  dictionary,  and  heap.  The 
stack  and  dictionary  are  adjustable, 
while  the  heap  gets  what  is  left. 

5)  Finally,  the  Neon  menu  provides 
five  items: 

Echo  to  Printer  echoes  all  text  that 
appears  in  the  Neon  window  to  the 
printer,  including  text  that  is  typed 
by  the  programmer.  This  is  especially 
useful  in  that  it  allows  the  program¬ 
mer  to  keep  a  record  of  the  input  and 
output  of  a  program  being  debugged. 
Echo  During  Load  displays  the  text 
from  a  source  file  being  loaded  in  the 
Neon  window  as  it  is  being  compiled. 
This  is  useful  in  helping  the  program¬ 
mer  debug  a  new  module  or  class 
definition. 

Show  Free  Space  displays  the  amount 
of  memory  available  in  the  dictionary 
and  the  heap  in  the  Neon  window. 
The  last  two  entries  in  the  Neon 
menu  deal  with  modules.  Modules 
are  separately  compilable,  relocata¬ 
ble  code  segments  that  are  loaded 
and  purged  from  memory  dynamical¬ 
ly.  This  allows  large  applications  to 
run  without  having  to  be  completely 
resident  in  memory.  These  modules 
act  similarly  to  the  Macintosh  code 
segments  in  that  they  are  dynamical¬ 
ly  loaded  into  memory  and  purged 
when  not  in  use.  Many  of  the  menu 
items  in  Neon  are  defined  by  mod¬ 
ules,  including  Grep  and  Examine. 
Show  Module  Status  lists  all  purgea- 
ble  modules  currently  defined  in  the 
dictionary  and  shows  whether  they 
are  loaded  by  printing  their  memory 
locations  (or  0  if  not  loaded). 

Purge  Modules  purges  all  modules 
from  the  heap,  freeing  up  space  for  a 
memory-intensive  operation. 

Because  the  Neon  window  pro¬ 
vides  no  editing  capability,  the  sys¬ 
tem  comes  with  an  editor  to  edit  class 
definitions  within  Neon.  This  editor 
is  an  enhanced  MockWrite  desk  ac¬ 
cessory,  from  CE  Software.  I  found  it 
easy  and  convenient  to  use,  though  I 
would  prefer  a  built-in  editor  in 

Dr.  Dobb’s  Journal,  October  1985 


which  I  could  select,  edit,  and  exe¬ 
cute  Neon  statements  directly.  Once 
a  class  definition  is  created,  it  may  be 
loaded  into  the  system  by  either  typ¬ 
ing  //  followed  by  the  filename  or  by 
selecting  the  Load  item  in  the  file 
menu.  Either  action  begins  compila¬ 
tion  of  the  file.  After  the  file  has  been 
completely  compiled  and  defined  in 
the  system,  you  may  create  new  ob¬ 
jects,  send  messages  to  previously- 
created  objects,  and  so  on. 

After  a  few  hours  with  Neon,  I  felt 
quite  at  home  creating  objects,  send¬ 
ing  messages,  and  building  graphical 
tools.  The  fact  that  Neon  is  an  inter¬ 
active  programming  environment  is  a 
great  advantage  in  building  applica¬ 
tions  for  the  Macintosh  because  the 
turn-around  time  is  much  shorter  and 
experimentation  is  easier.  Neon  also 
provides  a  facility  for  “sealing  off”  an 
application,  creating  a  double-click¬ 
ing  program  without  access  to  the 
Neon  environment. 

Forth  Improvements 

Besides  support  for  objects  and  class¬ 
es,  Neon  has  other  improvements  to 
standard  Forth.  The  main  objection 
to  Forth  is  usually  the  need  for 
strange  and  unintelligible  stack  ma¬ 
nipulation  in  function  definitions; 
Neon  avoids  this  through  local  vari¬ 
ables  and  named  parameters.  Con¬ 
sider  the  example  in  the  Neon  tutori¬ 
al  for  calculating  a2  +  b2: 

:formulal  {  a  b  —  solution  } 

a  a  *  —  a 

bb* 

a  +  . CR ; 

The  input  parameters  are  named  be¬ 
tween  the  braces,  and  are  pulled  off 
the  stack  and  stored  in  the  local  vari¬ 
ables  a  and  b.  The  arrow  (— *■)  stores 
the  value  on  the  stack  into  the  named 
parameter.  Local  variables  may  also 
be  defined  within  the  braces  by  pre¬ 
ceding  the  variable  list  with  a  back¬ 
slash.  Again,  from  the  Neon  tutorial, 
consider  the  formula  (a  +  b-3c)/ 
(b  +  3c): 

:formula2  { a  b  c  \  num  den  —  result ) 
ab  +  3c*-*  num 

3  c  *  b  H - *  den 

num  den  /  ; 


The  numerator  and  denominator  are 
calculated  separately,  stored  in  local 
variables,  and  then  the  formula  is  cal¬ 
culated.  With  local  variables  and 
named  parameters,  Neon  programs 
are  downright  readable. 

Summary 

Neon  is  nicely  designed.  It  is  clear 
that  the  developers  had  an  excellent 
understanding  of  what  object-orient¬ 
ed  programming  is  supposed  to  be. 
With  the  basic  classes,  examples,  and 
tutorial,  you  could  be  writing  inter¬ 
esting  programs  that  support  the 
Macintosh  User  Interface  Standard 
in  a  matter  of  weeks.  This  compares 
very  favorably  with  the  months  that 
most  people  take  to  build  the  support 
routines  that  a  real  application  re¬ 
quires.  Kriya  is  also  planning  to  sup¬ 
port  a  clearinghouse  service  for  use¬ 
ful,  public  domain  classes  written  in 
Neon  for  use  by  software  developers. 

Neon  is  also  a  good  programming 
language  for  occasional  use,  though 
it  has  so  many  features  and  is  so  flexi¬ 
ble  that  it  could  take  a  little  while  to 
learn  what  is  provided  in  the  system. 
The  documentation  is  thorough  and 
well-written,  and  the  tutorial  is  inter¬ 
esting  and  useful. 

As  a  programming  environment, 
Neon  is  very  well  suited  to  the  Mac¬ 
intosh.  The  built-in  classes  are  well 
designed  and  useful,  and  provide  a 
much  improved  interface  to  the  Mac¬ 
intosh  Toolbox  routines.  It  is  flexible, 
powerful,  fast  and  a  lot  of  fun.  It  is 
also  the  only  implementation  of  an 
object-oriented  system  for  the  Mac¬ 
intosh  so  far.  Because  of  the  support 
for  classes  and  objects,  organization 
of  large  programs  would  be  quite 
manageable,  while  with  standard 
Forth  working  on  a  large  system  with 
thousands  of  words  might  become 
confusing.  The  only  disadvantage 
that  Neon  has,  in  my  opinion,  is  the 
backwards  Forth  syntax.  If  some¬ 
body  comes  up  with  a  new  infix 
parser-scanner  to  allow  Smalltalk¬ 
like  expressions,  Neon  could  be  a  real 
winner  in  the  programming  language 
sweepstakes. 

MacFORTH  Level  1, 

Version  1.2;  Level  2, 
Version  2.1;  Level  3, 
Version  3.0 


99 

7Q7 


Company:  Creative  Solutions,  Inc. 
4701  Randolph  Rd,  Ste.  12 
Rockville,  MD  20852 
(301)984-0262 
Computer:  Apple  Macintosh 
(128K) 

Price:  Level  1 — $149,  Level  2 — 
$249  (includes  Level  1), 

Level  3 — $499  (includes 
Levels  1  and  2) 

(Circle  Reader  Service  No.  103) 
Reviewed  by  Alan  Clute 

The  Macintosh  is  a  giant  toolkit.  An 
ideal  language  to  take  advantage  of 
this  toolkit  is  Forth.  MacFORTH, 
from  Creative  Solutions,  Inc.,  is  an 
excellent  Forth  for  the  Macintosh. 
MacFORTH  delivers  the  richness  and 
power  of  the  Macintosh  without  get¬ 
ting  overly  complex.  The  package, 
while  being  simple  to  use,  still  leaves 
the  programmer  enough  breathing 
room  to  develop  programs  that  are 
powerful  and  make  good  use  of  the 
Mac’s  capabilities. 

MacFORTH  is  a  production  lan¬ 
guage.  Current  products  written  in 
MacFORTH  include  PeachTree’s 
Back  to  Basics  Accounting,  Brain- 
works’  ChipWits,  and  Ideaform’s 
MacLabeler.  An  interesting  new 
product  is  CSI’s  StudioMac,  a  music 
program  that  includes  tutorials, 
source  code,  MIDI  interface  capabili¬ 
ty,  and  complete  plans  to  build  an  in¬ 
expensive  MIDI  interface  for  the 
Macintosh. 

Contents  of  Package 

Table  1  (see  page  101)  shows  the 
main  contents  of  each  level  of  Mac¬ 
FORTH.  The  three  levels  are  nested, 
and  upgrades  are  available  for  the 
difference  in  list  price.  Because  Level 
2  includes  more  and  is  in  some  ways 
easier  to  work  with  than  Level  1,  it  is 
probably  the  best  package  to  get  first, 
especially  at  mail-order  prices.  For 
the  record,  this  review  concerns  itself 
with  Versions  1.2,  2.1,  kernel  2.3, 
and,  briefly,  with  Level  3  version  3.0. 

MacFORTH  has  been  around  a 
fairly  long  time,  as  Macintosh  soft¬ 
ware  goes.  Level  1  was  first  released  in 
April  of  1984,  with  the  first  version  of 
Level  2  coming  out  in  July  of  that 
year.  MacFORTH  was  the  first  seri¬ 
ous  native  language  for  the  Macin¬ 


tosh.  It  makes  good  use  of  a  128K 
Macintosh  and  can  be  used  with  only 
one  disk  drive  (if  you  are  so  inclined). 

MacFORTH  is  not  copy  protected, 
so  it  can  easily  be  backed  up  or  in¬ 
stalled  on  a  hard  disk.  The  accompa¬ 
nying  license  agreement  says  in  no  un¬ 
certain  terms  that  you  should  not  give 
or  sell  copies  to  others,  but  CSI  is  to  be 
congratulated  for  not  otherwise  inter¬ 
fering  with  your  use  of  the  program. 

Forth  Implementation 

MacFORTH  is  derived  from  the 
Forth-79  standard,  optimized  for  the 
Macintosh  environment.  It  is  a  32-bit 
Forth,  which  is  more  suitable  for  the 
large  address  space  of  the  Mac  than 
16-bit  implementations.  Considered 


Level  Feature 

1  •  Going  forth  tutorial 

•  Block-oriented  editor 

•  Macintosh  interface 

•  File  system 

•  Printer/Serial 

interface 

•  Programming  aids 

•  Source  code 


2  •  Level  1  included 

•  System  tools 

•  Advanced  graphics 

•  Text  editing 

•  Controls 

•  Clipboard  I/O 

•  Floating  Point 

•  Source  code 


3  •  Level  2  included 

•  Application 
workshop 

•  Resource  workshop 

•  Kernel  workshop 


just  as  Forth,  MacFORTH  is  a  good 
one.  Converting  programs  from  an¬ 
other  standard  version  of  Forth 
should  be  rather  straightforward. 

The  internal  implementation  is  di¬ 
rect  threaded  with  a  token  table  to 
keep  all  word  references  to  16  bits. 
Thus,  program  code  requires  about 
the  same  amount  of  dictionary  space 
as  in  a  16-bit  version  of  Forth.  Vari¬ 
ables  occupy  32  bits.  Fetch  and  store 
operators  come  in  several  flavors  to 
support  8,  16,  or  32  bit  values. 

MacFORTH  uses  traditional  Forth 
blocks  for  source  code,  but  these 
blocks  are  within  block  files.  Mac¬ 
FORTH  thus  lives  very  comfortably 
within  the  Macintosh  environment. 

Programs  can  do  disk  I/O  using 


Comments 

Disk  based,  interactive. 

Similar  to  MacWrite. 

Menus,  windows,  mouse,  sound, 
event  handler. 

Text,  record,  virtual,  and 
block  files.  MacWrite  text  files. 

Window  or  screen  output,  full 
serial  I/O. 

Trace,  debug,  error  traps,  direct 
calls  to  toolbox  routines. 

Forth  extensions,  editor,  many 
examples,  terminal  program, 
download  conversion,  standard 
file-open  dialogue. 

Heap  management,  68000  assembler, 
snapshot  (vocabulary  save). 

Polygons,  regions,  pictures, 
rectangle  arithmetic  and 
scrolling. 

TextRecords  and  TextFields. 

Buttons,  boxes,  and  scroll  bars. 

Read  and  write  from/to  clipboard 

ieee-754  standard 

Graphics  demo,  note  card  data 
management  demo  (fully  annotated 
in  manual),  many  examples, 
source  for  above  extensions. 


Turnkey  production  tools  and 
tutorial.  Supports  512K 
Mac,  overlays  on  1 28K  Mac. 

Macintosh  resource  customization 
and  tutorial. 

Forth  kernel  customization  and 
tutorial. 


Table  1: 

Contents  of  MacFORTH  Levels 


100 


Dr.  Dobb’s  Journal,  October  1985 


block  files,  or  can  choose  to  use  data 
files  with  text,  fixed-length  record,  or 
byte-addressable  virtual  formats. 
The  entire  storage-management  vo¬ 
cabulary  includes  about  90  words. 

Documentation 

MacFORTH  is  extremely  well  docu¬ 
mented.  Most  of  the  documentation 
covers  the  Macintosh  interface,  but 
then,  most  of  the  Forth  is  quite  stan¬ 
dard.  The  level  1  manual  is  about  160 
pages,  plus  glossary  and  appendices. 
Level  2  runs  over  200  pages,  plus 
glossary,  and  Level  3  adds  200  more 
pages,  which  show  you  step  by  step 
how  to  turn  a  running  program  into  a 
production  disk. 

The  manuals  make  clear  presenta¬ 
tions.  The  glossaries  are  well  orga¬ 
nized,  well  indexed,  and  well  written. 
The  only  thing  obviously  missing  is  a 
reference  in  each  glossary  entry  to 
the  page  where  the  word  is  discussed 
in  context. 

MacFORTH  pretty  much  assumes 
you  know  Forth,  although  it  comes 
with  a  Forth  tutorial  and  a  rich  set  of 
examples  that  would  prove  useful 
even  to  a  beginner.  As  is  often  ad¬ 
vised,  the  beginner  really  should  arm 
him/herself  with  some  instructional 
book  like  Starting  FORTH  or  Think¬ 
ing  FORTH  by  Leo  Brodie  (Prentice- 
Hall,  1981  and  1984)  instead  of  rely¬ 
ing  on  a  language  manual.'  In 
addition,  a  new  book  should  be  out  by 
the  time  you  read  this,  MacFORTH: 
Programming  for  the  Rest  of  Us  by 
Dave  Colburn,  one  of  the  authors  of 
MacFORTH  (also  available  from 
CSI). 

The  Going  Forth  tutorial,  a  split¬ 
screen  affair  with  paged  text  on  one 
side  and  the  live  MacFORTH  system 
on  the  other,  provides  a  hands-on  in¬ 
troduction  to  MacFORTH.  Source  is 
on  disk  for  those  who  want  to  expand 
the  tutorial  for  others. 

You  get  plenty  of  source  code  with 
these  packages,  both  “real-world 
stuff,  like  the  block  editor,  and  “tu¬ 
torial”  material  like  the  Note  Card 
example.  This  last  is  part  of  Level  2 
and  includes  23  screens  of  source  and 
20  pages  of  detailed  manual  text.  In 
all.  Level  1  includes  about  120 
screens  of  source,  and  level  2  about 
140  screens. 


Forth  and  the  Macintosh 

Forth  has  been  described  as  a  lan¬ 
guage  for  the  development  of  prob¬ 
lem-oriented  languages.  MacFORTH 
is  a  Macintosh-oriented  language. 
The  “problems”  of  the  Macintosh — 
how  to  use  windows,  pull-down 
menus,  graphics,  and  other  fea¬ 
tures —  have  been  smoothly  ad¬ 
dressed  by  MacFORTH. 

The  MacFORTH  vocabulary  is 
huge,  over  900  words  in  Level  1  with 
another  250  or  so  added  in  Level  2. 
Most  of  these  are  the  “application 
words”  that  give  you  direct  control 
over  the  Macintosh.  The  Macintosh 
and  its  fabled  toolbox  are  made  di¬ 


rectly  available,  letting  you  do  things 
“the  Macintosh  way”  with  relative 
ease.  Don’t  be  intimidated  by  the 
large  vocabulary:  flexibility  comes 
from  lots  of  little  words,  rather  than  a 
few  large  ones,  and  the  Macintosh 
has  many  of  these  small  units.  The 
window-handling  package  alone  has 
over  70  words  associated  with  it. 

Examples 

Straightforward  use  of  windows  and 
pull-down  menus,  for  example,  turns 
out  to  be  simplicity  itself.  The  code  in 
Figure  1  (at  left)  creates  a  window 
with  a  close  box  and  a  size  box.  Fig¬ 
ure  2  (at  left)  shows  an  example 
menu  definition,  with  3  items.  The 


( create  window ) 

1 5  CONSTANT  testWindow 
NEW. WINDOW  testWindow 

( define  title ) 

"  Example  Window" 

( define  rectangle ) 

50  50  100  300 
( add  boxes ) 

CLOST.BOX  SIZE. BOX  + 

(  add  window  to  the  screen  ) 
testWindow  ADD. WINDOW 

Figure  1 

Code  to  Create  Window 


(  create  menu,  define  title ) 

11  CONSTANT  testMenu 
0  "  Example"  testMenu  NEW. MENU 
( define  item  text ) 

"  First;Second;Third(;"  testMenu  APPEND. ITEMS 
(  add  menu  to  the  screen  ) 

DRAW. MENU. BAR 

Figure  2 
Menu  Definition 


(  define  menu  handler ) 
testMenu  MENU. SELECTION: 

(  CASE  statement  for  items ) 

CASE  1  OF  FirstProgram  ENDOF 

2  OF  SecondProgram  ENDOF 

3  OF  ThirdProgram  ENDOF 
ENDCASE 

( turn  off  title  hilite ) 

0  HILITE.MENU  ; 

Figure  3 

Selection  of  Menu  Item 


testWindow  W. TITLE 
testWindow  W. BOUNDS 
testWindow  W.  ATTRIBUTES 


Dr.  Dobb's  Journal,  October  1985 


101 

789 


first  two  are  “active”  and  the  third  is 
“inactive”  (grayed  out  and  incapable 
of  being  selected).  The  trailing  (  in 
Third(  causes  that  item  to  be  inactive. 
Type  style  (bold,  underline,  etc.)  and 
command  key  equivalents  are  easily 
assigned  using  other  character  suffix¬ 
es.  Selection  of  a  menu  item  is  easily 
linked  to  the  proper  actions  using  a 
defining  word  (Figure  3,  at  left). 
Fancier  uses  of  scrollbars,  control 
buttons,  and  the  like  are  implement¬ 
ed  with  the  clarity  and  simplicity  so 
essential  to  good  Forth  style. 

The  Level  2  manual  includes  a  ref¬ 
erence  section  giving  the  Pascal 
name,  MacFORTH  name,  and  stack 
usage  for  the  over  150  toolbox  traps 
directly  callable  by  the  user.  For  ad¬ 
vanced  applications,  MacFORTH 
provides  simple  low-level  words  to  ac¬ 
cess  any  toolbox  routine. 

Support 

CSI  maintains  a  technical  hotline  for 
owners  of  Level  1  and  Level  2,  and 
publishes  a  quarterly  newsletter.  The 
hotline  is  open  during  business  hours 
and  is  a  normal  toll  call. 

An  important  part  of  the  Mac¬ 
FORTH  “safety  net”  is  the  Special 
Interest  Group  (S1G)  on  Compu¬ 
Serve.  This  is  currently  open  to  regis¬ 
tered  owners  of  MacFORTH  only, 
and  according  to  CSI  about  10%  of 
MacFORTH  owners  participate.  As 
with  most  such  SIGs,  this  one  offers 
data  libraries  with  public  domain 
software  written  in  MacFORTH.  One 
data  library  is  devoted  to  notices  and 
patches  for  known  bugs.  A  terminal 
program  (with  source  code)  is  includ¬ 
ed  on  the  Level  1  disk. 

User  Interface 

You  would  expect  a  first-class  Macin¬ 
tosh  toolkit  implementation  to  make 
very  good  use  of  the  Macintosh,  and, 
for  the  most  part,  MacFORTH  does 
just  that.  Windows  and  menus  for  the 
MacFORTH  environment  are  stan¬ 


102 

790 


dard  Macintosh.  The  text  editor  in¬ 
cluded  in  Level  1  is  a  subset  of 
MacWrite,  with  source  on  disk  if  you 
want  to  study  it  or  add  features. 

MacFORTH  is  so  Mac  oriented 
that  rough  spots  in  the  user  interface 
come  as  a  bit  of  a  shock.  For  example, 
the  Forth  text  interpreter  is  plain  va¬ 
nilla  Forth  and  doesn’t  seem  to  know 
about  mousing  around.  Keystrokes 
just  sort  of  lie  there  on  the  screen,  and 
to  edit  a  mistake  you  have  to  back  up 
and  retype.  Gasp!  Fortunately  the 
CompuServe  SIG  comes  to  the  rescue 
here  with  a  handy  little  utility  that 
stores  the  last  ten  commands,  in  edit¬ 
able  form,  in  a  pull-down  menu. 
Nothing  like  a  user’s  group  to  rescue 
one  from  primitive  conditions. 

Another  minor  annoyance  is  that 
text  in  the  MacFORTH  window  is  not 
restored  after  being  overlaid  by  the 
editor  window.  When  you  exit  the 
editor  you  are  left  with  a  blank  rect¬ 
angle  chopped  out  of  your  dialogue 
with  MacFORTH.  This  conserves 
memory,  but  is  tacky.  Those  of  you 
with  512K  Macs  will  want  to  add 
screen  save/restore  to  the  editor  with 
a  few  lines  of  code. 

Developer  Package,  Level  3 

I  was  only  able  to  look  at  the  manual 
for  Level  3.  This  package  is  for  devel¬ 
opers  who  want  to  produce  turnkey 
products  for  distribution.  CSI  used  to 
charge  a  per-copy  royalty,  but  the 
new  Level  3  package  replaces  that 
policy  and  includes  a  royalty-free  li¬ 
cense  agreement. 

The  Level  3  manual  provides  some 
excellent  tutorial  material  and  seems 
to  be  as  concerned  with  helping  you 
turn  out  a  good  stand-alone  product 
as  the  Level  1  is  with  helping  you 
make  good  use  of  the  Macintosh. 

The  support  situation  is  a  bit  dif¬ 
ferent  for  Level  3.  CSI  is  understand¬ 
ably  wary  of  spending  a  lot  of  time  on 
the  phone  with  developers,  so  Level  3 
owners  are  expected  to  use  the  Com¬ 


puServe  SIG  instead  of  the  CSI  hot¬ 
line  when  they  need  help.  In  practice, 
this  seems  to  work  out  fine,  to  judge 
from  the  message  traffic  on  the  SIG 
(which  includes  heavy  participation 
by  CSI  staff).  If  you  are  planning  to 
use  MacFORTH  to  develop  a  turnkey 
product  or  program  for  distribution, 
you  might  buy  Level  2,  use  the  hot¬ 
line  and  the  SIG  while  learning  Mac¬ 
FORTH  and  during  program  develop¬ 
ment,  and  then  upgrade  to  Level  3 
when  you  are  in  the  final  stages. 

Conclusion 

It  is  clear  that  the  authors  of  Mac¬ 
FORTH  want  you  to  get  your  hands 
on  the  Macintosh  and  use  it  well.  The 
manuals,  samples,  and  examples  all 
support  this  “Power  to  the  People” 
approach.  Most  of  the  manual  is 
about  how  to  use  the  Macintosh  with 
the  tools  provided  by  MacFORTH. 
The  result  is  that,  for  the  most  part, 
you  don’t  have  to  sDend  hours  or  days 
poring  over  Inside  Macintosh  to  get 
the  Macintosh  to  do  something,  but 
can  instead  learn  by  example  and 
hands-on  experimentation.  Because 
it  is  so  easy  to  try  things  in  Forth, 
using  it  on  the  Mac  should  lead  to 
even  greater  reduction  of  program 
development  time  than  would  using 
Forth  on  a  more  limited  machine. 


DDJ 


Dr.  Dobb’s  Journal,  October  1985 


—  -  -  — — *-  - 

Mac  Toolbox  Listing 

/* 

*  np_dvr.c  version  1.0,  July  20,  1985 

*  MacSCSI  non-partitioned  driver  routine. 

*  This  version  was  based  on  '  e  ramdisk.asm  example  found  in 

*  Aztec  C  1.06  and  was  developed  on  the  Mac  under  Aztec  C. 

* 

*  The  changes  are  Copyright  1985  by  John  L.  Bass,  DMS  Design 

*  PO  Box  1456,  Cupertino,  CA  95014 

*  Right  to  use,  copy,  and  modify  this  code  is  granted  for 

*  personal  non-commercial  use,  provided  that  this  copyright 

*  disclosure  remains  on  ALL  copies.  Any  other  use,  reproduction, 

*  or  distribution  requires  the  written  consent  of  the  author. 

* 

*  Sources  are  available  on  diskette  from  Pastime,  PO  Box  12508 

*  San  Luis  Obispo,  Ca  93406  —  (805)  546-9141.  Write  for  ordering 

*  information  on  this  and  other  Mac  products. 

V 

♦  asm 
; :ts=8 


DRVNOM 

equ 

5 

;change  this  if  conflicts  with  others 

1 

Start 

dc.w 

$4f  00 

;locked,  read,  write,  Ctrl,  status 

dc.w 

0 

;no  delay 

dc.w 

0 

7no  events 

dc.w 

0 

;no  menu 

dc.w 

open-Start 

/open  routine 

dc.w 

rdwrt-Start 

;prime  routine 

dc.w 

control 

-Start 

;control  routine 

dc.w 

status- 

Start 

/status  routine 

dc.w 

tst-Start 

/close  routine 

dc.b 

8 

dc.b 

".MacSCSI" 

/name  of  driver 

ds 

0 

/for  alignment 

MAX MAP 

* 

equ 

640 

drBILen 

equ 

16 

drNmAlBlks 

equ 

18 

drAlBlkSiz 

equ 

20 

drClpSiz 

equ 

24 

drAlBISt 

equ 

28 

drFreeBks 

equ 

34 

template 

dc.w 

$d2d7 

/signature  word 

dc.l 

0 

/init  time 

dc.l 

0 

/backup  time 

dc.w 

0 

/attributes 

dc.w 

0 

/#  of  files 

dc.w 

4 

/first  dir  block 

dc.w 

0 

/♦  of  dir  blocks 

dc.w 

0 

/♦  of  allocation  blocks  total 

dc.l 

0 

/bytes/allocation  block 

dc.l 

0 

/bytes/allocation  call 

dc.w 

4 

/first  data  block 

dc.l 

1 

/next  free  file  # 

dc.w 

0 

/#  of  unused  allocation  blocks 

dc.b 

7 

/length  of  name 

Dr.  Dobb’s  Journal,  October  1985 


106 

791 


dc.b 

"MacSCSI " 

?name  of  drive 

ds 

0 

7for  alignment 

t 

stabuf 

dc.w 

0 

7current  track 

dc.b 

0 

7write  protect  in  bit  7 

dc.b 

1 

7disk  in  place 

dc.b 

1 

7drive  installed 

dc.b 

0 

;sidedness  of  drive 

dc.l 

0 

7next  queue  entry 

dc.w 

0 

;  unused 

dc.w 

DRVNUM 

7drive  number 

drvref 

dc.w 

0 

;driver  ref  num 

dc.w 

0 

7file  system  ID 

dc.b 

0 

;sidedness  of  disk 

dc.b 

0 

7needs  flush  flag 

dc.w 

0 

7error  count 

t 

diskbase_ 

7 

openf lg 

dc.l 

0 

7base  of  disk  memory 

7 

open 

dc.w 

0 

?once  only  open  flag 

lea 

openflg,a0 

?get  once  only  flag 

tst  .w 

(a0 ) 

7is  it  open  already? 

bne 

skip 

7yes  —  just  exit 

st 

( a  0 ) 

7set  once  only  flag 

move.l 

♦14, d0 

7size  of  drive  queue  entry 

dc.w 

$a51e 

;NewPtr  -  system  heap 

clr  .w 

10  ( a  0 ) 

7local  file  system 

move.l 

♦DRVNUM, d0 

7drive  number  5 

swap 

d0 

move.w 

24 (al) ,d0 

7driver  reference  number 

lea 

drvref , a2 

;get  address  of  driver  reference  number 

move.w 

d0 ,  (a2) 

7set  up  status  buffer 

dc.w 

$a04e 

7AddDriver 

move.l 

(al) ,-(sp) 

jpush  handle  to  resource 

dc.w 

$a992 

;DetachResource 

link 

a6 , 4-50 

7allocat  the  block  on  the  stack 

move.l 

sp,  a0 

7setup  as  arg  to  mountvol  trap 

move.w 

♦DRVNUM, 22 (a0)  ;set  our  drive  number  in  block 

dc.w 

$a00f 

7ask  for  it  to  be  mounted 

unlk 

a6 

7dear  block  from  stack 

skip 

move.l 

♦  0,d0 

7set  non-error  return 

7 

rdwrt 

rts 

;exit  back  to  caller 

movem.  1 

d0-d7/a0-a6 , - 

(sp)  ;save  regs 

move . 1 

a  0 , Pbp_ 

move.l 

rts 

al,Dp_ 

;save  arguments 

restore. 

move . 1 
rts 

Dp_,al 

;  pass  DCEptr 

♦endasm 

♦include 

<memory.h> 

♦include 

<resource.h> 

♦include 

<desk ,h> 

♦include 

<pb.h> 

/* 

*  varibles  used  by  asm  routines  ' 

2  cx 

O  a 

Dr.  Dobb’s  Journal,  October  1985 

*  Dp  and  Pbp  are  device  driver  arguments  handled  by  save/restore 

*  ptr  and  dvrarg  needed  by  AddDriver  call 

V 

DCEPtr  Dp; 

ParmBlkPtr  Pbp; 


/* 

*  Local  Stuff 

V 

int  DvrState; 

jsr  ScsiRdWr_ 

movem.l  (sp) +  , d0-d7/a0-a6 
bra  tst 


;Call  the  C  stuff 
;restore  regs 
;exit  via  IOdone 


#1 , 26 (a0 )  ; is  it  KilllO 

tst  ;no,  exit 

#0,d0 

;just  return 


control 

cmp.  w 
bne.s 
move .  1 
rts 

; 

status 

cmp.w  #8,26 (a0) 
bne.s  tst 
movem.l  a0/al,-(sp) 
lea  28 ( a  0 ) ,al 

lea  stabuf,a0 

move.l  #22, d0 
dc.w  $a02e 
movem.l  (sp)+,a0/al 

; 

tst 

move.l  #0,d0 
movem.l  d4-d7/a4-a6 , - (sp) 
move.l  $8fc,a0 
jsr  (a0) 

movem.l  (sp) +fd4-d7/a4-a6 
rts 

public  _Uend_,_Dorg_,_Cend_ 


;is  it  status  request?? 

; save  regs 

;get  dest  address 

;move  22  bytes 
;BlockMove 
;restore  regs 


;okay  return 

;save  regs  going  into  IOdone 
;get  IOdone  address 
;call  IOdone 

;restore  them  after  IOdone 
;and  jump  to  it 


save. 


lea 


Start+(_Uend_-_Dorq_)+(_Cend_-Start) ,a4  ;  setup  baseadr 


/* 

*  ScsiRdWr  —  do  a  driver  read/write  function. 


*/ 

ScsiRdWr ()  { 

register  struct  ioParam  *ip; 
long  len,part; 

short  blkno; 
char  *addr; 

save ( ) j 


} 


ip  =  &  Pbp->u. iop; 

part  =  Dp->dCtl Posit ion  &  0xFFFFFE00 ; 
len  =  (ip->ioReqCount  +  511)  &  0xFFFFFE00; 
addr  =  ip->ioBuf fer ; 
while(len  >=  512)  { 

blkno  =  part>>9 ; 
if ( (Pbp->ioTrap  &  0xff)  ==  2)  { 

ScsiRead (blkno,  addr); 

}  else  { 

ScsiWrite (blkno,  addr); 


1 

len  -=  512; 
addr  +=  512; 
part  +=  512; 

ip->ioAct Count  =  ip->ioReqCount; 
restore ( ) ; 


/*  exit  to  I/O  Done  */ 


Fnd  Listings 


Dr.  Dobb' s  Journal.  October  1985 


109 

7Q3 


CP/M  EXCHANGE 


by  Bob  Blum 


The  CP/M  Exchange  RCP/M  system 
is  available  for  your  use  24  hours  a 
day,  7  days  a  week.  Reach  it  by  dial¬ 
ing  (404)  449-6588. 

My  queue  runneth  over.  It  seems  that 
I  always  have  far  more  projects  un¬ 
derway  than  I  will  ever  have  time  to 
complete.  My  workmates  character¬ 
ize  my  problem  as  simply  not  being 
able  to  manage  my  time  efficiently.  I 
know  better,  I  know  the  real  reason 
that  this  phenomenon  constantly 
haunts  me. 

My  time  is  being  slowly  pilfered 
away  by  the  software  developers  who 
continue  to  write  programs  for  CP/ 
M.  It’s  an  insidious  plot.  What  rea¬ 
sonable  person  would  continue  to 
support  an  outdated  operating  sys¬ 
tem  such  as  CP/M  and  the  archaic  8- 
bit  microprocessors  it  runs  on?  The 
schemers  know  of  my  weakness  for 
trying  every  new  program  that’s  ad¬ 
vertised,  and  they  use  it  to  their  best 
advantage.  Some  are  even  so  bold  as 
to  send  me  their  addicting  wares  of 
slavery  without  even  letting  me  call 
to  volunteer  myself  for  human 
sacrifice. 

So,  as  you  can  see,  I’m  not  at  fault 
for  falling  victim  to  my  weaknesses. 

All  fun  aside,  more  and  more  new 
8-bit  CP/M  software  is  being  intro¬ 
duced  each  day.  I  applaud  all  of  those 
who  have  the  foresight  not  to  aban¬ 
don  a  strong  8-bit  market.  By  some 
standards  I  may  be  misguided,  but  I 
rest  assured  that  I  am  in  the  best  of 
company. 

In  the  Queue 

I’m  anxiously  waiting  for  my  review 
copy  of  the  latest  single  board  com¬ 
puter  from  Micro  Mint.  This  little 
barn  burner,  the  size  of  a  mini-floppy 
drive,  sports  the  HR64180,  the 
newest  8-bit  chip  from  Hitachi.  I’m 
told  that  this  device  is  at  least  as  pow¬ 


erful  as  an  8-Mhz  Z80  and  offers  a 
much  higher  level  of  integration,  in¬ 
cluding  I/O  ports  and  other  features 
that  normally  require  external  de¬ 
vices.  The  most  exciting  part  is  its  ca¬ 
pability  to  address  directly  512K  of 
memory.  My  plans  call  for  a  review 
and  then  for  porting  CP/M  Plus  over 
to  it,  taking  full  advantage  of  all  of  its 
new  advanced  features.  I  think  in  a 
short  time  that  a  new  level  of  perfor¬ 
mance  will  be  established. 

Even  though  DR  I  has  decided  to 
put  CP/M  Plus  on  the  mature  list, 
and  will  no  longer  actively  support  it, 
I  am  continuing  to  port  it  to  different 
computers.  My  most  recent  adven¬ 
ture  is  with  the  Intercontinental  8-bit 
S- 1 00  master  board;  on-board  memo¬ 
ry  management  and  many  other  per¬ 
formance  features  make  the  ICM 
board  a  natural  for  CP/M  Plus. 

Among  the  top  ranking  features  of 
CP/M  Plus  is  its  capability  to  time 
and  date  stamp  files  automatically  at 
each  access.  This  feature  is  especially 
important  to  me  because  I  have  the 
untidy  habit  of  not  religiously  track¬ 
ing  my  disk  files  during  a  heated  de¬ 
velopment  session.  I  wait  for  the  pro¬ 
ject  to  conclude  before  trying  to 
make  good  sense  out  of  my  files  and 
then  sometimes  have  trouble  recall¬ 
ing  the  order  in  which  I  created  them, 
and,  most  important,  which  is  the 
most  current.  Until  Plu*Perfect  Sys¬ 
tems  began  to  sell  DateStamper  there 
was  no  software  available  to  add  this 
desirable  feature  to  CP/M  2.2.  I’ve 
been  using  DateStamper  for  several 
weeks  and  have  found  it  a  most  useful 
tool  and  safeguard.  I  will  be  review¬ 
ing  it  in  a  future  issue  of  this  column. 

Finally,  my  stack  of  things  to  do 
wouldn’t  be  complete  without  a  new 
programming  tool.  I  have  been  using 
DSD80,  a  DDT-compatible  debug¬ 
ger  with  many  new  and  useful  added 
functions.  It’s  a  very  solid  package 


that  turns  the  drudgery  of  debugging 
into  an  art  form.  I  will  be  reviewing  it 
in  the  future. 


Encore,  Encore 

After  producing  the  fastest  full  fea¬ 
tured  Z80  assembler,  SLR  Systems 
could  do  nothing  for  an  encore  but 
bring  out  the  fastest  fully  featured 
8080  assembler. 

SLRMAC  shares  all  the  opera¬ 
tional  features  and  configuration  op¬ 
tions  of  its  family  member  Z80ASM. 
A  total  of  32  permanently  configura¬ 
ble  options  combined  with  20  run¬ 
time  command  line  parameters  make 
for  the  ultimate  in  flexibility  and  ease 
of  use.  I  found  several  of  the  configu¬ 
ration  options  to  be  most  interesting. 

A  maximum  error  threshold  count 
can  be  set  that  automatically  aborts 
the  assembly  when  reached.  This  op¬ 
tion  precludes  resetting  your  comput¬ 
er  or  in  some  other  way  manually 
aborting  an  assembly  if  excessive  er¬ 
rors  are  present. 

Another  option  dealing  with  errors 
sets  a  limit  on  the  number  of  errors 
that  can  be  displayed  on  the  CRT  be¬ 
fore  the  display  is  frozen.  It’s  no  long¬ 
er  necessary  to  sit  poised  to  strike  the 
CTL-S  key  combination  to  stop  the 
CRT  display  in  an  attempt  to  catch  a 
glimpse  of  an  error  flying  by  at  light¬ 
ning  speed.  Instead,  the  display  auto¬ 
matically  stops  upon  reaching  the  set 
error  count  and  waits  for  the  opera¬ 
tor’s  signal  to  continue. 

One  other  option  that  I  particular¬ 
ly  like  is  that  addresses  on  the  listing 
can  be  generated  in  either  logical  or¬ 
der,  high  byte  first,  or  the  normal  low 
byte-high  byte  fashion.  The  mental 
step  of  swapping  the  address  bytes 
slows  me  down  as  I  move  around  in  a 
program  listing. 

Command  line  arguments  allow  al¬ 
most  all  of  the  permanent  configura¬ 
tion  options  to  be  overridden  and  the 


114 


Dr.  Dobb’s  Journal,  October  1985 


specification  of  many  more  options 
that  are  significant  to  an  individual 
assembly. 

One  particularly  important  option 
deals  with  the  type  of  binary  file  that 
is  output  from  the  assembly.  The  H 
option  directs  the  output  file  to  be  in 
the  most  common  .HEX  format.  To 
execute  a  program  written  in  this  for¬ 
mat,  of  course,  requires  that  it  first  be 
converted  to  a  .COM  file  by  process¬ 
ing  it  through  the  LOAD  utility  pro¬ 
gram.  If  the  program  consists  of  a 
single  phase  (all  routines  are  self- 
contained)  then  another  option  can 
be  used  that  will  dramatically  speed 
the  process  of  converting  the  source 
program  to  an  executable  .COM  file. 
By  including  the  A  option  on  the 
command  line,  SLRMAC  will  as¬ 
semble  the  program  in  a  single  pass 
through  the  source  file  and  create  a 
.COM  output  file  ready  for  execu¬ 
tion.  This  option  will  also  work  for 
programs  that  contain  forward  refer¬ 
ences.  It  seems  that  SLR  went  the  ex¬ 
tra  mile  and  included  forward  refer¬ 
ence  resolution  logic  in  the  assembler 
so  that  a  single  pass  assembly  is  now 
a  workable  and  timesaving  reality. 

If  you’re  a  modular  programmer, 
as  I  am,  then  the  three  options  deal¬ 
ing  with  relocatable  output  files  will 
be  of  keen  interest  to  you.  Two  are 
variations  of  the  pseudo-standard 
Microsoft  naming  convention,  while 
the  other  permits  the  design  maxi¬ 
mum  of  seven  characters  to  be  uti¬ 
lized.  To  provide  advanced  features 
not  otherwise  available  in  other  bina¬ 
ry  formats,  an  SLR  format  can  also 
be  selected.  This  format  is  not  only 
more  compact  than  the  others  but  it 
also  provides  for  a  full  16-character 
external  name.  This  increased  name 
size  has  proven  to  be  very  valuable  to 
me  in  extremely  complex  systems 
containing  many  hundreds  of  individ¬ 
ual  modules,  not  to  mention  how 
much  more  quickly  the  link  editing 
operation  runs. 

The  restrictions  pertaining  to 
statement  labels  found  in  most  as¬ 
semblers  have  been  removed  from 
SLRMAC  in  most  cases.  It’s  not  at 
all  uncommon  in  some  assemblers  to 
be  forced  to  follow  all  labels  with  a 
colon.  In  others,  the  archaic  practice 
of  requiring  that  only  certain  types  of 

Dr.  Dobb’s  Journal,  October  1985 


statements  have  labels  followed  by  a 
colon  is  followed.  SLRMAC  doesn’t 
require  that  any  label  be  followed  by 
a  colon  except  in  the  case  where  a  du¬ 
plication  exists  between  a  reserved 
word  or  a  macro  name.  In  all  other 
instances  the  use  of  the  colon  is  en¬ 
tirely  optional.  One  final  word  about 
labels  in  SLRMAC:  a  label  of  any 
length  not  exceeding  the  maximum 
line  length  of  128  characters  is  per¬ 
mitted  and  the  beginning  16  charac¬ 
ters  are  significant. 

Many  problems  can  occur  when 
passing  relocatable  expressions  from 
program  to  program.  Most  assem¬ 
blers  only  allow  the  most  simple  uses 
of  this  type  of  expression.  For  in¬ 
stance,  when  loading  the  external  val¬ 
ue  into  a  register,  any  type  of  math 
involving  this  type  of  expression  is 
prohibited,  which  many  times  re¬ 
quires  that  a  great  deal  of  extra  code 
be  written.  SLRMAC  allows  full  ex¬ 
pression  usage  via  a  special  relative 
data  type.  In  this  way,  arithmetic  op¬ 
erations  involving  multiple  relocata¬ 
ble  expressions  can  be  resolved  prop¬ 
erly  at  link  editing  time.  But  a  word 
of  caution  is  in  order;  the  relative 
data  type  is  only  accepted  by  SLR’s 
own  link  editing  program. 


The  standard  program  counter 
maintenance  pseudo-ops  are  avail¬ 
able  in  SLRMAC.  These  include 
ORG,  CESG,  DSEG,  ASEG,  and 
COMMON,  to  name  a  few.  A  modi¬ 
fied  form  of  COMMON  is  available 
to  assign  up  to  5  additional  address 
spaces  that  can  be  assigned  beginning 
addresses  at  link  editing  time.  This 
can  be  very  important  if  the  program 
being  written  is  targeted  for  a  ma¬ 
chine  control  environment  in  which 
dedicated  portions  of  the  address 
space  must  be  defined.  Again,  this  is 
an  extension  to  the  standard  binary 
output  files  and  as  such  is  only  avail¬ 
able  in  the  SLR  format  relocatable 
file. 

When  defining  data  storage  areas 
it  is  not  at  all  uncommon  to  need  a 
string  delimiter  placed  at  the  end  of, 
for  example,  a  console  message.  The 
DEFZ  data  definition  statement  will 
automatically  generate  a  byte  con¬ 
taining  binary  zeros  at  the  end  of  the 
quoted  string.  And  the  DC  statement 
will  automatically  turn  bit  7  on  in  the 
last  byte  of  the  defined  string. 

A  full  complement  of  conditional 
assembly  pseudo-ops  and  a  full  mac¬ 
ro  capability  are  provided.  Two  pseu¬ 
do-ops  that  I  have  found  to  be  espe¬ 
cially  useful  are  IFIDN  and  IFDIF. 
IFIDN  tests  for  equality  between  two 
strings  and  sets  the  true  condition  ac¬ 
cording  to  the  result.  IFDIF  performs 
exactly  the  opposite  function  of  test¬ 
ing  for  inequality  between  the  two 
strings  and  sets  the  true  condition  ac¬ 
cording  to  the  result. 

The  method  used  to  process  the 
MACLIB  pseudo-op  is  unique  and 
worthy  of  comment.  Intel  mnemonic 
codes  were  designed  to  be  used  with 
the  8080,  and  there  is  no  provision 
made  for  the  additional  instructions 
available  on  the  Z80.  When  the  pro¬ 
gram  is  being  written  for  the  Z80  and 
full  use  of  its  instruction  set  is  de¬ 
sired,  the  extended  instruction  mne¬ 
monics  are  usually  added  to  the  as¬ 
sembly  through  a  macro  library.  This 
solves  the  problem,  but  at  the  expense 
of  slower  run  time  and  the  necessity 
to  maintain  another  macro  library. 
SLRMAC,  on  the  other  hand,  direct¬ 
ly  recognizes  the  extended  set  of  Z80 
mnemonics.  Upon  encountering  the 
MACLIB  Z80  statement  the  extend- 

115 

795 


ed  set  of  mnemonics  are  simply 
turned  on,  resulting  in  no  speed  deg¬ 
radation  due  to  the  loading  of  a  mac¬ 
ro  library  and  macro  generation. 

SLRMAC  is  another  notable 
product  from  SLR  Systems.  It  not 
only  does  the  job  that  it’s  advertised 
to  do  but  it  is  also  free  of  the  many 
petty  annoyances  and  inaccuracies 
that  plague  so  many  similar  products. 
Its  many  advanced  features,  ease  of 
use,  and  blinding  speed  make  it  a 
must  for  anybody  in  need  of  an  Intel 
mnemonic  assembler. 

Your  Attention  Please 

Before  progressing  further  with  the 
discussion  of  the  Ampro  Little  Board 
computer  begun  last  month,  a  few 
words  on  Z80  interrupts  are  in  order. 
The  Z80  and  its  family  of  related  pe¬ 
ripheral  support  chips  have  an  excel¬ 
lent  built-in  interrupt  system.  Each 
support  chip  that  has  more  than  one 
active  element  has  its  own  internal  in¬ 
terrupt  priority  scheduler  and  the  ca¬ 
pability  to  be  daisy-chained  into  a 
system.  This  structure  does  not  re¬ 
quire  assistance  from  interrupt  prior¬ 
ity  schedulers  in  all  but  the  biggest 
systems. 

The  main  controlling  element  in 
the  system,  the  Z80  CPU,  can  handle 
an  interrupt  in  one  of  three  ways  de¬ 
pending  on  the  setting  of  the  inter¬ 
rupt  mode.  This  fact  permits  the  soft¬ 
ware  to  determine,  to  a  large  degree, 
how  complex  the  interrupt  system 
will  be. 

The  most  basic  interrupt  mode, 
mode  0,  operates  identically  to  that  of 
the  8080  interrupt  response  mode 
and  was  included  to  provide  full  com¬ 
patibility  with  that  earlier  and  less 
powerful  CPU.  This  mode  is  set  by 
executing  the  IM  0  instruction.  With 
this  mode,  the  interrupting  device 
can  place  any  instruction  on  the  data 
buss  and  the  CPU  will  execute  it. 
Thus,  the  interrupting  device,  not  the 
memory,  supplies  the  next  instruction 
to  be  executed. 

Mode  1,  when  enabled  by  the  IM  1 
instruction,  causes  the  CPU  to  exe¬ 
cute  a  restart  to  memory  location  38h 
in  response  to  an  interrupt.  This  ac¬ 
tion  is  identical  to  that  of  the  non¬ 
maskable  interrupt  except  the  call  lo¬ 
cation  in  memory  is  38h  instead  of 


66h. 

Finally,  mode  2,  the  most  powerful 
interrupt  mode,  combines  three  sys¬ 
tem  elements  to  resolve  the  effective 
memory  address  loaded  into  the  pro¬ 
gram  counter.  With  a  single  8-bit  val¬ 
ue  supplied  by  the  interrupting  pe¬ 
ripheral  device  an  indirect  call  can  be 
made  to  any  memory  location. 

With  this  mode  the  CPU  forms  a 
16-bit  memory  address.  The  upper 
eight  bits  are  supplied  from  the  Z80 
interrupt  register,  and  the  lower  eight 
bits  are  supplied  by  the  highest  prior¬ 
ity  interrupting  peripheral  device. 
This  16-bit  address  is  used  as  an  indi¬ 
rect  memory  address  pointing  to  a 
stored  table  of  pointer  words  that 
point  to  the  processing  routine  re¬ 
sponsible  for  servicing  the  interrupt. 
Actually,  only  seven  bits  are  required 
from  the  interrupting  device  because 
bit  0  is  always  zero.  This  structure 
might  seem  overly  complex  until  you 
consider  that  through  software  the 
service  routine  tables  can  be  changed 
at  will,  providing  the  ultimate  in 
flexibility. 

Interrupts  and  the  Little  Board 

The  Ampro  Little  Board  computer 
satisfied  all  the  requirements  I  set  for 
my  RBBS  system  except  the  need  for 
a  real-time  clock.  My  first  impulse 
was  to  attach  one  through  a  serial 
port,  but  I  soon  remembered  that 
both  ports  were  busy;  one  with  the 
CRT  and  the  other  with  the  modem. 
The  lack  of  any  unused  I/O  ports  sty¬ 
mied  any  chance  I  had  of  using  an 
external  clock.  The  only  option  open 
to  me  was  to  look  to  the  Little  Board 
for  a  solution. 

Digging  into  the  schematics  of  the 
Little  Board  revealed  a  fairly 
straightforward  solution  to  my  prob¬ 
lem.  The  engineers  responsible  for 
designing  the  Little  Board  used  the 
Z80  and  its  related  support  chips  ex¬ 
clusively  to  minimize  interface  logic 
requirements  and  arranged  them  to 
allow  full  use  of  their  daisy-chained 
interrupt  capabilities.  So  it  seemed 
that  all  I  needed  was  to  find  an  un¬ 
used  timer,  or  some  other  source  of 
regular  interrupts,  and  a  little  soft¬ 
ware  to  implement  a  real-time  clock. 

Fortunately,  my  search  was  a  short 
one.  Channel  2  of  the  Clock  Timer 


Circuit  (CTC),  used  for  generating 
the  baud  rates  for  the  serial  ports, 
was  left  totally  unused.  All  that  was 
needed  to  start  it  working  as  a  real¬ 
time  clock  was  the  few  lines  of  code 
of  Listing  One  (page  1 20). 

My  first  task  was  to  define  a  table 
of  pointer  values  (section  2  in  the  list¬ 
ing)  addressing  the  interrupt  process¬ 
ing  routines.  The  address  of  the  first 
entry  of  this  table  would  later  be 
loaded  into  the  interrupt  vector  regis¬ 
ter.  Following  this  table  are  the  pro¬ 
cessing  routines.  I  chose  to  place  the 
clock  values  in  low  memory  locations: 
08h  hours,  09h  minutes,  OAh  sec¬ 
onds,  and  OBh  tenths  of  seconds. 

Next,  I  had  to  insure  that  the  con¬ 
stant  flow  of  interrupts  from  the 
CTC  would  not  adversely  affect  any 
other  time-sensitive  system  compo¬ 
nent  such  as  the  floppy  disk  control¬ 
ler.  The  only  positive  way  to  do  this 
was  to  disable  the  interrupts  (sections 
3  through  6  in  the  listing)  prior  to  en¬ 
tering  the  data  transfer  routines.  In 
practice,  this  proved  to  be  absolutely 
necessary  because  of  excessive  disk 
errors  when  running  with  interrupts 
enabled. 

The  side  effect  of  this,  however,  is 
to  destroy  the  clock’s  accuracy.  Since 
it’s  unknown  exactly  how  long  the 
clock  will  be  stopped  during  a  disk 
data  transfer,  it’s  impractical  to  add 
in  a  fudge  factor  upon  exiting  the 
disk  routines.  By  comparing  the  com¬ 
puter  clock  against  a  stopwatch  I 
found  that  during  a  highly  disk  inten¬ 
sive  period  the  computer  clock  would 
run  approximately  50%  slow.  Under 
more  normal  circumstances  the  clock 
wasn’t  accurate,  but  was  close 
enough  for  my  purpose  of  timing  how 
long  a  user  was  on  the  RBBS. 

To  achieve  my  goal  of  at  least  95% 
accuracy  I  would  have  to  run  the 
floppy  disk  controller  with  interrupts 
enabled,  but,  as  I  already  pointed  out, 
that  produces  an  unbearable  error 
rate.  My  only  choice  was  to  modify 
the  hardware  slightly  to  allow  both 
the  CTC  and  the  floppy  disk  control¬ 
ler  to  run  with  interrupts  enabled. 
That  is  a  project  I  am  currently  work¬ 
ing  on.  Once  complete,  I  will  publish 
the  software  and  other  modifications 
in  this  column. 

Getting  back  to  the  real-time  clock 


118 


Dr.  Dobb's  Journal,  October  1985 


routines,  the  last  addition  I  had  to 
make  to  the  BIOS  (sections  7  and  8 
in  the  listing)  was  to  initialize  the 
CTC  and  the  interrupt  register  with 
the  proper  constants  at  cold  boot 
time. 

If  you  are  inclined  to  make  these 
changes  to  your  machine,  be  sure  that 
you  are  running  version  2.3  of  the 


BIOS,  also  known  as  the  hard  disk 
BIOS.  Have  a  CTC  reference  man¬ 
ual  handy  to  use  when  setting  initial 
values. 

One  final  comment:  My  system 
has  a  hard  disk  that  ports  into  the 
Little  Board  through  a  SCSI  peer 
buss  adapter.  I  have  found  that  this 
configuration  runs  completely  error 


free  because  the  hard  disk  controller 
is  a  buffered  device  able  to  transfer 
data  while  other  tasks  are  active. 

DD| 

Reader  Ballot 

Vote  for  your  favorite  feature/article. 

Circle  Reader  Service  No.  1 95. 


CP/M  Exchange  Listing  (Text  begins  on  page  114) 


1)  Add  at  line  number  551  after  statement  FDALV  EQU  50 


INTRPT 

SET 

YES 

7 

SET 

INTERRUPTS  TO  NO  AS 

DEFAULT 

TIMER 

EQU 

YES 

7 

USE 

CHANNEL  2  OF  CTC  AS 

A  TIMER 

TYPER 

EQU 

NO 

7 

USE 

BUFFERED  KEYBOARD 

2)  Add  at  line  number  1028  after  statement  WTBLEN  EQU  $-WINCH3 


IF  TIMER 


MODE  2  INTERRUPT  VECTORS  FOR  THE  CTC 


ORG 

(S+17)  AND 

0FFF0H 

timers i nt Stable 

dw 

BAUDSA 

DW 

BAUDSB 

DW 

TIMERSRTN 

DW 

DSKSINT 

ENDIF 

TIMERSRTN : 

PUSH 

PSW 

; SAVE  THOSE  REGISTERS  THAT  WE  DESTROY 

PUSH 

H 

•  * 
t 

LX  I 

H,  12 

; POINT  AT  HUNDREDS 

INR 

M 

; INCREMENT  IT 

MOV 

A,  M 

;GET  HUNDREDS  DIGIT 

SUI 

10 

; AT  LIMIT 

JNZ 

TIMERSEXIT 

;N0 

MOV 

M ,  A 

; ZERO  HUNDREDS 

DCX 

H 

; POINT  AT  TENS 

INR 

M 

; INCREMENT  IT 

MOV 

A,M 

;GET  TENS  DIGIT 

SUI 

10 

; AT  LIMIT 

JNZ 

TIMERSEXIT 

;N0 

MOV 

M,  A 

; ZERO  TENS 

DCX 

H 

; POINT  AT  SECONDS 

INR 

M 

; INCREMENT  IT 

MOV 

A,  M 

;GET  SECOND  DIGIT 

SUI 

60 

;AT  LIMIT 

JNZ 

TIMERSEXIT 

;N0 

MOV 

M,  A 

; ZERO  SECOND 

DCX 

H 

? POINT  AT  MINUTES 

Dr.  Dobb's  Journal,  October  1985 


120 

797 


CP/M  Exchange  Listing 


(Listing  continued,  text  begins  on  page  114) 


INR 

M 

; INCREMENT  IT 

MOV 

A,  M 

;GET  MINUTE  DIGIT 

SUI 

60 

; AT  LIMIT 

JNZ 

TIMER$EXIT 

;NO 

MOV 

M,  A 

;  ZERO  MINUTE 

DCX 

H 

; POINT  AT  HOURS 

INR 

M 

; INCREMENT  IT 

MOV 

A ,  M 

;GET  HOUR  DIGIT 

SUI 

24 

; AT  LIMIT 

JNZ 

TIMER$EXIT 

;NO 

MOV 

M,  A 

;  ZERO  HOUR 

TIMER$EXIT: 

POP  H 

POP  PSW 

BAUD$A : 

BAUD$B  s 
DSK$INT : 

El 

RETI 


3)  Add  at  line  number  3144  after  label  WR: 

IF  INTRPT 

DI 


at  line 

number  3159  after 

label  WREXIT: 

IF 

INTRPT 

El 

ENDIF 

at  line 

number  3199  after 

label  RD: 

IF 

INTRPT 

DI 

ENDIF 

at  line 

number  3214  after 

label  RDEXIT : 

IF 

INTRPT 

El 

ENDIF 

at  line 

number  4431  before  label  LOGMSG:  and  before  JMP  GOCPM, 

IF 

TIMER 

XRA 

A 

GET  A  ZERO 

STA 

8 

CLEAR  TIMER  SAVE  AREAS 

STA 

9 

* 

STA 

10 

* 

STA 

11  i 

,  * 

STA 

12  ; 

.  * 

MV  I 

a, (low  timer$i nt Stable)  and  0f0h 

OUT 

CTCA0  ; 

: SET  INTERRUPT  VECTOR  LOW  8  BITS 

MVI 

A, HIGH  TIMER$INT$TABLE 

STA  I 

: SET  INTERRUPT  VECTOR  HIGH  8  BITS 

IM2 

[SET  INTERRUPT  MODE  2 

MVI 

A, 0A7H 

:TIMER  MODE  -  256  PRESCALER  -  CONSTANT 

[FOLLOWS  -  RESET  -  CONTROL 

OUT 

CTCA2 

122 


7QQ 


Dr.  Dobb's  Journal.  October  1985 


MV  I 
OUT 
ENDIF 


A, 156 
CTCA2 


TIME  CONSTANT 


\ 


8)  Add  at  line  number  4458  and  after  label  LOGMD : 


IF  TIMER 

DB  CR , LF , 1  Real  Time  Clock  Active' 

ENDIF 


End  Listing 


Dr.  Dobb's  Journal,  October  1985 


123 

799 


Modula  Tools 


Dr.  Dobb's  Journal 


Editorial 

Editor-in-Chief  Michael  Swaine 
Managing  Editor  Frank  DeRose 
Technical  Editor  Alex  Ragen 
Editorial  Assistant  Sara  Noah 

Contributing  Editors  Robert  Blum, 

Dave  Cortesi, 

Ray  Duncan, 

Allen  Holub 
Copy  Editor  Vince  Leone 
Typesetter  Jean  Aring 
Production 

Production  Manager  Bob  Wynne 

Art  Director  Shelley  Rae  Doeden 

Production  Assistant  Alida  Hinton 
Cover  Artist  Kazuo  Morita 

Circulation 

Sub.  Fulfillment  Mgr.  Stephanie  Barber 
Subscription  Mgr.  Maureen  Kaminski 
Book  Marketing  Mgr.  Jane  Sharninghouse 
Single  Copy  Sales  Mgr.  Stephanie  Barber 
Administration 
Finance  Manager  Sandra  Dunie 
Business  Manager  Betty  Trickett 
Accounts  Payable  Supv.  May  da  Lopez-Quintana 
Accounts  Payable  Asst.  Denise  Giannini 
Billing  Coordinator  Laura  Di  Lazzaro 
Accountant  Marilyn  Brown 
Adm.  Coordinator  Kobi  Morgan 
Advertising 
Advertising  Director 
Shawn  Horst  (415)  424-0600 
Systems  Manager  Ron  Copeland 
Administrative  Manager  Anna  Kittleson 
Advertising  Sales 

Walter  Andrzejewski  (617)  567-8361 
Lisa  Boudreau  (415)424-0600 
BethDudas  (714)643-9439 
Michele  Beaty  (31 7)  875-0557 


M&T  Publishing,  Inc. 

Chairman  of  the  Board  Otmar  Weber 
Director  C.F.  von  Quadt 
President  Laird  Foshay 


Dr.  Dobb's  Journal  (USPS  307690)  is  published 
monthly  by  M&T  Publishing.  Inc.,  2464  Embarcadero 
Way,  Palo  Alio,  CA  94303,  (415)  424-0600.  Second 
class  postage  paid  at  Palo  Alto  and  at  additional  entry 
points. 


November  1985 
Volume  10,  Issue  1 1 


In  this  Issue 

Mark  Edwards’s  review  of  ten  programmable  editors  is  the  centerpiece  of 
this  issue,  and  we  think  it  is  another  step  in  the  direction  we  want  to  take  in 
reviewing  technical  software  tools.  Edwards  has  asked  all  the  questions  we 
would  ask  about  a  programmer’s  editor  and  has  tabulated  the  results;  and 
he’s  done  it  for  ten  editors. 

This  month  we  also  focus  on  Modula-2,  the  language  that  Pascal’s  father 
Niklaus  Wirth  thinks  will  replace  Pascal  for  serious  programming.  Brian 
Anderson  gives  a  little  lesson  in  Modula-2  programming  while  presenting 
his  bit-manipulation  routines,  and  David  Carroll’s  comparison  of  Modula- 
2  and  Pascal  introduces  the  language  to  those  who  have  not  yet  encoun¬ 
tered  it.  We’ve  also  supplied  a  brief  list  of  sources  of  Modula-2  compilers 
and  tools. 

This  issue’s  cover  image,  courtesy  of  Aurora  Systems,  is  a  work  of  com¬ 
puter  art  by  Kazuo  Morita  of  Tokyo. 

Next  month  we  present  our  second  annual  operating  systems  issue.  We 
expect  to  offer  a  variety  of  tools  to  extend  operating  system  performance 
for  a  variety  of  operating  systems.  We’ll  also  examine  three  “operating 
environments”  for  8088-type  machines:  DRI’s  GEM,  IBM’s  TopView,  and 
Microsoft’s  Windows,  looking  at  how  they  work  with  MSDOS  and  at  some 
of  the  subtler  programming  issues  they  raise. 


Address  correction  requested.  Postmaster:  Send  Form 
3579  to  Dr.  Dobb’s  Journal ,  P.O.  Box  27809,  San 
Diego.  CA  92128.  ISSN  0278-6508 

Customer  Service:  For  subscription  problems  call: 
outside  CA  800-321-3333;  within  CA  619-485-9623  or 
566-6947.  For  book,  back  issue,  or  disk  order  problems 
call  415-424-1474. 


Subscription  Rates:  $25  per  year  within  the  United 
States,  $46  for  airmail  to  Canada,  $62  for  airmail  to 
other  countries.  Foreign  subscriptions  must  be  pre-paid 
in  U.S.  Dollars,  drawn  on  a  US.  Bank. 

Foreign  Distributor:  Worldwide  Media  Service,  Inc., 
386  Park  Ave.  South,  New  York,  NY  10016,  (212) 
686- 1 520  TELEX:  620430  (WUI) 


Entire  contents  copyright  °  1985  by  M&T 
Publishing,  Inc.  unless  otherwise  noted  on 
specific  articles.  All  rights  reserved. 


People's  Computer  Company 

Dr.  Dobb's  Journal  is  published  by  M&T  Publishing, 
Inc.  under  license  from  People's  Computer  Company, 
2682  Bishop  Dr.,  Suite  107,  San  Ramon,  CA  94583,  a 
non-profit,  educational  corporation. 


4 

802 


Dr.  Dobb’s  Journal,  November  1985 


Dr.  Dobb’s  Journal 


ARTICLES 


Modula-2  vs.  Pascal  for  Microcomputers: 

An  Update 

by  David  W.  Carroll 

28 

Why  you  might  want  to  program  in  Modula-2,  how  it 
differs  from  Pascal  in  general  and  from  Turbo  Pascal  in 
particular,  and  where  to  find  Modula-2  development  tools. 

Reader  Ballot  No.  191. 

Bit  Manipulation  in  Modula-2 

by  Brian  R.  Anderson 

38 

In  Modula-2  the  definition  and  implementation 
components  of  a  process  are  implemented  separately. 
Watch  one  programmer  develop  definition  and 
implementation  modules  for  some  simple  Modula-2  tools. 

Reader  Ballot  No.  192. 

REVIEW 

Programming  Editors,  Programmable  Editors 

by  Mark  Edwards 

60 

From  BRIEF  to  XyWrite:  ten  editors  contrasted  and 
evaluated  as  program-development  tools.  Reader  Ballot  No. 

196. 

COLUMNS 

Dr.  Dobb's  Clinic 

by  D.E.  Cortesi 

14 

Readers  and  the  Resident  Intern  analyze  the  Ross  puzzle; 
Wired  Tales,  an  anthology  of  computer-maintenance 
horror  stories.  Reader  Ballot  No.  190. 

The  Software  Designer 

by  Paul  Heckel 

48 

The  author  of  The  Art  of  Friendly  Software  Design 
discusses  the  design  decision  he  made  in  developing  a  new 
software  metaphor.  Reader  Ballot  No.  193. 

1 6-Bit  Software  Toolbox 

by  Ray  Duncan 

88 

68000  routines  for  square  roots,  binary  search,  and 
pseudo-random  number  generation,  the  80286  vs.  the 

8086;  an  MSDOS  DUMP  filter.  Reader  Ballot  No.  1 94. 

CP/M  Exchange 

by  Bob  Blum 

102 

A  look  at  the  U.S.  Robotics  modem,  with  a  sample 
program  showing  how  to  implement  all  its  features. 

Reader  Ballot  No.  1 95. 

DEPARTMENTS 

Editorial 

6 

Letters 

8 

Of  Interest 

122 

Advertiser  Index 

128 

Dr.  Dobb’s  Journal,  November  1 985 


5 

803 


EDITORIAL 


In  late  August  and  early  September,  with  no  trace  of  embarrassment,  PC 
Week  announced  the  “first  tangible  results  of  AI  research;”  Electronics 
described  new  products  that  “move  AI  closer  to  the  computing  main¬ 
stream;”  and  Computer-world  proclaimed  that  “AI  is  on  the  road  to  reality.” 

These  claims  that  AI  is  once  more  just  around  the  corner  were  prompted 
by  product  announcements  at  the  International  Joint  Conference  on  Artifi¬ 
cial  Intelligence  in  August  and  expectations  of  product  announcements  at 
Comdex  this  month.  We’ve  heard  such  claims  before,  but  this  time  they 
may  be  accurate. 

It’s  not  just  the  fact  that  Lisp  machines  and  AI  workstations  are  getting 
cheaper  or  the  fact  that  Lotus,  Ashton-Tate,  and  Microsoft  are  ostenta¬ 
tiously  spending  money  on  AI  development.  There  are  real  products  com¬ 
ing  to  market  that  employ  real  AI  features. 

Two  recently  announced  database  programs,  ANSA  Software’s  Paradox 
and  Symantek’s  Q&A,  employ  AI  techniques,  as  does  Javelin  Software’s 
Javelin  financial-modeling  program.  Q&A’s  natural-language  interface  is 
a  descendant  of  Symantek  founder  Gary  Hendrix’s  Ladder  system  that  he 
began  developing  while  at  SRI  in  the  1970s.  (These  developments  and  the 
Borland-Analytica  merger,  with  its  prospect  of  a  powerful  $99  database 
package,  should  at  least  make  the  database  field  more  interesting.) 

Hendrix’s  work  was  originally  done  in  Lisp  and,  although  a  germ  of  Lisp 
survives  in  the  product,  major  portions  are  written  in  C  and  assembly  lan¬ 
guage.  This  is  a  direction  that  other  AI  programs  are  taking  as  well.  Teknow- 
ledge  is  porting  its  S.  1  knowledge-based  system-development  software  from 
Lisp  to  C  for  memory  efficiency,  performance,  and  most  notable  for 
compatibility  with  what  they  refer  to  as  “conventional  computing  practice.” 
“Conventional  computing  practice,”  Teknowledge  is  telling  us,  means  C. 

Neither  Teknowledge  nor  any  other  firm  thinks  C  is  a  good  language  for 
developing  AI  applications,  but  it’s  obvious  they  regard  it  as  the  appropri¬ 
ate  language  for  delivering  systems  that  require  programmer  involvement. 
Lisp  is  still  the  leading  language  in  AI  development — a  status  that  won’t  be 
hurt  by  the  current  shift  toward  Common  Lisp  (which  the  trades  will  inev¬ 
itably  and  casually  label  the  standard,  de  facto  or  otherwise).  HP,  Apollo 
Computer,  Intellicorp,  DEC,  and  Xerox  have  all  announced  Common  Lisp 
implementations  or  products  based  on  Common  Lisp.  Prolog,  the  other 
significant  AI  programming  language  (which  was  written  explicitly  for  AI 
work)  gained  support  when  IBM  introduced  a  Prolog  implementation  for  its 
VM  operating  system.  But,  as  Michael  Genesereth  and  Matthew  Ginsberg 
point  out  in  September’s  Communications  of  the  ACM ,  the  resolution  prin¬ 
ciple  is  probably  not  an  adequate  deductive  method  for  really  effective 
logic  programs,  and  Prolog  is  little  more  than  a  software  implementation  of 
the  resolution  principle.  It’s  possible  that  we  don’t  yet  have  the  AI  language 
we  need  to  develop  powerful  Al-based  commercial  software. 

Then  again,  IBM’s  new  expert-system  development  product  was  written 
in  Pascal — a  reminder  that  you  don’t  necessarily  need  just  the  right  tool  for 
the  job. 

At  any  rate,  we  at  DDJ  are  optimistic  enough  about  the  near-term  appli¬ 
cability  of  AI  techniques  that  we’re  publishing  another  issue  with  this 
theme  in  April.  But  a  good  application  of  AI  techniques  is  appropriate  to 
any  issue,  so  consider  yourself  invited  to  send  us  yours. 


Michael  Swaine 


6 

804 


Dr.  Dobb's  Journal,  November  1985 


LETTERS 


Replies  to  "Turbo  Pascal 
vs.  the  Standard" 

Dear  DDJ , 

We  are  responding  to  “Dr.  Dobb’s 
Clinic”  on  TURBO  PASCAL  (July 
1985).  Hopefully,  the  following  will 
clarify  some  of  the  statements  made 
by  Mr.  Cortesi  in  the  article. 

•  The  comments  about  TURBO  PAS¬ 
CAL  running  under  CP/M  2.2  and 
CP/M  Plus  are  not  accurate.  Please 
refer  to  your  own  review  of  TURBO 
PASCAL  in  your  June  1984  issue 
(referenced  below). 

•The  discussion  about  the  way  the 
compiler  treats  available  memory 
storage  needs  clarification.  The  pro¬ 
grammer  has  total  control  over  the 
amount  of  storage  allocated  by  a 
TURBO  PASCAL  program.  A  brief 
glance  at  the  CP/M-80  memory  map 
(on  page  291  of  the  Version  3.0  Refer¬ 
ence  Manual)  reveals  why  the  amount 
of  available  memory  must  be  fixed  at 
compilation  time:  static  variables  start 
at  the  end  address  and  stretch  down¬ 
wards.  For  this  reason,  both  Version 
2.0  and  3.0  allow  you  to  lower  the  end 
address  of  your  program  and  thus  en¬ 
able  .COM  files  to  work  on  systems 
with  less  memory  available. 

•  Regarding  TURBO  PASCAL  and 
educational  institutions,  our  compiler 
is  well  received  and  widely  used. 
Computer  science  departments  at 
more  than  400  universities  around 
the  world  have  chosen  TURBO  PAS¬ 
CAL  to  be  their  “standard”  program¬ 
ming  language;  ACM  has  selected 
TURBO  PASCAL  as  the  language  for 
its  national  and  international  pro¬ 
gramming  contests;  and  the  Ad¬ 
vanced  Student  Placement  Testing 
Service  has  designated  TURBO  PAS¬ 
CAL  its  official  testing  language. 

This  brings  us  to  the  main  thrust  of 


Mr.  Cortesi’s  article.  He  points  out 
the  ways  in  which  TURBO  PASCAL  is 
nonstandard  and  states  that  “Bor¬ 
land  has  an  obligation”  to  support  all 
standard  Pascal  features.  We  give 
careful  consideration  to  our  users’  re¬ 
quests  for  enhancements  and  addi¬ 
tions  to  TURBO  PASCAL  and,  unlike 
most  software  companies,  we  have  al¬ 
ready  implemented  many  of  these 
features  in  our  two  major  upgrades. 
We  will  consider  Mr.  Cortesi’s  sug¬ 
gestions  within  this  context. 

Why  do  we  use  the  Jensen/ Wirth 
report?  We  consider  it  the  most  im¬ 
portant  standard  and,  for  the  most 
part,  follow  its  guidelines.  We  are  not 
the  only  ones  to  downplay  the  differ¬ 
ences  between  ISO  and  Jensen/ Wirth. 
Dr.  Dobb’s  says,  “For  most  users,  the 
difference  between  the  two  standards 
will  be  small.  . . .  The  compiler  [TUR¬ 
BO  PASCAL]  accepts  almost  all  stan¬ 
dard  Pascal  statements.  It  is  therefore 
possible  to  write  highly  portable  pro¬ 
grams”  (June,  1984).  We  have  care¬ 
fully  designed  efficiency  into  our  com¬ 
pilers — there  are  plenty  of  large, 
cumbersome  systems  available  that 
provide  total  standard  support. 

TURBO  PASCAL  provides  all  the 
tools  and  utilities  necessary  for  the 
vast  majority  of  programmers  to  pro¬ 
duce  fast,  high-quality  scientific,  busi¬ 
ness  and  personal  software.  This  speed 
and  power  have  made  TURBO  PAS¬ 
CAL  the  industry  standard  and  the 
choice  for  more  than  400,000  pro¬ 
grammers.  TURBO  PASCAL’S  price, 
documentation  and  support  have 
made  it  the  best  software  value  in  the 
business. 

BORLAND  INTERNATIONAL 

4113  Scotts  Valley  Dr. 

Scotts  Valley,  CA  95066 

I  overstated  the  problems  with  CP/M 
storage  allocation — see  Winograd’s 


letter  below.  Regarding  all  other 
points,  /  stand  by  my  prior  state¬ 
ments. — D.  E.C. 

Dear  DDJ, 

I  read  with  interest  D.  E.  Cortesi’s 
comments  about  TURBO  PASCAL  in 
the  July  “Dr.  Dobb’s  Clinic.”  He  is 
correct  in  noting  that  because  TUR¬ 
BO  PASCAL  doesn’t  look  at  the 
BDOS  entry  address  at  location  6,  a 
program  compiled  with  a  larger  TPA 
will  not  run  in  an  environment  with  a 
smaller  one.  I  originally  experienced 
the  same  problem.  Text  processing 
programs  that  I  wrote  in  TURBO 
PASCAL  wouldn’t  run  from  the 
WordStar  no-file  menu,  and  pro¬ 
grams  I  compiled  on  my  Kaypro  10 
crashed  on  my  Otrona,  which  has  a 
smaller  TPA. 

Fortunately,  there  is  a  very  simple 
solution  to  the  problem.  When  you 
command  TURBO  PASCAL  to  com¬ 
pile  to  disk  (to  do  this  you  use  the  O 
and  C  options  from  the  main  menu), 
you  will  see  a  screen  such  as  is  found 
in  the  Table,  page  12.  This  screen  re¬ 
flects  the  memory  arrangement  of 
my  Kaypro  10. 

The  Start  Address  is  the  address  for 
the  first  byte  of  the  program  code. 
This  is  one  byte  higher  than  the  end  of 
the  TURBO  run-time  library.  The  End 
Address  is  the  highest  address  for  pro¬ 
gram  code,  which  is  normally  (BDOS 
—  1 ),  minus  700  to  1,000  bytes  for  the 
loader  which  allows  other  programs  to 
be  executed  from  the  TURBO  main 
menu  (see  p.  261  of  the  TURBO  PAS¬ 
CAL  3.0  manual). 

To  solve  the  problem  raised  by  Mr. 
Cortesi,  simply  reset  the  End  Address 
from  the  above  screen.  Start  by  press¬ 
ing  E.  TURBO  PASCAL  will  then 
prompt 

End  address:  A 100 


Dr.  Dobb’s  Journal,  November  1985 


8 

805 


As  shown  by  the  entry  above,  I  usual¬ 
ly  choose  AlOOh  as  my  End  Address. 
This  ensures  that  a  program  will  exe¬ 
cute  on  any  system  with  a  40K  TPA. 
This  should  be  enough  even  for  an¬ 
cient  homebrew  systems,  and  it 
should  allow  programs  to  run  on  al¬ 
most  any  machine  from  the  Word¬ 
Star  no-file  menu  or  with  memory- 
resident  utilities,  such  as  keyboard 
enhancers,  installed.  If  not,  simply  go 
back  to  the  above  menu  and  increase 
the  End  Address  until  the  “Not 
enough  memory”  message  stops  ap¬ 
pearing  when  the  compiled  program 
is  run. 

Ed  Wi  nograd 

4704  Edison  Lane 

Boulder,  CO  80301 

All  true  and  I  should  have  known  it. 
But  this  is  still  very  restrictive:  you 
have  to  size  your  program  to  the 
worst  possible  case,  and  it  can  never 
take  advantage  of  storage  over  that 
minimum.  — D.  E.  C. 

Dear  DDJ, 

While  it  is  apparent  that  Mr.  Cortesi 
possesses  a  working  knowledge  of 
Pascal,  it  is  also  apparent  that  he  has 
not  worked  with  standards  organiza¬ 
tions.  He  makes  such  statements  as 
“the  standard”  and  “There  is  a  Pas¬ 
cal  standard,”  giving  one  the  impres¬ 
sion  that  there  exists  “one  and  only 
one”  standard  for  Pascal.  There 
could  be  nothing  farther  from  the 
truth!  Secondly,  having  been  person¬ 
ally  involved  with  several  projects 
keyed  against  emerging  standards, 
“draft,”  “final  draft,”  and  “stan¬ 
dard”  do  not  have  the  same  meaning, 
practically  or  in  any  other  sense  of 
the  word! 

Mr.  Cortesi  then  proceeds  to  tell 
me  that  ANSI,  IEEE,  and  ISO  are  one 
big  happy  family.  First,  he  ignores 
the  fact  that  each  one  of  these  inde¬ 
pendent  standards  organizations  has 
published  their  “standard”  for  Pascal 
separately,  identified  by  each  organi¬ 
zation’s  individual  “numbering/la¬ 
beling”  plan.  Mr.  Cortesi  should  have 
identified  each  standard  by  appropri¬ 
ate  label/name,  organization,  and 
date,  and  then  identified  which  of 
these  was  “the  standard”  for  the  pur¬ 
poses  of  his  article. 


Finally,  I  find  it  particularly  diffi¬ 
cult  to  accept  the  premise  that  merely 
because  a  programming  language  is 
standard  (adheres  to  some  recog¬ 
nized  body’s  standards)  it  is  in  some 
way  inherently  portable.  I  would  be 
extremely  pleased  to  see  your  journal 
tackle  this  very  volatile  issue  in  the 
near  future. 

Bruce  Hutfless 

Tacoma,  WA 

There  is  just  one  U.S.  domestic  Pas¬ 
cal  standard.  It  was  the  joint  work  of 
the  IEEE  and  ANSI  and  was  pub¬ 
lished  in  identical  form  by  both  bod¬ 
ies  under  the  title  American  Nation¬ 
al  Standard  Programming  Language 
Pascal.  The  foreword  to  the  docu¬ 
ment  details  the  close  liaison  that 
was  maintained  between  the  U.S. 
joint  committee  and  its  international 
counterparts.  There  is  a  formal 
statement:  “ Differences  of  Technical 
Substance  Between  the  Standard  and 
the  International  Standard,  as  Rep¬ 
resented  by  ISO/D/S  7185.”  Except 
for  the  U.S.  omission  of  conformant 
arrays,  these  are  exceedingly  narrow 
technical  points.  There's  a  careful 
statement  of  how  ‘‘compliance  with 
this  standard  is  equivalent  to  com¬ 
pliance  at  level  0  with  the  interna¬ 
tional  standard.”  The  joint  commit¬ 
tee’s  intent  is  quite  clear:  they  mean 
for  the  U.S.  and  international  stan¬ 
dards  to  agree  with  each  other.  For 
all  practical  purposes,  there  is  true 
worldwide  agreement  on  a  single 
standard  for  Pascal. — D.E.C. 

Dear  DDJ , 

Allow  me  to  congratulate  you  on 
your  excellent  critique  of  TURBO 
PASCAL  that  appeared  in  the  July  is¬ 
sue  of  DDJ.  Frankly,  I  wish  that 
someone  else  had  written  it,  since 
many  people  still  remember  Dave 
Cortesi’s  review  of  JRT  Pascal  and 
his  reputation  of  being  picky  about 
Pascal  implementations  may  lessen 
the  impact.  Nevertheless,  he  certain¬ 
ly  said  things  that  desperately  needed 
to  be  said.  All  the  uniformly  glowing 
praise  for  TURBO  was  beginning  to 
bother  me.  Finally  someone  has  real¬ 
ized  (in  print)  that,  as  implementa¬ 
tions  of  Pascal  go,  TURBO  is  very 
mediocre. 


My  own  experience  with  TURBO 
PASCAL  has  been  rather  unpleasant. 
Over  the  past  three  years  I  have  writ¬ 
ten  roughly  60,000  lines  of  Pascal 
code  for  a  wide  variety  of  applica¬ 
tions.  I  do  almost  all  my  work  under 
VAX/VMS,  which  has  an  absolutely 
first-rate  Pascal  compiler  (full  sup¬ 
port  of  the  ISO  standard  with  option¬ 
al  warnings  on  nonstandard  features, 
highly  optimizing,  2000  lines/minute 
average  compile  time  on  a  VAX-11/ 
750,  source-level  debugging,  etc.). 

I  get  a  request  to  port  code  to 
MSDOS  about  once  a  week.  When 
TURBO  first  became  available,  it 
sounded  great.  The  reviews  made  it 
sound  like  the  ultimate  compiler.  I 
borrowed  a  manual  from  a  friend  and 
read  it.  It  was  obviously  Pascal  im¬ 
plemented  by  people  who  did  not  un¬ 
derstand  the  basic  philosophy  of  the 
language.  Worse,  the  implementa¬ 
tion  was  completely  unusable.  I  write 
strictly  standard-conforming  code 
most  of  the  time,  but  I  frequently  use 
all  the  features  TURBO  elected  to 
omit,  many  of  which  are  virtually  im¬ 
possible  to  work  around.  The  result  is 
that  TURBO  is  absolutely  useless. 
This  is  very  difficult  to  explain  to 
neophyte  TURBO  owners  who  want 
to  use  my  code.  “Buy  a  real  Pascal 
compiler  instead  of  a  compiler  for 
some  random  language  invented  by 
Borland”  does  not  tend  to  go  over 
well. 

After  countless  arguments  about 
standards  (and  more  trips  to  the 
bookshelf)  my  customary  response 
now  is  to  simply  give  people  the  code 
they  want  and  tell  them  to  go  for  it. 
To  forestall  the  inevitable  arguments 
I  also  give  them  a  listing  with  all  non¬ 
standard  features  flagged  by  the 
VAX  Pascal  compiler.  After  a  few  at¬ 
tempts  they  usually  give  up  or  just 
switch  to  some  other  implementation. 

Edwin  Earl  Freed 

Box  430 

Perkins,  OK  74059 
Dear  DDJ , 

I  read  your  Clinic  column  in  the  July 
1985  issue  with  particular  interest 
because  I  teach  an  introductory  pro¬ 
gramming  course  to  engineering  stu¬ 
dents  using  TURBO  PASCAL.  Your 
points  have  generally  been  well 


10 

806 


Dr.  Dobb’s  Journal,  November  1985 


made,  and  I  can  only  hope  that  Bor¬ 
land  does  the  necessary  to  improve 
compatibility.  Although  Version  1.0 
undoubtedly  was  written  on  a  shoe¬ 
string  and  in  a  hurry,  there  has  been 
plenty  of  time  (and  money)  for  im¬ 
provements  to  Version  3.0.  In  fact,  I 
expect  to  continue  using  Version  2 
because  there  has  been  so  little  im¬ 
provement  in  Version  3.0. 

I  do  expect  to  continue  to  use  it  for 
teaching,  and  I  don’t  agree  with  your 
objection  to  its  use  for  this  purpose. 
Because  of  the  fast  compilation  and 
built-in  editor,  students  learn  faster 
than  with  any  alternative  I  have  seen. 
What  they  learn  appears  to  be  valid 
“standard  Pascal”.  You  have  not 
identified  any  features  that  are  viola¬ 
tions.  There  is  more  than  enough  to 
keep  student  programmers  busy  for  a 
semester  (or  two)  without  ever  getting 
to  the  New/Dispose  and  Get/Put 
problems,  although  the  omission  of 
the  Page  procedure  is  disappointing. 
They  should  be  able  to  progress  to  an¬ 
other,  more  complete  compiler  if  they 
really  need  such  advanced  features. 

Edward  H.  Wiser 

North  Carolina  State 
University 

School  of  Agriculture  and 
Life  Sciences 

Dear  Mr.  Kahn, 

I  recently  purchased  a  number  of 
copies  of  the  MSDOS  implementation 
of  Version  3.0  of  TURBO  PASCAL 
for  our  department.  Although  there 
is  much  to  be  pleased  with,  I  have  a 
number  of  concerns  that  are  very 
substantial.  Fortunately,  as  I  was 
gathering  my  thoughts  on  the  sub¬ 
ject,  there  appeared  in  the  July  issue 
of  Dr.  Dobb's  Journal  an  article,  by 
D.  E.  Cortesi,  which  expresses  my 
views  to  perfection.  I  assume  you  will 
have  seen  it.  Personally,  I  find  the  ab¬ 
sence  of  Get  and  Put  an  enormous 
handicap.  Their  replacements  are 
really  pernicious. 

To  Mr.  Cortesi’s  implied  requests 
for  a  version  of  TURBO  PASCAL  that 
implements  Pascal  in  a  more  “stan¬ 
dard”  way,  let  me  add  my  own.  The 
current  implementation  will  not  be 
acceptable  for  use  in  classes  that 
teach  Pascal.  I  will  advise  those  I 
know  to  avoid  using  it. 


Phillip  Emig 

Department  of  Mathematics 
California  State  University, 
Northridge 

Northridge,  CA  91330 
Dear  DDJ, 

I  sent  you  a  letter  last  month  in  which 
I  demonstrated  the  way  to  correct  a 
problem  in  TURBO  PASCAL’S  code 
structuring.  However,  it  seems  that 
the  letter  must  have  been  lost  in  the 
shuffle  somewhere,  as  D.  E.  Cortesi’s 
column  in  the  July  issue  claims  that 
TURBO  PASCAL  is  "...  unusable 
under  CP/M  Plus,  and  dubious  under 
CP/M  2.2”.  The  problem  he  describes 
is  real,  but  as  can  be  seen  in  my  letter, 
the  fix  is  extremely  simple.  And  to 
date,  no  one  else  has  offered  or  pub¬ 
lished  this  procedure. 

Sincerely, 

James  R.  Shiflett 
P.O.  Box  1236 
Stafford,  TX  77477 

Mr.  Shiflett  had  sent  a  discussion 
of  how  the  CP/M  version  of  TURBO 
allocates  storage,  including  an  in¬ 
volved  method  by  which  you  can 
trick  it  into  using  all  available  stor¬ 
age.  He's  put  the  same  information 
up  on  the  Borland  SIG  on  Compu¬ 
Serve.  I  thought  that,  while  he’d 
done  a  first  class  job  of  systems 
work,  his  solution  was  a  typical  ex¬ 
pert’s  hack:  unsafe,  prone  to  error, 
dependent  on  a  particular  compiler 
version  and  a  particular  operating 
system  and  thus  absolutely  nonpor¬ 
table,  and  in  general  not  something  1 
would  want  to  use  or  publicize. — 
D.E.C. 


TgX  Features 

Dear  DDJ , 

Thank  you  for  the  fine  review  you 
gave  our  product,  PCTgX.  We  hope 
you  will  give  Personal  TEX  a  chance 
to  mention  recent  enhancements  to 
our  product.  We  might  add  that  these 
are  not  proposed  additions,  but  actu¬ 
al  features  that  are  part  of  the  prod¬ 
uct  being  shipped  today. 

•  We  have,  now,  a  TgX  driver  for  the 
Corona  Laser  Printer.  It  produces 
300-dot-per-inch  TEX  finals  at  two 


81/2-by-l  1  pages  per  minute  on  an  XT 
and  six  pages  per  minute  on  an  AT. 

•  We  offer  a  complete  TgX  publish¬ 
ing  package  for  the  PC  consisting  of 
PCTEX,  the  Corona  driver,  and  a  Co¬ 
rona  Laser  Printer,  all  for  only 
$3,395. 

•  We  carry  the  complete  line  of  Text- 
set  device  drivers  (Apple  Laser¬ 
Writer,  screen  preview,  and  more) 
and  OCLC  Metafoundry  fonts  (Hel¬ 
vetica,  Copperplate,  Schoolbook,  and 
others).  (Lack  of  screen  preview  was 
one  of  the  complaints  of  the 
reviewers.) 

Our  unbundled  basic  TEX  package 
is  still  only  $279 — a  little  over  half  of 
the  competition’s  bundled  price.  And 
we’ve  got  Michael  Spivak,  the  gadfly 
author  of  The  Joy  of  TEX,  on  our 
team.  Michael  has  written  a  delight¬ 
ful  introduction  to  TgX  together  with 
an  easy-to-use  macro  package  he 
calls  VANILLA  that  comes  only  with 
PCTEX. 

We  would  like  to  let  potential  buy¬ 
ers  of  document-formatting  software 
know  that  TgX  is  all  we  do.  When 
you  talk  TEX  with  us,  you’ve  got 
100%  of  our  attention. 

By  the  way,  we  will  soon  be  ship¬ 
ping  our  implementation  of  TEX, 
Version  1.5. 

Lance  Carnes 

President,  Personal  TEX,  Inc. 

20  Sunnyside  Avenue 

Suite  H 

Mill  Valley,  CA  94941 

We  have  received  an  announcement 
of  an  agreement  under  which  Addi- 
son-Wesley,  distributors  of  Micro- 
T EX,  will  also  license  and  distribute 
the  Textset  laser  printer  and  screen 
preview  drivers. — Ed. 


Memory 

compile  -» 

Corn-file 

cHn-file 

Start  address: 

20E2  (min  20E2) 

End  address: 

CA42  (max  CD06) 

Find  run-time  error  Quit 

> 

Table 

Adjusting  TURBO  Memory 

DD| 


Dr.  Dobb's  Journal,  November  1985 


12 

ft07 


DR.  DOBB'S  CLINIC 


by  D.  E.  Cortesi 


Watt  Did  Ross  Dew? 

In  June  we  published  a  short  BASIC 
program  by  David  Ross  and  asked 
“Watt  duzzit  dew?”  Several  of  you 
wrote  to  explain  it  to  us.  Some  pro¬ 
vided  improvements;  we’ll  get  to 
those  in  a  bit. 

Ross’s  original  program  appears  in 
Listing  One  (below).  Watt  duzzit 
dew?  Richard  Oakland  of  Fon  Du 
Lac,  Wisconsin,  says,  “In  theory, 
Ross’s  program  determines  the  digits 
of  the  number  2*  and  places  the  char¬ 
acter  representations  of  those  digits 
into  successive  elements  of  a  string 
array  of  dimension  k  +  1 .  It  concludes 
by  printing  the  array.”  Why  is  it  so 
complicated,  when 

1 30  PRINT  2'K 

would  accomplish  about  the  same 
thing?  “It  seems  likely,”  Oakland 
says,  “that  the  object  was  to  display 
with  full  precision  numbers  that  have 
more  digits  than  single  precision  can 
generally  handle  .  .  .  the  author 
might  even  have  hoped  to  display 
numbers  that  overflow  single  preci¬ 


sion,  numbers  like  2400,  for  example.” 

Why  k  +  1  digits?  David  Barker  of 
Mt.  Clemens,  Michigan,  says  that 
this  number  “can  be  justified  because 
it  requires  £+1  digits  to  represent 
10*,  and,  because  2*  is  less  than  10*, 
you  will  always  be  able  to  express  2* 
in  k+  1  digits  or  less.” 

How  did  Ross  want  his  program  to 
work?  Let’s  go  through  it  line  by  line, 
as  several  readers  did.  Look  first  at 
lines  110  and  140,  which  are  closely 
related: 

1 10  DEF  FNL(X)  =  LOG(X)/T 

140T=LOG(10) 

We  don’t  all  work  with  logarithms 
every  day,  so  here’s  a  brief  review. 
The  BASIC  function  LOG(  )  returns 
the  natural  log  of  a  number,  the  loga¬ 
rithm  to  the  base  e.  This  function  is 
usually  written  ln(  )  to  distinguish  it 
from  the  common  log ,  or  logarithm 
to  the  base  10,  that  we  met  first  in 
school.  Why  does  BASIC  provide  only 
natural  log?  Because  it  can  be  used  to 
obtain  a  logarithm  to  any  base:  the 
log  of  a  number  x  to  any  base  b  is 


100  REM  BASIC  PUZZLE  BY  D.  ROSS 
105  REM  MODIFIED  FOR  MBASIC  BY  DEC 
110  DEF  FNL (X) =LOG (X) /T 
120  INPUT  "K=" , K  :  DIM  A$(K+1) 

130  FOR  1=1  TO  K+l  :  A$(I)  =  "0"  :  NEXT  I 
140  T  =  LOG (10) 

150  Z  =  ASC ( " 0 " } 

160  A0  =  K*FNL ( 2 ) 

170  B  =  1 

180  WHILE  B  >  0 

190  A  =  A0+FNL (B) 

200  N  =  INT(A) 

210  IF  N<0  THEN  280 

220  L  =  A-N 

230  X  =  10'L 

240  D  =  I NT (X) 

250  A$(K— N+l)  =  CHR$ (D  +  Z) 

260  B  =  B-10” (N-A0+FNL(D) ) 

270  WEND 

280  FOR  1=1  TO  K+l  :  PRINT  A$(I);  :  NEXT  I 


Listing  One 


14 


ln(x)/ln(/>) 

Function  FNL(X)  is  taking  advan¬ 
tage  of  that  identity  to  provide  the 
common  log  of  its  argument  X.  It 
would  have  been  clearer  if  written 

DEF  FNL(X)  =  LOG(X)/  LOG(IO) 

although  slightly  slower  in  execution. 

Line  120  gets  the  power  to  which  2 
is  to  be  raised.  It  should  be  forced  to 
be  an  integer  because  the  program  is 
clearly  meant  to  produce  an  integer 
output.  You  should  also  test  to  see 
whether  it  is  positive;  2°  is  just  1  and  a 
negative  power  will  cause  errors  later. 

Line  1 30  creates  the  array  of  digits 
and  clears  it  to  zeros.  Clearing  is  nec¬ 
essary,  says  David  Shochat  of  Los 
Angeles,  because  “there  is  one  sense 
in  which  this  algorithm  is  efficient. 
The  digit  calculated  at  each  iteration 
is  actually  the  next  nonzero  digit.  If 
we  use  K  =  1 0  the  ‘0’  in  T  024’  will  be 
skipped  over.  This  is  why  the  buffer 
has  to  be  initialized  to  zeros  at  the 
start.” 

Line  160  sets  up  the  target  of  the 
conversion,  the  number  2*: 

160  A0=K*FNL(2) 

The  crux  of  Ross’s  program  is  that 
this  number,  which  might  be  very 
large  indeed,  can  be  represented  safe¬ 
ly  in  BASIC  by  its  common  loga¬ 
rithm.  Because  of  the  identity 

logU*)  =  £*Iog(x) 

Ross  can  get  the  log  of  2*  without 
ever  evaluating  the  number  itself. 

The  Achilles’  heel  of  the  program 
is  that,  in  most  editions  of  Microsoft 
BASIC,  LOG  and  all  the  other  tran¬ 
scendental  functions  are  evaluated 
only  in  single  precision,  so  the  value 


Dr.  Dobb’s  Journal,  November  1985 


of  2k  is  known  only  to  six  significant 
digits,  regardless  of  its  size.  As  Dave 
Shuman  of  Pendleton,  Indiana  puts 
it,  “sizable  numbers  rapidly  exceed 
the  capacity  of  significant  digits  cal¬ 
culated  in  line  160.  Even  if  the  rest  of 
the  program  contained  infinite  signif¬ 
icance,  the  necessary  information  has 
been  lost.”  No  doubt,  that’s  why  Ross 
didn’t  put  a  DEFDBL  at  the  head  of 
his  program.  Some  BASIC  implemen¬ 
tations,  however,  do  support  LOG 
with  greater  precision,  as  we’ll  see 
later. 

Now  that  we  have  some  idea  of 
what  the  program  tries  to  do,  let’s 
look  at  the  loop  in  which  it  does  it.  Its 
first  step  is 

190  A  =  A0  +  FNL(B) 

David  Shochat’s  analysis  helped  us 
see  that  here  Ross  is  using  another 
logarithmic  identity 

log(xX7)  =  log(x)  +  log(^) 
to  compute 

A  =  log(BX2*)  =  log(2*)  +  log(B) 

The  first  time  through  B=  I,  so  A  is 
just  AO.  Now  the  program  takes  that 
value  apart: 

200  N  =  INT(A) 

210  IF  N<0  THEN  GOTO  280 

220  L  =  A  — N 

Shochat  says  “N  and  L  are  the  char¬ 
acteristic  and  mantissa,  respectively, 
of  the  log  of  BX2A,  that  is: 

BX2*=10<n  +  l>=10nX10l 

Dave  Shuman  puts  it  this  way:  “Line 
200  determines  which  digit  N  is  next 
(the  integer  portion  of  a  logarithm 
determines  the  decimal  position  of  its 
antilog);  line  210  terminates  the  loop 
in  case  the  logarithm  didn’t  come  out 
exact;  and  line  220  gets  the  rest  of  the 
logarithm,  L,  which  tells  what  the 
digits  are  beginning  at  position  N.” 

The  next  lines  process  the  number 
further: 

230  X=  10‘L 

240  D=  IjsJT(X) 


Shochat  analyzes  this  as  setting  up  so 
that 

BX2*  =  XX10N,  1<X<10 


“which  is  just  scientific  notation.” 
Shuman  seconds  him:  “the  antilog  of 
X  is  the  number  in  the  form 
a.bcde. .  .  .”  Its  integer  part,  assigned 
to  D,  is  the  next  digit  to  display.  The 
next  line, 

250  A$(K-N+1)  =  CHR$(D  +  Z) 

assigns  that  digit  as  an  ASCII  charac¬ 
ter  to  the  output  array.  It  isn’t  hard  to 
see  how  this  code  might  pick  off  the 
initial  digit  of  A0,  but  how  does  it  get 
the  rest  of  them?  The  next  line  is  the 
key: 

260  B  =  B  —  I0*(N  —  A0  +  FNL(D)) 

Shuman  says  it  removes  the  new  digit 
by  adjusting  the  factor  B:  “At  each 
cycle,  if  you  multiply  B  times  A0,  the 
original  power  of  2,  you  should  get 
the  rightmost  N—  1  digits  of  A0.” 

Shochat  tries  to  explain  how.  “On 
the  first  iteration  B=  1,  so  the  first  D 
we  get  is  the  leading  digit  in  the  deci¬ 
mal  representation  of  2k.  Now  the 
new  B  value  has  to  lop  off  somehow 
the  digit  just  found.  It  is 

g_  l()N-A0  +  iog(D) 

which  is 

B-10NX(l/10A0)X10'o8<D> 
which  is 

B— 10NX(1/2*)XD 

Now  the  trick  is  to  think  of  this  mul¬ 
tiplied  through  by  2k,  which  gives 

(BX2*)  — (DX10N) 

Thus  the  new  BX2*  is  just  the  old 
BX2‘  with  D’s  contribution  re¬ 
moved.  At  the  end  of  the  iteration 
where  D  is  the  last  digit,  this  will 
bring  B  down  to  zero  and  end  the 
loop.”  If  it  doesn’t  work  out  even  be¬ 
cause  of  round-off  errors,  the  value  of 
A  calculated  in  line  190  will  be  less 
than  1,  so  the  characteristic  of  its  log 


I 

assigned  to  N  will  be  less  than  zero 
and  line  210  will  bail  out. 

Why  Doesn't  It  Work? 

This  is  all  very  ingenious  but,  as  you 
know  if  you’ve  tried  the  program,  it 
doesn’t  work  very  well.  Using  M  BA¬ 
SIC  on  CP/M  or  IBM  BASIC  before 
Version  3,  it  produces  correct  an¬ 
swers  for  only  K=l,  2,  and  3  (pro¬ 
ducing  2,  4  and  8).  Then  it  gets  pro¬ 
gressively  less  accurate.  How  come? 

There  are  two  problems.  Shuman 
says,  “The  most  immediate  problem 
is  the  conversion  in  line  240,  where 
the  real  number  X  is  converted  to  the 
integer  D.  For  the  rightmost  digit  we 
run  into  the  classic  problem  of  fixed/ 
float  equivalence.  The  next  digit 
might  be  4,  for  instance,  but  X  was 
calculated  as  3.99578  and  D  will  then 
be  3.  In  all  but  the  rightmost  digit, 
the  number  X  will  be  higher  than  the 
digit  value,  reflecting  the  digits  yet  to 
come,  and  the  conversion  is  OK. 

“I  inserted  a  new  line  280.  Because 
we  can  tell  by  inspection  that  all  re¬ 
sults  for  K  greater  than  zero  should 
be  even  numbers,  the  new  line  280 
adds  1  to  any  results  that  end  in  an 
odd  digit.” 

When  this  fix  is  made,  the  program 
produces  correct  answers  up  to  the 
point  where  the  number  of  digits  in  2k 
exceeds  the  precision  of  the  values  in 
A0,  A  and  B,  which  are  in  turn  limit¬ 
ed  by  the  precision  of  LOG.  It  can  do 
K  =  20  in  our  CP/M  system.  Jack  Fay 
of  Seattle  got  to  K  =  24  in  Applesoft 
BASIC.  Larry  Manns  of  Trappe, 
Pennsylvania,  got  right  answers  to 
K  =  35  on  a  Wang.  By  using  double 
precision  on  all  variables  in  Microsoft 
BASIC  3.0,  Shuman  got  up  to  K  =  51 
on  an  IBM  PC/AT.  Shochat  worked  it 
to  K  =  100  on  a  VAX. 

The  fundamental  constraint  is 
that,  although  logarithms  allow  Ross 
to  cope  with  the  magnitude  of  very 
large  numbers,  the  limited  precision 
of  float  variables  keeps  the  program 
from  displaying  their  many  digits  ac¬ 
curately.  “My  hat  is  off  to  David 
Ross,”  Shuman  says.  “In  one  elegant 
little  program  he  gives  a  demonstra¬ 
tion  of  two  classic  problems  in  com¬ 
puter  number  representation:  loss  of 
significance  and  inexact  representa¬ 
tion  of  whole  numbers.” 


Dr.  Dobb’s  Journal,  November  1985 


15 

809 


The  program  in  Listing  Two  (page 
21)  shows  our  attempt  to  stretch  the 
precision  of  MBASIC  to  its  outer  lim¬ 
its.  It  squeezes  out  two  more  digits  to 
reach  K  =  24  by  using  double  preci¬ 
sion  and  literals  for  ln(10)  and 
LOG(2).  By  some  fluke  it  also  gets 
K  =  30  right,  but  not  the  numbers 
above  or  below  it. 

Other  Ways 

Two  readers  tried  to  do  Ross  one  bet¬ 
ter.  The  first  was  Orin  Safier.  He 
simply  automated  what  you’d  do  to 
calculate  a  large  power  of  2  by  hand: 
you’d  write  down  the  largest  power  of 
2  you  knew  or  could  look  up;  then 
you’d  sit  there  doubling  it  over  and 
over,  producing  something  like  the 
following  table 

65,536 

131,072 

262,144 

With  a  little  practice  you  can  com¬ 
pute  the  digits  of  the  next  number  al¬ 
most  as  fast  as  you  can  write  them. 
But  why  not  let  a  program  do  the 
doubling? 

Our  rewrite  of  Safier’s  program 
appears  in  Listing  Three  (page  22). 
We  changed  the  original  only  for 
longer  variable  names  and  to  use  a 
Boolean  expression,  not  an  IF,  to  han¬ 
dle  the  carry.  Safier  says  the  program 
“will  print  2k  with  perfect  accuracy” 
but  “involves  many  iterations  for 
high  values  of  k,  and  is  thus  abstract¬ 
ly  less  satisfactory  than  Ross’s.” 
That’s  a  good  point.  Can  you  work 
out  an  approximate  expression  for 
how  many  iterations  Ross’s  loop  will 
make  as  a  function  of  the  input,  K? 
What  about  Safier’s? 

And  here’s  an  extra-credit  puzzle 
for  APL  users.  Can  you  implement 
Safier’s  method,  replacing  the  inner 
loop  with  non-looping  array  opera¬ 
tions?  You’ll  have  to  find  a  way  to 
handle  the  carries  in  parallel. 

David  Barker  also  set  out  to  better 
Ross,  but  used  Pascal  instead  of  BA¬ 
SIC.  Like  Safier,  he  aimed  to  beat  the 
limited  precision  of  float  numbers  by 
implementing  his  own  ultralong  inte¬ 
gers,  storing  each  decimal  digit  as 
one  entry  in  an  array.  But  where  Sa¬ 
fier  arrived  at  2k  by  doubling  1,  K 


times,  Barker  looked  deeper.  He  ex¬ 
plained  it  to  us  in  terms  of  binary 
multiplication.  We’d  prefer  to  ex¬ 
plain  it  to  you  in  terms  of  one  of  the 
identities  of  the  exponential  function, 
namely  that 

x y=xPXx‘> 

provided  that  y=p+q  (for  example, 
25  =  23X22).  Next,  notice  that  any 
odd  number  y  can  be  decomposed 
into  a  p  of  1  and  a  q  of  the  even  num¬ 
ber  one  less  than  y,  whereas  any  even 
number  y  can  be  decomposed  into 
equal  p  and  q  simply  by  breaking  it  in 
half. 

Those  facts,  plus  the  identity  that 
anything  to  the  first  power  is  itself, 
practically  lead  us  by  the  nose  to  a 
recursive  definition  of  a  power  func¬ 
tion.  The  case  of  x[  is  the  one  whose 
answer  we  know  immediately.  The 
rules  for  decomposition  supply  us 
with  a  way  to  make  the  magic  recur¬ 
sive  statement,  “If  I  knew  the  answer 
to  the  simpler  cases  xp  and  *?,  I  could 
compute  xy. ” 

PowerOf(x,y) 

if  y  =  1  then  return  x 
if  odd(y)  then 

let  t  =  PowerOf(x,y—  1 ) 
let  t  =  t  *  x 
else 

let  t  =  PowerOf(x,y/2) 
let  t  =  t  *  t 
endif 
return  t 

Actually,  the  two  legs  of  the  “if”  can 
be  collapsed  into  one  to  make  a  sim¬ 
pler  function: 

PowerOf(x,y) 

if  y  =  1  then  return  x 
let  t  :=  PowerOf(x,y  div  2) 
let  t  :=  t  *  t 
if  odd(y)  then 
let  t :  =  t  *  x 
return  t 

That’s  a  general  power  function. 
When  we  know  that  x  is  2,  it  gets  sim¬ 
pler: 

TwoToThe(k) 

if  k  =  1  then  return  2 
let  t  =  TwoToThe(k  div  2) 


18 

nin 


let  t  =  t  *  t 
if  odd(k)  then 
double(t) 
return  t 

Although  these  are  recursive  func¬ 
tions,  no  level  of  recursion  actually 
does  anything  until  the  next  lower 
level  has  finished.  Therefore,  the 
variable  t  needn’t  be  local  to  the  func¬ 
tion;  each  completed  level  may  leave 
its  result  in  a  global  t  for  the  next  lev¬ 
el  to  work  on. 

Barker  did  our  TwoToThe(k) 
function  in  Pascal,  implementing  t  as 
a  many-digit  integer  expressed  as  an 
array.  If  you  give  it  9865  digits,  he 
says,  you  can  evaluate  2  raised  to  the 
power  32767  in  a  couple  of  hours — 
and  he  sent  a  solid  page  of  digits  to 
prove  it. 

We  liked  his  program  so  much  we 
couldn’t  let  it  alone.  We  typed  it  in 
and  then  kept  tweaking  away  at  it, 
trying  to  make  the  multiplication  of 
big  integers  (which  dominates  the 
run  time)  go  faster.  The  much- 
abused  result,  so  far  from  Barker’s 
original  that  he  might  not  recognize 
it,  appears  in  Listing  Four  (page  22). 
It’s  for  Turbo  Pascal.  Some  of  the 
tweaks  may  be  beneficial  only  in  the 
8-bit  version  of  Turbo,  whose  integer 
mul,  div,  and  mod  functions  are  de¬ 
cidedly  lethargic. 

Wired  Tales 

We  asked  if  anyone  had  maintenance 
experiences  to  share,  good  or  bad.  It 
seems  the  bad  experiences  are  easier 
to  recall,  or  more  fun  to  recount. 
Your  responses  put  us  in  mind  of 
Weird  Tales ,  the  old  magazine  of  su¬ 
pernatural  horror.  Consequently,  we 
decided  to  collect  them  together  to 
form  an  anthology  of  tales  of  the  sub- 
natural  horrors  of  hardware.  We  call 
it  Wired  Tales. 

No  Parking 

When  he  rested  his  head 
he  got  it  in  the  neck! 

After  working  with  computers  for 
eleven  years  and  using  my  S-100  sys¬ 
tem  for  seven,  says  Michael  M.  Dodd 
of  Northbrook,  Illinois,  I  upgraded 
to  a  Compaq  Deskpro.  I’d  had  it  just 
three  weeks  when  I  blew  the  power 


Dr.  Dobb's  Journal,  November  1985 


supply  by  turning  the  computer  off, 
then  on,  too  quickly.  Are  all  switching 
power  supplies  this  fragile?  In  seven 
years  I  never  had  a  problem  with  the 
old  Processor  Technology  supply. 

Two  weeks  later  I  turned  the  Desk- 
pro  on  and  the  33Mb  Rodime  hard 
disk  wouldn’t  run.  It  wasn’t  even  turn¬ 
ing.  The  dealer  replaced  it  at  no 
charge,  then  told  me  that  I  should 
never  use  the  head-parking  program. 
It  seems  this  disk  automatically  parks 
its  head  when  power  is  removed.  Ap¬ 
parently,  the  supplied  parking  pro¬ 
gram  runs  the  head  into  the  bolts  that 
mount  the  platters,  jamming  its  rota¬ 
tion  like  a  stick  in  the  spokes  of  a  bike. 

I  restored  all  my  data  from  tape 
backup  to  the  new  disk  and  was  back 
in  business  —  almost.  dBase  III 
wouldn’t  run  because  of  its  copy-pro¬ 
tection  scheme.  Yes,  Ashton-Tate 
will  replace  the  diskette,  but  this 
means  I  can  never  restore  data  from 
tape  without  uninstalling  dBase  III.  I 
paid  money  for  this? 

The  Victim 

They  rolled  him  for  $60 

and  left  him  catatonic! 

Let  me  tell  you  about  my  printer, 
croaks  Glenn  English  of  Austin,  Tex¬ 
as.  It’s  a  Dataproducts  B-300.  I’ve 
run  several  trees  of  paper  through  it, 
but  recently  it  has  developed  quirks. 

The  first  was  in  the  ribbon  rollers. 
The  ribbon  lives  in  a  plastic  box.  It’s 
pulled  out  one  end,  dragged  through 
the  print  head,  and  stuffed  back  in 
the  box  by  a  pair  of  rollers  made  of 
something  that  looks  like  transparent 
rubber.  Mine  wore  out.  Dataproducts 
carries  them — for  $60  a  pair.  They 
last  three  years,  so  that’s  OK,  but  the 
delivery  schedule  is  not.  Datapro¬ 
ducts  insists  it  takes  two  months  to 
process  an  order  for  parts  they  have 
in  stock.  (They  have  an  ‘expedited’ 
service  for  many  more  bucks.) 

I  went  to  the  local  DEC  office  and 
asked  for  rollers  for  an  LP-25  (a  B- 
300  in  a  new  box).  They  couldn’t  fig¬ 
ure  out  what  I  was  talking  about.  If  I 
had  the  Dataproducts  part  number, 
just  maybe  they  could  cross-reference 
it,  but  they  couldn’t  find  it  under  their 
DEC  model  number.  Luckily,  a  DEC 
maintenance  tech  happened  by  and 
found  them  for  me.  Price?  $60. 


But  now  the  machine  is  down  in  a 
most  peculiar  way.  It  runs  its  power- 
on  self  tests  OK  and  its  internal  rip¬ 
ple-print  test  as  well.  Put  it  on-line 
and  its  on-line  LED  lights  up.  But 
send  it  data  and  it  just  sits  there. 

Dataproducts  does  have  a  service 
department,  but  they  sent  me  to 
TRW.  TRW  won’t  work  on  it  because 
it  is  in  my  house.  (I  have  no  idea 
why.)  One  of  their  techs  offered  to 
swap  boards  with  me  if  I’d  leave  a 
deposit  of  $700.  When  I  plugged  in 
the  new  board  the  printer  went  com¬ 
pletely  catatonic.  That’s  where  it 
stands  now.  In  the  future  I  think  I’ll 
stay  away  from  Big  Kids  hardware.  If 
my  ProWriter  breaks,  I  can  get  a  new 
one  for  what  it’s  going  to  cost  to  get 
the  B-300  back  on  the  air! 

Satisfied  At  Last 

The  first  one  wanted 
too  much  and  died! 

I  chose  Shugart  851  drives.  Jay  S. 
Rouman  of  Mt.  Pleasant,  Michigan, 
tells  us,  because  they  were  supposed 
to  be  the  standard  of  the  industry,  and 
besides  (I  heard)  they  just  never 
failed.  Wrong.  A  plague  of  intermit¬ 
tent  ‘bad  sector’  errors  settled  in  and  I 
began  looking  for  a  cure.  I’ve  spent 
more  than  a  few  hours  at  the  repair 
bench  but  thought  that  a  disk  drive 
should  be  best  left  to  the  folks  at  the 
factory. 

They’d  be  glad  to  repair  my  drive, 
they  said,  for  a  flat  fee  of  $250.  It 
warmed  my  heart  to  know  they’d  suf¬ 
fer  no  financial  loss;  nevertheless  I 
looked  elsewhere.  An  independent  re¬ 
pair  outfit  offered  a  flat  fee  of  $200. 

There  was  a  message  in  this,  I 
thought.  With  the  help  of  a  friend  I 
tracked  the  problem  to  the  index  sen¬ 
sor.  This  circuit  consisted  of  one  IC 
and  a  few  resistors.  Resistors  never 
fail,  so  I  replaced  the  IC.  Wrong. 

Now,  the  resistors  were  in  a  pack, 
and  replacement  ought  to  have  been  a 
snap;  after  all,  resistor  packs  are  pret¬ 
ty  simple.  Wrong.  This  one  had  very 
strange  values  in  a  strange  configura¬ 
tion  and  was  custom  made  for  Shu¬ 
gart. 

I  jumped  for  joy  when  the  clerk  in 
Shugart’s  parts  department  said  the 
pack  was  just  $  1.50  and  they’d  be  glad 
to  ship  it  to  me.  Then  he  told  me  about 


the  $50  minimum  order.  I  complained 
that  32  and  a  third  spare  packs  was  a 
bit  much  for  my  two-drive  system,  but 
it  got  me  only  sympathy.  He  suggest¬ 
ed  I  contact  repair  outfits  or  that  I 
fabricate  the  part  myself. 

The  homemade  resistor  pack  (a 
wire  sculpture  of  '/8-watt  resistors) 
got  the  drive  in  service,  but  I  was 
steaming.  I  asked  at  several  repair 
shops,  but  the  best  I  could  get  was  a 
promise  to  batch  my  order  with  one 
of  theirs  whenever  they  next  might 
send  one. 

Then  I  happened  on  an  ad  for 
Hamilton/ Avnet.  That’s  a  big  com¬ 
pany  with  many  offices  but  still,  the 
ad  had  the  words  Shugart  and  parts 
on  the  same  page  and  gave  a  toll-free 
number.  I  called  and  it  was  wonder¬ 
ful!  They  had  the  part  in  stock.  They 
would  sell  me  a  single  one.  They’d 
ship  it  UPS.  They  wouldn’t  insist  on  a 
purchase  order  or  even  a  credit 
card  -  they’d  bill  me!  And  they  were 
as  good  as  their  word. 

Since  then,  Shugart  has  gone  out 
of  business  (I  like  to  think  it’s  be¬ 
cause  of  that  $50  minimum-order 
policy)  and  I  haven’t  needed  Hamil¬ 
ton/ Avnet  again.  Still,  they’re  the 
first  place  I’ll  call  if  I  ever  need  parts. 

DD| 


Dr.  Dobb's  Journal,  November  1985 


21 

811 


Dr.  Dobb's  Clinic  (Text  begins  on  page  14) 
Listing  Two 


100  REM  Logarithm-based  display  of  2/'K  after  D.  Ross 
110  DEFDBL  A-Z  :  DEFINT  I 

120  REM  if  your  LOGO  is  double,  use  LN10=LOG(10)  here 
130  LN10  =  2.302585093#  'natural  log  of  10 
140  REM  get  max  mileage  out  of  short  LOGO 
150  DEF  FNCLOG# (X#)  =  LOG(X#)/LNl0 

160  REM  if  your  LOGO  is  double,  use  CLOG2=FNCLOG# (2#) 

170  CLOG 2  =  .301029996#  1  common  log  of  2 

180  REM  get  integral  power  IK  and  set  up 

190  INPUT  "Power  of  2", -IK  :  IF  IK<=0  THEN  END 

200  DIM  A$ (IK+1 )  :  FOR  I  =  0  TO  IK  :  A$(I)="0"  :  NEXT 

210  IZ  =  ASC ( "0") 

220  A0  =  IK  *  CLOG 2 

230  B  =  1 

300  WHILE  B  >  0 

310  A  =  A0  +  FNCLOG# (B) 

320  IN  =  I NT (A)  :  IF  IN<0  THEN  399 

330  ID  =  I NT (10#  “  (A-IN) ) 

340  A$ ( IK-IN+1 )  =  CHR$ ( ID+IZ ) 

350  B  =  B  -  10#  *  (IN  -  A0  +  FNCLOG# (ID+0#) ) 

390  WEND 

399  REM  early  exit  point 

400  IF  (ID  AND  1)  AND  (IDO)  THEN  A$(IK+1)  =  CHR$ (ID+IZ+1 ) 
410  FOR  I=IK-INT (A0 ) +1  TO  IK+1  s  PRINT  A$(I);  :  NEXT 

420  PRINT  s  CLEAR  :  RUN 

End  Listing  Two 

Listing  Three 


100 

ii0 

120 

130 

140 

150 

160 

170 

200 

210 

220 

230 

240 

250 

260 

270 

280 

290 

300 

310 

320 

330 


REM  display  2"K  by  doubling 
REM  after  Orin  Safier 
DEFINT  A-Z 

INPUT  "Power  of  2";  K 

SIZE  =  1+  I NT (  K  *  LOG ( 2 ) /LOG (10)) 

DIM  DIGITS (SIZE) 

DIGITS  (1)  =  1  :REM  2*0  =  1 
MSD  =  1  : REM  count  of  live  digits 
FOR  I  =  1  TO  K 
C  =  0 

FOR  J  =  1  TO  MSD 

Q  =  DIGITS (J) 

Q  =  Q+Q+C 

C  =  -  (Q>9 )  :REM  "TRUE"  is  -1 
DIGITS (J)  =  Q  -  (C*10) 

NEXT  J 

IF  C  THEN  MSD  =  MSD+1  :  DIGITS (MSD)  = 
NEXT  I 

FOR  I  =  MSD  TO  1  STEP  -1 

PRINT  CHR$ (DIGITS  (I)  +  ASC("0")); 

NEXT  I 

PRINT  :  CLEAR  :  RUN 


End  Listing  Three 


Listing  Four 


program  TwoPower; 

{ $A-  8-bit  turbo,  allow  recursion  } 

{ $R-  turbo,  range  checks  off  after  test  } 

const  maxdigit  =  2048; 

type 

digindex  =  1.. maxdigit;  {  index/count  over  digits  } 

digit  =  0..9;  {  stored  digit  of  "number"  ) 

addig  =  0..99;  {  temporary  arith.  result  } 

{  type  of  a  multi-digit  decimal  integer:  it  consists  of  an  ) 

{  array  of  digits  with  digits[l]  being  the  LEAST  signifi-  } 

{  cant  and  digits [msd]  being  the  MOST  significant.  } 

number  =  record 

msd  :  digindex; 

digits  :  array  [digindex]  of  digit 


22 


QiO 


Dr.  Dobb’s  Journal,  November  1985 


end ; 


var 

n0,  nl  :  number;  {  one  is  current  result,  depending  } 
which  :  boolean;  {  ..on  value  of  this  switch  } 
power  :  integer; 

timestab  :  array [digit , digit]  of  addig; 

{  procedure  to  initialize  timestab.  see  MULBYN.PAS  for  use} 

procedure  initab; 
var  x,y  :  digit; 
begin 

for  x  :=  0  to  9  do 

for  y  :=  0  to  9  do 

timestab [x,y]  ;=  x  *  y 

end; 

{  procedures  to  manipulate  "numbers"  ...first,  one  to  write  } 
{  a  number  to  standard  output  as  in  write  (n).  } 


procedure  Shownumtvar  n:  number); 

var  j  :  digindex; 

begin 

write ( '  ' ) ; 

for  j  :=  n.msd  downto  1  do 

wr ite (chr (ord (' 0 ') +n. digits  [j ])  ) 

end; 

{  ...then  one  to  set  a  "number"  to  a  one-digit  constant.  } 

procedure  Setnum(var  n:  number;  d:  digit); 
begin 

with  n  do  begin 
msd  :=  1; 
digits[l]  :=  d 

end 

end; 


{  ..one  to  clear  out  k  digits  to  zero  (before  multiply)  and  } 
{  also  set  number  to  one-digit  zero.  } 


procedure  Zernum(var  n:  number;  k:  digindex); 

var  j:  digindex; 

begin 

with  n  do  begin 
msd  :=  1; 

for  j  :=  1  to  k  do  digitslj]  :=  0 

end 

end; 


{  ...one  to  double  a  number  by  adding  it  to  itself.  } 

procedure  Double(var  n:  number); 
var  j  :  digindex;  dig,  Cy  :  addig; 
begin 

with  n  do  begin 
Cy  :=  0; 

for  j  :=  1  to  msd  do  begin 
dig  :=  digits  [  j }  ; 
dig  :=  dig  +  dig  +  Cy; 
if  (dig  <  10)  then  Cy  :=  0 
else  begin 

dig  :=  dig  -  10; 

Cy  :=  1; 

end; 

digitsfj]  :=  dig 
end;  {for  j} 

if  (Cy  <>  0)  then  begin  {  number  gets  longer  } 
if  (msd  =  maxdigit)  then 
begin 

writeln ( 'Oops,  overflow  on  double'); 
Halt  {turbo  terminator} 

end; 

msd  :=  msd+1; 
digits[msd]  :=  1 
end  {if  cy} 
end  {with  number  n} 

end; 


Dr.  Dobb’s  Journal,  November  1985 


24 


QIO 


Dr.  Dobb's  Clinic 


(Listing  continued,  text  begins  on  page  14) 


Listing  Four 

{  ...and  a  general  integer-multiply  routine  after  that  sent  } 
{  by  David  Barker:  z  gets  x*y  in  the  style  of  pencil  and  ) 
{  paper  multiplication.  Used  in  this  program  only  to  square} 
{  a  number  as  in  z  gets  x*x.  } 


procedure  Mulnum(var  x,  y,  z:  number); 
var 

i,j,k  :  digindex; 
xdig,  dig,  Cy  :  addig; 
begin 

I 

if  ( (x.msd+y.msd)  >  maxdigit)  then 
begin 

writeln ( 'Oops,  multiply  overflow!'); 
Halt  {turbo-specific  termination} 

end; 


Zernum(z, x.msd+y.msd) ; 

k  :=  2;  {in  case  x=0,  which  it  can't  in  this  program} 
for  i  :=  1  to  x.msd  do  begin 
Cy  :=  0; 

xdig  :=  x.digits[i]; 
if  (xdig<>0)  then  begin 
k  :  =  i ; 

if  (xdig  >  1)  then 

{Si  MULBYN.PAS  mul-by-2-to-9  code} 

else 

{$1  MULBYl . PAS  mul-by-1  code} 
end  {xdig  >  0} 
else 

{x.digit[i]  =  0,  nothing  to  do} 
end  {for  i}; 
with  z  do  begin 

if  digits[k]=0  then  k  :=  k-1; 
msd  :=  k 

end 


end ; 

{  In  this  program  we  have  only  2  "numbers,"  n0  and  nl.  We  } 

{  start  out  using  n0  and  flop  between  it  and  nl  as  we  have  } 

{  to  square  each  temporary  result.  The  following  procs  use  } 

{  boolean  "which"  to  apply  Shownum,  Mulnum,  and  Double  to  } 

{  "it"  —  "it"  being  the  one  of  n0,  nl  currently  holding  the} 

{  result  of  computations.  } 

procedure  Showit; 
begin 

if  which  then  Shownum(nl)  else  Shownum(n0) 

end; 

procedure  Doublit; 
begin 

if  which  then  Double (nl)  else  Double (n0) 

end  ; 

procedure  Squarit; 
begin 

if  which 

then  Mulnum  (nl,nl , n0) 
else  Mulnum(n0,n0,nl) ; 
which  :=  not (which) 

end; 

{Here  is  the  recursive  power-of-two  algorithm, almost  exactly  } 
{  as  worked  out  in  the  text.  } 

procedure  TwoToThe (K  :  integer); 
begin 

if  (K  <  4)  then  begin 

{  bottom  of  recursion,  initialize  number  } 
which  :=  false;  {  using  n0  } 
case  K  of 

0  :  Setnum(n0,l) ;  {  doesn't  happen  in  this  prog.  } 

1  :  Setnum(n0,2) ; 


25 


Dr.  Dobb's  Journal,  November  1985 


2  s  Setnum(n0,4)  ; 

3  :  Setnum(n0,8) ; 
end 

end 

else  begin 

{  not  at  bottom,  recurse  to  initialize,  then  square  } 
TwoToThe (K  div  2); 

Squarit; 

if  odd(K)  then  Doublit 

end 

end; 


begin 

initab; 

repeat 

write ( ' Power  of  2  (0  to  end):  ');  readln (power) ; 
if  (power  >  0)  then  begin 
TwoToThe (power) ; 

Showit;  writeln 

end 

until  (power  <=  0) 

end. 

{ - MULBYl .  PAS -  } 

{  code  to  multiply  1  *  y. digits  into  z. digits,  placed  in  ) 

{  an  out-of-line  include  file  for  clarity  } 
begin 

for  j  :=  1  to  y.msd  do  begin 

dig  :=  y.digits[j]  +  Cy  +  z.digitstk); 
if  (dig  <  10)  then  Cy  :=  0 
else  begin 
Cy  :=  1; 
dig  :=  dig-10 

end; 

z.digitstk]  :=  dig; 
k  :  =  k+1 
end  {for  j}; 
z. digits [ k ]  :=  Cy 

end  {xdig=l} 

\NP 

{ - MULBYN .  PAS -  } 

{  code  to  multiply  xdig  *  y. digits  into  z. digits  when  xdig>l) 

{  placed  out-of-line  for  clarity  } 

begin 

for  j  :=  1  to  y.msd  do  begin 

{  note:  in  next  statement,  a  direct  "xdig  mul  y .digits [j  ]  "  } 

{  is  replaced  by  a  table  lookup.  This  reduces  execution  } 

(  time  by  15%  in  8-bit  turbo  but  might  be  SLOWER  in  other  } 

{  pascals  or  in  PC  turbo.  } 

dig  :=  timestab [ xdig, y. digits  [j ] ]  +  Cy  +  z.digitstk]; 
Cy  : =  0; 

{  note:  in  8-bit  turbo  the  following  ...  } 
while  (dig>9)  do  begin 
dig  :=  dig-10; 

Cy  :=  Cy+1 

end; 

{  ...cut  execution  time  by  50%  compared  to  the  more  direct  } 
{"if  (dig>9)  then  begin  Cy:=dig  div  10,  dig:=dig  mod  10  end"} 

{  ...this  might  NOT  be  true  in  other  pascals  or  in  PC  turbo  } 

z.digitstk]  :=  dig; 
k  :=  k+1 
end  {for  j}; 
z.digitstk]  :=  Cy 
end  {xdig>l} 


End  Listings 


SIS 


Modula-2 
versus  Pascal  for 
Microcomputers: 
An  Update 

by  David  W.  Carroll 


Even  Niklaus  Wirth  admits  that  current 
implementations  for  Pascal  restrict  its 
usefulness  to  writing  academic  or 
"toy"  programs.  Will  Modula-2  vali¬ 
date  the  structured  Pascal  model  for 
serious  software  development ? 


David  W.  Carroll  is  the  author  of  Telecommunications 
for  the  PCjr  (Micro  Text/Prentice  Hall )  and  Program¬ 
ming  with  Turbo  Pascal  (Micro  Text/McGraw-Hill).  He 
operates  the  High  Sierra  RBBS  ( 209-296-3534 ),  a  bulletin 
board  dedicated  to  the  exchange  of  information  about 
Turbo  Pascal  and  other  high-level  languages. 

The  May  1984  issue  of  DDJ  (#91 )  featured  the  arti¬ 
cle  “Introduction  to  Modula-2  for  Pascal  Program¬ 
mers”  by  Hugh  McLarty  and  David  Smith1.  In  that 
article  the  authors  covered  the  differences  between  Stan¬ 
dard  (ISO)  Pascal  and  Modula-2.  The  present  article  is 
also  aimed  at  programmers  familiar  with  Pascal,  but  dis¬ 
cusses  more  practical  aspects  of  using  Modula-2  in  the 
microcomputer  environment. 

Developers  of  microcomputer  software  have  been  slow 
to  adopt  structured,  high-level  programming  languages. 
PL/M  and  PL/I-80,  Pascal  and,  most  recently,  C  have 
been  used  to  develop  large  applications  programs,  compil¬ 
ers  and  assemblers,  but  only  a  few  machine-level  systems 
have  been  written  in  these  languages,  notably,  parts  of 
CP/M  in  PL/M  and  all  of  Unix  in  C.  There  are  two  main 
reasons  for  this:  first,  some  of  these  languages  do  not  al¬ 
low  direct  access  to  the  machine;  secondly,  the  code  gen¬ 
erated  by  available  compilers  is  often  inefficient. 

To  see  how  inefficient  compiled  code  can  be,  you  need 
only  observe  the  difference  in  operating  speed  between 
Lotus  1-2-3,  which  is  coded  in  assembly  language,  and 
Context  MBA,  which  is  coded  in  C.  The  speed  problems 
experienced  by  Microsoft  in  using  C  to  develop  Windows 
finally  caused  them  to  revert  to  assembly  language  for 
large  segments  of  the  program. 

PL/M  and  PL/I-80  are  powerful  languages,  but  have 
never  caught  on  for  development  of  commercial  applica¬ 
tions.  PL/M  has  been  confined  to  the  Intel  development 
system  environment  and  PL/I-80  suffers  from  the  same 
problems  as  its  older  sibling  for  mainframes,  PL/I,  name¬ 
ly,  size  and  complexity. 

Pascal  was  originally  intended  to  be  a  teaching  tool,  a 
model  language  to  demonstrate  and  foster  structured  pro¬ 
gramming  in  computer  science  courses.  It  was  not  meant 
to  be  a  language  in  which  large  systems  programs  were 
written,  and  so  does  not  include  many  of  the  features  that 
are  helpful,  or  necessary,  for  that  task.  Because  the  lan¬ 
guage  is  well  structured  and  easy  to  learn,  many  software 
vendors  extended  it  to  include  more  features.  However, 
because  a  formal  standard  for  Pascal  was  a  long  time 
coming,  many  different  versions  were  developed,  result¬ 
ing  in  the  current  incompatibilities  between  most  exten¬ 
sions  of  the  language. 

C  is  a  very  popular  high-level  hanguage  that  does  pro¬ 
vide  access  to  the  machine  level.  It  does  not,  however,  en¬ 
force  typing  or  offer  the  other  structured  concepts  that 
Pascal  does  (for  better  or  for  worse).  C  code  can  also  be¬ 
come  very  obscure  (some  may  read  elegant)  and  difficult 
to  maintain. 


Copyright  ®  1985  by  David  W.  Carroll,  23522  Shake 
Ridge  Rd.,  Volcano  CA  95689. 


28 


Dr.  Dobb’s  Journal,  November  1985 


Origins  of  Modula-2 

Niklaus  Wirth,  who  originally  designed  Pascal  in  1971, 
created  Modula-2  in  1977  and  released  the  first  technical 
report  on  the  language  in  March,  1980.  Modula-2  is 
based  largely  on  Pascal  and  Mesa,  a  modular  language 
developed  by  Xerox;  the  support  for  concurrent  process¬ 
ing  comes  from  an  earlier  Wirth  language  for  real-time 
programming  called  Modula.  Modula-2  eliminates  most 
of  the  deficiencies  of  Standard  Pascal.  It  also  provides 
features  that  allow  large  systems  to  be  broken  down  into 
small  components  that  can  be  developed  independently  in 
a  truly  top-down  fashion.  Although  other  structured  lan¬ 
guages  try  to  achieve  this  goal  through  the  use  of  proce¬ 
dures  and  functions,  Modula-2  achieves  a  much  higher 
level  of  modularization  and  data  isolation.  Dr.  Wirth  not¬ 
ed  at  conferences  at  Stanford  University  and  Sacramen¬ 
to,  CA  in  June  1985;  “I  [developed]  Modula-2  because  all 
the  compilers  that  were  becoming  available  [for]  Pascal 
were  fine  for  toy  [small]  programs,  fine  for  introductory 
courses  ...  I  [wanted]  to  show  that  structured  program¬ 
ming  languages  [were]  not  just  for  the  school.  Their  real 
value  comes  when  you  do  big  systems.  For  that  you  need 
efficient  compilation.” 

Pascal's  Limitations 

If  you  program  in  Pascal,  you  are  familiar  with  its  limita¬ 
tions.  It  is  true  that  extended  versions  of  Pascal  have  been 
developed.  There  exist,  however,  no  standards  for  these 
extensions,  so  that  any  application  that  uses  them  is  not 
portable.  A  summary  of  the  things  missing  from  Standard 
Pascal  is  presented  below: 

1 .  Separate  Compilation 

2.  Machine  level  interface 

Bit-wise  operators 
Direct  port  and  memory  access 
Absolute  addressing 
Interrupt  structure 

3.  Dynamic  strings 

4.  Multitasking 

5.  Procedure  libraries 

6.  Definable  abstract  data  types 

7.  Programmer  definable  scope  of  objects 

8.  An  elegant  way  to  exit  loops  before  completion 

The  Benefits  of  Modula-2 

The  major  advantage  of  Modula-2  is  the  concept  of  the 
module.  Modules  are  stand-alone,  self-contained  units. 
Although  they  are  compiled  individually ,  they  are  not 
independent.  A  module  consists  of  two  parts,  a  definition 
module  and  an  implementation  module.  The  definition 
module  declares  all  portions  of  the  module  that  are  visible 
from  the  outside,  that  is,  all  exported  objects,  as  well  as 
any  imported  objects  that  are  needed  to  declare  the  ex¬ 
ported  objects.  This  allows  the  compiler  to  check  in  the 
library  files  for  all  required  import  symbols  and  to  gener¬ 
ate  a  symbol  file  for  the  new  module.  In  addition,  the 
inner  components  of  the  module  are  invisible  to  other 
modules,  except  for  the  items  exported.  The  implementa¬ 


tion  module  contains  the  actual  program  code.  It  is  used 
to  generate  an  object  code  file  to  be  combined  with  other 
modules  when  the  program  is  ready  to  be  linked  together. 

The  following  example  shows  how  Modula-2  can  be 
useful  in  developing  a  large  system.  Suppose  that  a  de¬ 
fense  system  is  being  built  in  which  all  files  must  be  en¬ 
crypted  before  being  written  to  disk.  With  Modula-2, 
once  the  parameters  have  been  specified,  the  encryption 
routine  can  be  developed  independently  by  a  small  group 
of  programmers  with  a  high  security  clearance.  The  rest 
of  the  module  will  be  invisible.  In  the  meantime,  other 
programmers  can  develop  the  rest  of  the  system  using  a 
dummy  file  access  module  in  the  library.  When  the  en¬ 
cryption  module  is  completed,  it  is  included  in  the  library, 
and  the  system  program  is  re-compiled.  A  summary  of  the 
benefits  of  Modula-2  is  presented  below: 

1 .  Modules 

Separate  Compilation  with  parameter  checking 
Control  over  visibility 
Declarations  vs.  code  sections 

2.  Definable  abstract  data  types 

3.  Multitasking  capability 

4.  Interrupt  handling 

5.  Low-level  definitions 

6.  Libraries 

7.  Larger  programs 

Using  Modula-2 

Modula-2  programs  are  generated  in  stages.  First,  the 
source  file  is  compiled  and  the  symbol  (SYM)  and  link 
(LNK)  files  are  produced.  At  this  time,  all  external  ob¬ 
jects  to  be  imported  are  checked.  If  they  are  not  currently 
available  in  the  library  modules,  they  are  flagged.  When 
all  modules  have  been  compiled,  the  master  program 
module  is  linked  to  all  other  needed  modules.  Finally,  the 
output  file  is  produced  in  whatever  form  is  supported  by 
the  particular  compiler  (assembly,  object  [.COM  or 
.EXE],  or  load  file). 

Pascal  Programmers  and  Modula-2 

Experienced  Pascal  (and  C)  programmers  will  have  little 
difficulty  acquainting  themselves  with  the  basic  features 
of  Modula-2  within  a  few  hours;  they  should  be  familiar 
with  the  large  number  of  standard  library  modules  within 
a  few  weeks.  However,  the  features  that  support  the  de¬ 
velopment  of  multitasking/multi-user  programs  go  be¬ 
yond  the  scope  of  Pascal  and  will  naturally  take  longer  to 
learn. 

Differences  from  Pascal 

An  excellent  reference  book  for  programmers  making  the 
transition  from  Pascal  to  Modula-2  is  Modula-2  for  Pas¬ 
cal  Programmers,  by  Richard  Gleaves2.  I  will  present 
here  a  brief  summary  of  major  differences  between  Pascal 
and  Modula-2.  However,  a  discussion  of  the  added  sup¬ 
port  for  multitasking  is  beyond  the  scope  of  this  article. 

•  Modula-2  specifically  uses  the  ASCII  character  set.  This 


Dr.  Dobb’s  Journal,  November  1985 


29 

R17 


eliminates  problems  in  porting  to  mainframes  that  use 
other  schemes.  In  Modula-2  all  identifiers  are  case  sensi¬ 
tive.  This  means  that  GETDATA,  GetData,  and  getdata 
are  three  different  identifiers.  Also,  underscores  are  not 
allowed  in  identifiers.  All  reserved  words  must  be  written 
in  upper  case.  Comments  use  only  (*  and  *)  as  delimiters 
and  may  be  nested. 

•  A  new  type  has  been  added,  the  unsigned  integer  or 
CARDINAL  type,  used  for  counting  numbers.  Reals  and 
Integers  cannot  be  mixed  in  Modula-2,  as  the  implicit 
type  conversion  has  been  eliminated. 

•  Program  structure  is  very  similar  to  that  found  in  Pas¬ 
cal,  but  there  are  some  minor  differences.  The  reserved 
word  BEGIN  is  no  longer  used  to  delimit  blocks  (except 
for  procedure  blocks);  instead,  all  control  structures  allow 
multiple  statements  and  require  an  END  delimiter  (ex¬ 
cept  REPEAT  . . .  UNTIL  blocks).  The  FOR  structure  has 
eliminated  the  DOWNTO  keyword  and  now  allows  the 
step  value  to  be  declared.  The  declaration  section  of  a 
procedure  block  no  longer  requires  a  specific  order  of  dec¬ 
larations  (in  Pascal,  LABEL,  CONST,  TYPE,  VAR,  PRO¬ 
CEDURE  or  FUNCTION).  The  last  statement  in  a  proce¬ 
dure  must  be  an  END  with  the  same  identifier  as  the 
procedure  heading.  FORWARD  declarations  are  no  long¬ 
er  required;  procedures  may  be  referenced  before  they  are 
declared.  PROGRAM  declarations  are  no  longer  used;  in¬ 
stead,  the  MODULE  is  the  compilation  unit. 

•  FUNCTIONS  are  not  available  in  Modula-2;  instead,  a 
typed  procedure  is  used  that  must  include  a  parameter  list. 
Procedures  and  typed  procedures  may  have  an  empty  pa¬ 
rameter  list.  RETURN  may  be  used  to  terminate  a  proce¬ 
dure,  and  a  value  may  be  included. 

•  A  new  LOOP  structure  is  included,  delimited  with  an 
END.  The  loop  continues  until  an  EXIT  statement  is  en¬ 
countered,  at  which  point  control  transfers  to  the  state¬ 
ment  following  the  END. 

•  Formal  open  array  parameters  are  allowed  in  proce¬ 
dures  in  the  form  ARRAY  of  T.  The  size  of  the  array  is 
determined  at  the  time  of  activation  by  using  the  HIGH 
utility  procedure. 

•A  significant  change  in  Modula-2  is  that  all  utility  and 
file  functions  are  now  performed  by  library  modules. 
Thus,  even  the  simplest  program  module  must  IMPORT 
I/O  procedure  identifiers  and  EXPORT  values. 

Small  Pascal  programs  can  be  converted  easily  to  Mo¬ 
dula-2.  One  major  difference,  as  noted  above,  is  the  case 
sensitivity  of  Modula-2:  all  keywords  must  be  written  in 
upper  case  and  other  identifiers  must  be  consistent.  The 
program  structure  is  nearly  the  same.  Control  structures 
simply  do  not  use  a  BEGIN,  although  they  require  an  END. 
IMPORT  and  EXPORT  statements  are  required  to  access 
library  routines  like  I/O.  Functions  must  be  changed  to 
typed  procedures. 

Longer  programs  will  benefit  from  separate  compila¬ 


tion  of  modules  and  the  use  of  hidden  objects  allowed  in 
Modula-2. 

Modula-2  vs.  Turbo  Pascal 

Ever  since  the  release  of  Turbo  Pascal  two  years  ago  by 
Borland  International,  there  has  been  a  resurgence  of  in¬ 
terest  in  the  use  of  Pascal  on  microcomputers.  With  over 
350,000  copies  of  Turbo  Pascal  sold,  a  large  base  of  new 
Pascal  users  has  been  created.  In  fact,  one  prominent  ana¬ 
lyst  of  the  software  market  has  suggested  that  the  influ¬ 
ence  of  Turbo  Pascal  will  cause  Pascal  or  a  Pascal-like 
language  (Modula-2  or  Ada)  to  become  the  language  of 
choice  for  educators,  engineers,  software  developers,  and 
hackers,  and  possibly  even  to  replace  BASIC  through  the 
1980s  and  1990s.  So,  now  that  so  many  people  are  using 
Turbo  Pascal,  why  should  you  consider  switching  to  Mo¬ 
dula-2? 

Well,  first  let’s  get  one  thing  straight:  Turbo  Pascal  is 
not  a  true  Pascal.  As  D.E.  Cortesi  pointed  out  in  the  July 
1 985  DDP,  Turbo  does  not  conform  to  ISO  Standard  Pascal 
with  respect  to  several  required  features.  / See  this 
month's  Letters  section  for  response  to  Dave’s  column. 
Ed.]  In  addition.  Turbo  has  a  large  number  of  extensions 
designed  to  overcome  many  of  the  limitations  of  Standard 
Pascal.  These  two  facts  limit  the  portability  of  all  pro¬ 
grams  written  in  Turbo  Pascal. 

Moreover,  Turbo  Pascal  has  limitations  of  its  own.  The 
most  obvious  one  is  that  on  the  PC/MSDOS  version  both 
the  program  and  the  formal  data  area  are  limited  to  64K. 
Of  course,  the  heap  area  consists  of  all  remaining  memo¬ 
ry,  but  not  all  data  elements  can  be  made  up  of  pointer- 
variables! 

Another  limitation  of  Turbo  Pascal  is  that  it  only  pro¬ 
duces  .COM  files,  .CHN  (chain)  files,  and  overlay  files. 
Furthermore,  it  is  difficult  to  link  separately  compiled 
Turbo  programs  or  separately  assembled  machine  lan¬ 
guage  programs.  All  parts  of  a  Turbo  program  must  be 
compiled  together  at  the  same  time.  There  is  no  facility 
for  separate  compilation.  If  you  are  writing  a  10,000  line 
program,  this  can  be  a  significant  factor. 

What,  then,  is  the  value  of  Turbo  Pascal.  It  is  useful  for 
writing  small  programs,  but  not  large  systems.  It  is  also 
very  important  for  demonstrating  structured  program¬ 
ming  concepts,  as  Wirth  originally  intended.  Borland  is 
expected  to  release  Turbo  Modula-2  by  the  end  of  1985 
(Beta  versions  of  the  compiler  for  Z80  CP/M  systems  are 
currently  being  tested).  The  8086  version  will  probably 
solve  one  of  the  problems  with  Turbo  Pascal  by  allowing 
the  use  of  the  entire  available  memory  in  an  IBM  PC  for 
both  program  and  data  storage. 

The  design  of  Modula-2  virtually  requires  separate 
steps  for  compilation  and  linking,  as  well  as  extensive  li¬ 
brary  checking  during  compilation.  This  means  that  Mo¬ 
dula-2  compilers  will  be  somewhat  slower  to  use  for  pro-  I 


30 


Dr.  Dobb's  Journal,  November  1 985 


Tools  for  Modula-2  Programmers 

Modula-2  compilers  are  currently  available  for  many 
large  and  small  systems.  In  addition  to  compilers,  some 
companies  (such  as  Information  Systems,  listed  below) 
are  developing  lines  of  Modula-2  utilities.  Modula  Corpo¬ 
ration  is  even  selling  a  commercial  version  of  the  Lilith, 
the  Modula-2  workstation  developed  by  Wirth’s  team  in 
Zurich.  (Some  200  of  the  original  Liliths  were  built.)  The 
following  list,  prepared  with  help  from  contributors  Brian 
Anderson  and  Ed  Joyce,  contains  addresses  of  vendors  of 
Modula-2  compilers  and  programming  tools  for  micro¬ 
computers  and  some  minicomputers.  We  expect  to  be  able 
to  add  to  this  list  in  February,  when  we  hope  to  review 
several  Modula-2  compilers. 


Modula  Tools 

Borland  International 
4585  Scotts  Valley  Dr. 

Scotts  Valley,  CA  95066 
(800) 556-2283 

Borland  has  a  Modula-2  compiler  for  CP/M  in  Beta  test, 
scheduled  for  release  this  year,  with  an  MSDOS  port  to 
follow. 

Fachbereich  fur  Informatik 
Universitat  Hamburg  ; 

Scbluterstrasse  70 
D-2000  Hamburg  1 3 
West  Germany 

This  is  a  source  for  a  Modula-2  compiler  for  VAX/VMS 
systems. 

Hochstrasser  Computing  AG 
Leonhardeshalde  21 
CH-8001  Zurich 
Switzerland 
01-47-55-48 

Four  graduates  of  ETH,  where  Wirth  developed  Modula- 
2,  have  written  a  Modula-2  compiler  for  Z80  CP/M 
systems. 

Information  Systems 
1901  N.  Fort  Myer  Drive 
Arlington,  VA  22209 
(703) 522-8898 

Thomas  Woteki  has  developed  a  suite  of  Modula-2  pro¬ 
gramming  tools  using  Logitech’s  Modula-2/86. 

Interface  Technologies 

3336  Richmond,  Suite  200 

Houston,  TX  77098 

(800)  922-9049  and  (713)  523-8422 

M2SDS  is  a  low-priced  compiler  for  MSDOS  systems; 

SDS-XP  is  a  more  expensive  version.  Preliminary  reports 


indicate  that  these  products  still  contain  a  number  of 
bugs.  ITS  has  established  a  bulletin  board  (713-523-7255, 
300/1200  baud)  to  provide  information  and  services  for 
Modula-2  programmers. 

Logitech 

805  Veterans  Blvd. 

Redwood  City,  CA  94063 
(415)  365-9852 

Logitech  has  roots  in  early  Modula-2  and  Lilith  develop¬ 
ment  in  Switzerland;  we  reviewed  an  early  version  of  its 
Modula-2/86  compiler  for  MSDOS  systems  in  February 
of  this  year.  The  latest  version  is  1.10.  Logitech  also  pub¬ 
lishes  the  MODULA-2  Newsletter  to  provide  information 
about  its  product  line. 

Modula  Corporation 
1673  West  820  North 
Provo,  UT  84601 

(801 )  375-7400  and  (800)  LILITH2 
Modula  Corporation  has  compilers  for  Apple  II,  Lisa, 
Macintosh,  and  MSDOS.  Its  MacModula  runs  on  512K 
or  128K  machines. 

Maritime  Infosystems,  Ltd. 

6660  Reservoir  Road 
Corvallis,  OR  97333 
(503)929-2552 

The  Mosys  Modula-2  System  is  a  fully  extensible,  adapt¬ 
able  Modula-2  programming  support  environment  for 
Sage,  Stride  and  Pinnacle  computers. 

Dr.  Josef  A.  Muheim 
BBC  AG 
Abteilung  ESL 
Werk  Turgi 
CH-5401  Baden 
Switzerland 

Dr.  Muheim  has  a  Modula-2  compiler  for  PDP-l/RSX-1 1 
systems. 

P.  Robinson 
Computer  Laboratory 
University  of  Cambridge 
Corn  Exchange  St. 

Cambridge  CB2  3QG 
England 

Mr.  Robinson  has  a  Modula-2  compiler  for  VAX/Unix 
systems. 

Scenic  Computer  Systems,  Corp. 

14852  NE  31st  Circle 
Redmond,  WA  98502 
(206)  885-5500 

Scenic  distributes  a  Modula-2  compiler  for  the  68000. 


Dr.  Dobb's  Journal,  November  1985 


31 

819 


BENCHMARK 

Compile 

Link 

Execute 

Turbo  Pascal  Ver  2.0 

0:01 

n/a 

0:16 

Logitech  Modula-2 

1:27 

1:34 

0:25' 

Verl.10 

All  times  in  min:sec 

‘Include s  loading  runtime  module 

Test  run  on  TAVA  PC  XT  from  hard  disk. 

Table 

Compile,  Link,  and  Execution  Times  for  Sieve  of 
Eratosthenes  Benchmark 


gram  development  than  fast  Pascal  compilers  like  Turbo. 
Short  programs  for  finding  prime  numbers  (using  the 
Sieve  of  Eratosthenes  algorithm)  are  shown  in  Pascal  in 
Listing  One  (page  34)  and  Modula-2  in  Listing  Two 
(page  34).  The  compile  and  link  times  for  Logitech’s  Mo¬ 
dula-2  compiler  (Version  1.10)  and  Turbo  Pascal  (Ver¬ 
sion  2.0)  are  shown  in  the  first  two  columns  of  the  Table 
(above).  Here,  as  expected.  Turbo  Pascal  is  the  clear  win¬ 
ner.  The  third  column  shows  the  time  required  for  the 
execution  of  the  compiled  code.  Although  the  code  gener¬ 
ated  by  the  Logitech  compiler  seeems  to  run  more  slowly, 
part  of  the  figure  consists  of  the  time  required  to  load  the 
runtime  module,  about  10  seconds.  Thus,  in  execution 
time  the  numbers  are  actually  comparable. 

Notes 

1  H.  McClarty  and  D.  W.  Smith,  “An  Introduction  to  Mo¬ 
dula-2  for  Pascal  Programmers,”  DDJ  #91,  May  1984 
pp.  22-27. 

2  Gleaves,  Richard,  Modula-2  for  Pascal  Programmers, 
Springer- Verlag,  1984. 

3  D.E.  Cortesi,  “Turbo  Pascal  vs.  the  Standard,”  Dr. 
Dobb’s  Clinic,  DDJ  #105  July  1985,  pp.  12-18. 

Further  Reading 

Cashman,  M.,  “Extensions  and  Performance  Improve¬ 
ments  Keep  Pascal  Computing,”  Digital  Design,  August 
1984,  pp.  106-107. 

Cooper,  Doug,  Standard  Pascal  User  Reference  Manual, 
Norton  1983. 

Jensen,  Kathleen  and  Wirth,  Nikalus,  Pascal  User  Man¬ 
ual  and  Report,  Springer-Verlag  1974. 

Joyce,  Edward,  Modula-2:  A  Seafarer’s  Guide  and  Ship¬ 
yard  Manual,  Addison- Wesley,  1985. 

Meng,  B.,  “Ada  and  Modula-2:  True  Systems  Lan¬ 
guages?”  Digital  Design,  August  1985  pp.  74-79. 

Wirth,  N.  Programming  in  Modula-2,  Springer-Verlag, 
1983. 

-  “Modula-2,  An  Overview,”  Micro  Cornucopia 

#25,  August-September  1985  pp.  lb-19. 

(Listings  begin  on  next  page) 

DD) 


32 


Dr.  Dobb’s  Journal,  November  1985 


Modula-2  vs.  Pascal  (Text  begins  on  page  28) 

Listing  One 


(  Eratosthenes  Sieve  for  Prime  numbers  in  Pascal  } 

program  prime; 

const 

size  =  8190; 


var 

flags  :  array  [0. .size]  of  boolean; 
i,  prime,  k,  count,  iter  :  integer; 


begin 

writeln ( ' 10  iterations'); 
for  iter  :=  1  to  10  do 
begin 

count  : =  0 ; 
for  i  :=  0  to  size  do 
flags[i]  :=  true; 
for  i  :=  0  to  size  do 
if  flagsti]  then 
begin 

prime  :=  i  +  i  +  3; 
{writeln (prime) ; } 
k  :=  i  +  prime; 
while  k  <=  size  do 
begin 

flags[k]  :=  false; 
k  :=  k  +  prime 
end;  {while} 
count  :=  count  +1 
end;  {for,  if] 
writeln  (iter)  ; 
end;  {for  } 

writeln (count , '  primes') 
end. 


{  do  program  10  times  ) 

{  prime  counter  } 

{  set  flags  all  true  } 

{  found  a  prime  } 

{  twice  the  index  +  3  ) 

{  first  multiple  to  kill  } 

{  zero  a  non-prime  } 

{  next  multiple  } 

{  primes  found) 

{  #  of  primes  found  in  10th  pass  } 

End  Listing  One 


Modula-2  vs.  Pascal 

Listing  Two 


(*  Compute  a  table  of  the  first  n  primes  numbers.  Print 

primes  (optional).  Use  the  the  Sieve  of  Eratosthenes  algorithm.*) 

MODULE  sieve; 

FROM  Terminal  IMPORT  WriteString,  WriteLn; 

FROM  InOut  IMPORT  WriteCard; 

CONST  size  =  819  0; 

VAR  flags:  ARRAY { 0 .. size]  OF  BOOLEAN; 
i , j , prime, k, count :  CARDINAL; 

BEGIN 

FOR  j  :=  1  TO  10  DO 

WriteString (' Pass  =  '); 

WriteCard(j,2) ; 

WriteLn; 
count  :=  0; 

FOR  i  :=  0  TO  size  DO  flags [i]  :=  TRUE  END; 

FOR  i  :=  0  TO  size  DO 
IF  flags!!]  THEN 
prime  :=  i+i+3; 
k  :=  i+prime; 

WHILE  k  <=  size  DO 
flags [k]  :=  FALSE; 

INC(k ,pr ime) 

END; 

INC (count) ; 

(*  WriteCard (pi ime, 8)  ; 

WriteLn  *) 

END 

END 

END; 

WriteLn;  WriteCard (count, 0) ; 

WriteSt ring ( '  primes');  WriteLn 
END  sieve. 


End  Listings 


Dr.  Dobb's  Journal,  November  1985 


33 

821 


Bit  Manipulation  in  Modula-2 


by  Brian  R.  Anderson 


Though  Modula-2  provides  a  fa¬ 
cility  to  manipulate  data  bits 
directly  (via  BITSETs),  the 
methods  available  are  not  always  con¬ 
venient.  Possibly,  the  most  common 
use  of  this  facility  in  high-level  lan¬ 
guage  programming  is  manipulating 
character  data  (bytes).  A  trivial  ex¬ 
ample  would  be  clearing  the  high  bits 
of  all  characters  in  a  WordStar  file.  In 
C,  this  is  an  easy  matter  of  a  single 
instruction  (for  each  character): 

ch  &=  0x7F;  /*  bitwise  and  */ 

In  Modula-2  the  same  operation 
takes  five  instructions: 

I  :=  ORD  (ch); 

(*  I  is  a  CARDINAL*) 

B  :=  BITSET  (I); 

(*  Bis  a  BITSET*) 

EXCL  (B,  7);  (*  Exclude  bit  7  *) 

I  :=  CARDINAL  (B); 
ch  :=  CHR  (I); 


This  definition  module  is  shown  in 
Listing  1  (page  40)  and  provides  the 
user  (i.e.,  programmer)  with  an  inter¬ 
face  to  the  implementation  module. 

The  first  implementation  module 
that  I  wrote  was  in  Modula-2.  My  in¬ 
tention  was  eventually  to  write  the 
code  in  assembly,  but  I  wanted  a 
working  module  to  which  I  could  com¬ 
pare  the  assembler  version  during  the 
debugging  phase.  The  Modula-2  im¬ 
plementation  module  is  shown  in  List¬ 
ing  2  (page  40).  Despite  the  awkward¬ 
ness  of  the  Modula-2  syntax,  this 
module  was  somewhat  easier  to  write 
than  the  corresponding  assembler 
module. 

The  Modula-2  compiler  that  I  am 
using  (Hochstrasser  Z80)  allows  in¬ 
tegration  of  standard  MAC/REL  files 
with  Modula  programs.  This  is  ac¬ 
complished  by  way  of  a  conversion 
program.  The  assembly  code  is  devel¬ 
oped  in  the  usual  manner  and  then 


The  implementation  module  can  be  coded  in 
assembly  language  for  speed  without  affecting  the 
definition  module. 


There  has  to  be  a  better  way! 
Though  I  knew  from  the  outset  that  it 
was  impossible  to  match  C’s  simplic¬ 
ity,  I  set  out  to  write  a  collection  of 
procedures  that  would  make  it  easier 
and  more  intuitive  to  perform  com¬ 
mon  and  important  bit-manipulation 
operations  in  Modula-2 

The  first  step  was  to  write  a  defini¬ 
tion  module  with  eight  common  bit 
manipulations  (set  a  bit,  reset  a  bit, 
test  a  bit,  shift  left  all  bits,  shift  right 
all  bits,  AND/OR/XOR  two  bytes). 


Brian  R.  Anderson,  2977  E.  56th 
Ave.,  Vancouver,  B.C.  Canada 


assembled  using  M80  or  RMAC.  A 
name  translation  file  may  be  speci¬ 
fied  during  conversion,  at  which  time 
the  REL  file  becomes  an  MRL  file 
(Modula  Object  Code).  (M80  short¬ 
ens  all  identifiers  to  6  characters  and' 
maps  all  characters  to  upper  case, 
whereas  this  Modula-2  allows  24- 
character  identifiers  of  both  upper 
and  lower  case.)  The  assembler  im¬ 
plementation  module  is  shown  in 
Listing  3  (page  44);  the  name  conver¬ 
sion  file  is  shown  as  Listing  4  (page 
46). 

With  either  implementation,  the 
operation  of  clearing  the  high  bit  of 


38 

822 


Dr.  Dobb's  Journal,  November  1985 


characters  in  a  WordStar  file  now  be¬ 
comes  as  simple  for  the  Modula-2  pro¬ 
grammer  as  it  is  for  the  C  program¬ 
mer: 


Bit  Manipulation  (Text  begins  on  page  38) 
Listing  One 


Reset  (ch,  7);  (*  reset  bit  7  of  ch  *) 

In  fact,  this  is  simpler  and  clearer 
than  the  C  code  to  perform  the  same 
function. 

Performance 

From  the  point  of  view  of  the  pro¬ 
grammer  the  two  implementation 
modules  look  identical.  This  is  guar¬ 
anteed  because  they  both  use  the 
same  definition  module.  Though  no 
error  checking  is  done,  the  proce¬ 
dures  behave  sensibly.  If  they  are 
asked  to  set  bit  9,  for  example,  they 
simply  circle  around  and  set  bit  9  -  8 
(i.e.,  bit  1).  You  should  note  that 
these  procedures  consider  the  least 
significant  bit  to  be  bit  zero. 

The  assembler  module  performs 
considerably  better  with  regard  to 
code  size  and  speed.  The  Modula-2 
implementation  compiles  to  555 
bytes  of  code  and  10  bytes  of  data, 
whereas  the  assembly  implementa¬ 
tion  results  in  only  129  bytes  of  code 
and  no  data.  The  procedures  in  the 
assembler  version  are  also  4  times 
faster  (on  average). 

Conclusions 

Bit  manipulation  in  Modula-2  need 
not  be  as  cumbersome  as  suggested  by 
the  definition  of  the  language.  The 
provision  of  assembly  language  inter¬ 
face  for  high-level  language  compilers 
allows  significant  performance  im¬ 
provements. 

I  would  like  to  hear  from  other  pro¬ 
grammers  if  the  facilities  provided  by 
the  Bits  module  are  useful  enough  to 
be  considered  for  inclusion  in  stan¬ 
dard  libraries.  Perhaps  other  proce¬ 
dures  should  be  included  (Rotate?). 
Let  us  know  what  you  think  before  we 
approach  the  Modula  Users  Society 
(MODUS)  with  a  proposal. 


bits.def 

DEFINITION  MODULE  Bits; 

(*  bit  manipulation  module  *) 

(*  BA  June  10,  1985  *) 

EXPORT  QUALIFIED 

BYTE,  Set,  Reset,  Test,  ShiftLeft,  ShiftRight,  And,  Or,  Xor; 

TYPE 

BYTE  =  CHAR; 

PROCEDURE  Set  (VAR  A  :  BYTE;  bit  :  CARDINAL); 

PROCEDURE  Reset  (VAR  A  :  BYTE;  bit  :  CARDINAL); 

PROCEDURE  Test  (A  :  BYTE;  bit  :  CARDINAL)  :  BOOLEAN; 

PROCEDURE  ShiftLeft  (VAR  A  ;  BYTE); 

PROCEDURE  ShiftRight  (VAR  A  :  BYTE) ; 

PROCEDURE  And  (A,  B  :  BYTE)  :  BYTE; 

PROCEDURE  Or  (A,  B  :  BYTE)  :  BYTE; 

PROCEDURE  Xor  (A,  B  :  BYTE)  :  BYTE; 

END  Bits. 

End  Listing  One 


Listing  Two 


bits. mod 

IMPLEMENTATION  MODULE  Bits; 

(*  bit  manipulation  module  *) 

(*  BA  June  10,  1985  *) 

VAR 

cA,  cB  ;  CARDINAL;  (*  cardinal  value  for  parameters  A  &  B  *) 
bA,  bB  ;  BITSET;  (*  bitset  value  for  parameters  A  &  B  *) 

Result  :  BITSET;  (*  result  for  and/or/xor  *) 


PROCEDURE  Set  (VAR  A  :  BYTE;  bit  :  CARDINAL); 

BEGIN 

cA  :=  ORD  (A);  (*  convert  BYTE  to  CARDINAL  *) 

bA  :=  BITSET  (cA) ;  (*  'coerce'  CARDINAL  to  BITSET  *) 

INCL  (bA,  bit  MOD  8);  (*  set  the  bit  (make  sure  in  range 

0  — >  7)  *) 

cA  :=  CARDINAL  (bA) ;  <*  'coerce  BITSET  back  to  CARDINAL  *) 

A  :=  CHR  (cA);  (*  convert  CARDINAL  back  to  BYTE  *) 

END  Set; 


PROCEDURE  Reset  (VAR  A  :  BYTE;  bit  :  CARDINAL); 
BEGIN 

cA  :=  ORD  (A) ; 
bA  :=  BITSET  (cA) ; 

EXCL  (bA,  bit  MOD  8); 
cA  :=  CARDINAL  (bA) ; 

A  :=  CHR  (cA) ; 

END  Reset; 


PROCEDURE  Test  (A  :  BYTE;  bit  :  CARDINAL)  :  BOOLEAN; 


(Listings  begin  on  next  page) 


DD) 

Reader  Ballot 

Vote  for  your  favorite  feature/article. 
Circle  Reader  Service  No.  1 92. 


BEGIN 

cA  :=  ORD  (A) ; 
bA  :=  BITSET  (cA) ; 

IF  (bit  MOD  8)  IN  bA  THEN 
RETURN  TRUE; 

ELSE 

RETURN  FALSE; 

END; 

END  Test; 


Dr.  Dobb's  Journal,  November  1985 


39 

823 


o/i  /vtdnifjuiduon  (Listing  continued,  text  begins  on  page  38) 

Listing  Two 


PROCEDURE  ShiftLeft  (VAR  A  :  BYTE) ; 

BEGIN 

cA  :=  ORD  (A) ; 

cA  :=  cA  *  2;  (*  Shift  Left  is  equivalant  to  Multiplication  by  2*) 
A  :=  CHR  (cA) ; 

END  ShiftLeft; 

PROCEDURE  ShiftRight  (VAR  A  :  BYTE); 

BEGIN 

cA  :*  ORD  (A); 
cA  :=  cA  DIV  2; 

A  :=  CHR  (cA) ; 

END  ShiftRight; 


PROCEDURE  And  (A,  B  :  BYTE)  :  BYTE; 

BEGIN 

cA  i —  ORD  (A);  (*  both  BYTEs  must  be  forced  to  BITSET  *) 

bA  ;=  BITSET  (cA) ; 
cB  :=  ORD  (B); 
bB  ;=  BITSET  (cB) ; 

Result  ;=  bA  *  bB;  (*  AND  the  two  bitsets  *) 

cA  :=  CARDINAL  (Result);  (*  force  Result  back  to  BYTE  *) 
A  :=  CHR  (cA) ; 

RETURN  A; 

END  And; 


PROCEDURE  Or  (A,  B  :  BYTE)  :  BYTE; 
BEGIN 

cA  :=  ORD  (A); 
bA  :=  BITSET  (cA) ; 
cB  ;=  ORD  (B) ; 
bB  :=  BITSET  (cB) ; 

Result  :=  bA  +  bB; 

cA  ;=  CARDINAL  (Result); 

A  :=  CHR  (CA); 

RETURN  A; 

END  Or; 


PROCEDURE 

Xor  (A,  B  :  BYTE)  :  BYTE; 

BEGIN 

cA 

:=  ORD  (A) ; 

bA 

:=  BITSET  (cA)  ; 

cB 

:=  ORD  (B) ; 

bB 

!=  BITSET  (CB) ; 

Result  :=  bA  /  bB; 

cA 

:=  CARDINAL  (Result); 

A  : 

=  CHR  (cA) ; 

RETURN  A; 
END  Xor ; 


END  Bits. 


End  Listing  Two 

(Listing  Three  begins  on  page  44) 


42 

R?a 


Dr.  Dobb’s  Journal,  November  1 985 


Bit  Manipulation  (Listing  continued,  text  begins  on  page  38) 

Listing  Three 


j IMPLEMENTATION  MODULE  Bits? 
>(*  bit  manipulation  module  *) 
;  (*  BA  June  13, 1985  *) 

NAME  (’BITS') 


.Z80 

EXPORT  QUALIFIED 

BYTE,  Set,  Reset,  Test, 


PUBLIC 

SET,  RESET, 

? 

;  TYPE 

;  BYTE  = 

CHAR ; 

; 

CSEG 

; 

MODS  EQU 

00000111B 

; 

;  PROCEDURE 

Set  (VAR  A 

; 

SET:  POP 

IY 

POP 

BC 

EX 

(SP) ,IY 

LD 

A , MOD8 

AND 

C 

LD 

B,  A 

XOR 

A 

CCF 

INC 

B 

FINDS:  RLA 

DJNZ 

FINDS 

OR 

(IY) 

LD 

(IY)  , A 

RET 

; 

;  PROCEDURE 

Reset  (VAR 

; 

RESET:  POP 

IY 

POP 

BC 

EX 

(SP) ,IY 

LD 

A.MOD8 

AND 

C 

LD 

B,  A 

XOR 

A 

CCF 

INC 

B 

FINDR:  RLA 

DJNZ 

FINDR 

CPL 

AND 

(IY) 

LD 

(IY) , A 

RET 

; 

;  PROCEDURE 

Test  (A  :  ] 

; 

TRUE  EQU 

00000001B 

TEST:  POP 

HL 

POP 

BC 

EX 

(SP) ,HL 

LD 

IY ,  0 

ADD 

I Y ,  SP 

LD 

A , MOD8 

AND 

C 

LD 

B,  A 

XOR 

A 

CCF 

INC 

B 

FINDT:  RLA 

DJNZ 

FINDT 

AND 

L 

JP 

Z, FALSE 

LD 

A, TRUE 

FALSE:  LD 

( I Y+2 ) , A 

RET 

?  PROCEDURE 

ShiftLeft 

ShiftLeft,  ShiftRight,  And,  Or,  Xor; 
,  SHL,  SHR,  ANDB,  ORB,  XORB 


;Mask  to  calculate  bit  MOD  8 


BYTE;  bit  :  CARDINAL); 

;Return  Address 
;  bit  t 

.•BYTE's  address  < - >  Return  Address 

;Make  sure  bit 
;  in  range  0  — >  7 
; ' Safe '  bit  #  in  B 
;Clear  Accum.  &  CY 
;Set  CY  to  make  mask 
;Adjust  count 
; Rotate  mask 
;  until  count  is  zero 
;Set  the  bit 
.•Return  it  to  BYTE 


BYTE;  bit  :  CARDINAL); 

;Return  Address 
;bit  # 

;BYTE ' s  address  < - >  Return  Address 

;Make  sure  bit 
;  in  range  0  — >  7 
;  '  Safe  '  bit  #  in  B 
;Clear  Accum.  &  CY 
;Set  CY  to  make  mask 
;Adjust  count 
;Rotate  mask 
;  until  count  is  zero 
;Invert  mask 
;Reset  (clear)  the  bit 
;Return  it  to  BYTE 


BYTE;  bit  :  CARDINAL)  :  BOOLEAN; 


; 


;Return  Address 
;bit  « 

;BYTE  < - >  Return  Address 

;Clear  a  Pointer 

;Make  Copy  of  Stack  Pointer 

;Make  sure  bit 

;  in  range  0  — >  7 

;  '  Safe '  bit  I  in  B 

;Clear  Accum.  &  CY 

;Set  CY  to  make  mask 

;Adjust  count 

;Rotate  mask 

;  until  count  is  zero 

;Check  if  bit  set 

;If  zero,  return  FALSE 

;  else,  return  TRUE 

;Store  function  Return  Value 


BYTE) ; 


(Continued  on  page  46) 


Dr.  Dobb’s  Journal,  November  1985 


44 

825 


Bit  Manipulation 

Listing  Three 


SHL: 

POP 

HL 

EX 

(SP) ,HL 

SLA 

(HL) 

RET 

(Listing  continued,  text  begins  on  page  38) 


(Return  Address 

(BYTE's  address  < - >  Return  Address 

(Shift  Left  (Arithmetic) 


PROCEDURE  ShiftRight  (VAR  A  :  BYTE) ( 


SHRs 

POP 

HL 

EX 

(SP) ,HL 

SRL 

RET 

(HL) 

7 

7 

PROCEDURE 

And  (A,  E 

7 

ANDB 

:  POP 

HL 

POP 

DE 

EX 

(SP) , HL 

LD 

IY,  0 

ADD 

IY,  SP 

LD 

A,  L 

AND 

E 

LD 

RET 

(IY+2) ft 

7 

7 

PROCEDURE 

Or  (A,  B 

7 

ORB: 

POP 

HL 

POP 

DE 

EX 

(SP) , HL 

LD 

IY,  0 

ADD 

I Y ,  SP 

LD 

A,  L 

OR 

E 

LD 

RET 

(IY+2 ) ,  1 

7 

7 

PROCEDURE 

Xor  (A,  1 

7 

XORB :  POP 

HL 

POP 

DE 

EX 

(SP) ,HL 

LD 

IY ,  0 

ADD 

IY ,  SP 

LD 

A,  L 

XOR 

E 

LD 

RET 

(IY+2),. 

7 

(END  Bits. 

7 

END 

(Return  Address 

(BYTE's  address  < - >  Return  Address 

(Shift  Right  (Logical) 


BYTE)  :  YTE( 


(Return  Address 
(  '  B  ' 

('A'  < - >  Return  Address 

(Clear  a  Pointer 

(Make  Copy  of  Stack  Pointer 

(Move  'A'  to  Accum. 

(And  with  'B' 

(Store  function  return  value 


BYTE)  :  BYTE ( 


(Return  Address 
(  '  B ' 

('A'  < - >  Return  Address 

(Clear  a  Pointer 

(Make  Copy  of  Stack  Pointer 

(Move  'A'  to  Accum. 

(Or  with  'B' 

(Store  function  return  value 


B  :  BYTE)  :  BYTE,- 


(Return  Address 
»  '  B ' 

('A'  < - >  Return  Address 

(Clear  a  Pointer 

(Make  Copy  of  Stack  Pointer 

(Move  'A'  to  Accum. 

(Xor  with  'B' 

(Store  function  return  value 


End  Listing  Three 


Listing  Four 


(*  Translation  Table  for  converting  Identifiers  *) 

(*  from  Assembler  format  to  Modula  format.  *) 

(*  Table  is  used  by  module  converter,  which  converts  *) 

(*  standard  REL  file  into  MRL  (Modula  ReLocatable)  Object  file.  *) 


BITS 

Bits 

SET 

Bits. Set 

RESET 

Bits. Reset 

TEST 

Bits .Test 

SHL 

Bits.ShiftLeft 

SHR 

Bits. ShiftRight 

ANDB 

Bits. And 

ORBB 

Bits. Or 

XORB 

Bits. Xor 

End  Listings 


46 

ROfi 


Dr.  Dobb's  Journal,  November  1985 


THE  SOFTWARE  DESIGNER 


Zoom  racks:  Designing  a  new 
Software  Metaphor 

by  Paul  Heckel 


The  poet's  eye,  in  a  fine  frenzy  roll¬ 
ing, 

Doth  glance  from  heaven  to  earth, 
from  earth  to  heaven; 

And  as  imagination  bodies  forth 
The  forms  of  things  unknown,  the 
poet's  pen 

Turns  them  into  shapes,  and  gives  to 
airy  nothing 

A  local  habitation  and  a  name. 

A  Midsummer  Night’s  Dream 

In  my  book  The  Elements  of  Friend¬ 
ly  Software  Design 1  I  treat  the  design 
of  applications  software  as  a  commu¬ 
nications  craft  such  as  writing,  film- 
making,  or  advertising.  For  over  two 
years  at  Quickview  Systems,  we  have 
been  refining  the  ideas  expressed  in 
this  book  and  putting  them  into  prac¬ 
tice  in  a  product  called  Zoomracks. 
Zoomracks  offers  users  a  new  envi¬ 
ronment  and  a  new  productivity  tool. 
We  have  incorporated  in  this  product 
four  innovations  in  user-interface 
design: 

1 )  A  new  computer  metaphor:  the 
rack.  A  rack  is  a  familiar  object.  Peo¬ 
ple  know  how  items  are  organized  on 
a  rack.  Consequently,  a  rack  is  an  ef¬ 
fective  organizational  model  for  a 
computer  program.  We  think  that 
Zoomracks  will  become  a  conceptual 
extension  of  the  physical  rack  in  the 
same  way  that  VisiCalc  became  a 
conceptual  extension  of  the  physical 
spreadsheet. 

2)  A  new  viewing  mechanism  called 
Smart  Zooms.  Unlike  windows. 
Smart  Zooms  shows  the  “big  pic¬ 
ture”  rather  than  the  details. 

3)  A  method  of  using  toggles  and  in¬ 
verses  to  give  even  the  novice  a  feel¬ 
ing  of  control  in  using  the  software. 

4)  A  concept,  called  Fieldscrolls,  that 
allows  a  user  to  construct  a  wide  vari¬ 
ety  of  database  schemes  by  specifying 

I  a  minimum  amount  of  information. 


Though  the  first  two  concepts  were 
fundamental  from  the  beginning,  the 
last  two  evolved  almost  accidently 
into  something  more  powerful  than 
we  had  expected.  This  article  is  in¬ 
tended  to  explain  what  our  thought 
processes  were  in  designing  and  de¬ 
veloping  Zoomracks  and  not  just 
what  the  product  is. 

The  Opportunity 

When  you  develop  a  new  product, 
you  should  limit  the  number  of  fun¬ 
damental  goals  that  you  hope  to 
achieve.  These  goals  will  determine 
where  thought,  energy,  and  time  are 
spent.  They  will  determine  what  gets 
put  in  and  what  is  left  out — at  least 
until  the  final  stages  of  product  de¬ 
velopment.  In  developing  Zoomracks 
our  fundamental  goals  were: 

1.  We  wanted  to  present  information 
on  a  computer  screen  effectively, 
compactly,  and  in  a  way  that  was  in¬ 
dependent  of  screen  size1 2.  Although 
Zoomracks  is  being  introduced  on  the 
IBM  PC,  it  was  designed  with  the  lap 
and  hand-held  computers  of  the  fu¬ 
ture  in  mind.  As  semiconductor  tech¬ 
nology  advances,  computers  will  get 
smaller  and  smaller.  By  1990,  a  cir¬ 
cuit  board  the  size  of  a  credit  card 
will  hold  4  megabytes  of  RAM3.  The 
microscopic  size  of  electronic  compo¬ 
nents  will  allow  the  manufacture  of 
computers  that  are  much  smaller 
than  those  available  today.  Yet,  the 
lap  and  hand-held  computers  will  be¬ 
come  practical  and  popular  only  if 
software  is  available  that  makes  ef¬ 
fective  use  of  smaller  screens.  Pro¬ 
grams  that  can  operate  only  with 
larger  and  more  expensive  displays 
will  naturally  become  less  attractive. 
Of  course,  software  that  can  utilize 
smaller  displays  must  work  at  least  as 
effectively  with  standard  size  com¬ 
puter  screens. 


The  Japanese  are  experts  at  mak¬ 
ing  things  small.  Their  approach  is  to 
distill  out  only  the  essence  and  to 
eliminate  what  is  not  essential4.  We 
tried  to  do  the  same  thing  in  develop¬ 
ing  our  software. 

2.  We  wanted  to  provide  an  organiza¬ 
tion  mechanism  that  was  general 
enough  to  handle  many  kinds  of  in¬ 
formation  but  was  also  simple  and 
comprehensible  to  the  user. 

When  there  are  pocket  computers 
that  have  the  power  of  a  desktop 
computer,  what  will  you  want  them 
to  do?  We  think  you  will  want  them 
to  keep  track  of  the  personal  informa¬ 
tion  that  you  want  with  you  at  all 
times:  names  and  addresses,  appoint¬ 
ments,  things  to  do,  notes,  memos, 
and  the  records  of  business  expenses 
and  automobile  mileage  that  the  IRS 
demands.  Zoomracks  was  designed 
to  organize  and  manage  this  kind  of 
information.  It  was  designed  to  be  a 
general  purpose  tool  as  opposed  to  a 
specific  application. 

3.  We  wanted  to  give  users  a  variety 
of  ways  to  view  their  information. 

4.  We  wanted  Zoomracks  to  be  easy 
to  use. 

.  .  .  the  idea  itself  probably  is  the 
most  important  element  of  the  entire 
illustration.  Certainly,  if  the  idea  is 
not  good  and  if  it  does  not  interest 
and  intrigue  people,  any  other  good 
qualities  which  the  picture  may  pos¬ 
sess  will  be  lost  because  they  will  not 
be  seen.  It  is  an  utter  waste  of  effort 
to  paint  a  beautiful,  story-telling 
picture  unless  it  is  based  on  a  good 
central  idea — one  which  can  be 
readily  understood. 

Norman  Rockwell 

It  may  be  a  good  thing  to  copy  reali¬ 
ty;  but  to  invent  reality  is  much, 
much  better. 

Giuseppe  Verdi. 


Dr.  Dobb’s  Journal,  November  1985 


48 

827 


VisiCalc  demonstrates  how  a  good 
unifying  concept  works.  At  the  point 
of  sale,  the  question,  “Can  I  do  X 
with  VisiCalc?”  is  transformed  into 
“Could  you  do  X  on  a  manual 
spreadsheet?”  In  the  office,  “How 
would  I  do  X  with  VisiCalc?”  is 
turned  into  “How  would  you  do  X  on 
a  manual  spreadsheet?”  The  user  can 
do  a  lot  just  on  the  basis  of  informa¬ 
tion  he  already  has. 

Our  unifying  concept  is  a  rack  sim¬ 
ilar  to  magazine  racks  or  the  racks 
that  hang  next  to  factory  time  clocks. 
We  started  with  racks  because  it  was 
a  simple,  familiar  concept  that  we 
felt  could  be  developed  both  visually 
and  in  terms  of  complexity.  First,  we 
will  describe  a  single  rack,  but  imag¬ 
ine  it  as  one  of  several  side  by  side. 
Later  we  will  describe  how  Smart 
Zooms  display  several  racks  on  the 
screen  at  once. 

Every  single  object  shown  in  a  pic¬ 
ture  should  contribute  directly  to  the 
central  theme.  All  other  things 
should  be  ruthlessly  discarded. 

Norman  Rockwell 

Consider  a  time-card  rack.  The  first 
line  of  each  card  is  always  visible. 
Any  card  can  be  removed  and  the  de¬ 
tails  examined.  Cards  can  be  insert¬ 
ed,  removed,  and  moved  into  other 
slots  in  the  same  or  different  racks. 
Typically,  cards  in  a  rack  have  the 
same  form  (time  cards),  but  are  dif¬ 
ferent  in  content  (people  and  hours 
worked).  They  are  probably  arranged 
in  some  order  (by  employee  number 
or  name).  Several  time-card  racks 
might  be  next  to  each  other.  These 
are  the  essential  features  of  time-card 
racks.  This  is  what  people  know  and 
expect.  This  is  the  idea  that  serves  as 
the  unifying  concept  for  Zoomracks. 

Once  we  have  selected  the  meta¬ 
phor,  we  can  begin  to  extend  and 
transform  our  computerized  racks  to 
eliminate  the  limits  that  physical 
racks  have.  First,  we  can  make  racks 
arbitrarily  long.  A  Zoomrack  can 
hold  2  cards  or  2,000,  growing  or 
shrinking  to  meet  the  number  of 
cards  in  it.  Second,  a  rack  can  auto¬ 
matically  keep  cards  in  order  by  the 
first  field.  People  expect  cards  and 
lists  to  be  ordered  by  the  first  field,  as 

50 


in  a  library  catalog  or  a  phone  book. 
In  a  physical  rack,  inserting  a  new 
card  in  proper  order  requires  moving 
all  the  cards  below  it  to  make  room. 
A  card  inserted  in  a  Zoomrack  auto¬ 
matically  goes  to  its  alphabetic  place. 
Third,  although  all  the  cards  in  a 
physical  time-card  rack  are  the  same 
size,  the  cards  in  a  Zoomrack  can  be 
of  different  lengths.  At  one  extreme, 
a  Zoomrack  can  hold  a  set  of  cards 
that  contain  only  one  brief  item  on 
the  first  line.  If  you  remove  the  card, 
you  see  that  the  rest  of  it  is  empty.  In 
this  instance,  the  set  of  cards  serves 
as  a  list  of  brief  items  of  information. 
At  the  other  extreme,  a  Zoomrack 
can  be  a  file  system.  In  this  case,  the 
first  line  contains  the  file  label;  when 
you  remove  the  card,  you  find  that 
the  rest  of  it  is  filled  by  a  lengthy  doc¬ 
ument.  Thus,  you  can  use  racks  for 
lists,  card  files  or  for  document  files 
as  you  wish. 

Exaggerate  the  essential  and  leave 
the  obvious  vague. 

Vincent  van  Gogh 

By  striking  one  key,  you  can  toggle 
between  multicard  mode,  where  you 
get  a  display  of  the  first  line  of  several 
You  start  with  an  idea  or  vision. 
From  there  you  proceed  to  the  tech¬ 
niques  and  technology  required  to 
bring  that  vision  to  life.  A  painter’s 
first  concern  is  not  with  his  paints 
and  canvas,  but  with  the  theme  and 
mood  of  his  picture.  He  imagines 
something  in  his  own  mind  that  he 
wants  to  convey  to  the  minds  of  his 
audience.  The  tools  of  his  profession 
are  merely  means  (often  uncoopera¬ 
tive)  to  that  end. 

The  programmer,  like  the  painter, 
is  a  communicator.  The  same  princi¬ 
ples  govern  his  creative  efforts;  his 
activities  are  arranged  in  the  same  hi¬ 
erarchy.  The  most  important  task  for 
him  is  to  choose  a  unifying  idea.  This 
unifying  idea  is  like  the  spine  of  a 
book  that  holds  all  the  pages  togeth¬ 
er.  It  is  only  after  this  idea  has  been 
fixed  that  the  programmer  can  think 
about  techniques  of  presentation  or 
technologies  for  communication.  Our 
first  concern,  then,  will  be  the  unify¬ 
ing  idea. 

When  an  actor  studies  a  play,  he 


looks  for  a  unifying  concept  around 
which  he  can  build  his  entire  portray¬ 
al.  A  program,  too,  must  have  a  sim¬ 
ple,  communicable  concept  that 
holds  everything  together.  The  con¬ 
cept  behind  a  spreadsheet  program  is 
the  array  of  cells,  each  of  which  con¬ 
tains  a  number,  an  equation  or  text. 
Everything  else  is  detail— important 
detail,  but  detail  nonetheless.  A  good 
concept  is  not  merely  a  reproduction 
of  reality  (like  an  image  in  a  mirror), 
but  creates  a  new  reality.  It  might 
take  time  to  attract  an  audience,  but 
a  good  unifying  concept  will  find  res¬ 
onance  among  users.  VisiCalc,  and 
more  recently  ThinkTank,  opened  up 
important  new  markets  because  they 
embodied  such  concepts. 

The  unifying  concept  becomes  the 
basis  for  communication  with  the  au¬ 
dience.  Once  chosen,  it  defines  an 
easily  intelligible  framework  into 
which  everything  else  fits.  It  is  the 
metaphor  that  strikes  a  familiar 
chord  in  the  audience  and  allows 
them  to  talk  about  your  product  in 
familiar  terms:  “It’s  just  like  a 
spreadsheet.”  It  generates  word-of- 
mouth  reports,  creates  new  markets, 
and  brings  people  in  to  purchase  your 
product. 

cards  in  the  Zoomrack,  and  single¬ 
card  mode,  where  you  view  the  de¬ 
tails  of  one  card.  This  toggling  be¬ 
comes  a  reflex  action,  like  switching  a 
light  on  and  off.  By  making  it  easy  to 
flip  cards  in  and  out  we  hoped  to 
make  Zoomracks  more  like  a  physi¬ 
cal  rack. 

Acting  is  a  great  profession  as  long 
as  no  one  catches  you  at  it. 

Spencer  Tracy  to  Burt  Reynolds 

One  of  our  design  objectives  was  to 
modify  the  perceptions  of  the  user. 
Ordinarily,  he  perceives  himself  as 
giving  instructions  to  a  computer;  we 
wanted  him  to  perceive  himself  as 
manipulating  real,  familiar  objects. 
If  you  see  someone  on  television,  that 
person  seems  real.  From  the  evidence 
of  your  eyes  you  believe  in  his  exis¬ 
tence  as  much  as  you  believe  in  the 
existence  of  the  person  sitting  next  to 
you  in  the  same  room.  A  good  writer 
makes  his  characters  seem  real.  You 
can  probably  conjure  up  a  mental 


non 


Dr.  Dobb’s  Journal,  November  1985 


picture  of  Ebenezer  Scrooge  or 
Huckleberry  Finn  without  much  ef¬ 
fort.  They  have  been  transported 
from  the  imagination  of  Charles 
Dickens  and  Mark  Twain  to  your 
imagination.  They  may  be  fictional 
characters,  but  they  are  real  to  you. 

Just  as  the  job  of  a  communicator 
is  to  create  a  reality  in  the  mind  of  his 
audience,  the  job  of  the  software  de¬ 
signer  is  to  create  a  reality  in  the 
mind  of  the  user.  When  an  author 
creates  the  “willing  suspension  of  dis¬ 
belief,”  the  reader  becomes  oblivious 
to  the  fact  he  is  reading  a  book.  The 
software  designer  helps  to  suspend 
disbelief  by  minimizing  the  distrac¬ 
tions  caused  by  the  mechanics  of  us¬ 
ing  the  software. 

One  way  to  lessen  distractions  and 
make  racks  more  real  is  to  ensure 
that  commands  work  the  same  in 
both  multicard  and  single-card  dis¬ 
play  modes  (and,  as  we’ll  see  later  on, 
in  multirack  and  single-rack  modes). 
For  example,  the  command  for  going 
to  the  next  card  in  the  rack — and 
thus  the  mental  process  required  to 
perform  this  task  is  always  the 
same.  When  the  form  of  a  command 
depends  on  what  mode  you  are  in, 
you  have  to  focus  on  the  mechanics  of 
the  operation.  This  either  actually 
disrupts  your  perception  that  the 
racks  are  real,  or  at  least  makes  them 
psychologically  more  distant. 

Each  rack  has  a  format  that  speci¬ 
fies  its  fields  of  information  (e.g., 
name,  phone,  city).  All  the  cards  in 
one  Zoomrack  are  of  the  same  for¬ 
mat,  but  different  racks  can  have  dif¬ 
ferent  formats.  One  Zoomrack  could 
be  used  for  names  and  addresses,  an¬ 
other  for  appointments  (sorted  by 
date  and  time),  a  third  for  notes,  a 
fourth  for  memos,  and  so  on. 

You  can  specify  a  format  for  any 
Zoomrack.  More  importantly,  you 
can  easily  change  it  after  the  rack  is 
already  loaded  with  cards.  This  has 
two  advantages. 

1.  People  don’t  want  to  have  to  get  it 
right  the  first  time.  You  want  to  be 
able  to  try  something,  and  when  you 
see  a  way  to  do  it  better,  change  it. 
Adding,  deleting,  or  moving  fields 
should  be  easy. 

2.  People  might  want  to  concentrate 


information  of  special  interest  on  the 
first  line  of  a  card  where  it  can  be 
viewed  in  multicard  display  mode. 
For  example,  if  you  have  a  card  with 
the  name  and  telephone  number  of  an 
individual  on  the  first  line,  you  might 
want  to  move  the  name  of  the  per¬ 
son’s  company  or  the  date  of  an  im¬ 
portant  meeting  with  him  onto  the 
same  line. 

One  command  toggles  the  display 
of  labels  on  and  off.  This  helps  you 
use  screen  space  efficiently  because 
labels  are  left  off  most  of  the  time. 
You  only  need  labels  when  you  are 
entering  information  or  are  first  us¬ 
ing  Zoomracks.  Usually  labels  are 
background  noise:  you  are  interested 
in  your  information,  not  the  name  of 
your  information.  Again,  we  are  try¬ 
ing  to  provide  the  ability  to  view  the 
essential. 

Seeing  is  forgetting  the  name  of  the 
thing  you  see. 

Paul  Valery 

Zoomracks  consists  of  several  racks. 
Each  rack  contains  an  unlimited 
number  of  cards.  Each  card  has  fields 
and  a  format  that  specifies  the  num¬ 
ber  of  fields  and  how  they  are  to  be 
displayed.  Each  field  has  unlimited 
lines.  Everything  else  is  detail.  (Our 
current  implementation  limits  you  to 
10  Zoomracks,  29  fields  per  Quick- 
card,  80  characters  per  line,  and  the 
amount  of  RAM  memory.) 

Within  this  framework.  Zoom- 
racks  offers  three  different  capabili¬ 
ties:  you  can  have  short  fields  like 
those  found  in  databases,  text  fields 
for  multipage  notes,  and  columns  of 
information  for  forms  such  as  sales 
orders  or  spreadsheets.  The  obvious 
way  to  do  this  is  to  have  three  types  of 
field  for  columns,  documents,  and  da¬ 
tabase  entries  and  a  bunch  of  rules 
for  the  user  to  follow.  Unfortunately, 
this  doesn’t  allow  for  easy  use.  While 
seeking  a  solution  to  this  problem,  we 
happened  upon  the  Fieldscroll 
concept. 

Each  Quickcard  is  made  up  of 
Fieldscrolls  that  contain  the  text  of 
the  fields.  To  use  a  chemical  analogy, 
if  the  Fieldscroll  is  an  element,  a 
Quickcard  is  a  molecule,  and  a  rack  is 


a  set  of  identical  molecules.  The  for¬ 
mat  command  lets  you  change  the 
chemical  structure. 

The  format  command  lets  you 
specify  how  your  Fieldscrolls  are  ar¬ 
ranged,  or,  more  precisely,  which  line 
each  Fieldscroll  is  hung  from,  and,  in 
the  case  of  multiple  Fieldscrolls  on 
one  line,  the  position  of  each  within 
the  line.  Additional  lines  in  a  Field- 
scroll  are  displayed  successively  on 
blank  lines  underneath.  Thus,  a 
Fieldscroll  can  be  used  in  any  of  the 
three  different  ways  mentioned 
above: 

1.  Database  Mode:  You  display  one 
field  (typically,  a  name  or  phone 
number)  on  one  line.  Here  the  Field- 
scroll  consists  of  the  single  field  on 
the  single  line. 

2.  Document  or  Text  Mode:  You  dis¬ 
play  a  document  across  the  entire 
width  of  the  display  on  consecutive 
lines.  Here  the  Fieldscroll  consists  of 
the  first  line  and  the  several  lines  for 
text  underneath  it. 

3.  Column  Mode:  You  display  several 
narrow  fields  on  the  first  line.  Here 
each  narrow  field  is  a  Fieldscroll. 
Now,  if  you  hang  blank  lines  from 
each  field,  these  lines  end  up  being 
the  same  width  and  you  have  trans¬ 
formed  your  set  of  narrow  fields  into 
columns. 

You  determine  the  meaning  of 
your  Fieldscrolls  on  a  card.  The  for¬ 
mat  specification  (where  the  name  of 
the  field  and  its  location  in  the  dis¬ 
play  are  stored)  is  merely  an  aid  to 
your  understanding.  Inconsistencies 
between  cards  and  format  (e.g., 
wrong  field  type,  line  too  long)  can¬ 
not  occur. 

Fieldscrolls  were  developed  acci¬ 
dently.  Zoomracks  was  evolving  from 
an  electronic  Rolodex  product  into  a 
multirack  database  product.  We  de¬ 
cided  to  add  word  processing.  Be¬ 
cause  our  database  supported  fields 
with  subfields  (or  lines),  we  decided 
to  treat  the  fields  as  text  scrolls  and 
the  subfields  as  lines  in  the  scroll.  We 
had  a  move  command  in  our  electron¬ 
ic  Rolodex  that  moved  fields  around 
to  change  the  format.  The  quick  and 
dirty  (and,  we  thought,  temporary) 
solution  was  to  display  additional 


Dr.  Dobb’s  Journal,  November  1985 


49 

829 


lines  of  a  Fieldscroll  on  succeeding 
blank  lines  that  did  not  have  Field- 
scrolls  on  them.  We  thought  that  af¬ 
ter  we  had  used  the  product  for  a 
while  we  would  get  a  better  feeling 
for  the  problems  involved  and  design 
a  much  better  mechanism  for  speci¬ 
fying  complex  formats. 

We  ended  up  keeping  the  original 
solution  (although  we  did  polish  it  up 
a  bit).  The  basic  interface  is  simple. 
All  the  user  really  has  to  do  to  format 
a  rack  is  to  move  Fieldscrolls  around. 
If  you  put  several  on  a  line,  they  be¬ 
come  narrow  columns;  if  you  put  only 
one  on  a  line,  it  becomes  wide.  You 
can  insert  blank  lines  to  make  them 
as  long  or  as  short  as  you  want.  The 
display  is  updated  immediately. 

Most  importantly,  virtually  all  re¬ 
quests  that  a  user  can  make  are  legal 
and  are  transformed  without  loss  of 
data  into  visible  results  that  can  be 
examined  and  modified.  Any  format 
can  display  any  information  because 
there  are  no  limitations  on  type  or 
size.  As  a  result,  several  sources  of 
error  are  eliminated.  Specifically: 

1.  Cutting  a  card  from  one  rack  and 
pasting  it  into  another  is  always  a  vis¬ 
ible  operation  with  no  source  of  error 
even  though  the  formats  of  the  racks 
might  be  radically  different. 

2.  Practically  (but  not  strictly)  speak¬ 
ing,  there  is  no  such  thing  as  an  im¬ 
properly  formatted  external  ASCII 
file. 

3.  The  only  important  format  opera¬ 
tion  is  Move  (move  this  Fieldscroll  to 
there).  It  is  always  allowed  and  has 
no  special  cases.  For  example,  a  text 
field  can  be  on  a  line  with  other 
fields.  Other  less  important  opera¬ 
tions  are:  insert  Fieldscroll,  cut  Field- 
scroll,  split  line,  join  line,  and  edit 
Fieldscroll  label. 

When  you  use  Fieldscrolls,  a  single 
phone  number  might  appear  in  a  sev¬ 
eral  line  text  field,  or  the  text  of  a 
memo  might  be  crammed  into  a 
phone  number  field,  but  these  are  not 
errors.  No  information  is  lost.  The 
user  can  see  the  results  and  fix  his 
problem  just  by  changing  his  format. 
People  find  error  messages  annoying. 
They  puncture  the  illusion  of  reality. 
We  have  attempted  to  make  Zoom- 


racks  as  flexible  as  possible  to  reduce 
the  number  of  error  messages  with 
which  the  user  must  deal. 

Of  Mice  and  Menus 

At  the  bottom  of  the  hierarchy  of 
things  with  which  the  designer  of  an 
effective  user-interface  must  be  con¬ 
cerned  are  what  I  call  technologies 
for  communication.  These  are  the 
hardware  and  software  technologies 
that  help  you  communicate  with  a 
computer  on  a  technical  level:  mice, 
voice  recognition,  menus,  icons,  high- 
resolution  graphics,  color,  and  so  on. 
These  technologies  can  help  to  im¬ 
prove  the  presentation  of  an  idea,  but 
can  never  transform  a  bad  idea  into  a 
good  one  or  a  dull  presentation  into 
an  interesting  one.  Good  ideas  that 
are  well-presented  are  much  more 
important  than  flashy  technologies. 

New  technologies  are  merely  tools 
that  a  software  designer  must  manip¬ 
ulate  in  his  attempt  to  communicate 
with  the  user  a  little  better.  They  are 
never  magic  panaceas  that  eliminate 
the  need  to  solve  fundamental  com¬ 
munication  problems.  Technologies 
can  be  seductive — they  can  distract 
the  designer  from  his  real  concerns. 
Making  a  color  film  is  easy.  Making 
a  good  film — whether  it  be  black  and 
white  or  color — is  hard.  The  same  is 
true  of  software  products. 

Presentation  and  Staging 

The  task  I  am  trying  to  achieve 
above  all  else,  is  to  make  you  see. 

D.W.  Griffith 

We  have  seen  that  the  most  impor¬ 
tant  element  in  a  good  user-interface 
is  the  unifying  idea  and  that  the  least 
important  is  the  technology  used  for 
communication.  In  between  these 
two  in  degree  of  importance  is  the 
technique  used  for  presentation.  In 
theater  this  is  staging:  how  the  scen¬ 
ery  is  arranged,  where  the  players 
stand,  and  how  they  face  each  other 
and  the  audience.  It  is  central  to  how 
an  audience  sees  a  play.  Before  the 
time  of  D.W.  Griffith,  people  made 
movies  by  showing  one  continuous 
long-range  shot  of  the  action.  To  in¬ 
troduce  a  variety  of  perspectives  into 
his  presentations  and  to  get  us  to  see, 
Griffith  invented  the  language  of 


movies  that  we  know  today.  He  add¬ 
ed  close-up,  medium-range,  and  pan¬ 
orama  shots  and  edited  them  into  a 
montage  that  controlled  what  his  au¬ 
dience  saw. 

We  need  to  find  ways  to  add  a  vari¬ 
ety  of  perspectives  to  our  software 
products,  just  as  Griffith  did  with 
movies.  Currently,  the  main  presen¬ 
tation  mechanism  is  windows. 
Though  many  graphic  systems  give 
you  the  capability  to  zoom  in  and  out, 
windows  in  text-oriented  systems 
generally  have  the  same  unchanging 
depth  that  movies  had  before  Grif¬ 
fith.  If  early  movies  provided  the  un¬ 
varying  remoteness  of  the  long  shot, 
windows  generally  provide  the  un¬ 
varying  intimacy  of  the  close-up. 

I  want  to  reach  that  state  of  conden¬ 
sation  of  sensations  that  constitutes 
a  picture. 

Henri  Matisse 

Zoomracks  lets  you  have  several 
racks  on  the  screen  at  once.  If  you 
have  a  rack  of  appointments,  another 
of  names  and  addresses,  and  a  third 
of  notes,  you  might  want  to  display 
them  all  at  the  same  time.  If  windows 
are  used  to  display  files,  you  do  see 
detail.  However,  when  a  window  gets 
smaller  or  is  hidden  by  other  win¬ 
dows,  you  lose  the  overall  view. 
Smart  Zooms  allow  you  either  to  ob¬ 
tain  a  panoramic  view  of  several 
racks  of  information  or  to  zoom  in  on 
any  one  of  them  to  see  its  details. 

Zoomracks  lets  you  have  up  to  ten 
racks  on  the  screen  at  once,  each  of 
which  is  displayed  with  a  Smart 
Zoom.  If  there  are  eight  racks  on  the 
screen,  then  each  of  them  is  com¬ 
pressed  to  '/s  of  the  screen  width.  Es¬ 
sential  information  is  distilled  from 
the  cards  on  a  rack  and  displayed. 
Vowels  are  deleted  and  words  and 
fields  are  truncated  to  fit.  You  will 
find  this  works  quite  well  when  three 
or  four  racks  are  shown  in  80  col¬ 
umns;  it  is  useful  even  when  all  ten 
racks  are  on.  You  get  a  visual  over¬ 
view  of  the  information  in  each  rack 
and  your  imagination  fills  in  missing 
details.  The  characters  that  are  pres¬ 
ent  suggest  the  full  text — especially  if 
you  are  already  familiar  with  it. 

The  interface  with  the  Zoomracks 


54 

830 


Dr.  Dobb’s  Journal,  November  1985 


display  consists  of  just  three 
commands: 

1.  Typing  any  digit  from  0  to  9  will 
turn  on  the  corresponding  rack  (add 
it  to  the  working  set  of  racks  dis¬ 
played  in  multiple  rack  mode)  and 
cause  that  rack  to  become  the  current 
rack  (to  be  highlighted  and  be  the  ob¬ 
ject  of  any  subsequent  commands). 

2.  The  Zoom  command  toggles  be¬ 
tween  a  panoramic  display  of  all  the 
racks  that  are  turned  on  and  a  screen¬ 
sized  display  of  the  current  rack. 

3.  One  last  command  turns  off  the 
current  rack  (removes  it  from  the 
working  set  of  racks). 

Just  as  all  the  commands  work  the 
same  in  single-card  and  multicard 
display  mode,  so  all  commands  work 
identically  in  single-rack  and  multi¬ 
rack  mode. 

Zoomracks  does  not  have  a  work 
line  at  the  bottom  of  the  screen. 
Rather,  it  positions  the  work  line  over 
the  current  field.  This  is  likely  to  be 
controversial,  because  many  people 
are  accustomed  to  command  lines. 
We  feel,  however,  that  most  users  fo¬ 
cus  their  attention  on  the  field  where 
they  are  working  and  that  something 
that  pops  out  of  a  field  seems  more 
natural  than  something  on  a  line  at 
the  bottom  of  the  screen. 

Toggles  and  Inverses 

The  user-interface  for  Zoomracks  is 
table  driven:  for  every  key  input  there 
is  a  corresponding  name  in  the  com¬ 
mand  menu  and  a  subroutine  that  is 
invoked.  Reconfiguring  this  com¬ 
mand  network  is  a  simple  matter  of 
restructuring  the  table.  Consequent¬ 
ly,  during  development  we  were  able 
to  make  major  changes  in  the  user- 
interface  without  modifying  the  pro¬ 
gram  proper.  This  enabled  us  to  post¬ 
pone  the  creation  of  a  final 
user-interface  until  all  the  program 
functions  were  fully  operational. 
During  this  time  we  used  Zoomracks 
and  carefully  observed  what  we  liked 
and  did  not  like. 

Quite  late  in  the  development  we 
decided  that  the  first  target  computer 
would  be  the  IBM  PC  and  that  func¬ 
tion  keys  should  therefore  be  central 
to  the  user-interface.  Early  in  devel¬ 


opment  we  had  two  toggle  com¬ 
mands,  Zoom  Rack  and  Zoom 
Quickcard,  that  we  liked  very  much. 
We  assigned  these  two  functions  to 
FI  and  F2.  We  assigned  several  other 
functions  to  the  other  function  keys. 
At  this  time  we  wondered  whether  it 
would  be  possible  to  make  all  the 
function  keys  toggle.  The  answer  was 
yes.  As  a  result  of  making  all  the 
?  action  keys  toggle  we  can  tell  the 
user,  “You  can  hit  any  function  key 
at  any  time  and  it  will  do  something 
visible  someplace  on  the  screen.  If 
you  do  not  like  what  it  does,  press  it 
again  and  it  will  undo  what  it  did.” 

Part  of  the  reason  for  doing  this 
was  that  we  realized  that  people  often 
lose  function  key  templates.  We 
thought  it  would  be  nice  to  let  the 
user  hit  any  key  until  he  found  the 
right  one. 

In  an  early  implementation  of 
Zoomracks  we  had  an  exit  command. 
When  we  looked  at  it  closely,  we 
found  it  was  really  three  different 
commands:  accept  input  (as  in  car¬ 
riage  return),  return  up  the  menu 
tree,  and  exit  from  a  major  command 
mode  such  as  Edit  or  Format.  It 
seemed  that  exit  was  a  command  con¬ 
cerned  with  the  mechanics  of  doing 
things  rather  than  with  what  was  be¬ 
ing  done.  We  didn’t  like  this.  There¬ 
fore,  we  eliminated  all  the  exits, 
largely  by  turning  them  into  toggles. 
Both  Format  and  Edit  Format  be¬ 
came  modes  that  could  be  toggled  on 
and  off.  Command  menus  such  as 
Zoom  or  Modify  became  toggles  that 
turned  on  a  series  of  options. 

As  we  were  discovering  the  value 
of  toggles,  we  realized  that  they  were 
just  a  special  type  of  inverse  com¬ 
mand.  Almost  everyone  is  familiar 
with  the  undo  command,  which  can¬ 
cels  the  effect  of  the  last  command 
(or  in  a  few  implementations  the  last 
several  commands).  We  felt  that  ev¬ 
ery  command  could  have  an  inverse 
or  reversing  command.  For  example, 
the  command  TAB  =  next  Fieldscroll 
had  its  inverse  BACKTAB  =  previous 
Fieldscroll.  We  placed  inverse  com¬ 
mands  next  to  each  other  on  the 
menu  and  used  small  symbols  to  iden¬ 
tify  them  as  pairs. 

What  is  important  is  the  feeling 
that  users  get  when  using  toggles  and 


inverses.  Because  things  are  revers¬ 
ible,  they  aren’t  worried  about  mak¬ 
ing  a  mistake.  Just  as  important  is  the 
fact  that  toggles  make  them  more  ad¬ 
venturous;  they  know  if  something 
does  not  work,  they  can  get  out  of  it. 

From  our  experience  with  Zoom- 
racks  in  Alpha  and  Beta  test  we  have 
learned  the  following: 

1.  Zoomracks  is  good  at  presenting 
information  on  the  screen  in  a  dis¬ 
tilled  form — particularly  when  you 
are  dealing  with  several  racks  of 
information. 

2.  The  commands  for  toggling  be¬ 
tween  single-card  and  multicard 
modes  and  single-rack  and  multi¬ 
rack  modes  allow  you  to  view  your 
information  in  different  ways. 

3.  Zoomracks  provides  a  useful  orga¬ 
nizational  metaphor5.  It  is  a  two-di¬ 
mensional  file  system  that  can  be  ex¬ 
tended  to  store  macros,  spreadsheets, 
communication  cards,  electronic 
mail  files,  and  several  other  kinds  of 
information.  A  Zoomrack  can  even 
be  treated  as  a  relational  database  ta¬ 
ble.  Of  these  potential  capabilities, 
only  macros  are  currently  imple¬ 
mented  in  Zoomracks. 

Zoomracks  lets  you  define  a  rack 
of  macros.  The  first  character  of  the 
first  field  in  each  card  is  used  for  the 
macro  name;  another  field  contains 
its  definition.  Because  a  macro  is  a 
Quickcard  in  a  rack,  the  user  knows 
how  to  view,  copy,  delete,  edit,  and 
comment  macros.  He  only  has  to 
learn  the  commands  to  execute  and 
define  them.  Macros  enhance  the  ba¬ 
sic  power  of  Zoomracks  the  ability 
to  work  with  several  racks  at  once. 
For  example,  a  macro  can  find  and 
display  all  your  appointments,  to-do 
items,  and  notes. 

4.  Users  of  Zoomracks  find  that  they 
end  up  doing  many  operations  in 
multirack  or  multicard  mode.  Opera¬ 
tions  such  as  inserting  a  card,  editing, 
or  changing  a  format  are  done  with¬ 
out  the  mental  effort  of  going  to  sin¬ 
gle-rack  or  single-card  mode. 

5.  As  lap  and  hand-held  computers 
become  smaller  and  more  powerful, 
we  expect  Smart  Zooms  to  become 
particularly  useful  because  they  al¬ 
low  computers  to  make  efficient  use 
of  smaller  displays  and  the  same  user 


Dr.  Dobb's  Journal.  November  1985 


55 

831 


interface  to  be  used  on  desktop  com¬ 
puters  as  well. 

Conclusion 

The  development  of  Zoomracks  was 
not  as  linear  and  smooth  as  this  arti¬ 
cle  might  suggest.  Things  that  are 
clear  and  obvious  now  were  fuzzy  in 
the  early  stages  of  design.  The 
thought  processes  described  here 
were  more  important  in  recognizing, 
selecting,  and  polishing  ideas,  than  in 
generating  them  in  the  first  place. 

/  always  have  two  things  in  my 
head — I  always  have  a  theme  and 
the  form.  The  form  looks  for  the 
theme,  theme  looks  for  the  form,  and 
when  they  come  together,  you’re  able 
to  write. 

W.  H.  Auden 

If  you  look  at  many  of  the  early  mov¬ 
ies  you  will  notice  that  the  actors  use 
exaggerated  gestures.  They  seem 
strange  and  create  a  distance  be¬ 
tween  us  and  what  happens  on  the 
screen.  We  also  see  this  when  tech¬ 
niques  appropriate  for  stage  where 


the  actors  must  be  at  a  distance  from 
their  audience — are  used  in  film. 
Griffith  realized  that  film  was  a  more 
intimate  medium  and  set  about  de¬ 
veloping  the  techniques  that  are  ap¬ 
propriate  to  it. 

A  similar  thing  happened  in  popu¬ 
lar  music  when  Bing  Crosby  recog¬ 
nized  that  the  microphone  opened  up 
the  possibility  for  more  intimate 
communication  with  an  audience. 
The  techniques  of  the  theater,  where 
songs  were  belted  out  to  large  audi¬ 
ences,  were  inappropriate  for  radio 
and  the  phonograph.  Crosby  devel¬ 
oped  new  techniques  to  go  with  the 
new  medium.  As  computers  get 
smaller  and  are  used  in  a  more  per¬ 
sonal  way  by  more  people,  we  have  to 
develop  techniques  of  making  soft¬ 
ware  that  can  reduce  the  psychologi¬ 
cal  distance  between  computers  and 
their  users. 

Zoomracks,  Quickcards,  Field- 
scrolls,  and  Smart  Zooms  are  trade¬ 
marks  of  Quickview  Systems.  Rolo¬ 
dex  is  a  trademark  of  Rolodex 
Corporation. 


Notes 

1  Heckel,  Paul,  The  Elements  of 
Friendly  Software  Design,  Warner 
Books,  1984. 

2  Tufte,  The  Visual  Display  of  Quan¬ 
titative  Information,  Graphics 
Press,  1983. 

3  Finke,  Douglas,  “Major  Trends  in 
Memory”,  Transportable  and  Bat¬ 
tery-power  Personal  Computers 
Proceedings,  Future  Computing, 
1984. 

4  O- Young  Lee,  Smaller  is  Better: 
Japan’s  Mastery  of  the  Miniature, 
Kodansha  Press. 

5  The  idea  of  the  organizational  met¬ 
aphor  is  well  discussed  by  Chuck 
Clanton  in  “The  Future  of  Meta¬ 
phor  in  Man-Computer  Systems” 
in  the  December,  1983  issue  of 
Byte,  pp.  263-280. 


DD| 


Reader  Ballot 

Vote  for  your  favorite  feature/article. 
Circle  Reader  Service  No.  1 93. 


58 

832 


Dr.  Dobb’s  Journal,  November  1985 


As  a  programmer,  you  spend  many 
hours  with  your  editor.  If  it  is  a  good 
one  and  well-suited  to  your  style  of 
editing,  it  can  greatly  speed  your  pro¬ 
duction  of  code.  If  it  is  a  bad  one,  or 
forces  you  to  adopt  an  alien  style,  it 
can  impede  your  work,  spoil  your  out¬ 
look,  and  raise  your  blood  pressure. 

Beyond  the  basic  functions,  it  is  a 
matter  of  opinion  what  features  an 
editor  should  offer.  To  choose  an  edi¬ 
tor  that  is  right  for  you,  you  need  to 
know  both  what  the  different  editors 
offer  and  what  you  yourself  want  your 
editor  to  do.  In  this  review  I  describe 
some  of  the  features,  strengths  and 
weaknesses  of  ten  editors  for  the  IBM 
PC  and  PC  clones.  It  is  up  to  you  to 
select  the  editor  with  the  style,  perfor¬ 
mance,  and  price  that  meet  your 
needs. 

Features 

What  features  might  be  of  impor¬ 
tance  to  the  programmer?  The  tables 
that  accompany  this  review  list  and 
evaluate  many  of  the  specifications 
and  features  of  the  various  editors. 
They  are  by  no  means  comprehen¬ 
sive,  but  they  do  cover  some  impor¬ 
tant  aspects: 

Size  (see  Table  1,  page  62) 

If  you  are  working  on  a  machine  with 
limited  memory,  such  as  the  PCjr,  you 
need  an  editor  that  fits.  Similarly,  if 
you  use  floppy  diskettes,  you  probably 
want  your  editor  to  be  compact,  so 
that  it  can  be  placed  on  the  same  dis¬ 
kette  as  your  compiler  and  linker.  If 
you  work  with  both  CP/M-80  and 
MSDOS,  you  may  prefer  an  editor 
available  for  both  eight-bit  and  six- 


Mark  U.  Edwards,  Department  of 
History,  University  Hall,  Purdue 
University,  West  Lafayette,  Indiana 
47907. 


teen-bit  machines.  If  you  work  with 
large  files,  you  may  wish  to  choose  an 
editor  with  virtual  memory. 

Documentation  and  Help  (see 
Table  2,  page  62) 

You  may  not  care  whether  an  editor 
comes  with  a  tutorial.  On  the  other 
hand,  a  manual  that  does  not  have  a 
good  index  or  fails  to  group  topics 
conveniently  will  cause  you  to  lose  a 
significant  amount  of  time  when 
searching  for  a  particular  piece  of 
information. 

Editing  Commands  (see  Table  3, 
page  62) 

With  some  editors  you  perform  most 
fundamental  tasks  while  in  edit 
mode:  you  move  your  cursor  around 
the  screen,  insert,  delete,  and  so  on, 
with  sequences  of  control,  alt,  and 
function  keys.  Other  editors  add  a 
command  mode.  This  is  used  to  exe¬ 
cute  more  complicated  commands 
and  even  macro  language  programs. 
Of  the  other  editing  functions  noted 
in  the  table,  the  ability  to  reconfigure 
the  keyboard  is  particularly  impor¬ 
tant.  You  may  not  like  the  way  the 
editing  commands  are  assigned  to 
keys.  If  the  keyboard  can  be  reconfi¬ 
gured,  you  can  change  the  assign¬ 
ments  to  reflect,  say,  your  preference 
for  WordStar’s  keyboard  assign¬ 
ments.  If  the  keyboard  cannot  be  re¬ 
configured,  either  you  must  employ  a 
keyboard  enhancer,  like  SuperKey  or 
ProKey,  (assuming  that  the  enhancer 
is  compatible  with  your  editor  and 
does  not  steal  space  you  need  for 
files)  or  you  must  adapt  to  the  config¬ 
uration  of  the  editor. 

Search  and  Replace  (see  Table  4, 
page  66) 

Here  there  are  many  choices.  Do  you 
need  an  editor  that  can  search  from 


the  cursor  backward  toward  the  be¬ 
ginning  as  well  as  forward  toward  the 
end  of  the  file?  Is  the  ability  to  search 
for  a  string  as  you  type  it,  one  charac¬ 
ter  at  a  time,  an  essential  feature,  or 
just  a  nice  extra?  Wild  cards  in  a 
search  allow  you  to  locate  general 
textual  patterns  as  well  as  specific 
strings.  Regular  expressions,  familiar 
to  users  of  the  Unix  utility  grep1,  al¬ 
low  the  user  to  specify  practically  any 
pattern,  including  alternate  patterns. 
You  can,  for  example,  search  for  all 
occurrences  of  “int”  or  “long  int”  in 
one  search.  Furthermore,  if  the  editor 
supports  regular  expressions  in 
search-and-replace  operations,  you 
have  a  powerful  translating  tool.  This 
enables  you,  for  example,  to  replace 
all  Pascal  if-then  statements  with  the 
equivalent  C  construct.  In  Table  16 
(page  74)  you  can  compare  the  vari¬ 
ous  wild  cards  or  regular  expressions 
offered  by  the  different  editors. 

File  and  Window  Management 
(see  Table  5,  page  66) 

Most,  but  not  all  of  these  editors,  al¬ 
low  you  to  edit  files  larger  than  RAM. 
Some  do  this  automatically,  through 
some  virtual  memory  scheme.  Others 
require  (or,  if  you  see  this  as  an  ad¬ 
vantage,  allow)  you  to  page  a  large 
file  manually.  It  can  be  extremely 
helpful  to  be  able  to  edit  one  file 
while  looking  at,  and  excerpting 
pieces  from,  another.  You  must  con¬ 
sider,  however,  how  many  files  you 
need  to  be  working  on,  and  looking 
at,  simultaneously. 

Text  Formatting  Commands  (see 
Table  6,  page  66) 

For  the  most  part,  a  programmer’s 
editor  need  not  be  a  full-featured 
word  processor.  Even  so,  some  text 
formatting  capability  can  be  useful 
on  occasion,  for  example,  for  adding 


Dr.  Dobb’s  Journal,  November  1985 


60 

833 


comments  to  code.  One  formatting 
command  that  is  especially  valuable 
for  producing  well-structured  code  is 
the  ability  automatically  or  manually 
to  vary  the  line  indentation. 

Printing  (see  Table  7,  page  66) 

Do  you  need  the  ability  to  print  while 
editing?  Most  editors  allow  you  to 
print  all  or  part  of  the  active  file. 
Some  can  print  one  file  while  you  edit 
another. 

Undo  (see  Table  8,  page  68) 

Some  editors  allow  you  to  undo  dele¬ 
tions.  Some  also  allow  you  to  undo 
commands  other  than  deletions.  Obvi¬ 
ously,  this  can  be  a  useful  feature.  If 
you  can  undo  everything  you  do,  you 
are  protected  from  most  mistakes. 
Such  a  capability  can,  however,  eat  up 
memory  and  slow  performance. 

Keystroke  Macros  (see  Table  9, 
page  68) 

If  you  have  ever  used  one  of  the  key¬ 
board  enhancers,  such  as  ProKey  or 
SuperKey,  you  are  familiar  with  key¬ 
stroke  macros:  a  series  of  keystrokes 
is  assigned  to  a  single  key;  then,  when 
that  key  is  pressed,  the  keyboard  en¬ 
hancer  plays  back  the  keystrokes  as  if 
you  were  typing  them  in  yourself. 
Several  of  the  editors  have  this  or  a 
similar  capability  built-in. 

Macro  Language  (see  Table  10, 
page  69) 

Keystroke  macros  can  take  you  only 
so  far.  To  automate  complicated  edit¬ 
ing  tasks,  such  as  complex  transla¬ 
tions  or  the  automatic  balancing  of 
opening  and  closing  parentheses  in  C 
or  Pascal,  you  need  a  macro  language 
that  includes  full  conditional  branch¬ 
ing  and  the  ability  to  set,  manipulate, 
and  evaluate  variables.  Actually,  a 
macro  language  can  be  used  to  ex¬ 
tend  the  capabilities  of  an  editor  in 
whatever  direction  you  choose.  In  ef¬ 
fect,  with  a  good  macro  language  you 
can  create  your  own,  customized  edi¬ 
tor.  This  does  mean,  however,  that 
you  have  to  master  another  program¬ 
ming  language.  The  macro  languages 
incorporated  in  the  editors  under  re¬ 
view  are  based  on  one  of  two  models: 
some  resemble  variants  of  Lisp  and 
normally  must  be  “compiled,”  or  at 

61 

834 


least  “tokenized,”  before  use;  others 
resemble  Digital  Equipment  Corpo¬ 
ration’s  TECO  and  are  made  up  of 
short  one  or  two  letter  commands 
that  can  be  created  and  executed  on 
the  fly.  In  my  opinion,  the  Lisp-like 
style  is  easier  to  learn  and  to  use,  at 
least  for  complex  macros.  Yet,  pro¬ 
grammers  who  are  familiar  with  the 
terse  commands  of  a  TECO-like  edi¬ 
tor  can  make  real  gains  in  speed  of 
creation,  at  least  for  simpler  macros. 
To  help  you  decide  which  kind  of  lan¬ 
guage  is  best-suited  to  your  needs,  I 
have  provided  sample  programs  using 
the  different  macro  languages  (see 
Listing,  page  83). 

Subprocesses  (see  Table  11, 
page  69) 

MS  DOS  2.0  and  above  supports  an 
exec  function,  that  is,  it  allows  one 
program  to  execute  another  program, 
even  command.com.  An  editor  that 
takes  advantage  of  this  capability  al¬ 
lows  the  user  to  run  a  compiler,  linker 
or  the  MSDOS  built-in  commands,  ei¬ 
ther  from  the  editor  itself  or  while  the 


editor  is  still  resident  in  memory.  Of 
course,  there  must  be  enough  memory 
left  in  your  system  for  the  second 
program. 

Error  Handling  (see  Table  12, 
page  69) 

At  the  very  least,  you  should  expect 
your  editor  not  to  fail  if  you  leave 
your  drive  door  open.  Also,  if  you  at¬ 
tempt  to  quit  the  editor  without  sav¬ 
ing  your  file,  the  editor  should  warn 
you  that  you  will  lose  all  your  modifi¬ 
cations  if  you  proceed.  This  is  error 
handling  at  a  fairly  elementary  level, 
but  there  is  some  variation  among  the 
editors  on  this  score. 

Benchmarks  (see  Table  13,  page 
70) 

Speed  is  not  everything.  Still,  if  you 
have  to  wait  too  long  for  routine  edit¬ 
ing  tasks  to  be  completed,  you  may 
become  frustrated  and  more  likely  to 
commit  errors.  To  test  speed  of  exe¬ 
cution  I  chose  several  tasks  that  a 
programmer  might  well  undertake  in 
a  session.  If  your  compiler  says  that 
there  is  an  error  on  line  436  of  your 
file,  you  want  to  get  to  that  line  as 
quickly  as  possible.  So,  using  a  dual 
drive  Zenith  161  running  MSDOS 
2.1,  I  timed  how  long  it  took  the  edi¬ 
tors  to  load  a  28K  text  file  and  jump 
to  line  436.  I  also  counted  the  number 
of  keystokes  that  were  needed  to  ac¬ 
complish  this  task.  Then,  I  timed  how 
long  the  editor  took  to  write  the  file  to 
disk  with  a  backup.  I  also  created  a 
five  hundred  line  file  that  simulated 
an  assembly  language  program  with 
comments  on  every  line.  Comments 
were  preceded  by  text,  spaces,  and 
tabs  in  various  combinations.  To  test 
an  editor’s  search  and  replace  com¬ 
mand,  1  replaced  the  five  hundred 
semi-colons  with  “REM.”  Next,  after 
restoring  the  semi-colons,  I  had  the 
program  change  all  the  assembly  lan¬ 
guage  comments 

;<comment> 
to  C-style  comments 

/*<comment>*/ 

For  some  editors,  this  required  writ¬ 
ing  a  keystroke  macro  or  a  macro  lan- 

Dr.  Dobb’s  Journal,  November  1985 


BRIEF 

EC 

EDIX 

EMACS 

Epsilon 

MIX 

Pmate 

VEDIT  + 

XTC 

XyWrite 

1.3 

2.1 

4.02 

0.8 

2.03 

1.2 

3.37 

2.01 

3.0 

II  plus 

A.  size  of  program 

73K 

60K 

81K 

237K 

79K 

47K 

29K3 

26K 

100K 

72K 

B.  "full  system"  size 

1 70K 

271K 

106K 

488K 

11 8K 

86K 

29K3 

51K 

122K 

106K 

C.  minimum  memory 

192K 

128K 

192K 

384K 

1 92K 

128K 

64K3 

64K 

1 92K 

E.  DOS 

2.0 

2.0 

2.0 

2.0 

2.0 

2.04 

2.04 

2.04 

2.0 

2.0 

F.  file  size  limit 

disk 

memory 

250K 

memory 

disk 

disk1 

disk 

disk 

disk1 

disk 

G.  maximum  line  length 

512 

IK 

255 

avail. 

memory 

media 

limited 

255 

250 

1000 

2552 

255 

Notes: 

1  Must  manually  page  larger  files.  Cannot  page  backwards. 

2  Set  at  startup. 

3  This  is  the  basic  model.  In  Version  4.0  there  are  three  other  models:  Menu  model  is  33K,  Fortran  model  32K,  and  C  model  55K.  The 

C  model  requires  at  least  1 28K. 

4  Also  has  a  CP/M  version. 

Table  1 

Size 


( 


BRIEF 

EC 

EDIX 

EMACS 

Epsilon 

MIX 

Pmate 

VEDIT + 

XTC 

XyWrite 

A. pages 

223 

361 

164 

258 

65 

98 

196 

302 

91 

518 

B.  table  of  contents 

Y 

Y 

Y 

Y 

Y 

Y 

Y 

Y 

Y 

Y 

C.  index 

fair2 

fair1 

good 

Y 

fair2 

fair2 

none 

fair3 

good 

good 

D.  reference  card 

Y 

N 

N 

N 

N 

N 

N 

N 

N 

N 

E.  on-line  help 

excellent  excellent 

good 

fair 

fair 

fair 

fair 

fair 

fair 

fair 

F.  tutorial 

43  pp. 

on-line 

on-line 

N 

on-line 

N 

N 

37  pp. 

N 

157  pp. 

Notes: 

’  The  current  manual  (8-1-85)  is  atrocious,  but  the  on-line  help  and  tutorial  are  so  good  that  a  manual  is  hardly  necessary! 

2  Largely  a  list  of  commands  with  little  or  no  cross  references  or  topical  entries. 

3  Good  index  for  VEDIT  manual,  no  index  for  VEDIT  PLUS. 

Table  2 

Documentation  and  Help 


BRIEF 

EC 

EDIX 

EMACS 

Epsilon 

MIX 

Pmate 

VEDIT  + 

XTC 

XyWrite 

A.  modes 

Edit 

Edit 

Edit 

Edit 

Edit 

Cmmd. 

Cmmd. 

Cmmd. 

Cmmd. 

Cmmd. 

&  Edit 

&  Edit 

&  Edit 

&  Edit 

&  Edit 

B.  cursor  movement 

full 

full 

full 

full 

full 

full 

full 

full 

full 

full 

C.  deletion 

full 

full 

full 

full 

full 

full 

full 

full 

partial1 

full 

D.  block  movement 

full 

full 

partial2 

full 

full 

full 

full 

full 

partial1 

full 

E.  reconfigure  keyboard 

Y 

partial3 

Y 

Y 

Y 

Y 

Y 

Y 

N 

Y 

F.  extensible 

8 

4 

5 

8 

5 

B 

7 

6 

7 

7 

Notes: 

'  Deletion  and  movement  of  blocks  is  done  by  whole  lines. 

2  Block  copies  and  block  moves  leave  newline  characters  before  and  after  the  inserted  block. 

3  Single  control  keys  and  twenty  function  keys  can  be  assigned  macros. 

4  Keyboard  macros  that  can  be  assigned  to  a  select  number  of  keys. 

5  Keyboard  macros  that  can  be  assigned  to  any  key. 

6  Macro  programs  that  can  be  executed  from  the  command  line. 

7  Macro  programs  that  can  be  executed  from  the  command  line  or  assigned  to  a  select  number  of  keys. 

8  Keyboard  and  macro  programs  that  can  be  assigned  to  any  key  or  executed  from  the  command  line. 

Table  3 

Cursor  Movement,  Inserting,  Deleting 


Dr.  Dobb's  Journal,  November  1985 


62 

fins 


BRIEF 

EC 

EDIX 

EMACS 

Epsilon 

MIX 

Pmate 

VEDIT  + 

XTC 

XyWrite 

A.  search  backwards 

V 

N 

5 

Y 

Y 

N 

Y 

N 

N 

Y 

B.  incremental  search 

Y 

N 

N 

Y 

Y 

N 

N 

N 

Y 

N 

C.  query  replace 

Y 

Y 

Y 

Y 

Y 

Y 

Y 

Y 

Y 

Y 

D.  wild  cards 

Y 

partial’ 

Y 

Y 

N 

N 

Y 

Y 

Y 

Y 

E.  regular  expressions 

Y 

N 

Y 

Y 

N 

N 

N 

partial2 

N 

N 

F.  wild  cards  in 

Y 

partial’ 

partial6 

Y 

partial3 

N 

N 

N 

partial4 

N 

replacements 

G.  undo  replacements? 

Y 

N 

Y 

N 

N 

N 

N 

N 

N 

Notes: 

’  Has  one  wild  card  to  match  (and  replace)  a  single  character. 

2  Limited  alternate  pattern  matching  (A  or  B  or  C  etc.). 

3  Can  do  a  recursive  edit  upon  finding  an  entry. 

4  Can  place  macros  in  the  replace  text. 

6  Searches  only  forwards  but  automatically  wraps  around  to  beginning  of  buffer  and  continues  back  to  where  it  started. 
6  Only  one  wild  card  that  can  substitute  for  everything  found  in  the  search  pattern. 

Table  4 

Searching  and  Replacing 


BRIEF 

EC 

EDIX 

EMACS 

Epsilon 

MIX 

Pmate 

VEDIT  + 

XTC 

XyWrite 

A.  number  of  buffers 
or  files 

Disk 

Capacity 

5 

12 

avail. 

memory 

Disk 

Capacity 

2 

11 

37 

11 

2 

B.  number  of  windows 

52 

5 

4 

8 

11 

2 

1 

1 

8 

2 

C.file  merging 

Y 

Y 

Y 

Y 

Y 

Y 

Y 

Y 

Y 

Y 

D.  virtual  memory 

Y 

N 

N 

N 

Y 

N’ 

Y 

Partial2 

N’ 

Y 

E.  Access  to  DOS  file 
management 

Y 

Y 

Y 

Y 

Y 

Y 

N3 

Y 

Y 

Y 

F.  optional  backup  Y  Y 

Notes: 

’  Can  page  a  large  file  in  and  out  manually. 

2  Can  page  a  large  file  in  and  out  automatically. 

Y 

Y 

N 

Y 

Y 

Y 

N 

Y 

3  Available  in  version  4.0. 

Table  5 

File  and  Window  Management 


BRIEF 

EC 

EDIX 

EMACS 

Epsilon 

MIX 

Pmate 

VEDIT  + 

XTC 

XyWrite 

A.  tab  setting 

variable 

fixed 

variable 

fixed 

fixed 

variable 

variable 

variable 

fixed 

variable 

B.  margin  setting 

Y 

Y 

Y 

Y 

Y 

Y 

Y 

Y 

Y 

Y 

C.  centering 

Y 

Y 

N 

Y 

Y 

Y 

N’ 

N 

Y 

Y 

D.  word  wrap 

Y 

Y 

Y 

Y 

Y 

Y 

Y 

Y 

Y 

Y 

E.  indenting 

auto 

auto 

auto 

Y 

manual 

manual 

manual 

manual 

manual 

Notes: 

1  Manual  gives  a  macro  that  could  be  placed  on  one  of  the  select  macro  keys. 


Table  6 

Text  Formating  Commands 


BRIEF 

EC 

EDIX 

EMACS 

Epsilon 

MIX 

Pmate 

VEDIT  + 

XTC 

XyWrite 

A.  print  active  file 

Y 

Y 

Y 

Y’ 

Y» 

Y 

Y 

Y 

Y 

Y 

B.  print  while  editing 

N 

Y 

N 

N 

N 

N 

N 

N 

Y 

Y 

Notes: 

'  Must  open  a  PRN  file  and  write  to  it. 


Table  7 
Printing 


66 

83fi 


Dr.  Dobb's  Journal,  November  1985 


A.  undo  deletions 

BRIEF 

EC 

EDIX 

EMACS 

Epsilon 

MIX 

Pmate 

VEDIT  + 

XTC 

XyWrite 

1 .  character 

Y 

N 

N 

Y 

N 

partial2 

N 

partial2 

N 

N 

2.  word 

Y 

partial1 

N 

Y 

Y 

partial2 

Y 

partial2 

N 

Y 

3.  line 

Y 

partial1 

partial3 

Y 

Y 

partial2 

Y 

partial2 

Y 

Y 

4.  block 

Y 

partial1 

partial3 

Y 

Y 

N 

Y 

Y 

Y 

Y 

B,  undo  other 

Y 

N 

N 

Y 

N 

N 

N 

N 

N 

N 

commands 


Notes: 

'  Only  if  using  Alt-D  command. 

2  Only  if  cursor  has  not  been  moved  from  the  line. 

3  Only  with  certain  commands. 

Table  8 

Undo 


BRIEF 

EC 

EDIX 

EMACS 

A.  "on  the  fly" 

Y1 

Y2 

N 

Y 

B.  save  and  restore 

N 

Y 

Y 

Y 

C.  pause  for  user 

N 

N 

N 

Y 

□.templates 

Y 

Y 

Y 

Y 

Notes: 

1  Only  one  "on-the-fly"  macro. 

2  For  twenty  function  keys. 


Epsilon 

MIX 

Pmate 

VEDIT  + 

XTC 

XyWrite 

Y 

Y 

N 

N 

Y 

N 

Y 

Y 

N 

N 

Y 

Y 

Y 

N 

N 

N 

Y 

Y 

Y 

Y 

N 

N 

Y 

Y 

Table  9 

Keystroke  Macros  (built-in  keyboard  enhancer) 


Dr.  Dobb's  Journal,  November  1985 


68 

837 


BRIEF  EC 

EDIX  EMACS 

Epsilon  MIX 

Pmate 

VEDIT+ 

XTC 

XyWrite 

A.  model 

Lisp  none 

none  Lisp 

none’  none 

Teco 

Teco 

Teco 

Teco2 

B.  full  conditionals 

Y 

Y 

Y 

Y 

Y 

Y 

C.  "on  the  fly" 

N3 

N3 

Y 

Y 

Y 

Y 

D.  ease  of  use  for 

fair 

fair 

good 

good 

good 

fair4 

simple  macro 

E.  speed  of  creation 

fair 

fair 

good 

good 

good 

fair4 

for  simple  macro 

F.  ease  of  use  for 

excellent 

excellent 

fair 

fair 

poor6 

poor4 

complex  macro 

G.  speed  of  creation 

good 

good 

fair 

fair 

poor4 

poor4 

for  complex  macro 

Notes: 

1  The  Beta  version  I  saw  had  a  C-like  macro  language  with  full  conditionals.  I  did  not  test  it  extensively. 

2  Not  a  complete  macro  language.  Limited  documentation  and  no  telephone  support. 

3  Can  be  compiled  and  loaded  automatically,  making  them  almost  "on  the  fly." 

4  Some  macros,  especially  complicated  ones,  are  likely  to  be  impossible.  See  note  (1) 

5  Cannot  use  complex  boolean  expressions  in  a  single  branch  test. 

Table  10 

Macro  Language 


BRIEF 

EC 

EDIX 

EMACS 

Epsilon 

MIX 

Pmate 

VEDIT  + 

XTC 

XyWrite 

A.  invoke  DOS 

Y 

Y 

N4 

Y 

Y 

Y 

Y3 

N 

Y 

Y 

B.  concurrency 

N 

N 

N 

N 

Y 

N 

N 

N 

N2 

N 

C.  run  compiler 

Y' 

Y 

N4 

Y’ 

Y 

Y 

Y3 

N 

Y 

Y 

E.  Specify  program 

Y 

N 

N 

Y 

N 

N 

N 

N 

N 

N 

memory 


Notes: 

1  Automatic  compilation  and  flagging  of  errors,  if  any. 

2  Can  do  fore-  and  background  processes  within  the  editor. 

3  In  Version  4.0. 

4  The  accompanying  Professional  Writer's  Package  can  be  used  to  invoke  EDIX,  WORDIX,  INDIX,  and  other  programs. 

Table  11 

Subprocesses 


BRIEF  EC 

A.  disk  error  recovery  good  poor’ 

B.  Abandon  edit  confirm  Y  Y 


EDIX  EM  ACS  Epsilon  MIX 

good  poor2  good  poor2 

Y  Y  Y  N 


Pmate  VEDIT+  XTC  Xy Write 

none3  poor2  poor2  fair4 
Y  Y  N  Y 


Notes: 

1  See  capsule  review. 

2  MSDOS  does  the  error  recovery.  Ignore  or  Abort  can  lead  to  loss  of  edit. 

3  Hung  the  machine  completely.  This  was  fixed  in  Version  4,  but  MSDOS  still  did  the  recovery. 

4  Recovered  OK,  but  said  there  was  no  such  file  on  read. 


Table  12 

Error  Handling 


69 

838 


Dr.  Dobb’s  Journal,  November  1985 


guage  program;  with  others,  a  regu¬ 
lar  expression  in  a  replace  did  the  job. 
Next,  after  once  more  restoring  the 
file  to  its  original  condition,  I  had  the 
editor  strip  out  all  the  comments 
along  with  any  blanks  or  tabs  preced¬ 
ing  the  comments.  Finally,  for  those 
editors  with  a  true  macro  language,  I 
wrote  a  simple  program  to  count 
opening  and  closing  curly  braces  and 
tested  the  program  on  my  IBM  PC/ 
AT.  This  last  benchmark  gives  you  a 
rough  idea  of  both  the  speed  (on  an 
AT)  and  the  intelligibility  of  the  dif¬ 
ferent  macro  languages  (see  Listing, 
page  83). 


Miscellaneous  Features  (see 
Table  14,  page  72) 

I  also  tested  whether  the  editor 
worked  with  Borland’s  SideKick  and 
SuperKey.  Here  I  assumed  that  the 
results  could  be  generalized  to  other 
memory  resident  productivity  tools.  I 
have  also  noted  whether  IBM’s  ex¬ 


tended  ASCII  character  set  can  be  ac¬ 
cessed  and  displayed.  Finally,  since 
several  editors  include  a  built-in  cal¬ 
culator,  I  have  added  that  to  my  list. 

Overall  Evaluation  (see  Table  1 5, 
page  72) 

The  last  table  consists  of  my  own  sub¬ 
jective  evaluation  of  these  various 
specifications  and  functions.  I  explain 
some  of  my  judgments  in  the  capsule 
reviews  of  each  editor  (see  below). 
One  might  expect  that  the  price  of  an 
editor  (both  list  and  after  discount,  if 
it  can  be  purchased  at  discount) 
should  bear  some  relation  to  its  fea¬ 
tures,  but  that  is  not  always  the  case. 

Editor-specific  Comments 

In  each  of  the  following  capsule  re¬ 
views  (see  Table  17,  page  83,  for  spe¬ 
cific  product  information)  I  focus  on 
the  particular  strengths  and  weak¬ 
nesses  of  a  given  editor  along  with  se¬ 
lected  special  features.  Together  with 
the  tables  and  the  listing,  these  cap¬ 


sule  reviews  should  give  you  a  sense 
of  what  an  editor  is  like. 

One  note  about  the  versions  re¬ 
viewed.  The  tables  display  the  speci¬ 
fications,  features  and  performance 
of  the  production  version  of  the  edi¬ 
tor  that  was  available  on  August  1, 
1985.  Several  of  the  editors  had  sig¬ 
nificant  enhancements  in  the  works, 
and  I  was  able  to  see  Beta  test  ver¬ 
sions  of  the  enhanced  editors.  In  such 
cases  I  have  added  a  paragraph  on 
the  enhancements  to  my  capsule  re¬ 
view.  The  tables,  however,  show  the 
figures  for  the  production  version 
rather  than  the  Beta  version. 

BRIEF  (Solution  Systems) 

BRIEF’s  built-in  commands  are  easy 
to  use  and  to  learn.  Still,  if  you  do  not 
like  a  particular  key  assignment  or 
the  effect  of  a  command,  you  can 
change  it.  The  keyboard  is  fully  re- 
configurable  and  any  macro  program 
written  in  the  powerful  Lisp-like 
macro  language  can  be  assigned  to 


BRIEF 

EC 

EDIX 

EMACS 

Epsilon 

MIX 

Pmate 

VEDIT  + 

XTC 

XyWrite 

A.  loading  itself  and 
test  file  and  move 
to  line  436  (seconds) 

29 

23 

32 

83 

29 

44' 

16 

28 

86 

35'° 

(keystrokes) 

5 

5 

5 

5 

5 

8 

6 

8 

9 

7 

B.  writing  test  file 

and  backup  (seconds) 

30 

2 

18 

24 

9 

i 

23 

30 

233 

7 

C.  simple  search  and 
replace  (seconds) 

28 

7 

1074 

16 

23 

49 

38 

17 

1084 

185 

D.  change  ASM 
comments  to  C 
(seconds) 

47 

2447 

6899 

36 

53 

341s 

65 

125 

1924 

328  4 

E.  stripping  comments 
(seconds) 

30 

3554 

22 

234 

5 

25  min.4 

6 

F.  Scroll  back  5 
screens  (seconds) 

<3 

<2 

<  4 

<  7 

<  3 

<4 

<2 

<2 

<2 

<  3 

G.  Brace  count  macro 
program  (min:sec) 

0:06 

0:11 

4:16 

5:36 

54:00 

Notes: 

1  MIX  loaded  only  about  1 5K  of  test  files. 

2  An  earlier  version  of  EC  did  this  in  8  seconds;  the  current  version  was  unable  to  do  it  at  all.  See  capsule  review. 

3  Does  not  do  an  automatic  backup. 

4  Updates  screen  after  every  operation. 

6  Using  "Change  Invisible"  command.  With  regular  replace,  which  updates  the  screen,  it  took  125  seconds. 

8  I  could  not  create  a  program  that  stripped  comments  from  <space>  <tab>  <comment>  lines. 

7  Done  by  holding  the  function  key  down  until  the  keystroke  buffer  was  filled.  Could  not  search  for  either  tab  or  space  before  the  semi¬ 

colon. 

8  Overflowed  the  keyboard  buffer  and  put  in  extra,  unwanted  comment  delimiters. 

9  This  is  the  time  for  a  "fully  automated"  keyboard  macro.  Using  a  more  simple  macro  and  repeatedly  holding  the  envoking  key  down 

until  the  keyboard  buffer  was  full.  I  did  the  conversion  in  322  seconds.  Updates  the  screen  after  every  operation. 

10  Must  specify  page  and  line  number  on  the  page. 

Table  13 
Benchmarks 


Dr.  Dobb’s  Journal,  November  1985 


70 

839 


any  key  or  invoked  by  name. 

BRIEF  has  an  outstanding  undo  fa¬ 
cility.  The  default  configuration  al¬ 
lows  the  last  30  editing  commands  to 
be  undone.  This  number  can  be 
raised  to  a  maximum  of  300  com¬ 
mands.  Until  you  reach  this  maxi¬ 
mum  or  run  out  of  RAM,  every  com¬ 
mand  you  issue  can  be  undone.  So  if 
you  make  ten  changes  and  then  real¬ 
ize  that  the  first  one  was  an  error,  you 
can  undo  all  the  changes  back  to  the 
mistake.  This  ability  probably  slowed 
BRIEF  down  on  the  benchmarks,  but 
it  meant  that  I  was  able  to  undo  the 
results  of  each  test  with  one  key¬ 


stroke!  Needless  to  emphasize,  this 
facility  can  save  endless  grief. 

BRIEF  has  powerful  search  and 
translate  commands.  These  use  regu¬ 
lar  expressions  and  function  much 
like  the  Unix  grep  utility  (see  Table 
16  for  a  list  of  the  expressions  that 
are  supported).  For  example,  the 
string 

m?l  !de 

would  match  “male”  or  “made”  or 
“mile”  or  “mode,”  and  so  on.  The 
power  of  these  operations  is  most  ap¬ 
parent  when  you  perform  translation 


tasks.  When  you  use  regular  expres¬ 
sions  it  is  easy,  for  example,  to 
change  every  Pascal  if-then  state¬ 
ment  into  its  C  equivalent.  Unfortu¬ 
nately,  BRIEF  cannot  search  for 
strings  that  extend  over  several  lines. 

As  with  several  of  the  other  edi¬ 
tors,  you  can,  given  sufficient  memo¬ 
ry,  create  from  within  BRIEF  a  sub¬ 
process  that  runs  DOS.  Within  this 
subprocess  any  valid  DOS  command 
can  be  executed  and  other  programs 
can  be  run.  BRIEF  comes  with  several 
macro  packages  that  use  this  ability 
to  compile  source  files.  With  one  key¬ 
stroke  you  can  tell  BRIEF  to  write  the 


BRIEF 

EC 

EDIX 

EMACS 

Epsilon 

MIX 

Pmate 

VEDIT  + 

XTC 

XyWrite 

A.  compatibility  with 

Y1 

Y 

Y 

Y 

N2 

Y 

Y 

Y 

Y 

N3 

Sidekick  and 
SuperKey 

B.  IBM  extended  ASCII 

Y 

Y 

Y 

N 

Y 

N 

partial2 

Y 

Y 

Y 

supported 

C.  Built-in  calculator 

N 

Y4 

N 

N 

N 

N 

N 

Y6 

N 

ye 

Notes: 

1  Must  use  -p  flag  on  startup. 

2  Control-Alt,  used  to  invoke  Sidekick,  is  a  prefix  for  some  Epsilon  commands.  Does  not  work  with  SuperKey. 

3  Works  with  Sidekick,  but  not  with  SuperKey. 

4  Has  logical,  arithmetic,  and  shift  functions  as  well  as  ASCII  conversion. 

6  Has  logical  and  arithmetic  functions. 

6  Has  four  arithmetic  functions. 

Table  14 
Other  Features 


BRIEF 

EC 

EDIX 

EMACS 

Epsilon 

MIX 

Pmate 

VEDIT  + 

XTC 

XyWrite 

A.  price 

$195.00 

$49.50 

$195.00 

$495.00  $225.00  $29.95 

$225.00  $225.00  $99.00  $295.00 

B.  discounted? 

Y 

N 

Y 

N 

N 

N 

Y 

Y 

N 

Y 

C.  ease  of  use 

good 

good 

good 

fair 

good 

fair 

fair 

good 

good 

good 

D.  documentation 

good 

good1 

good 

fair 

fair 

fair 

poor 

good 

good 

fair2 

E.  editing  power 

excellent 

good 

good 

excellent  excellent 

fair 

excellent  excellent 

good 

excellent 

F.  formatting  power 

good 

good 

fair6 

good 

good 

good 

good 

good 

good 

excellent 

G.  translating  power 

excellent 

fair 

good 

excellent 

fair 

fair 

good 

good 

good 

good 

H.file  handling 

good 

good 

good 

fair 

good 

fair 

good 

good 

fair 

good 

1.  error  handling  and 
undoing 

excellent 

poor 

good 

good3 

good 

poor 

poor 

fair 

fair 

good 

J.  macro  power 

excellent 

fair 

fair 

excellent 

good 

fair 

good 

good 

good 

fair 

K. subprocesses 

good 

good 

none 

good 

excellent 

none 

none 

none 

good 

good 

L.  speed 

good 

good 

fair 

fair4 

good 

fair 

good 

good 

poor6 

good 

Notes: 

'  On-line  documentation  is  excellent,  current  written  documentation  inadequate. 

2  Documentation  is  good  except  for  the  macro  language. 

3  Error  handling  is  relatively  poor,  but  the  undo  facility  is  excellent. 

4  On  memory  bound  applications,  quite  good.  But  accesses  the  disk  frequently  to  load  various  macros. 
6  With  WORDIX  combination,  it  is,  of  course,  excellent. 

6  Its  macro  language  is  so  slow  as  to  be  unusable  for  even  simple  tasks. 

Table  15 

Summary 


72 

B40 


Dr.  Dobb’s  Journal,  November  1985 


source  code  file  to  disk,  to  invoke  the 
compiler,  and,  if  any  errors  are  gen¬ 
erated,  to  return  to  the  source  file  and 
place  the  cursor  on  the  offending  line 
of  code!  This  same  procedure  is  used 
to  “tokenize”  programs  written  in 
BRIEF’s  Lisp-like  macro  language 
and  load  them  into  BRIEF. 

BRIEF  comes  with  a  library  of  mac¬ 
ros,  including  a  brace  checker,  rou¬ 
tines  for  automatic  indentation  of  C 
code,  incremental  search,  and  key¬ 
stroke  to  ASCII  translation.  I  have 
used  BRIEF’s  macro  language  to  write 
a  programmer’s  calculator,  C  lan¬ 
guage  templates  and  editing  routines, 
disk  utility  routines,  and  a  program  to 
translate  one  formatting  language 
into  another.  I  found  the  language 
easy  to  use  and  more  than  adequate 
for  even  complex  editing  needs. 

With  its  macro  and  help  libraries 
and  its  macro  language  compiler, 
BRIEF  takes  up  half  a  double-sided 
IBM  diskette.  I  have  used  it  exten¬ 
sively  on  my  two-drive  Zenith  with¬ 
out  any  difficulty,  but  it  is  better  suit¬ 
ed  to  a  hard-disk  system. 

EC  (C  Source) 

Except  for  a  macro  language  and  vir¬ 
tual  memory  buffering  of  files,  EC  of¬ 
fers  most  of  the  functions  a  program¬ 
mer  needs  at  a  very  affordable  price. 
If  you  have  sufficient  RAM,  you  can 
work  on  five  files  at  once,  moving 
quickly  and  easily  between  them.  The 
keyboard  is  partially  reconfigurable, 
allowing  you  to  create  keystroke 
macros  for  twenty  function  key  com¬ 
binations  and  the  twenty-six  alpha¬ 
betic  control  keys.  There  is  a  nicely 
implemented  interface  to  DOS  that 
enables  you  (once  again  if  you  have 
sufficient  RAM)  to  run  programs 
from  within  EC.  The  on-line  help  and 
tutorial  (and  associated  menu  sys¬ 
tem)  were  the  best  of  the  bunch. 

In  addition  to  the  standard  editing 
commands  found  on  most  editors,  EC 
offers  a  number  of  unusual  features. 
There  is,  for  example,  a  List  com¬ 
mand  that  searches  for  a  specified 
string  and  then  presents  you  with  a 
list  of  the  lines  containing  the  string 
together  with  the  line  number.  Find, 
Replace,  and  List  can  be  made  to 
work  over  specified  areas  of  marked 
text.  There  is,  however,  no  backward 


Find  or  Replace  and  only  one  wild 
card,  substituting  for  any  single  char¬ 
acter,  is  supported. 

EC  implements  a  calculator  that 
does  arithmetic,  logical,  and  shift  op¬ 
erations  and  also  translates  charac¬ 
ters  into  their  ASCII  codes.  EC  also 
has  routines  for  comparing  files  and 
matching  opening  and  closing  braces, 
parentheses,  and  square  brackets.  If 
you  wish,  EC  will  automatically  in¬ 
dent  and  cancel  indent  after  opening 
and  closing  braces  to  facilitate  the 
production  of  structured  code. 

EC  offers  no  file  buffering  to  disk, 
either  manual  or  automatic,  so  you 
must  have  enough  RAM  for  your 
files.  This  is  even  more  important  if 
you  intend  to  run  other  programs 
from  within  EC. 

Over  the  course  of  this  review  I  was 
sent  at  least  a  half  dozen  updates. 
Each  new  revision  had  additional, 
useful  features  but  also  a  comple¬ 
ment  of  new  bugs  and  quirks.  The  fi¬ 
nal  review  version  was  unable  to  do  a 
backup  and  save  on  my  test  file  or  on 
my  chart  of  features;  I  lost  an  hour’s 
worth  of  work  as  a  result.  The  au¬ 
thors  of  EC  are  available  by  phone, 
to  the  middle  of  another.  You  can, 
however,  write  a  macro  to  eliminate 
this  problem. 

EDIX  is  easy  to  learn  and  to  use.  It 
comes  with  both  on-line  help  and  tu¬ 
torial.  It  is  not  very  fast — probably 
because  it  is  constantly  updating  the 
screen.  For  example,  in  the  search  and 
replace  command  that  I  used  to  strip 
comments  from  my  test  file,  every 
character  deletion  occurred  one  at  a 
time  and  the  screen  was  updated  ac¬ 
cordingly! 

Emacs  (UniPress  Software) 

The  original  Emacs  was  written  by 
Richard  Stallman  at  MIT.  Over  the 
years  it  has  spawned  a  host  of  imita¬ 
tions.  Two  of  the  editors  reviewed  in 
this  article,  BRIEF  and  Epsilon,  are 
spiritual  kin.  The  Emacs  distributed 
by  UniPress  Software  is  based  on 
James  Gosling’s  Unix  version  of 
Emacs.  It  is  most  faithful  to  the  full 
glory  (and  complexity)  of  the  original 
Emacs  that  I  used  for  years  on  a 
DEC- 20.  It  has  the  capacity  to  handle 
multiple  files  and  windows,  to  run 
scores  of  macro  programs,  to  define 


and  redefine  keys  and  macros  on  the 
fly,  to  transform  itself  into  a  special¬ 
ized  editor  for  specialized  chores,  in 
short,  to  do  just  about  anything  you 
might  want  an  editor  to  do.  In  order 
to  have  this  much  power,  however, 
you  must  pay  a  price:  disk  accesses 
are  frequent,  a  massive  amount  of 
memory  and  disk  capacity  is  required 
and  the  documentation  is  less  than 
complete.  You  should  also  keep  in 
mind  that  a  couple  of  the  editors  un¬ 
der  review  here  are  almost  as  power¬ 
ful  as  Emacs. 

The  original  Emacs  was  an  exten¬ 
sion  of  the  TECO  editor  and  used  an 
extended  TECO  as  its  macro  lan¬ 
guage.  UniPress’  Emacs  uses  “mock 
lisp”  (MLisp)  instead.  I  personally 
find  Mlisp  much  easier  to  use.  Emacs 
comes  with  a  rich  collection  of  Mlisp 
programs,  including  such  delights  as 
“electric-c”,  which  substantially  auto¬ 
mates  the  structuring  of  C  code,  “ab- 
brev,”  which  allows  you  to  specify  ab¬ 
breviations  for  Emacs  to  expand 
automatically  as  you  type,  and 
“undo”,  which  allows  you  to  undo  the 
last  100  commands  or  1000  charac¬ 
ters,  whichever  is  reached  first.  The 
search  and  replace  commands  can  use 
regular  expressions  that  allow  com¬ 
plex  conditional  searches  and 
translations. 

You  need  at  least  384K  to  run 
Emacs,  and  it  really  isn’t  comfortable 
with  less  than  512K.  The  program, 
which  weighs  in  at  237K  before  allo¬ 
cating  any  of  its  many  buffers,  is  total¬ 
ly  memory  resident  and  has  no  virtual 
memory  management.  The  files  you 
edit  must  fit  into  the  remaining  mem¬ 
ory.  Emacs  also  frequently  accesses 
the  disk  for  various  MLisp  programs 
that  it  needs,  so,  although  you  can  run 
it  on  a  two-drive  floppy  disk  system,  I 
would  not  recommend  it.  It  performs 
fairly  well  on  my  Enhanced  PC-AT. 

The  documentation  I  received  is  in¬ 
complete.  A  number  of  the  macro 
packages  included  with  the  editor 
come  with  little  or  no  explanation  be¬ 
yond  the  comments  in  the  code  files. 
Because  I  had  become  familiar  with 
Emacs  on  the  DEC-20,  I  found  that  I 
could  fill  in  many  of  the  gaps,  but  I 
would  not  want  to  begin  my  exposure 
to  Emacs  with  this  manual.  There  was 
mention  of  the  “Info”  data  base  for 


Dr.  Dobb's  Journal,  November  1985 


73 

841 


on-line  help  and  a  “Teach”  tutorial, 
but  neither  was  included.  This  Emacs 
is  really  closer  to  a  pre-release  or  Beta 
and  eager  and  willing  to  respond  to 
suggestions  and  complaints,  so  I  as¬ 
sume  this  problem  will  be  quickly 
fixed.  If  they  would  settle  down  with 
a  stable  version  and  get  all  the  bugs 
out,  EC  would  be  an  excellent  editor 
for  the  price. 

EDIX  (Emerging  Technology) 


EDIX  is  the  editor  in  Emerging  Tech¬ 
nology’s  Professional  Writer’s  Pack¬ 
age.  The  other  modules  in  this  pack¬ 
age  are  WORDIX,  a  text  formatter, 
SPELLIX,  a  spelling  checker,  and  IN- 
DIX,  an  automatic  indexer.  The  sepa¬ 
ration  of  editor  and  formatter  will  be 
familiar  to  all  who  have  used  the  Unix 
editor  vi  and  text  formatter nrofT.  As  a 
text  formatting  system,  the  EDIX/ 
WORDIX  combination  is  powerful 


and  flexible.  It  has  served  as  the  ker¬ 
nel  of  such  sophisticated  applications 
as  AcademicFont,  a  multilingual  text 
formatter  that  provides  output  in 
Greek,  Russian  and  other  European 
languages  with  non-Roman  charac¬ 
ters.  As  a  stand-alone  programmer’s 
editor  (available  separately),  EDIX 
offers  most  of  the  commands  that  a 
programmer  would  need,  along  with 
some  extras  such  as  regular  expres¬ 
sions  in  searches.  It  does  not  support 
virtual  memory,  although  it  can  use 
all  640K  of  RAM  in  your  machine.  It 
can  handle  twelve  files  simultaneously 
with  four  windows  on  the  screen.  You 
can  change  directories,  see  directory 
listings,  and  erase  files  from  within 
the  editor.  You  can  execute  DOS  in¬ 
ternal  commands  from  within  EDIX, 
but  you  cannot  run  subprocesses  such 
as  a  compiler. 

EDIX  supports  keyboard  macros. 
When  you  define  a  keyboard  macro 
in  EDIX,  you  write  it  into  a  buffer 
using  various  mnemonics  for  the  key¬ 
stroke  commands.  For  example,  to 
assign  to  Alt-Fl  a  macro  that  places 
C-style  comments  around  a  single 
line  and  then  moves  down  a  line,  you 
would  place  in  an  empty  buffer: 

f  104  @aly/*@alz  */@lnd 

and  then  invoke  the  config  command. 
If  you  wished  permanently  to  assign 
this  keystroke  macro  to  the  key  com¬ 
bination  Alt-Fl,  you  could  add  this 
line  to  your  initialization  file  and 
EDIX  would  read  it  in  automatically 
at  startup.  Using  macros  such  as 
these,  you  can  completely  reconfigure 
the  keyboard.  EDIX  will  not,  however, 
support  assignments  involving  multi¬ 
ple  keystrokes  such  as  WordStar’s 
*K“M  move-block  command. 

EDIX  allows  you  to  move  or  copy 
lines,  partial  lines,  and  columnar 
data.  The  latter  feature  is  particular¬ 
ly  nice.  Unfortunately,  if  you  wish  to 
move  or  copy  parts  of  lines,  you  must 
subsequently  remove  the  newline 
characters  that  EDIX  automatically 
places  at  the  beginning  and  end  of 
blocks.  This  may  be  acceptable  when 
using  WORDIX,  which  will  rejustify 
text  lines,  but  is  an  inconvenience  in 
moving  blocks  of  code  such  as  param¬ 
eter  lists  from  the  middle  of  one  line 

Dr.  Dobb's  Journal,  November  1985 


Command 

Function 

? 

Matches  any  single  character. 

• 

Matches  any  number  of  occurrences  of  any  character. 

@ 

Matches  any  number  of  occurrences  of  the  preceding  character  or 
expression. 

1 

Matches  either  the  preceding  or  following  character  or  expression 
(alternative). 

( 

Begins  a  group  of  expressions. 

} 

Ends  a  group  of  expressions. 

\ 

Escape  character;  if  the  following  character  has  a  special  meaning, 
it  is  treated  as  a  normal  character,  and  vice-versa. 

\n 

Matches  a  newline  (carriage-return,  line-feed)  sequence. 

\t 

Matches  a  tab  character. 

\c 

Places  the  cursor  under  the  following  matched  character 

<  or  % 

Matches  the  beginning  of  a  line. 

>  or  $ 

Matches  the  end  of  a  line. 

\# 

Substitute  the  actual  text  matched  by  the  #th  matched  group  of 
expressions  (#  is  a  digit  from  0  to  9). 

[.  .  .] 

Matches  any  one  of  the  characters  between  [  and  ]. 

[-•■■) 

Matches  if  the  character  is  not  any  one  of  the  characters  between 
[~  and]. 

a-b 

Matches  (or  does  not  match)  any  character  from  the  range  a  to  b 
when  within  [.  .  .]  or  [~  .  .  .]  (a  and  b  can  be  any  character). 

Table  16a 

BRIEF's  Regular  Expressions 

Command 

Function 

? 

Matches  any  single  character. 

* 

Matches  any  number  of  occurrences  of  any  character. 

@ 

Matches  any  number  of  occurrences  of  the  preceding  character  or 
expression. 

Alt-N 

Matches  a  newline  (carriage-return,  line-feed)  sequence. 

% 

Matches  the  beginning  of  a  line. 

$ 

Matches  the  end  of  a  line. 

Alt-L 

1  1 

Substitute  the  actual  text  matched  by  the  search  pattern.  This  is 
the  only  special  character  that  can  be  used  in  the  replacement 
string. 

Matches  any  one  of  the  characters  between  [  and  ]. 

r-  ■  i 

Matches  if  the  character  is  not  any  one  of  the  characters  between 
[~  and  ]. 

a  — b 

Matches  (or  does  not  match)  any  character  from  the  range  a  to  b 
when  within  [.  .  .]  or[*.  .  .]  (a  and  b  can  be  any  character). 

Table  16b 

EDIX's  Regular  Expressions 

74 

842 


version,  which  its  version  number 
(0.80)  also  suggests.  UniPress  also 
supplies  a  version  of  its  Emacs  for 
VAX/VMS  and  Unix  systems. 

Epsilon  (Lugaru  Software) 

Epsilon  closely  resembles  Emacs. 
The  one  major  difference  is  that  the 
current  release  does  not  have  a  macro 
language.  It  does,  however,  have  a 
full-featured  keystroke  macro  facili¬ 
ty,  which  can  be  used  to  extend  the 
editor  in  useful  ways.  The  keyboard  is 
completely  reconfigurable,  and  cus¬ 


Dr.  Dobb  s  Journal,  November  1 985 


tom-made  keystroke  macros  can  be 
assigned  to  any  key  or  given  a  name. 
You  can  easily  edit  a  number  of  large 
files  simultaneously  and  have  up  to 
eleven  windows  open  at  once.  Epsilon 
handles  the  virtual  memory  manage¬ 
ment  automatically.  It  offers  several 
types  of  search  command,  including 
incremental  search  both  forward  and 
backward.  It  does  not,  however,  have 
wild  cards.  It  handles  editing  func¬ 
tions  with  more  speed  than  many 
other  editors. 

Epsilon  is  the  only  editor  under  re¬ 


view  that  allows  you  to  continue  edit¬ 
ing  while  running  a  second  program 
concurrently.  Not  all  programs  can 
be  run  in  this  way,  but  most  compil¬ 
ers  and  linkers  will  work  fine.  There 
is  some  degradation  in  editing  speed 
when  the  two  programs  are  running 
simultaneously,  especially  if  the 
background  program  is  accessing  the 
disk  with  any  frequency.  Neverthe¬ 
less,  serious  editing  can  still  be  done. 

I  did  encounter  one  significant  prob¬ 
lem  with  the  concurrent  processing. 
In  the  current  version  there  is  no  way 
to  limit  the  amount  of  memory  that 
Epsilon  uses.  Instead,  Epsilon  takes 
memory  as  it  needs  it  and  does  not 
release  it  when  it  is  no  longer  being 
used.  So,  if  you  edit  a  couple  of  large 
files  and  use,  say,  the  help  facility, 
you  quickly  run  out  of  memory  for 
your  second  process. 

Emacs,  and  hence  Epsilon,  has  an 
especially  rich  set  of  editing  com¬ 
mands.  In  a  few  cases,  however,  I  wish 
that  the  the  folks  at  Lugaru  had  gone 
beyond  the  original  Emacs.  For  exam¬ 
ple,  when  you  mark  a  block  of  text  in 
Emacs,  the  only  way  you  can  see  ex¬ 
actly  what  you  have  marked  is  to 
switch  the  cursor  and  the  invisible 
mark  back  and  forth.  Epsilon  works 
the  same  way.  Other  editors  use  re¬ 
verse  video  or  some  other  visual  means 
to  identify  the  marked  block.  This  is  a 
minor  point,  but  it  illustrates  Epsilon’s 
close  dependence  upon  Emacs. 

I  also  have  had  the  opportunity  to 
see  a  version  of  Epsilon  in  Beta  test 
that  includes  a  C-like  macro  pro¬ 
gramming  language  and  regular  ex¬ 
pressions  in  searches.  I  was  told  that 
the  final  version  would  have  regular 
expressions  in  both  search  and 
search-and-replace  operations.  Using 
the  new  macro  language,  I  wrote  and 
timed  a  routine  to  count  braces.  It 
handled  my  test  file  in  10  seconds. 
Using  regular  expressions  in  a 
search-and-replace,  it  took  131  sec¬ 
onds  to  strip  out  the  comments  in  my 
test  file  of  500  assembly  language 
comments.  You  may  compare  these 
times  with  other  entries  in  Table  1 3, 
remembering  that  the  version  I  ex¬ 
amined  was  in  Beta  test. 

MIX  (MIX  Software) 

MIX  is  the  least  expensive  of  the  edi- 

76 

843 


Command 

Function 

Matches  any  character  except  newline. 

\w 

Matches  any  word  character  as  defined  by  the  syntax  table. 

\W 

Matches  any  non-word  character. 

\b 

Matches  at  a  boundary  between  a  word  and  a  non-word 
character. 

\B 

Matches  anywhere  but  at  a  boundary  between  a  word  and  a  non¬ 
word  character. 

\! 

Matches  the  regular  expression  preceding  or  following  it. 

Y 

Matches  at  the  beginning  of  the  buffer. 

V 

Matches  at  the  end  of  the  buffer. 

\< 

Matches  anywhere  before  dot. 

\> 

Matches  anywhere  after  dot. 

\= 

Matches  at  dot. 

\(.  •  A) 

Matches  what  it  brackets.  Used  with  the  next  command. 

\n 

Matches  a  copy  of  the  string  that  the  bracketed  regular  expres¬ 
sion  beginning  with  the  nth  \(  matched. 

[•  ■  ■] 

Matches  any  one  of  the  characters  between  [  and  ]. 

[-.  .] 

Matches  if  the  character  is  not  any  one  of  the  characters  between 
[*  and  ]. 

• 

Matches  zero  or  more  matches  of  the  regular  expression  that 
precedes  it. 

* 

Matches  the  beginning  of  a  line. 

$ 

Matches  the  end  of  a  line. 

\n 

In  replacement,  causes  the  string  matched  by  the  nth  bracket 
expression  to  be  inserted. 

& 

Causes  the  string  matched  by  the  entire  search  string  to  be 
inserted. 

Table  16c 

EMACS  Regular  Expressions 

Command 

Function 

Control-E 

Match  any  character 

Control-L 

Take  the  next  character  literally.  This  allows  a  wild  card  character 
to  be  searched  for. 

Control-N 

Match  anything  but  the  following  character. 

Control-S 

Matches  either  a  space  or  a  blank. 

Control-W 

Matches  any  word  terminator,  that  is,  any  character  other  than  a 
letter  or  a  digit. 

Table  16d 

Pmate's  Wild  Card  Characters 

tors.  Even  so,  it  comes  with  an  exten-  macros  using  combinations  of  the 
sive  range  of  features:  you  can  split  many  commands  built  into  MIX. 
the  screen  and  edit  two  files  at  once,  Unfortunately,  all  this  power  is 
fully  reconfigure  the  keyboard,  and  handicapped  by  the  small  work  space, 
run  other  programs  as  subprocesses  MIX  requires  you  to  page  larger  files 
from  within  the  editor.  Furthermore,  through  memory  manually.  Once  the 
although  MIX  does  not  provide  a  full  working  buffer  is  full,  you  must  write 
macro  language,  you  can  still  create  lines  out  before  you  can  load  addition- 
relatively  sophisticated  keyboard  al  lines  in.  Other  editors  under  review 


Command 

Function 

:  A 

Matches  any  alphabetic  letter,  upper  or  lower  case. 

:  B 

Matches  a  blank  or  tab. 

:c 

Matches  any  control  character. 

ID 

Matches  any  numeric  digit  (0  through  9). 

:f 

Matches  any  alphanumeric  (letter  or  digit). 

:l 

Matches  a  line  terminator  (carriage-return  linefeed,  form  feed,  or 
end  of  file). 

:  M 

Matches  any  sequence  of  zero  or  more  characters. 

IN 

Matches  any  character  except  the  following  character  or  pattern. 

IPr 

Access  contents  of  text  register  r  as  pattern  set. 

!  Rr 

Access  contents  of  text  register  r  as  search  string. 

IS 

Matches  any  separator  (not  a  letter  or  digit). 

IT 

Matches  selected  separators  (terminators). 

IU 

Matches  any  upper  case  letter. 

IV 

Matches  any  lower  case  letter. 

1  W 

Matches  a  single  or  multiple  spaces  or  tabs. 

1  X 

Matches  any  character. 

1  Y 

Matches  multiple  characters  until  next  pattern  matches. 

Table  16e 

VEDIT  PLUS’S  Wild  Card  Characters 

Command 

Function 

Control-A 

Matches  any  alphabetic  character. 

Control-B 

Matches  a  space  (ASCII  32). 

Control-C 

Matches  a  capital  letter. 

Control-D 

Matches  a  digit. 

Control-L 

Matches  a  lower  case  letter. 

Control-N 

Matches  any  alphanumeric  character  (letter  or  digit). 

Control-W 

Matches  a  space  or  a  tab. 

Table  16f 

XTC's  Wild  Card  Characters 

Command 

Function 

Control-Alt-A 

Matches  any  letter  or  digit. 

Control-Alt-L 

Matches  any  letter. 

Control-Alt-N 

Matches  any  digit. 

Control-Alt-W 

Matches  any  arbitrary  string  of  characters  up  to  80  characters  in 
length. 

Control- Alt-X 

Matches  any  character. 

1 

Table  16g 

Xy Write's  Wild  Card  Characters 

also  work  this  way,  but  MIX  has  a 
particularly  small  buffer  space.  It  ap¬ 
pears  to  be  only  about  15K  in  size. 
Most  commands,  such  as  the  replace 
command,  only  work  over  the  portion 
of  the  file  in  the  buffer.  To  perform  a 
replace  or  to  run  a  macro  over  a  large 
file,  you  must  read  in  a  portion  of  the 
file,  execute  the  commands,  write  out 
that  portion,  read  in  the  next  one,  exe¬ 
cute  the  commands,  and  so  on.  To  go 
back  to  a  portion  already  written  to 
disk,  you  must  write  out  the  whole  file, 
close  it,  and  then  read  it  in  again.  This 
could  be  done  with  a  macro;  neverthe¬ 
less,  it  is  inconvenient. 

It  is  fairly  simple  to  create  key¬ 
board  macros  to  perform  editing 
chores.  The  macros  can  be  assigned  to 
a  key  or  combination  of  keys  or  can  be 
given  a  name  and  executed  from  the 
command  line.  You  can  also  repeat  a 
command  or  macro  a  specified  num¬ 
ber  of  times.  I  used  these  two  features 
to  change  all  the  assembly  language 
comments  in  my  test  file  to  C-style 
comments.  While  doing  this,  I  en¬ 
countered  several  difficulties.  The  re¬ 
peat  command  apparently  works  by 
placing  copies  of  the  command  key- 
stroke(s)  in  the  keyboard  buffer.  This 
works  fine  as  long  as  the  number  of 
repetitions  is  small,  but,  when  that 
number  becomes  large,  you  run  out  of 
memory.  Moreover,  once  the  repeti¬ 
tion  of  a  command  begins,  there  ap¬ 
pears  to  be  no  way  to  abort,  either 
from  within  a  keyboard  macro  or 
from  the  keyboard  itself.  In  the  as- 
sembly-to-C  test,  I  exhausted  memory 
twice  and  then  ended  up  with  a  series 
of  extra  */ characters  at  the  end  of  the 
file.  MIX  updated  the  screen  after  ev¬ 
ery  operation,  which  added  to  the  time 
the  whole  process  took.  MIX  is  also 
available  for  8-bit  CP/M  computers. 

Pmate  (Phoenix  Computer 
Products) 

Pmate  is  both  lean  and  fast.  It  is  an 
assembly  language  program  original¬ 
ly  written  for  CP/M-80  and  machines 
with  no  more  than  64K.  Configured 
for  a  thousand  byte  garbage  stack 
and  a  five  thousand  byte  permanent 
macro  area,  Pmate  comes  to  about 
29K  of  code.  Obviously,  it  can  easily 
fit  inside  even  an  anemic  PCjr! 

Pmate  operates  in  two  modes:  an 


78 

R  AA 


Dr.  Dobh's  Journal,  November  1985 


editing  and  a  command  mode.  Al¬ 
though  the  editing  mode  is  full-fea¬ 
tured,  it  is  in  command  mode  that 
Pmate  shows  its  muscle.  Any  of  the 
more  than  120  commands  available 
in  Pmate’s  macro  language  can  be 
entered  singly  or  as  a  macro  program 
at  Pmate’s  command  line  and  execut¬ 
ed  immediately.  Commands  can  also 
be  entered  into  one  of  10  additional 
text  buffers  and  Pmate  can  be  direct¬ 
ed  to  take  its  commands  from  there. 
These  buffers  can  be  saved  to  disk, 
restored,  and  even  saved  inside 
Pmate’s  permanent  macro  area. 

Pmate  has  a  useful  set  of  wild 
cards  for  searches  (See  Table  1 6)  and 
a  LIFO  stack  that  holds  deletions. 
This  stack  can  be  popped  to  restore 
deleted  items  back  into  the  text.  Ten 
macros  can  be  placed  on  the  ten  func¬ 
tion  keys. 

Pmate’s  macro  language  is  power¬ 
ful,  but  cryptic.  The  language  is  de¬ 
signed  to  be  used  “as  you  go.”  There 
is,  therefore,  a  premium  on  terseness. 
The  shorter  the  commands,  the  more 
quickly  you  can  type  them — provided 
you  remember  them!  Most  of  the 
commands  in  Pmate  are  either  one  or 
two  letters  long.  With  only  two  let¬ 


Brief,  Version  1 .3 
Solution  Systems 
335-D  Washington  St. 

Norwell,  M A  02061 

(617)  659-1571  or  (800)  821-2492 

Price:  $195.00 

EC  Editor,  Version  2.1 
C  Source 
12801  Frost  Rd. 

Kansas  City,  MO  64138 
(816)353-8808 
Price:  $49.50 

Edix,  Version  4.02 

Emerging  Technology  Consultants,  Inc. 

4760  Walnut  Street 

Boulder,  CO  80301 

(303)  447-9495 

Price:  $195.00 

AcademicFont 
University  Microcomputers 
665  Monte  Rosa  Dr.  #9 1 5 
Menlo  Park,  CA  94025 
(415)  854-8845 
Price:  from  $275.00 


ters,  it  is  difficult  to  come  up  with 
meaningful  mnemonics  and,  as  a  re¬ 
sult,  the  commands  are  hard  to  re¬ 
member.  Compounding  the  problem 
is  Pmate’s  documentation,  which  is 
disorganized  and  lacking  an  index. 
Pmate’s  error  recovery  also  leaves 
much  to  be  desired:  it  hung  my  ma¬ 
chine  on  the  disk  door  open  test! 

The  preceding  comments  apply  to 
Version  3.37.  I  also  had  the  opportu¬ 
nity  to  use  a  preliminary  copy  of  Ver¬ 
sion  4.0.  This  new  version  comes  in 
three  configurations:  a  menu  driven 
version,  and  versions  specialized  for 
C  and  Fortran.  Included  with  the 
package  were  programs  that  allow 
multitasking  (Pmate  and  one  other 
program  running  simultaneously) 
and  support  for  the  use  of  a  mouse. 
On-line  help  is  also  available.  The  ex¬ 
tensions  to  Pmate  are  based  on 
Pmate’s  macro  language  and  demon¬ 
strate  its  power.  The  menu  driven 
and  mouse  versions  can  shield  the 
user  somewhat  from  Pmate’s  rather 
arcane  command  language.  The  C 
language  version  adds  a  number  of 
useful  macros  that  can  speed  the  gen¬ 
eration  and  debugging  of  C  code.  I 
have  not  programmed  in  Fortran  for 


Emacs,  Version  0.80 
UniPress  Software,  Inc. 

2025  Lincoln  Highway 
Edison,  NJ  08817 

(201 )  985-8000  or  (800)  222-0550 
Price:  $495.00 

Epsilon,  Version  2.03 
Lugaru  Software,  Ltd. 

P.O.  Box  110037 
5227  Fifth  Ave.,  Ste.  12 
Pittsburgh,  PA  1 5232 
(412)  621-5911 
Price:  $195.00 

MIX  Editor,  Version  1 .2 
MIX  Software 
2116  E.  Arapaho 
Suite  363 

Richardson,  TX  75081 

(214)  783-6001  or  (800)  622-4070 

Price:  $29.95 


Table  17 

Product  Information 


more  than  a  decade  and  do  not  have  a 
Fortran  compiler,  so  I  did  not  try  the 
Fortran  version.  The  other  versions 
worked  as  advertised.  The  supplied 
macro-language  file  for  the  C  rou¬ 
tines  is  uncommented,  and  demon¬ 
strates  how  cryptic  Pmate’s  language 
can  be.  Documentation  was  scant  and 
I  did  encounter  a  few  bugs,  but  my 
overall  impression  was  positive.  Ver¬ 
sion  4.0  also  partially  corrected  the 
bug  in  error  trapping;  it  still  let  DOS 
handle  the  error  and  the  abort  option 
returned  me  to  the  operating  system, 
but  at  least  the  machine  did  not  hang. 

VEDIT  PLUS  (Compuview) 

VEDIT  PLUS  is  an  enhanced  version 
of  VEDIT,  an  assembly  language  pro¬ 
gram  originally  written  for  8-bit  CP/ 
M  machines.  VEDIT  PLUS  adds  to 
VEDIT  expanded  text  registers,  a 
complete  programming  language, 
and  the  ability  to  edit  multiple  files. 
In  theory,  you  can  edit  up  to  thirty- 
seven  files  at  once.  Given  VEDIT 
PLUS’  memory  limitations,  however, 
this  would  not  be  practical,  unless  the 
files  were  very  small. 

VEDIT  PLUS  has  both  an  edit  and 
a  command  mode.  The  commands  in 


Pmate,  Version  3.37 

Phoenix  Computer  Products  Corp. 

1420  Providence  Highway,  Ste.  11 5 

Norwood,  MA  02062 

(617)  762-5030 

Price:  $225.00 

Vedit  Plus,  Version  2.01  and  2.31 

Compuview 

1955  Pauline  Blvd. 

Ann  Arbor,  Ml  48103 
(313)996-1299 
Price:  $225.00 

XTC,  Version  3. OF 
Wendin,  Inc. 

Box  266 

Cheney,  WA  99004 
(509)  235-8088 
Price:  $99.00 

Xywrite  II  Plus,  serial  number  above  1 
million 
Xyquest 
P.O.  Box  372 
Bedford.  MA01730 
(617)275-4439 
Price:  $295.00 


Dr.  Dobb’s  Journal,  November  1985 


80 

845 


the  edit  mode  can  be  assigned  to 
whatever  keys  you  wish,  with  one  im¬ 
portant  limitation:  VEDIT  PLUS  al¬ 
lows  you  to  designate  only  two  prefix 
keys.  Many  editors  use  prefix  keys. 
For  example,  in  the  WordStar  com¬ 
mand  "Q'D,  which  means  go  to  the 
end  of  the  current  line,  "Q  is  a  prefix 
key.  Another  frequently  used  prefix 
key  in  WordStar  is  "K.  If  you  were  to 
reconfigure  your  keyboard  with  VE¬ 
DIT  PLUS  to  resemble  the  WordStar 
keyboard,  you  could  designate  only 
two  prefix  keys,  say,  ‘Q  and  *K.  In 
that  case,  you  could  not  use  the  func¬ 
tion  and  cursor  keys  because  they 
also  require  a  prefix  key. 

VEDIT  PLUS  provides  a  full 
TECO-like  macro  language.  You  can 
use  this  language  to  write  editing  rou¬ 
tines  either  on  the  command  line  or  in 
a  text  register  from  which  the  editor 
can  be  directed  to  take  its  commands. 
These  macro  programs  can  be  edited, 
saved  to  disk,  and  restored  for  later 
use.  In  the  current  version  of  VEDIT 
PLUS  these  macros  cannot,  however, 
be  assigned  to  keys.  VEDIT  PLUS 
also  offers  a  particularly  robust  set  of 
wild  cards  that  approach  the  func¬ 
tionality  of  regular  expressions,  at 
least  for  searches  (See  Table  16). 
You  can  even  search  for  alternate 
patterns;  that  is,  search,  say,  for  “int” 
or  “char”  at  the  same  time.  You  can¬ 
not,  however,  search  backward. 
When  you  combine  the  macro  lan¬ 
guage  with  the  wild  cards,  you  can  do 
sophisticated  translations.  To  give 
you  an  idea  of  the  language’s  poten¬ 
tial,  there  is  a  separate  product  from 
Com pu View,  a  macro  program,  that 
allows  VEDIT  PLUS  to  translate  Z80 
assembly  code  to  8086  code.  With 
VEDIT  PLUS  itself  comes  a  file  com¬ 
parison  macro,  a  mailing  list  sorter, 
and  an  on-line  calculator. 

Like  Pmate’s  macro  language,  VE¬ 
DIT  PLUS’  language  is  terse  and 
somewhat  cryptic.  As  I  mentioned 
before,  it  is  impossible  with  only  one 
or  two  letters  to  give  meaningful 
names  to  as  many  commands  and  in¬ 
ternal  variables  as  a  true  macro  lan¬ 
guage  offers.  VEDIT  PLUS’  macro 
language  is,  fortunately,  reasonably 
well  documented,  although  the  mac¬ 
ro  language  manual  needs  an  index. 

The  current  version  of  VEDIT 


PLUS  still  lacks  a  number  of  features 
found  in  other  editors.  Although  you 
can  use  automatic  disk  buffering  as 
you  move  forward  and  backward  in  a 
large  file,  there  is  no  virtual  memory 
per  se  and  the  total  memory  reserved 
for  all  text  and  macro  buffers  is  a  re¬ 
stricting  64K.  You  cannot  view  two 
files  at  one  time,  but  must  switch  be¬ 
tween  them.  You  also  cannot  run  a 
program  from  within  VEDIT  PLUS, 
and  DOS  2.0  pathnames  are  not  yet 
supported. 

I  also  previewed  a  version  of  VE¬ 
DIT  PLUS  in  Beta  test  that  adds  brief 
on-line  help,  pathname  and  subdirec¬ 
tory  support,  and  reverse  search.  In 
the  Beta  version  each  buffer  can  allo¬ 
cate  up  to  60K  of  RAM,  a  significant 
improvement  over  the  older  version.  I 
am  told  that  additional  enhance¬ 
ments,  including  true  virtual  memory 
management,  are  planned. 

XTC  (Wendin,  Inc.) 

XTC  is  the  only  editor  of  the  nine  to 
have  its  source  code  included!  If  you 
wish,  you  can  can  change  the  code  it¬ 
self  and  recompile  the  editor.  The 
code  is  also  a  fine  source  for  useful 
Pascal,  C,  and  assembler  routines. 

The  editor  itself  is  full-featured 
and  powerful,  boasting  its  own  macro 
language  and  the  ability  to  do  multi¬ 
tasking  within  the  editor  itself.  This 
latter  feature  is  unique  among  the 
nine  editors.  This  is  the  ability  to  run 
a  macro  program  in  the  background 
and  continue  with  editing  in  the  fore¬ 
ground.  In  Version  3.0,  which  is  a 
substantial  upgrade  of  Version  2.0, 
you  can  invoke  DOS  and  run  compil¬ 
ers  and  other  programs  from  within 
the  editor.  This  version  also  allows 
wild  cards  in  search  strings  (see  Ta¬ 
ble  16)  and  auto-indention  for  C  or 
Pascal  programming.  Searches  pro¬ 
ceed  only  forward,  from  the  current 
cursor  location.  The  keyboard  cannot 
be  reconfigured. 

The  editor  is  line-oriented  in  many 
of  its  commands.  For  example,  with 
XTC’s  block  move  commands  you 
move  whole  lines  around  rather  than 
portions  of  lines.  Similarly,  the  Undo 
command  restores  only  deleted  lines 
rather  than  characters  or  words  de¬ 
leted  within  a  line.  This  limitation 
prevents  you  from,  for  example. 


copying  just  the  parameter  list  of  a 
procedure  from  one  location  to  an¬ 
other;  instead  you  must  copy  the 
whole  line. 

The  macro  language  includes  the 
full  range  of  loops  and  conditional 
structures  and  an  extensive  collection 
of  functions  that  allow  you  to  keep 
track  of  the  editor  environment. 
Macros  can  either  be  entered  in  their 
keystroke  form  and  used  immediate¬ 
ly,  or  written  out  in  a  longer  form  and 
compiled  with  a  separate  macro  com¬ 
piler.  They  are  limited,  however,  to 
eighty  keystrokes  each,  so  even  fairly 
simple  macros  must  be  broken  into 
several  pieces.  Macros  can  be  saved 
by  name  or  assigned  to  function  keys. 
In  branching  tests  only  one  Boolean 
operator  is  allowed.  In  other  words, 
you  cannot  evaluate  a  compound 
Boolean  expression  in  one  test.  It  is 
this  limitation,  and  the  fact  that  the 
program  is  constantly  updating  the 
screen  during  macro  programs,  that 
probably  accounts  for  XTC’s  incredi¬ 
bly  slow  performance  on  some  of  the 
benchmarks.  Whereas,  for  example, 
BRIEF  took  6  seconds  to  count  all  the 
curly  braces  in  my  test  file  and  VE¬ 
DIT  PLUS,  which  had  the  next  slow¬ 
est  time,  took  5  minutes  and  36  sec¬ 
onds,  XTC  took  over  54  minutes! 
Until  Wendin  substantially  speeds  up 
its  macro  language,  it  is,  for  all  prac¬ 
tical  purposes,  unusable. 

XyWrite  II  Plus  (Xyquest) 

XyWrite  II  Plus  is  sold  primarily  as  a 
word  processor,  and  as  such  has  gar¬ 
nered  some  outstanding  reviews2.  But 
it  can  also  be  used  successfully  as  a 
programming,  and  programmable, 
editor,  thanks  to  its  reconfigurable 
keyboard,  keystroke  programs,  and 
its  macro  programming  language.  It 
also  allows  you  to  run  a  compiler 
from  within  the  editor. 

Written  in  assembler,  XyWrite  is 
fast  and  compact,  weighing  in  at  only 
72K,  yet  offers  all  the  features  of  a 
powerful  “what  you  see  is  what  you 
get”  (WYSIWYG)  word  processor.  It 
boasts  a  useful  set  of  wild  cards  for 
searches  (see  Table  16).  The  last  de¬ 
letion  can  be  restored,  be  it  word, 
line,  or  block.  To  jump  to  a  particular 
line  in  a  file,  you  must  calculate  the 
page  on  which  the  line  would  reside. 


82 


Dr.  Dobb’s  Journal,  November  1985 


Using  XyWrite’s  extensive  list  of 
functions,  you  can  produce  editing 
programs  with  conditional  branch¬ 
ing.  These  programs  can  be  saved  to 
disk,  reloaded,  and  executed  from 
XyWrite’s  command  line.  Unfortu¬ 
nately,  the  programming  language 
lacks  certain  crucial  commands  (such 
as  a  command  to  determine  the  char¬ 
acter  on  which  the  cursor  rests)  and  is 
scantily  documented.  XyQuest  ex¬ 


plicitly  states  that  it  will  not  provide 
phone  support  for  the  features  of  its 
macro  language. 

XyWrite  takes  over  the  keyboard, 
so  it  cannot  be  run  with  SuperKey  and 
some  other  memory  resident  pro¬ 
grams.  It  will  work  with  SideKick,  if 
you  are  careful  to  follow  certain  rules 
about  opening  and  closing  SideKick’s 
windows. 


Notes 

1  For  an  MSDOS  version  of  grep,  see 
Allen  Holub’s  article  “grep.c:  A 
Unix-like  Generalized  Regular  Ex¬ 
pression  Parser”  in  DDJ ,  October 
1984,  #96. 

2  See,  for  example,  “Xywrite:  Way  to 
Go”  by  Peter  H.  Weil  in  PC  Maga¬ 
zine,  June  12,  1984. 

DD| 


Reviews  Listing  (Text  begins  on  page  60) 

•  *  * 

I 

;**  BRIEF’  —  demonstration  program:  brace  counter 
.  ** 

(macro  bracecnt 

( 

(int  count 

cur_char 

) 

(string  msg_pattern 
msg_text 

) 

(=  msg_pattern  "Excess  {  count  =  %d") 

(top_of_buf fer ) 

(while  (search_fwd  " [ \\ { \\ } ] " )  ?  look  for  {  or  } 

( 

(=  cur_char  (index  "{}"  (read  1)))  ;  determine  which  you  found 

(if  (==  cur_char  1)  ;  IP  cur_char  ==  1  THEN 

(++  count) 

;  ELSE 
( —  count) 

) 

(next_char) 

) 

) 

(sprintf  msg_text  msg_pattern  count) 

(message  msg_text) 

) 

) 


l  ** 

;  **  EMACS  —  demonstration  program:  brace  counter 

.  *  * 

/ 

(defun 

(bracecnt  count 

(beg inning-of -file) 

(while  (I  (eobp) )  ;  while  not  at  buffer  end 

(re-search-forward  ”{\\|}")  ;  find  either  {  or  } 

(if  (=  (preceding-char)  '{■)  ;  IF  ==  {  THEN 

(++  count) 

;  ELSE 


( —  count) 

) 

(forward-character) 

) 

(message  (concat  "Excess  {  count  =  "  count) ) 

) 


•  *  * 
t 

;**  PMATE  —  demonstration  program:  brace  counter 


Dr.  Dobb's  Journal,  November  1985 


83 

847 


Reviews  Listing 


(Listing  continued,  text  begins  on  page  60) 


0V1 

ua 

( 

@CV2 

@t="{ 

[VA1] 

et="j 

( -lVAl ] 
m 

@C=@2 

] 


b2e 

ei\ 


set  variable  1  to  0 
go  to  beginning  of  file 
begin  loop 

put  character  number  in  variable  2 
if  char  ==  { 

increment  variable  1 
if  char  ==  } 

decrement  variable  1 
move  forward  one  character 
until  at  end  of  buffer 
end  loop 

Since  there  is  apparently  no  easy  way  to  display 
the  number  on  the  status  line 
go  to  buffer  2 

insert  the  result  into  buffer  2 


R* 

R*  VEDIT+  —  demonstration  program:  brace  counter 

R* 

B  1  go  to  the  top  of  the  register  1 

0XS1  1  set  numeric  register  1  to  01 

[  1  begin  loop 

1  IP  char  ==  {  THEN  increment  variable  1  1 

(.c  =  123)  [ 
lXAl 
) 

1  IP  char  ==  }  THEN  decrement  variable  1  1 

(.c  =  125) [ 

-lXAl 

] 

c  1  move  a  character  1 

(.c  =  26)JL  1  IF  end  of  file  exit  loop  1 

]  1  end  loop 

8YT/Excess  {  =  /I  type  result  1 

XTl  1  stored  in  numeric  register  1  1 

—  XTC  demonstration  program:  brace  counter 


macro  "braces"  is 

11  :=  0; 
bottom_of_f ile; 

12  :=  LN; 
top_of_f ile; 
BRACES2; 

end  macro; 


—  integer  variable  1  is  set  to  0 

—  number  of  last  line,  used  to  check  if  done 

—  go  to  beginning 

—  call  BRACES2 


macro  "IN_LINE"  is 
goto_column  (1); 
repeat  loop 

if  CC  =  123  then 
il  :=  il  +  1; 
end  if; 

if  CC  =  125  then 
il  :=  il  -  1; 
end  if; 

right_character ; 
until  CN  >  LC 
end  loop; 
end  macro; 


—  must  be  all  caps  to  work 

—  go  to  beginning  of  line 

—  if  current  character  is  { 

—  if  current  character  is  ) 


—  until  past  last  column  with  non-blank  character 


Macro  "BRACES2"  is 

IN_LINE ;  —  do  first  line 

repeat  loop 
down_line; 

IN_LINE ; 
until  i2  =  LN 

end  loop;  —  until  on  bottom  line  of  file 

display_var iable  (il) ;  —  print  out  the  result 

end  macro; 


End  Listing 


84 

OAft 


Dr.  Dobb’s  Journal,  November  1985 


16-BIT  SOFTWARE  TOOLBOX 


by  Ray  Duncan 


The  programs  published  in  this 
month’s  column  are  available  for 
downloading  from  the  Laboratory 
Microsystems  RBBS  at  (213)  306- 
3530  (300  baud  or  1200  bps). 

68000  Square  Roots 

Mike  Morton  of  Boston  writes:  “I 
was  fascinated  by  the  speed  and  ele¬ 
gance  of  Jim  Cathey’s  68000  square 
root  routine  ( DDJ  May  1985).  If  Jim 
is  really  concerned  about  speed  he 
might  consider  these  simple  changes; 
perhaps  your  readers  would  be  inter¬ 
ested  in  them  too.  . .  . 

“1.  Small  change:  the  move.w 
#15,D4  can  be  changed  to  moveq 
#15,D4,  assuming  you  don’t  care 
about  the  upper  half  of  D4  (dbra  is  a 
work-oriented  loop).  This  saves  four 
cycles — big  deal. 

“2.  Never  shift  when  you  can  add: 
change  each  asl.l  #l,Dn  to  add.l 
Dn,Dn.  This  reduces  a  ten-cycle  in¬ 
struction  to  six,  saving  four  cycles. 
There  are  three  such  instructions  in 
each  loop,  each  executed  sixteen 
times.  Total  savings,  4X3X16  = 
192  cycles. 

“3.  Let’s  get  desperate:  surprisingly, 
you  can  trade  in  the  roxl.l  #l,Dn  in¬ 
structions  for  addx.l  Dn,Dn  (I 
haven’t  tested  this).  This  saves  two 
cycles  in  two  different  places  in  the 
loop,  for  a  total  of  64  cycles. 

“The  total  savings  are  260  cycles. 
This  is  an  improvement  of  14%  to 
1 7%  over  Jim’s  times.  Suppose  we  ap¬ 
ply  the  changes  to  the  unrolled  word- 
version  of  the  loop  that  Jim  suggests. 
The  only  change  is  for  technique  3,  in 
which  roxl.w  #  1  ,Dn  turns  into  addx.l 
Dn,Dn  15  times.  Each  of  these  saves 
four,  not  two,  cycles — this  means  the 
savings  are  2X  1 5  cycles  more  than  in 
the  longword  version.  The  total  dif¬ 
ference  from  the  original  word-size 
routine  to  the  new  one  is  290  cycles. 


almost  a  50%  improvement.” 

For  those  who  missed  it,  Jim  Cath¬ 
ey’s  original  code  is  run  in  this  issue 
as  Listing  One  (page  90). 

68000  Binary  Search  & 
PRNG 

Dr.  Michael  P.  McLaughlin  of 
McLean,  Virginia,  writes:  “I  am  sub¬ 
mitting  the  two  following  program¬ 
ming  ‘quickies’  in  response  to  your 
repeated  call  for  68000  code.  I  was  a 
bit  hesitant  to  send  them  in  because  I 
suspected  they  might  be  a  bit  too  triv¬ 
ial  for  your  typical  reader.  Neverthe¬ 
less,  I  have  used  them  extensively  in 
studies  in  machine  learning  and  am 
certain  of  their  usefulness. 

“The  first  [Listing  Two,  page  90] 
is  a  slightly  modified  binary  search 
routine.  The  modification  [consists  in 
the  fact  that  the  search  routine  re¬ 
turns]  the  place  where  the  missing 
item  would  be.  This  enables  one  to 
insert  the  missing  item  and  keep  the 
list  in  order.  A  short  example  of  the 
proper  use  of  the  search  routine  is 
also  included  [Listing  Three,  page 
90], 

“The  second  routine  [Listing  Four, 
page  9 1  ]  was  prompted  by  Dave  Cor- 
tesi’s  discussion  of  pseudo-random 
number  generators  [PRNGs  (see  the 
February  and  October  ’85  issues  of 
DDJ)].  Because  this  is  a  well-studied 
field  there  is  no  reason  to  be  uncer¬ 
tain  of  the  quality  of  the  generator. 
The  one  here  is  statistically  very 
good,  with  a  very  long  period.  It  could 
have  been  written  in  straightforward 
double  precision,  but  that  code,  albeit 
50%  shorter,  requires  more  time  per 
random  deviate.” 

80286  vs  8086 

Ross  P.  Nelson  of  San  Jose,  Califor¬ 
nia,  writes:  “Someone  recently 
showed  me  a  copy  of  your  column  in 


which  the  relative  performance  of  the 
Intel  80286  was  being  discussed  [see 
the  April  ’85  issue  of  DDJ],  As  a  for¬ 
mer  Intel  employee  who  has  worked 
directly  with  the  286  for  some  time,  I 
hope  I  can  clear  up  some  of  the  con¬ 
fusion. 

“Let  me  begin  by  comparing  the 
8088  and  8086.  They  run  at  approxi¬ 
mately  the  same  speed  when  per¬ 
forming  register-to-register  opera¬ 
tions.  When  memory  references  are 
included,  however,  the  8088  requires 
an  extra  four  clocks  for  each  16-bit 
memory  operation.  This  translates  to 
a  20%  to  30%  performance  degrada¬ 
tion  in  a  common  instruction  mix. 

“When  comparing  the  80286  (real 
mode)  to  the  8086  you  see  about  a 
1 50%  performance  increase  in  arith¬ 
metic  register-to-register  operations. 
Memory  reference  performance  var¬ 
ies,  depending  on  how  the  operand  is 
specified.  I’ve  enclosed  a  Table  [page 
89]  that  shows  the  instruction  timing 
for  different  memory  reference  in¬ 
structions.  The  base  time  is  the  time 
to  execute  the  instruction  only,  the 
EA  time  is  the  setup  time  required  to 
compute  the  Effective  Address.  All 
times  are  listed  in  clocks. 

“As  is  shown  in  the  table,  memory 
reference  instructions  on  the  286 
show  a  200%  to  500%  performance 
improvement  when  compared  to  their 
8086  counterparts.  For  an  average 
instruction  mix,  therefore,  it  is  rea¬ 
sonable  to  claim  that  a  286  will  run 
about  twice  as  fast  as  an  8086  operat¬ 
ing  at  the  same  clock  rate  and  about 
2.5  to  3  times  as  fast  as  an  8088. 

“The  standard  instruction  timings 
shown  in  the  table  are  not  affected  by 
placing  the  286  into  Protected  Mode. 
In  Protected  Mode,  however,  the  286 
takes  a  substantial  performance  hit 
every  time  a  segment  register  is  load¬ 
ed.  For  example,  the  instruction 


Dr.  Dobb’s  Journal,  November  1985 


88 

849 


MOV  DS,  AX 

which  requires  2  cycles  on  the  8088, 
8086,  and  80286  in  Real  Mode,  re¬ 
quires  17  cycles  on  the  80286  in  Pro¬ 
tected  Mode.  Similarly,  the  instruc¬ 
tion 

1NT21H 

which  requires  71  cycles  on  the  8088, 
51  cycles  on  the  8086,  and  24  cycles 
on  the  80286  in  Real  Mode,  takes  79 
cycles  on  the  80286  in  Protected 
Mode.  The  1NT  21 H  figures  assume 
an  operating  system  call  traps 
through  a  call  gate  to  a  higher  privi¬ 
lege  level.” 

This  Month's  Filter:  DUMP 

Richard  G.  Markley  of  La  Cres- 
centa,  California,  contributed  the 
MSDOS  filter  that  accompanies  this 
month’s  column  as  Listing  Five  (page 
92).  It  transforms  the  Standard  Input 
stream  into  a  hex  and  ASCII  dump 
and  writes  it  to  the  Standard  Output; 
both  input  and  output  can  be  redi¬ 
rected.  You  can  therefore  use  this  fil¬ 
ter  to  dump  a  file  or  character  device 
input  stream  in  object  format  to  an¬ 
other  file  or  to  a  character  device 
such  as  the  printer.  Here  are  some  ex¬ 
amples: 

DUMP  <file 

This  gives  a  continuous  scrolling 
dump 

DUMP  <file  >PRN 
This  dumps  a  file  on  the  printer 
DUMP  <filel  >file2 
This  dumps  a  file  into  another  file 

Pagination  can  be  achieved  with  the 
MORE  filter  that  is  supplied  with 
MSDOS.  Here  is  an  example: 

DUMP  <file  I  MORE 

Richard  writes,  “MORE.COM  doesn’t 
give  a  consistent  display.  The  first  dis¬ 
play  has  24  lines  of  new  text  and  sub¬ 
sequent  displays  have  23.  This  gives 
an  unbalanced  appearance  when  used 
in  conjunction  with  DUMP.  I  have 
found  the  problem  can  be  solved  by 
this  sequence  of  actions: 

A  >DEBUG  MORE.COM 


Use  DEBUG.COM  to  load  MORE- 
.COM  into  memory. 

-E  IDBOO 

Replace  the  byte  at  offset  1DBH 
with  00H.  This  resets  MORE’s  row 
counter  to  zero  instead  of  1 . 

-W 

Write  the  change  to  disk. 

-Q 

Exit  from  DEBUG. 


“The  above  patch  has  worked  suc¬ 
cessfully  on  MORE.COM  of  DOS 
Versions  2.0  through  3.0.” 


(Listings  begin  on  next  page) 
DD| 

Reader  Ballot 

Vote  for  your  favorite  feature/article. 
Circle  Reader  Service  No.  1 94. 


8086  80286 


Instruction 

Base 

EA 

Total 

Base 

EA 

Total 

MOV  BX,[SI] 

8 

5 

13 

3 

0 

3 

MOV  CX,[1  A22] 

8 

6 

14 

3 

0 

3 

MOV  AX,[BP+4] 

8 

9 

17 

3 

0 

3 

MOV  SI,[BP][DI  +  4] 

8 

11 

19 

3 

1 

4 

ADD  AX,[1  A22] 

9 

6 

15 

7 

0 

7 

SUB  BX,[BP+2] 

9 

9 

18 

7 

0 

7 

JMP  3A0 

15 

0 

15 

8 

0 

8 

JMP  [73EE] 

15 

6 

21 

8 

0 

8 

Figure  1 

Ross  Nelson's  comparison  of  some  instruction  times  on 
the  8086. and  80286. 


16-Bit 


(Text  begins  on  page  88) 


Listing  One 


Integer  Square  Root  (32  to  16  Bit) 
(Exact  method,  not  approximate) 
Call  with: 

DO.L  =  Unsigned  number 
Returns : 

DO.L  =  SQRT  (DO.L) 

Dl.L  <>  0  if  not  exact  root 

Uses : 

D1-D4  as  temporaries  - 

D1  =  Error  term 
D2  *■  Running  estimate 
D3  =  High  bracket 
D4  *  Loop  counter 


Notes : 

Result  first  in  DO.W,  but  is  valid  in  longword. 
Takes  from  1480  to  1832  cycles  (including  RTS). 
(Word  version  is  from  548  to  660  cycles) . 


lsqrt 

move . w 

115, d4 

moveq 

#0,  dl 

moveq 

1 0 ,  d2 

sqrtl 

asl .  1 

#  1 ,  dO 

roxl . 1 

#i,di 

asl .  1 

#  l,dO 

roxl .  1 

#  1 ,  dl 

asl .  1 

#l,d2 

move . 1 

d2,d3 

asl .  1 

f  l,d3 

cmp.  1 

d3  ,  dl 

bis 

sqrt2 

addq. 1 

#  1 ,  d2 

addq. 1 

#1  ,d3 

sub.  1 

d3  ,  dl 

sqrt2 

dbra 

d4 , sqrtl 

movel 

rts 

d2 ,  dO 

Loop  count  (bits-1  of  result) 
Result  in  D1 

Get  2  leading  bits  at  a  time  and 
into  Error  term  for  extrapolation. 
(Classical  method,  easy  in  binary) 

Running  estimate  *  2 


New  error  term  >  2*  running  est.? 

Yes,  we  want  1  bit  then. 

Fix  up  new  error  term. 

Do  all  16  bit-pairs. 

Returns  answer  in  DO.W 

End  Listing  One 


89 


oc  r\ 


Dr.  Dobb’s  Journal.  November  1985 


Listing  Two 


; BINARY  SEARCH  —  (USES  D4-D7,  A6) 

jTO  SEARCH  A  SORTED  ARRAY  OP  SIGNED  LONGWOKDS  BEGINNING  WITH  A  DUMMY  ENTRY 
/ SMALLER  THAN  ANY  POTENTIAL  ENTRY,  INITIALIZE  THE  REGISTERS  AS  FOLLOWS: 

/  A6  -  BASE  ADDRESS  OP  ENTIRE  ARRAY  (LONCWOKD) 

Z  D4  -  TARGET  LONCWORD 

/  D7  ■  LENGTH  OF  ARRAY,  IN  BYTES,  LESS  DUMMY  (LONGWOKD5 

/THE  SEARCH  WILL  RETURN,  IN  D6,  THE  DISPLACEMENT  (NUMBER  OP  BYTES  FROM  BASE) 
/OP  THE  START  OF  THE  TARGET  LONGWORD,  IF  PRESENT,  OR  -DISPLACEMENT  IF  THE 
/TARGET  IS  ABSENT. 


ENTRY 

BINSRCH 

BINSRCH 

CLR.L 

D5 

;U5  “  pointer  to  bottom 

CLR.L 

D6 

Bl 

CMP.  L 

D5.D7 

/bottom  >  top  7 

BMI.S 

FAILURE 

/yes,  exit 

HOVE . L 

D7,D6 

/else  D6  -  (D5+D7)  div  2 

ADD.  I. 

D5,D6 

LSR.L 

11,  D6 

AND.  L 

iOFFPFFFFDH, D6 

/back  to  longword  boundary 

CMP.L 

0  (A6.D6.L) ,D4 

/is  this  it? 

BEQ.S 

SUCCESS 

/yes 

BGT.S 

B2 

;no,  target  is  bigger 

SUDQ.L 

•  4,D6 

/target  is  smaller 

MOV  E . L 

D6,D7 

/try  lower  half 

BRA 

Bl 

B2 

ADDQ.L 

•  4 ,  D6 

/try  upper  half 

MOVE . L 

D6,D5 

BRA 

Bl 

FAILURE 

NEG.L 

D5 

/return  -displacement 

MOVE . L 

D5,D6 

SUCCESS 

RTS 

END 

BINSRCH 

End  Listing  Two 


Listing  Three 


'results  ^nScSPPI,0PBIM'E'-y  LINKED'  THIS  C*U-ING  routine  will  cive  the 


/would  be  entry  II 


EXTERN 

BINSRCH 

LEA 

ARRAY, A6 

MOVE. L 

1-128, D4 

MOVE. L 

128, D7 

BSR 

BINSRCH 

MOVE . L 

1-14, D4 

MOVE. L 

•28, D7 

HSR 

BINSRCH 

MOVE. L 

•492, D4 

MOVE . L 

•28, D7 

BSR 

BINSRCH 

MOVE. L 

•987654321, D4 

MOVE. L 

•28, D7 

BSR 

BINSRCH 

MOVE. L 

•1080000000,04 

MOVE. L 

•28, D7 

BSR 

BINSRCH 

RTS 

DC.  L 

-99999 

DC.L 

-14,27,492,18768, 

END 

EXAMPLE 

Listing  Five 


/returns  -4 
/entry  II 


/returns  4 
/entry  13 


/returns  12 
/entry  17 


/returns  28 
/would  be  entry  18 


/returns  -32 
/dummy 


End  Listing  Three 


Listing  Four 


/PSEUDO-RANDOM  NUMBER  GENERATOR  —  (USES  D3-D7) 

2**31_2>  IN  07  LONCWORD),  THIS  GENERATOR  YIELDS  A  NON 
/REPEATING  SEQUENCE  (RAND ( I ) )  USING  ALL  INTEGERS  IN  THE  RANGE  1  TO  2**31-2 
/THE  AVERAGE  EXECUTION  TIME  IS  342  MICROSECONDS  (AT  8  MHz).  THIS  GENERATOR.’ 
/REFERRED  TO  IN  THE  LITERATURE  AS  'GGUBS,'  IS  KNOWN  TO  POSSESS  GOOD 
/STATISTICS.  THE  ALGORITHM  IS: 
f 

/  RAND ( 1+1 )  ■  (16807* RAND(I) )  MOD  (2**31-1) 

/ 

/WHEN  PROPERLY  CODED,  THIS  ALGORITHM  WILL  TRANSFORM  RAND ( 0 )  -  1  INTO 
>-0(188.)  *522329238.  THE  FOLLOWING  IMPLEMENTATION  USES  SYNTHETIC  DIVISION, 

; 

/  Kl  -  RAND ( I )  DIV  127773 

/  RAND (iel)  -  16887* (RAND (I ) -Kl *127773 ) -Kl *2836 

/  IF  RAND (I el)  <  8  THEN  RAND(I+1)  -  RAND ( 1+1 )  +  2147483647 

; 

/REFERENCE: 

/  BRATLEY,  P. ,  B.L. 

I  (SPRINGER-VERLAG, 


FOX  and  L.E.  SCHRAGE, 
1983) . 


"A  GUIDE  TO  SIMULATION' 


RANDOM 


EXIT 
I  RAND  ( I ) 
DIV 


MOVE. L 

D7,D6 

BSR.S 

DIV 

MOVE. L 

D4,D5 

MULS 

•-2836, D5 

MULU 

142591, D4 

MOVE. L 

D4,D6 

LSL.L 

*1,D4 

ADD.  L 

D6,D4 

SUD.L 

D4,D7 

MOVE 

•  4,  D4 

MOVE. L 

D7,D6 

LSL.L 

•  3 ,  D7 

SUB.L 

D6,D7 

DBRA 

D4 , RAN 1 

ADD.  L 

D5,D7 

BPL.S 

EXIT 

ADD.  L 

RTS 

•2147483647 

ITS)  DIV 

127773  (17  BITS) 

LSL.L 

•  1 ,  D6 

CLR.L 

D4 

MOVE 

•14, D3 

MOVE 

D6,D5 

SWAP 

D6 

AND.  L 

•8FFFFH, D6 

LSL 

•  1  ,D4 

LSL.L 

•  1,D6 

LSL 

11, DS 

BCC.S 

DIV2 

ADDQ.L 

•  1  ,D6 

CMP.L 

•127773, D6 

BMI  .S 

DIV3 

SUB.L 

•127773, D6 

ADDQ 

•  1 ,  D4 

DBRA 

RTS 

D3 , DIV1 

END 

RANDOM 

/copy  RAND(I) 

/divide  D6  by  127773 
/ copy  Kl 
/D5  -  -2836-Kl 
/multiply  D4  by  127773 


/ D7  -  RAND(I ) -11*127773 
/counter 

/multiply  D7  by  16887 


/D7  -  RAND ( I  el ) 


/normalize  negative  result 
/D7  ■  RAND (lei) 


/shift  out  unused  bit 


/quotient 

/counter 

save  low  word  of  RAND ( I ) 

D6  -  RAND ( I )  DIV  2**15 

/line  up  quotient 

/ and  dividend 

/shift  in  bit  of  low  word 


/trial  subtraction 

/real  subtraction 
/put  1  in  quotient 
/decrement  counter  and  loop 


End  Listing  Four 


1 

2 

3 

4 

5 

6 

7 

8 

9 

10 
11 
12 

13 

14 

15 

16 

17 

18 
19 


=  000D 
=  000A 


=  0000 
=  0001 
=  0002 


page  55,132 

title  'DUMP  ---  Hex  and  ASCII  Dump  Filter* 

DUMP  filter  to  transform  the  Standard  Input  stream  into  a 

Hex  and  ASCII  dump,  which  is  written  to  the  Standard  Output. 

Version  1.0  Richard  G.  Markley  July  20,  1985 


If 


equ 

equ 


Odh 

Oah 


;  DOS  2.x  predefined  handles 


Std_ Input 
StdOutput 
Std  Err 


equ 

equ 

equ 


;  DOS  function  numbers 


; ASC 1 1  carriage  return 
; ASCI  I  line  feed 


; Standard  Input  device  or  file 
; Standard  Output  device  or  file 
.•Standard  Error  device  or  file 


20 

=  0030 

Get_Version 

equ 

030h 

;get  current  DOS  version 

21 

a  003  F 

Dev ice_ Input 

equ 

03fh 

;read  from  file  or  device 

22 

a  0040 

Device_Output 

equ 

040h 

; write  to  file  or  device 

23 

24 

a  004C 

Exit 

equ 

04ch 

;exit  with  return  code 

25 

ox 

0000 

code 

segment 

para  public 

'CODE 1 

27 

28 

29 

30 

31 

32 

33 

34 

35 

36 

37 

38 

39 

40 


0100 

0100 

0100  E9  021 F  R 


0103  0D  0A 
0105  OB  [ 


20 


dump 

;  data  area 
Column  Guide 


assume  cs : code, ds: code, es: code, ss: code 

org  lOOh 

proc  far 

jmp  start 


db 

db 


cr,  If 
1 1  dup  ( ' 


Dr.  Dobb’s  Journal,  November  1985 


91 

851 


16-Bit 

(Listing  continued,  text  begins  on  page  88) 

41 

42 

0110 

30  20  20  31  20  20 

db 

•0  12345678 

9  ' 

43 

32  20  20  33  20  20 

44 

34  20  20  35  20  20 

45 

36  20  20  37  20  20 

46 

38  20  20  39  20  20 

47 

012E 

41  20  20  42  20  20 

db 

•A  B  C  D  E  F',cr,lf 

48 

43  20  20  44  20  20 

49 

45  20  20  46  00  0A 

50 

=  003D 

ColGuideSize 

equ 

S-ColurnGuide 

51 

52 

0140 

03  [ 

Data_String 

db 

3  dup  ('  ') 

53 

20 

54 

1 

55 

56 

0143 

30  30  30  30  30  30 

Byte_Counter 

db 

•000000  • 

57 

20  20 

58 

0148 

19  [ 

Hex_String 

dw 

25  dup  (?) 

59 

???? 

60 

1 

61 

62 

017D 

08  [ 

ASCI l_String 

dw 

8  dup  (?) 

63 

???? 

64 

1 

65 

66 

0180 

00  0A 

db 

cr.  If 

67 

=  004  F 

String_Size 

equ 

S-Data  String 

68 

*  0021 

AppxStringLen 

equ 

(LENGTH  Hex  String)+(LENGTH  ASCII  String) 

69 

70 

018F 

0D  0A  00  0A 

New_Lines 

db 

cr,lf,cr,lf  ;two  blank 

l  i  nes 

71 

=  0004 

New_Lines_$ize 

equ 

S-New_tines 

72 

73 

0193 

?? 

L i ne_ Count 

db 

?  ; Count  of 

lines  per  block 

74 

75 

;  error  messages 

76 

77 

0194 

00  OA  44  55  40  50 

Pre_DOS_Error 

db 

cr, If, 'DUMP:  incorrect  DOS 

version' ,cr, If 

78 

3A  20  69  6E  63  6F 

79 

72  72  65  63  74  20 

80 

44  4F  53  20  76  65 

81 

72  73  69  6F  6E  00 

82 

OA 

83 

*  001F 

Pre_DOS_Si ze 

equ 

$-Pre_DOS_Error 

84 

85 

0183 

00  OA  44  55  40  50 

Input_Error 

db 

cr, If, 'DUMP:  input  device  error', cr.  If 

86 

3A  20  69  6E  70  75 

87 

74  20  64  65  76  69 

88 

63  65  20  65  72  72 

89 

6F  72  00  OA 

90 

»  001C 

I nput_S i ze 

equ 

$- Input_Error 

91 

92 

01CF 

00  OA  44  55  4D  50 

E"Pty_Error 

db 

cr,lf,'DUMP:  missing  input 

error' ,cr, If 

93 

3A  20  60  69  73  73 

94 

69  6E  67  20  69  6E 

95 

70  75  74  20  65  72 

96 

72  6F  72  00  OA 

97 

=  001D 

Empty_Size 

equ 

$-Empty_Error 

98 

99 

01EC 

OD  OA  44  55  40  50 

OutputError 

db 

cr, If, 'DUMP:  output  device 

error' ,cr, If 

100 

3A  20  6F  75  74  70 

101 

75  74  20  64  65  76 

102 

69  63  65  20  65  72 

103 

72  6F  72  00  OA 

104 

=  0010 

OutputSize 

equ 

$-Output_Error 

105 


106 

0209 

00  0A  44  55  40  50 

DiskFul l_Error 

db 

cr, If, 'DUMP:  disk  is  full',cr,lf 

107 

3A  20  64  69  73  68 

108 

20  69  73  20  66  75 

109 

6C  6C  00  0A 

110 

=  0016 

Disk_Ful l_Size 

equ 

$-Disk_Ful l_Error 

111 

112 

113 

021 F 

B4  30 

Start: 

mov 

ah, Get  Version  ; check  version  of  DOS 

114 

0221 

CD  21 

int 

2 1  h 

115 

116 

0223 

0A  CO 

or 

al,al  ;is  it  DOS  v.  2.0  or  higher? 

117 

0225 

75  03 

jnz 

Prepare  /yes,  proceed 

118 

0227 

E9  02E0  R 

jmp 

Error_1  ;no,  output  error  message 

119 

120 

022A 

Prepare: 

; prepare  for  processing 

121 

022A 

FC 

cld 

122 

0228 

BO  FFFF 

mov 

bp, *1  ; prevent  output  if  no  input 

123 

022E 

E8  0324  R 

cal  l 

Input  /perform  input  of  data 

124 

125 

0231 

Main_Loop: 

/output  heading 

126 

0231 

89  0030 

mov 

cx,Col_Guide_Si ze 

127 

0234 

BA  0103  R 

mov 

dx, offset  Column_Guide 

128 

170 

0237 

E8  0341  R 

call 

Output 

ICY 

130 

023A 

C6  06  0193  R  08 

mov 

Line  Count, 8  /number  of  lines  per  block 

131 

023F 

EB  OC  90 

jmp 

Do_Block 

132 

133 

0242 

Output_Lines: 

/send  2  blank  lines 

134 

0242 

B9  0004 

mov 

cx , New_L i nes_S i ze 

135 

0245 

BA  018F  R 

mov 

dx, offset  New_Lines 

136 

0248 

E8  0341  R 

call 

Output 

137 

024B 

EB  E4 

jmp 

Main_Loop 

96 


Dr.  Dobb’s  Journal,  November  /  985 


133 

139 

024D 

DoJUock: 

140 

141 

024D 

B8  2020 

142 

0250 

B9  0021 

143 

0253 

BF  014B  R 

144 

0256 

F3/  AB 

145 

146 

147 

0258 

BB  014B  R 

148 

0258 

BA  0010 

149 

025E 

BF  017D  R 

150 

0261 

EB  33  90 

151 

152 

0264 

B9  004 F 

OutputData: 

153 

0267 

BA  0140  R 

154 

026A 

E8  0341  R 

155 

156 

157 

0260 

BF  0147  R 

158 

159 

0270 

89  0005 

160 

161 

0273 

Adjust_Counter: 

162 

0273 

FE  05 

163 

0275 

8A  05 

164 

0277 

3C  39 

165 

0279 

76  13 

166 

027B 

3C  41 

167 

027D 

77  05 

168 

027F 

C6  05  41 

169 

0282 

EB  0A 

170 

171 

0284 

3C  46 

Check_For_F: 

172 

0286 

76  06 

173 

0288 

C6  05  30 

174 

028B 

4F 

175 

028C 

E2  E5 

176 

177 

178 

028E 

FE  0E  0193  R 

Check_Line_Cnt: 

179 

0292 

75  B9 

180 

0294 

EB  AC 

181 

182 

183 

184 

0296 

8A  04 

Do_tine: 

185 

0298 

8A  E8 

186 

029A 

3C  20 

187 

029C 

72  04 

183 

029E 

3C  7E 

189 

02A0 

76  02 

190 

191 

02A2 

B0  2E 

Not_Printable: 

192 

193 

02A4 

AA 

Printable: 

194 

195 

196 

197 

02A5 

B4  02 

198 

02A7 

87  FB 

Swap_Nibbles: 

199 

02A9 

Bl  04 

200 

02AB 

D2  C5 

201 

02  AD 

8A  C5 

202 

02AF 

24  OF 

203 

02B1 

04  30 

204 

02B3 

3C  39 

205 

02B5 

76  02 

206 

02B7 

04  07 

207 

208 

02B9 

AA 

Place_Digit: 

209 

02BA 

FE  CC 

210 

02BC 

75  EB 

211 

212 

213 

02BE 

87  FB 

214 

02C0 

43 

215 

02C1 

46 

216 

217 

218 

02C2 

40 

219 

02C3 

75  03 

220 

02C5 

E8  0324  R 

221 

222 

223 

02C8 

4A 

Check_Item  Cnt 

224 

02C9 

75  CB 

225 

02CB 

EB  97 

226 

227 

228 

229 

02CD 

0B  ED 

Finish_Output: 

230 

02CF 

75  27 

231 

0201 

B9  004 F 

232 

0204 

BA  0140  R 

233 

0207 

E8  0341  R 

234 

235 

236 

02DA 

2A  CO 

.•initialize  part  of  data 
; template  with  spaces 

mov  ax,2020h 

mov  cx,Appx_String_Len 

mov  di, offset  Hex_String 

rep  stosw 

.•initialize  pointers 
/and  convert  data 
mov  bx, offset  Hex_String 

mov  dx.size  ASCII~String 

mov  di, offset  ASCII_String 

jmp  Do_Line 

mov  cx.StringSize  ; write  a  line  to  Std  Output 

mov  dx, offset  Data_String 

call  Output 

;set  pointer  to  sixteen's 
;place  of  byte  counter 
mov  di, off set  Byte_Counter+4 

; number  of  places  in  byte 


mov 

ex. 5 

.•counter  minus  one 

inc 

byte  ptr  [di] 

/increment  input  offset  counter 
;incr  current  number  place  value 

mov 

cmp 

al,  [di] 
al,'9* 

;is  it  9  or  less? 

jbe 

CheckJJne  Cnt 

;yes,  check  no.  of  lines  output 

cmp 

a  l , ' A~ 

;no  is  it  larger  than  A? 

ja 

Check_For_F 

;yes,  jump 

mov 

byte  ptr  [di] , 1 

'A' 

jmp 

short  Check_line_Cnt 

cmp 

al, 'F* 

;is  it  ' F '  or  less? 

jbe 

Check_Line_Cnt 

;yes,  check  number  of  lines  output 

mov 

byte  ptr  [di],1 

'O'  ;no,  make  the  digit  'O' 

dec 

di 

/move  ptr  to  next  higher  place 

loop 

AdjustCounter 

;caryy  into  next  higher  digit 

dec 

L i ne_Count 

/check  number  of  lines  output 
/has  block  been  output? 

jne 

Do_Block 

/no,  output  another  line 

jmp 

Output_lines 

/yes,  output  blank  lines 

mov 

al,  [si] 

/place  ASCII  equivalent  of 

/byte  in  string 

/get  byte  from  buffer 

mov 

ch,al 

/keep  copy 

cmp 

al,'  • 

/control  code? 

jb 

Not  Printable 

/ yes ,  j  ump 

cmp 

al,7-' 

/no,  is  char  a  tilde  or  less? 

jbe 

Printable 

/yes,  use  it 

mov 

al,'.' 

/substitute  '.'  if  not  printable 

stosb 

mov 

ah,  2 

/store  into  ASCII  section  of  output 

/place  hex  equivalent  of  byte 

/ in  output  string 

/number  of  nibbles  in  a  byte 

xchg 

di  ,bx 

/get  hex  section  offset 

mov 

cl, 4 

/size  of  nibble  in  bits 

rol 

ch,cl 

/exchange  nibbles 

mov 

al  ,ch 

/save  copy 

and 

al  ,0fh 

/mask  off  high  4  bits 

add 

al,  '0* 

/convert  to  ASCII  char. 

cmp 

al, '9' 

/is  it  '0-9'? 

jbe 

Place  Digit 

/yes,  store  result 

add 

al  ,7  ” 

/put  it  into  range  • A - F ' 

stosb 

dec 

ah 

/store  into  hex  section  of  output 
/any  more  nibbles? 

jnz 

Swap_Nibbles 

/yes,  convert  again 

xchg 

di  ,bx 

/reposition  pointers 
/get  ASCII  section  offset 

inc 

bx 

/skip  space  in  hex  section 

inc 

si 

/move  input  pointer 

dec 

bp 

/input  data  when  buffer  empty 
/buffer  used  up  yet? 

jnz 

Check_Jtem_Cnt 

/no, check  item  count 

call 

dec 

Input 

dx 

/check  number  of  items  stored 
/store  more  data  in  string? 

jnz 

Do_Line 

/yes,  get  another  byte 

jmp 

Output_Data 

/no,  send  a  line 

or 

bp,  bp 

/output  last  data  string 

/if  necessary 

/was  there  any  input? 

jnz 

Error_3 

/no,  send  error  message 

mov 

cx.StringSize 

/yes  get  size  &  addr.  of  string 

mov 

dx, offset  Data_ 

String 

call 

Output 

/send  the  line 

sub 

f 

al  ,al 

/exit  to  DOS  with  ERRORLEVEL  set 
/return  code  *  0  for  success 

Dr.  Dobb's  Journal,  November  1985 


100 

853 


16-Bit  (Listing  continued,  text  begins  on  page  88) 


Listing  Five 


237 

238 

02DC 

B4 

4C 

Exi t_to_DOS: 

mov 

ah#Exit 

/terminate  with  Al=return  code 

239 

02DE 

CO 

21 

int 

21h 

240 

241 

02E0 

B9 

001 F 

Error_1 : 

mov 

cx,Pre  Dos  Size 

/wrong  DOS  version 

242 

02E3 

BA 

0194  R 

mov 

dx, offset  Pre  Dos  Error 

243 

02E6 

BD 

0001 

mov 

bp,1 

/save  ERRORLEVEL  value 

244 

02E9 

EB 

2E  90 

jmp 

Output_Err_Msg 

/send  error  message 

245 

246 

02EC 

B9 

001C 

Error_2: 

mov 

cx, Input  Size 

/input  device  error 

247 

02EF 

BA 

01B3  R 

mov 

dx, offset  Input  Error 

248 

02F2 

BO 

0002 

mov 

bp,2 

/save  ERRORLEVEL  value 

249 

250 

02F5 

EB 

22  90 

jmp 

Output_Err_Msg 

/send  error  message 

251 

02F8 

B9 

0010 

Error_3: 

mov 

cx, Empty  Size 

/empty  input  stream 

252 

02FB 

BA 

01CF  R 

mov 

dx, offset  Empty  Error 

253 

02FE 

BD 

0003 

mov 

bp, 3 

/save  ERRORLEVEL  value 

254 

255 

256 

0301 

EB 

16  90 

jmp 

Output_Err_Msg 

/send  error  message 

0304 

B9 

0010 

Error_4: 

mov 

cx,Output_Si ze 

/output  device  error 

257 

0307 

BA 

01EC  R 

mov 

dx. off  set  Output 

_Error 

258 

030A 

BO 

0004 

mov 

bp, 4 

/save  ERRORLEVEL  value 

259 

260 

C30D 

EB 

OA  90 

jmp 

Output_Err_Msg 

/send  error  message 

261 

0310 

B9 

0016 

Error_5: 

mov 

cx,Disk_Full_Size  ;output  device  is  full 

262 

0313 

BA 

0209  R 

mov 

dx, offset  Disk  Full  Error 

263 

0316 

BD 

0005 

mov 

bp, 5 

/save  ERRORLEVEL  value 

264 

265 

0319 

Output_Err_Msg: 

/send  message  to  Standard 

266 

/Error  device  &  pass  return 

267 

/code  back  to  DOS 

268 

0319 

B4 

40 

mov 

ah,Device_Output 

269 

03  IB 

BB 

0002 

mov 

bx,Std  Err 

/use  handle  for  Standard  Error 

270 

031E 

CD 

21 

int 

21  h 

271 

0320 

8B 

C5 

mov 

ax, bp 

/recover  return  code 

272 

0322 

EB 

B8 

jmp 

Exit_to_DOS 

/go  terminate 

273 

274 

0324 

Input 

proc 

near 

/get  data  from  Standard  Input 

275 

0324 

53 

push 

bx 

/save  hex  section  offset 

276 

0325 

52 

push 

dx 

/save  item  count 

277 

0326 

64 

3F 

mov 

ah,Device_Input 

278 

0328 

BB 

0000 

mov 

bx,Std  Input 

/use  handle  for  Standard  Input 

279 

032B 

B9 

F000 

mov 

cx, 60* 1024 

/size  of  input  buffer 

280 

032E 

BA 

034  F  R 

mov 

dx, offset  Buffer; address  of  buffer  for  data 

281 

0331 

CD 

21 

int 

21  h 

282 

283 

0333 

0335 

72 

OB 

B7 

CO 

jc 

or 

Error_2 
ax,  ax 

;jimp  if  input  device  error 
/any  input? 

284 

0337 

74 

94 

U 

FinishOutput 

/no,  send  last  string  &  exit 

285 

0339 

BE 

034  F  R 

mov 

si, off set  Buffer 

/set  pointer  to  input 

286 

033C 

88 

E8 

mov 

bp,  ax 

/length  of  input 

287 

033E 

5A 

pop 

dx 

/restore  item  count 

288 

033F 

5B 

pop 

bx 

/restore  hex  section  offset 

289 

0340 

C3 

ret 

290 

0341 

Input 

endp 

291 

292 

0341 

Output 

proc 

near 

/send  string  to  Standard  Output 

293 

0341 

B4 

40 

mov 

ah,Device_Output 

294 

0343 

BB 

0001 

mov 

bx,Std  Output 

/use  handle  for  Standard  Output 

295 

0346 

CD 

21 

int 

21  h 

296 

0348 

72 

BA 

jc 

Error_4 

/jump  if  output  device  error 

297 

034A 

36 

Cl 

cmp 

ax,cx 

/all  requested  bytes  written? 

298 

034C 

75 

C2 

jne 

Error_5 

/jump,  output  device  full 

299 

034E 

C3 

ret 

300 

034  F 

Output 

endp 

301 

302 

=  034  F 

Buffer 

equ 

$ 

/beginning  of  input  buffer 

303 

304 

034  F 

Dump 

endp 

305 

306 

034  F 

Code 

ends 

307 

308 

end 

Dixnp 

End  Listings 


102 


Dr.  Dobb’s  Journal,  November  1985 


CP/M  EXCHANGE 


U.S.  Robotics  Modem  Review 


by  Robert  Blum 


The  CP/M  Exchange  RCP/M  system 
is  available  for  your  use  24  hours  a 
day,  7  days  a  week.  Reach  it  by  dial¬ 
ing  (404)  449-6588. 

Second  only  to  the  microcomputer  it¬ 
self,  the  modem  is  the  most  useful 
piece  of  equipment  brought  to  us  by 
the  electronics  revolution.  Never  be¬ 
fore  has  information  been  so  readily 
available,  or  so  easily  accessed  as  it  is 
today.  From  your  living  room  you  can 
transfer  funds  among  bank  accounts, 
leave  a  purchase  order  to  be  executed 
by  your  broker  the  next  day,  or  gather 
information  from  the  database  on 
your  corporation’s  mainframe  for  that 
last  minute  report. 

Modems  aren’t  new.  They  have 
been  a  vital  part  of  commercial  data 
processing  for  years.  Only  recently, 
however,  has  the  owner  of  a  personal 
computer  been  able  to  afford  one, 
generally,  a  manual  300-baud  mo¬ 
dem.  Today  all  this  has  changed. 
There  are  now  available  numerous 
intelligent  1 200-bps  modems  for  the 
individual  to  choose  from.  As  this  re¬ 
view  is  being  written,  2400-bps  de¬ 
vices  are  being  introduced  that  prom¬ 
ise  better  transmission  quality  at 
twice  the  speed.  Incredible  as  it  may 
seem,  there  are  already  indications 
that  the  next  generation  of  modems 
will  feature  speeds  as  fast  as  9600  bps 
and  cost  little  more  than  the  current 
crop  (see  the  article  “Modems:  2400 
Bit/Sec  and  Beyond”  by  Dale  Walsh 
in  the  June  ’85  issue  of  DDJ). 

Modems  come  in  two  basic  forms: 
stand-alone  and  internal.  Stand¬ 
alone  modems  are  enclosed  in  a  case 
that  is  separate  from  the  computer. 
This  case  contains  only  the  modem 
itself  and  the  power  supply  needed 
for  independent  operation.  Attach¬ 
ment  to  the  computer  is  via  a  serial 
port  and  appropriate  cabling. 


If  an  extra  serial  port  is  not  avail¬ 
able  or  the  system  designer  is  forced 
to  enclose  the  communications  board 
in  the  same  case  as  the  rest  of  the 
system,  then  an  internal  modem  is 
needed.  An  internal,  or  bus-oriented, 
modem  does  not  require  a  serial  port 
for  connection  to  the  computer.  It 
plugs  directly  into  a  system’s  back¬ 
plane  and  responds  across  the  bus 
like  any  other  system  board.  Thus,  it 
provides  both  the  serial  interface  and 
the  modem  functionality. 

There  is  one  distinct  disadvantage 
to  the  bus-oriented  modem:  it  cannot 
be  moved  between  systems  of  differ¬ 
ent  types.  For  example,  if  an  S-100 
machine  is  replaced  by  an  IBM  PC  or 
PC  compatible,  the  S-100  modem 
and  any  driving  software  is  rendered 
useless.  The  choice  between  a  stand¬ 
alone  or  internal  modem  depends 
heavily  on  the  needs  and  preferences 
of  the  user. 

The  S-100  Modem 

Producing  a  bus-oriented  intelligent 
modem  that  maintains  reliable  1200- 
bps  operation  has,  to  my  knowledge, 
been  accomplished  only  by  U.S.  Ro¬ 
botics.  The  U.S.  Robotics  S-100  mo¬ 
dem  is  totally  self-contained  and 
plugs  directly  into  any  slot  of  the  S- 
100  bus.  Processor  services  are  pro¬ 
vided  through  two  dip  switch  selecta¬ 
ble  I/O  port  addresses  that  furnish 
performance  and  software  compati¬ 
bility  with  the  company’s  Password 
line  of  stand-alone  modems. 

The  designer  of  the  U.S.  Robotics 
S-100  modem  divided  the  two  sepa¬ 
rate  logic  sections,  serial  port  and  in¬ 
telligent  modem,  into  two  separate 
physical  sections  on  the  board.  The 
serial  interface,  USART,  and  the  nec¬ 
essary  support  circuits  occupy  the  left 
side  of  the  standard-size  S-100 
board.  The  right  side  is  fitted  with 


several  40-pin  chips  and  a  handful  of 
other  support  devices  that  form  the 
modem  section. 

The  Modem 

The  modem  circuit,  on  the  right  side 
of  the  board,  is  the  functional  equiva¬ 
lent  of  the  U.S.  Robotics  Autodial 
212A  or  Password  modems.  It  is  a 
fully  functional  auto-dial/auto-an- 
swer  modem  that  operates  at  0  to  300 
baud  and  1200  bps  according  to  the 
212A  protocol.  It  consists  of  two  cus¬ 
tom  programmed  8049  8-bit  micro¬ 
processors.  The  first  of  these  inter¬ 
acts  with  a  2921  signal-processing 
microprocessor  element  that  is  re¬ 
sponsible  for  directly  controlling  the 
phone  line  and  the  conversion  of  the 
analog  transmission  data  into  digital 
form.  The  other  handles  the  digital 
interface  with  the  serial  section  as 
well  as  the  execution  and  interpreta¬ 
tion  of  controlling  commands  sent  to, 
or  status  signals  returned  from,  the 
modem  section. 

Commands  are  sent  to  the  modem 
via  the  on-board  serial  interface  in 
the  form  of  upper  case  ASCII  text 
characters,  numbers,  and  special 
symbols.  The  modem  control  com¬ 
mands  are  a  subset  of  the  DC  Hayes 
command  set,  the  de  facto  standard. 

Because  the  S-100  modem  is  com¬ 
mand  driven,  an  escape  sequence  of 
characters  is  provided  to  interrupt 
the  modem  during  data  transmission. 
On  reset  or  power-up  the  default  es¬ 
cape  sequence  is  set  to  +  +  +.  To  en¬ 
sure  that  the  escape  sequence  is 
found  among  other  data  being  trans¬ 
mitted,  it  must  be  bracketed  by  a  pe¬ 
riod  of  at  least  one  second  during 
which  no  data  is  transmitted.  The  es¬ 
cape  sequence  is  more  important 
than  might  at  first  appear:  no  other 
method  of  returning  the  modem  to 
command  mode  is  available  outside 


Dr.  Dobb's  Journal,  November  1985 


103 

855 


A  Answer  mode.  Puts  the  modem  into 
answer  mode  waiting  for  a  ring. 

A/  Reexecute  the  last  command. 

AT  Attention.  This  two  character  se¬ 
quence  must  proceed  all 
commands. 

D  Dial  the  number  that  follows 
P  Dial  in  pulse  mode. 

T  Dial  in  tone  mode. 

,  Delay  two  seconds 

E  Local  echo.  Echo  all  commands. 

EO  Local  echo  off. 

El  Local  echo  on. 

F  Type  of  transmission. 

FO  Half  duplex. 

FI  Full  duplex. 

M  Loudspeaker  control 

MO  Loudspeaker  off. 

Ml  Loudspeaker  on 
until  carrier  lock. 

M2  Loudspeaker  al¬ 
ways  on. 

Q  Result  code  echo. 

QO  Result  messages  sent. 

Q.1  Result  messages 
suppressed. 

S  Set  controlling  register. 

SO  =  n  Answer  on  nth  ring . 

S2  =  n  Set  the  escape 
code  to  n. 

S7  =  n  Length  of  time  to 
wait  for  carrier. 

V  Verbal  or  Numeric  result  mode. 

VO  Numeric  mode. 

VI  Verbal  mode. 

X  Result  code  set. 

XO  Standard  result  codes  sent. 
XI  Extended  result  codes  sent. 

Z  Reset 

Table  1 

S-1 00  Modem  Command  Set 


Result  Codes 

Verbal  Mode  Numeric  Mode 


OK  0 

CONNECT  1 

RING  2 

NO  CARRIER  3 

ERROR  4 

CONNECT  1200  5 

Table  2 

S-1 00  Modem  Result  Codes 


of  dropping  the  DTR  line. 

When  the  escape  sequence  is  seen 
the  S-1 00  modem  immediately  dis¬ 
connects  from  the  phone  line  and  re¬ 
turns  to  command  mode.  This  action 
may  not  be  desirable  when  you  are 
communicating  with  systems  that  do 
not  allow  a  return  to  local  command 
mode  while  remaining  on-line. 

A  complete  list  of  all  the  com¬ 
mands  recognized  by  the  S-1 00  mo¬ 
dem  is  contained  in  Table  1  (at  left). 
The  basic  command  form  consists  of 
the  attention  signal  (AT)  followed  by 
one  or  more  options  and  a  single  car¬ 
riage-return  character.  For  example, 
dialing  a  number  and  waiting  for  a 
carrier  is  accomplished  with  one 
command: 

ATDT5551212 

Here  the  leading  AT  is  the  attention 
sequence  required  of  all  commands. 
The  D  is  the  dial  command  followed 
by  a  T,  which  designates  the  tone  di¬ 
aling  method.  The  rest  of  the  com¬ 
mand  is  the  number  to  be  dialed.  The 
modem  will  dial  the  desired  number 
without  any  further  attention  and 
will  return  the  CONNECT  or  CON¬ 
NECT  1200  result  code  if  a  connec¬ 
tion  is  made.  Otherwise,  the  NO 
CARRIER  result  code  will  be  sent  to 
indicate  that  no  suitable  connection 
was  made  in  17  seconds. 

Acknowledgement  of  commands 
and  the  operating  status  of  the  mo¬ 
dem  are  signaled  by  either  full  text 
messages  or  single  digits  sent  follow¬ 
ing  the  execution  of  commands.  Re¬ 
sult  codes  are  also  sent  to  signal  the 
controlling  software  of  conditions 
such  as  the  phone  ringing  and  the 
presence  or  absence  of  a  carrier.  A 
complete  list  of  the  result  codes  is 
contained  in  Table  2  (at  left). 

The  S-1 00  modem  will  also  auto¬ 
matically  switch  between  originate 
and  answer  modes.  When  answering 
the  telephone,  it  will  automatically 
choose  either  high-  or  low-speed  com¬ 
munications.  Result  codes  are  then 
sent  to  the  controlling  software  or 
terminal  device  to  indicate  whether  a 
high-  or  low-speed  connection  has 
been  established.  The  microcomputer 
can  then  adjust  its  own  rate 
accordingly. 


The  S-1 00  modem  comes  with  a 
speaker  that  reproduces  the  sound  of 
the  dialing  tones  and  provides  infor¬ 
mation  about  progress  towards  a  con¬ 
nection.  Once  communication  has 
been  established,  the  speaker  is  auto¬ 
matically  turned  off  if  the  appropri¬ 
ate  option  has  been  selected.  Al¬ 
though  the  speaker  is  contained  in  the 
case  of  the  computer,  it  is  generally 
loud  enough  to  be  heard. 

The  Serial  Interface 

The  serial  interface,  on  the  left  side  of 
the  board,  substitutes  for  the  usual 
RS-232  connection  between  the  com¬ 
puter  and  the  modem.  The  8251 A 
USART  provides  the  parallel  S-1 00 
bus  interface  and  subsequent  serial¬ 
ization  of  data  and  conversion  of  the 
baud  rate  to  match  that  of  the  mo¬ 
dem  section.  Communication  with 
the  USART  thus  takes  place  through 
two  of  the  commonly  used  I/O  chan¬ 
nels  of  the  8080,  Z80,  and  8086  fam¬ 
ily  of  microprocessors. 

The  8251 A  USART  is  a  program¬ 
mable  serial  communications  device. 
Programming  the  825 1 A  requires  the 
user  to  send  commands  to  the  825 1  A. 
The  8251 A  is  able  to  send  informa¬ 
tion  regarding  the  status  of  the  com¬ 
munications  to  the  microprocessor. 

Installing  the  Modem 

The  first  step  in  using  the  S-1 00  mo¬ 
dem  is  to  install  it  in  the  S-1 00  sys¬ 
tem.  Because  the  S-1 00  modem  uses 
the  I/O  port  system  of  the  micropro¬ 
cessor,  the  user  must  decide  which 
ports  to  dedicate  to  the  modem.  One 
of  the  ports,  the  one  with  the  lowest 
number,  is  used  to  transfer  data  to 
and  from  the  serial  interface.  The 
other  port  is  used  to  address  the 
Mode/Command  and  Status  regis¬ 
ters  of  the  825 1  A.  The  S- 1 00  modem 
is  designed  to  use  the  two  addresses 
at  the  top  or  bottom  of  any  I/O  port 
page.  Thus,  the  modem  may  be  ad¬ 
dressed  as  00,01;  0E,0F;  10,11; 
EE,EF  and  so  on.  This  provides  32 
different  locations  at  which  the  mo¬ 
dem’s  serial  interface  may  be  ad¬ 
dressed. 

Programming  the  S-1  OO  Card 
Modem 

Writing  programs  for  the  S-1 00  mo- 


104 


Dr.  Dobb's  Journal,  November  1985 


dem  requires  some  knowledge  of  the 
8251 A  USART  and  the  command 
structures  of  that  chip.  Reading  and 
writing  data  are  fairly  simple  tasks 
for  the  8251  A.  They  merely  require 
the  use  of  the  IN  and  OUT  commands 
of  the  assembler  along  with  the  ap¬ 
propriate  addresses.  Setting  up  the 
USART  for  communications  requires 
more  skill. 

The  Mode/Command  information 
is  used  to  program  the  USART  for  the 
correct  baud  or  bps  rate  and  word 
length.  Status  information  indicates 
whether  the  USART  is  ready  to  send 
or  receive  information.  Errors  are 
also  indicated  in  the  status  register. 

Listing  One  (at  right)  is  a  program 
fashioned  after  overlays  used  in  the 
popular  public-domain  BYE  or 
MBYE  programs.  It  is  a  comprehen¬ 
sive  example  of  how  to  implement  all 
of  the  S-100  modem’s  features.  A 
much  simpler  example  could  be  writ¬ 
ten  by  following  these  steps: 

1.  Program  the  8251 A  for  the  proper 
communications  mode 

2.  Check  the  status  for  incoming 
characters 

3.  Check  the  status  for  errors 

4.  Get  an  incoming  character 

5.  Process  incoming  character 

6.  Check  the  status  for  send  ready 

7.  Send  a  character  if  status  ready 

Once  the  proper  communications 
settings  have  been  made,  the  pro¬ 
gram  using  the  USART  may  send  or 
receive  information  in  any  order  de¬ 
sired.  The  status  should  always  be 
checked  before  sending  or  receiving 
information. 

Documentation 

The  manual  accompanying  the  S-100 
modem  consists  of  37  pages  printed 
on  one  side.  The  first  20  pages  pro¬ 
vide  information  relevant  only  to  the 
operation  of  the  S-100  modem;  the 
remainder  of  the  manual  is  a  repro¬ 
duction  of  the  8251 A  data  sheets. 
The  documentation  contains  only 
enough  information  to  get  you  start¬ 
ed.  The  examples  don’t  provide 
enough  guidance  on  how  to  interact 
with  the  modem  through  commands 
or  how  to  handle  result  codes.  A  large 
portion  of  the  time  I  took  writing  the 


example  found  in  Listing  One  was 
spent  in  front  of  the  CRT  as  I  used  my 
debugger  to  solve  the  mysteries  sur¬ 
rounding  the  workings  of  the  com¬ 
mand  system. 

User  Impressions 

I  own  two  modems,  the  newest  of 
which  is  the  S-100  modem.  During 
the  past  several  months  I  have  had 
the  unique  opportunity  to  switch  be¬ 
tween  them  when  I  met  a  communi¬ 
cations  problem.  Never  was  I  able  to 
pinpoint  a  situation  in  which  one  per¬ 
formed  better  than  the  other,  al¬ 
though  I  did  find  a  few  peculiarities 
in  the  S-100  modem  that  were  both¬ 
ersome.  One  particularly  aggravating 
problem  was  that  the  S-100  had  a 
nasty  habit  of  answering  the  phone 
when  no  call  had  actually  come  in. 


The  only  other  problem  serious 
enough  to  deserve  mention  here  is 
that,  when  dialing  out  at  300  baud, 
the  originating  carrier  would  be 
brought  up  immediately  without 
waiting  for  the  answer  tone. 

The  Program  Listing 

I  am  placing  this  program  in  the  pub¬ 
lic  domain.  If  you  find  a  problem  or 
add  an  exciting  feature,  let  me  know 
so  I  can  update  the  source  code.  You 
can  download  the  program  from  my 
RCP/M  (404-449-6588)  or  obtain  a 
copy  of  it  in  one  of  a  variety  of  for¬ 
mats  (write  me  care  of  DDJ). 

DD| 

Reader  Ballot 

Vote  tor  your  favorite  feature/article. 

Circle  Reader  Service  No.  1 95. 


CP/M  Exchange  Listing  (Text  begins  on  page  102) 


Thin  routine  is  in  the  form  of  an  overlay  for  the 
popular  MBYE  -  BYE  public  domain  RCP/M  programs. 
Although  not  entirely  compatible  with  those  programs 
only  minor  modifications  would  be  necessary  to  adapt  it 
for  use  with  them.  It  is  written  in  8080  mnemonic  to 
insure  maximum  compatibility  with  multiple  standard 
assemblers  and  CPUs. 


Define  the  equates  for  this  routine 


false : 

equ 

0 

true : 

equ 

not  false 

md_res_ok : 

equ 

•0' 

/result  -  OF 

md_res_con300 : 

equ 

•1' 

/  result  -  CONNECT  (300) 

md_re8_r ing : 

equ 

•2' 

;  result  -  RING 

md_re8_nocar : 

equ 

•3' 

» result  -  NO  CARRIER 

md_res_conl200 : 

equ 

•5' 

/  result  -  CONNECT  (1200) 

md_mhz : 

equ 

60 

;set  to  processor  speed  X  10 

md_cr : 

equ 

0dh 

/carriage  return 

md_J  f : 

equ 

0ah 

/line  feed 

md_eom : 

equ 

080h 

/end  of  message 

md_base_port : 

equ 

00h 

;modem  ports  start  at  zero 

md_data_port : 

equ 

md_base_port 

/data  port  address 

md_stat_port : 

equ 

md_data_port+l 

/status  port  address 

md_tr_rdy : 

equ 

01h 

/transmitter  empty 

md_rcv_rdy : 

equ 

02h 

/receiver  ready 

md_par_er r s 

equ 

08h 

/parity  error 

md_ovf_er r : 

equ 

01 0h 

/overflow  error 

md_f rm_er r i 

equ 

0  20h 

/framing  error 

md_er ror : 

equ 

md_pa  r_e r  r  +md_o v  f _ 

,err+md_f rm_err 

/general  mask  for  all  errors 

md_car rier : 

equ 

08  0h 

/data  carrier 

md_mode_8yn : 

equ 

0 

/mode  byte  for  synchronous 

md_mode_xl s 

equ 

lh 

/mode  for  async.  X  1  clock 

(Continued  on  next  page ) 


Dr.  Dobb’s  Journal,  November  1985 


105 

857 


CP/M  Exchange  Listing 

(Listing  continued,  text  begins  on  page  102) 


md  mode  xl6: 

equ 

2h 

/mode  for  async.  X  16  clock 

md  mode  x64: 

equ 

3h 

/mode  for  async.  X  64  clock 

md  mode  cl5: 

equ 

0h 

/mode  for  character  lnth  of  5 

md  mode  cl  6: 

equ 

4h 

/mode  for  character  lnth  of  6 

md  mode  cl  7: 

equ 

8h 

/mode  for  character  lnth  of  7 

md  mode  cl8: 

equ 

0ch 

/mode  for  character  lnth  of  8 

md  mode  nop: 

equ 

0h 

/mode  for  no  parity 

md  mode_pon: 

equ 

010h 

/mode  for  parity 

md  mode  oddp: 

equ 

0h 

/mode  for  odd  parity 

md  mode  evnp: 

equ 

020h 

/mode  for  even  parity 

md  mode  stpl: 

equ 

040h 

/mode  for  1  stop  bit 

md  mode  stpl5: 

equ 

080h 

/mode  for  1.5  stop  bit 

md_mode_stp2 : 

equ 

0c0h 

/mode  for  2  stop  bit 

md  cmd  xmton: 

equ 

lh 

/command  for  transmitter  enable 

md  cmd  xmtof : 

equ 

0h 

/command  for  transmitter  disable 

md  cmd  dtron: 

equ 

2h 

/command  for  DTR  active 

md  cmd  dtrof: 

equ 

0h 

/command  for  DTR  inactive 

md  cmd  rcvont 

equ 

4h 

/command  for  receiver  enable 

md  cmd  rcvof : 

equ 

0h 

/command  for  receiver  disable 

md  cmd  brkon: 

equ 

8h 

/command  for  sending  break 

md  cmd  brkof: 

equ 

0h 

/command  for  no  break 

md  cmd  eres: 

equ 

P10h 

/command  for  error  reset 

md  cmd  rtson: 

equ 

0  2  0h 

/command  for  RTS  active 

md  cmd  rtsof: 

equ 

0h 

/command  for  RTS  inactive 

md  cmd  ires: 

equ 

040h 

/command  for  internal  reset 

md_cmd_hunt : 

equ 

080h 

/command  for  hunt  mode  (not  used] 

outstr : 

macro 

if 

str ing 

not  nul  string 

1  x  i 
endif 

h, st ring 

/point  at  string  to  output 

call 

mend 

md_out_msg 

/call  subroutine 

outchr  t 

macro 

if 

byte 

not  nul  byte 

mvi 

endif 

c,byte 

/load  C  with  byte  value 

cal  1 
mend 

md_out 

/output  one  byte 

tstngo: 

macro 

test, good, error 

/save  in  C 

mov 

c,  a 

cpi 

if  not 

test 

nul  good 

/is  it  an  test 

j* 

endif 
if  not 

good 

nul  error 

/ yes  -  take  good  exit 

jnz 

mend 

error 

/no  -  take  error  exit 

sloout : 

macro 

if 

rept 

xchg 

xchg 

mend 

endif 

out 

mend 

port 

md_mhz  gt  40 
( (md_mhz-40 )/10 ) -1 

port 

/allow  8251-A  to  catch  up 
;* 

sloin: 

macro 

if 

rept 

xchg 

xchg 

mend 

endif 

in 

mend 

port 

md  mhz  gt  40 
( (md_mhz-40)/10) -1 

port 

/allow  8251-A  to  catch  up 
/♦ 

page 

This  routine  initializes  the  modem  to  the  defaults 
set  after  a  hardware  reset. 


md_init i 


call 

md_carck 

/is  carrier  still  up 

cnz 

md_hangup 

/yes  -  drop  the  line 

call 

md_set 1200 

/default  to  1200  baud 

call 

md_reset_modem 

/reset  the  MODEM 

ret 

/return  to  caller 

I 

I 

I 


(Continued  on  page  108) 


106 

858 


Dr.  Dobb’s  Journal,  November  1985 


CP/M  Exchange  Listing 

(Listing  continued,  text  begins  on  page  102) 


/  This  subroutine  exits  with  the  phone  on  hook. 

7 

f **•••••••**•••*****«*««**«******•***••********•***** 

j**************************************************** 


md_hangup: 


call 

md_wait_l 

;wait  one  second 

lxi 

h,md_dis_msg 

ipoint  at  disconnect  message 

call 

md_out_msg 

;write  string  to  modem 

call 

md_wait_l 

;wait  one  second 

call 

md_carck 

;carrier  should  have  dropped 

rz 

jit  did  -  return 

jP 

md_hangup 

/error  recovery  may  be  improved 
; for  now  just  continue  telling 
/the  modem  to  hangup 

Dial  a  number 


md_dial » 


outst r 

md_attn_ldin 

/send  AT 

outst r 

md_dial_ldin 

/send  D 

outst r 

md_tone_stub 

/send  T 

outst r 

md_n umber 

/send  number 

call 

md_read_res 

/get  the  result  code 

ret 

jreturn  to  caller 

The  carrier  check  routine  will  return  the  Z  condition 
code  if  the  carrier  is  not  present. 


md_carck  t 


call  md_statuB 

ani  md_carrier 


;get  modem  status 

/strip  all  but  carrier  bit 


ret 


/return  to  caller 


Output  a  control  string  to  the  modem.  The  string  must 
end  with  a  080h. 


md_out_m8g : 


mov 

cf  m 

;get  a  byte 

mov 

a ,  c 

/is  this  end  of  string? 

rlc 

7* 

rc 

/yes  -  return 

call 

md  out 

/output  it 

inx 

h 

/point  at  next  byte 

jmp 

md_out_msg 

/loop  until  complete 

Output  one  byte  to  the  modem 


md_out : 


call 

md_status 

/get  modem  status 

ani 

md_t r_rdy 

/is  transmitter  ready  to 
/receive  a  byte 

jz 

md_out 

/no  -  loop  until  ready 

mov 

a  ,c 

/put  data  byte  into  A 

out 

md_data_port 

/output  it 

ret 

/return  to  caller 

Input  one  byte  from  the  modem 


md_input : 


Dr  Dobb’s  Journal,  November  1985 


108 


859 


call 

md  status 

;get  USART  status 

ani 

md_rcv_rdy 

/is  a  byte  available 

j* 

md_input 

;no  -  loop  until  ready 

in 

ret 

,«•••*••••****•«**•*•** 

md_data_port 

;get  data  byte 
^return  to  caller 

/ 

l  Input  a  verbal  or  numeric  response  message  from  the 

/  modem  and  convert  to  a  numeric  response 

in  the 

/  A  register. 

/ 

md_read_re8 : 

call 

md_input 

/get  the  leading  CR 

tBtngo 

md_cr , md_read_res_ver 

; i f  CR  go  to  verbal  routine 

sta 

save  res  code  /save  result  code  for  exit 

call 

md_input 

/ get  the  trailing  CR 

tstngo 

md_cr fmd_read_res_ext2, 

md_read_re8_err 
/go  to  exit  if  CR  otherwise 
;go  to  error  routine 

rnd_read_res_ver : 

call 

md_input 

/get  the  leading  LF 

tstngo 

md_lf , rmd_read_re8_err 

/if  not  LF  go  to  error  routine 

call 

md_input 

/get  the  first  response  byte 

tstngo 

'O' ,md_read_res_ok 

/is  it  OK 

tstngo 

' C ' , md  read  res  con 

/is  it  CONNECT  or  CONNECT  1200 

tstngo 

'R' ,md_read_res_r ing 

1  is  It  RING 

tstngo 

' N ' ,md_read_res_ncar 

/is  it  NO  CARRIER 

/ 

/  Some  type  of  unexpected  error  has  occurred.  An  unknown  result 

/  message  has  been  sent.  Simply  empty  the  USART  and  return  with 

;  ERROR  posted  in  the  C  register. 

; 

md_read_res_err  t 

call 

md  wait  1 

/delay  one  second 

call 

md  status 

/has  any  further  data  been  sent 

ani 

md_rcv_rdy 

i* 

jnz 

md_read_res_er r 

/yes  -  loop  until  empty 

mvi 

a, 

/set  error  code  in  A 

sta 

save  res  code 

/save  for  exit 

jmp 

md_read_re8_ext2 

/return  to  caller 

md_read_res_ok : 

call 

md_input 

/get  the  K 

tstngo 

'K',,md  read_res  err 

/is  it  really  a  K 

mvi 

a,  '0' 

/set  our  numeric  result  code 

sta 

8ave_re8_code 

i* 

md_read_re8_ext  t 

call 

md_input 

/get  the  trailing  CR 

tstngo 

md_cr, fmd_read_res_err 

/ 1 8  it  really  a  CR 

md_read_res_extl : 

call 

md_input 

/get  the  trailing  LF 

tstngo 

md_lf , ,md_read_res_er r 

/ i 8  it  really  a  LF 

md_read_res_ext2 : 

mvi 

a ,  1 

/wait  time  of  .1  second 

call 

md  wait  la 

/allow  modem  to  recover 

Ida 

ret 

save_res_code 

/put  user  result  code  into  A 
/return  to  caller 

md_read_res_con : 

call 

md_input 

/get  the  0 

tstngo 

'O' , , md_read_res_err 

/is  it  really  a  0 

call 

md_input 

/get  the  N 

tstngo 

'N'ffmd_read  res  err 

/is  it  really  a  N 

call 

md_input 

/get  the  N 

tstngo 

' N ' , ,md  read  res  err 

/is  It  really  a  N 

call 

md_input 

/get  the  E 

tstngo 

' E ' , ,md_read_res_err 

/ i 8  it  really  a  E 

call 

md_input 

/get  the  C 

tstngo 

'C'  ,  find_read  res  err 

/is  it  really  a  C 

call 

md_input 

/get  the  T 

tstngo 

' T' , ,md_read_res_err 

/ 1 8  it  really  a  T 

mvi 

a.’l' 

/ set  the  300  baud  result 

sta 

save_res_code 

i* 

call 

md_input 

/get  the  trailing  CR  or  blank 

tstngo 

'  '  ,,md  read  res  extl 

/  i 8  it  really  a  BLANK 

call 

md_input 

/get  the  1 

tstngo 

'  l',,md  read_res_err 

/is  it  really  a  1 

call 

md_input 

/get  the  2 

tstngo 

' 2  * , , md_read_re8_er r 

/is  it  really  a  2 

( Continued  on  next  page) 

109  Dr.  Dobb’s  Journal,  November  1985 

Qcn 


CP/M  Exchange  Listing 

(Listing  continued,  text  begins  on  page  102) 


call 

md_input 

/ get  the  0 

tstngo 

'  0 ' , ,md_read_res_er r 

/is  it  really  a  0 

call 

md_input 

/get  the  0 

tstngo 

' 0 ' , fmd_read_res_err 

/is  it  really  a  0 

mvi 

a»  '  5* 

/set  the  1200  baud  result 

sta 

save_res_code 

»* 

Jmp 

md_read_re8_ext 

/exit  thru  common  routine 

md_read_res_ring : 


call 

md_input 

/get  the  I 

tstngo 

' I ' , ,md_read_res_er r 

/is  it  really  a  I 

call 

md_input 

/  get  the  N 

tstngo 

'N ' , ,md_read_res_err 

/is  it  really  a  N 

call 

md_input 

/ get  the  G 

t stngo 

'G' r , md_read_re8_err 

/is  it  really  a  G 

mvi 

a, *2* 

/set  ring  result  code 

sta 

save_res_code 

Z* 

jmp 

md_read_res_ext 

md_read_res_ncar : 


call 

md_input 

/get  the  0 

tstngo 

'O' , ,md_read_res_err 

/is  it  really 

a 

0 

call 

md_input 

/get  the  blank 

tstngo 

'  ' , ,md_read_res_err 

/is  it  really 

a 

BLANK 

call 

md_input 

/get  the  C 

tstngo 

'C* , ,md_read_res_err 

/is  it  really 

a 

C 

call 

md_input 

/get  the  A 

tstngo 

' A' , ,md_read_res_err 

/is  it  really 

a 

A 

call 

md  input 

/qet  the  R 

tstngo 

'R, ,md_read_res_err 

/is  it  really 

a 

R 

call 

md_input 

/get  the  R 

tstngo 

' R' , , md_read_res_err 

/is  it  really 

a 

R 

call 

md_input 

/get  the  I 

tstngo 

' I ' , ,md_read_res_err 

/is  it  really 

a 

I 

call 

md_input 

/get  the  E 

tstngo 

1 E' , ,md_read_res_err 

/is  it  really 

a 

E 

call 

md_input 

/ get  the  R 

tstngo 

' R' , ,md_read_res_err 

/ i 8  it  really 

a 

R 

mvi 

a, *3' 

/set  no  carrier 

result  code 

sta 

save_res_code 

;* 

jmp 

md_read_res_ext 

/return  to  user 

Get  USART  status  and  return  in  A 


md_8tatus  * 


in  md_stat_port  /get  modem  status 

ret  /return  to  caller 


This  routine  is  a  duplication  of  one  that  already  exists 
in  the  MBYE  or  BYE  programs.  It  is  included  here  to  make 
this  routine  complete  within  itself. 


md_wait_l t 


mvi 

a  r  1 0 

push 

psw 

call 

md_delay 

pop 

psw 

dcr 

a 

jnz 

md_wait_la 

ret 

.•constant  to  delay  for  1  second 


;save  loop  counter 
/delay  .1  second 
;restore  loop  counter 
l i 8  loop  complete 
/no  -  loop  until  complete 

/yes  -  return  to  caller 


This  subroutine  will  delay  returning  for  .1  of  a  second 
depending  on  the  processor  speed. 


md_delay t 

(Continued  on  page  112) 


Dr.  Dobb's  Journal.  November  1985 


110 

861 


CP/M  Exchange  Listing 

(Listing  continued,  text  begins  on  page  102) 


md_delay_a : 

lxi 

b, (4167* (md_mhz/10) )  +  ( 417* (md_mhz  mod  10)) 

/constant  times  mod  10  l 

dcx 

mov 

ora 

jnz 

b  /decrement  loop  counter 

a,b  /has  counter  elapsed 

c  /* 

md_delay_a  /* 

ret 

/return  to  caller 

Ask  the  MODEM  to  respond 


md_wake_up: 


outstr  md_attn_ldin 
outstr  md_eom_stub 
call  md_read_res 


/send  AT 
;send  CR 

;get  result  code 


ret 


/return  to  caller 


Reset  the  MODEM  to  it's  default  values 


md_reset_modem : 


outstr 

md_attn_ldin 

/send  AT 

outstr 

md_reset_stub 

/send  Z 

outstr 

md_eom_stub 

/send  CR 

call 

md  wait  1 

mvi 

ret 

a,  '0' 

/set  a  result  code  of  OR 

/return  to  caller 

Set  the  MODEM  to  not  answer  the  phone  if  it  rings 


md_set_noans : 


outstr 

md_attn_ldin 

/send  AT 

outst r 

md_setreg_ldin 

/send  S 

out st  r 

md  anscnt  stub 

/send  0= 

outchr 

*0' 

/send  no 

r  ingB 

outstr 

md_eom_8tub 

/send  CR 

call 

md_read_res 

/ get  the 

result  code  in  A 

ret 

/return  1 

to  caller 

Set  the  MODEM  to  answer  the  phone  after  1  ring 


rad_8et_ansl : 


outstr 

md_attn_ldin 

/send  AT 

outstr 

md_8etreg_ldin 

/send  S 

outst  r 

md  anscnt  stub 

/send  0 

outchr 

*1« 

/send  one  ring 

outstr 

md_eom_stub 

/send  CR 

call 

md_read_res 

/ get  the  result  code  in  A 

ret 

/return  to  caller 

Set  the  MODEM  to  ECHO  all  control  characters 


md_set_echo_on : 


outstr  md_attn_ldin 
outstr  md_echo_ldin 


/send  AT 
/send  E 


112 


npn 


Dr  Dobb's  Journal,  November  1985 


outstr  md_eon_stub 
outstr  md_eom_stub 
call  md_read_res 


; send  1 
;send  CR 

;get  the  result  code  in  A 


ret 


/return  to  caller 


»  Set  the  MODEM  to  not  ECHO  any  control  characterc 


md_set_echo_of  f : 


outstr 

md_attn_ldin 

/send  AT 

outstr 

md_echo_ldin 

/send  E 

outstr 

md_eof f_stub 

/send  0 

outstr 

md_eom_stub 

/send  CR 

call 

md_read_res 

/get  the  result  code 

ret 

/return  to  caller 

,*••*•*«******«** ***********  ******** ********** 

7  Set  the  MODEM  to  FULL  duplex  operation 

l 

,***•««•**•« *******  *******************  ******** 
j********************************************* 

md_set_f  ull_on : 


outstr 

md_attn_ldin 

/send  AT 

outstr 

md_duplx_ldin 

/send  F 

outstr 

md_fdup_stub 

/send  1 

outstr 

md_eom_stub 

/send  CR 

call 

md_read_res 

/get  the  result  code  in  A 

ret 

/return  to  caller 

7  Set  the  MODEM  to  HALF  duplex  operation 

md_ae t_ha 1 f_on  j 


out st  r 

md_attn_ldin 

/send  AT 

out 8 1  r 

md_duplx_ldin 

/send  F 

out  8 1  r 

md_hdup_stub 

/ send  0 

outstr 

md_eom_8tub 

/ send  CR 

call 

md_read_res 

/get  the  result  code 

ret 

/return  to  caller 

l 

7  Set  the  MODEM  speaker  on 

1 

,**•*••••*«•****••« *«•***•*«•*•* 
I ******************* ••*•*****••*■ 

md_.se  t_spkr_on  s 


outstr 

md_attn_ldin 

/send  AT 

outstr 

md_spkr_ldin 

/send  F 

outstr 

md_spkon_stub 

/send  2 

out  8 1  r 

md_eonv_Btub 

/send  CR 

call 

md_read_res 

/get  the  result  code 

ret 

/return  to  caller 

I 

7  Set  the  MODEM  speaker  off 

t 

,•*•****•••*«*•****•«•* *•«•«••*•* 
md_set_spkr_of f : 


outstr 

md_attn_ldin 

/send  AT 

outstr 

md_8pk  r_ldin 

/send  F 

outstr 

md_spkof  f_stub 

/send  0 

outstr 

md_eom_8tub 

/send  CR 

call 

rod_read_res 

/get  the  result  code  in  A 

ret 

/return  to  caller 

Set  the  MODEM  speaker  on  during  dialing 


Dr.  Dobb's  Journal,  November  1985 


113 

863 


CP/M  Exchange  Listing 

(Listing  continued,  text  begins  on  page  102) 


t 

,••••*••**••*«*** 

md_set_spkr_dial : 


outst r 

md_attn_ldin 

;send  AT 

outstr 

md_spk r_ldin 

;send  F 

outst r 

md_spkdl_8tub 

;send  1 

out  st  r 

md_eom_Btub 

/send  CR 

call 

md_read_res 

iget  the  result  code 

ret 

/return  to  caller 

/  Set  the  MODEM  result  messages  on 

,***«*«*« A****************************** 

I •***•« ******** ************ «***«« «*«•*«* 

md_set_res_on : 


outstr 

md_attn_ldin 

/send  AT 

outstr 

md_reslt_ldin 

/send  Q 

outstr 

md_reson_8tub 

/send  0 

outstr 

md_eom_8tub 

/send  CR 

call 

md_read_res 

/get  the  result  code  in  A 

ret 

/return  to  caller 

,•••*•••****«**»*««»**#***«*•**«*«******* 

l 

;  Set  the  MODEM  result  messages  off 

1 

I •*••***••*••*•*« ************************ 

md_set_res_of f : 


outstr 

md_attn_ldin 

/send  AT 

out  8 1  r 

md_re8lt_ldin 

/send  Q 

outstr 

md.resof f_stub 

/send  1 

out  8 1  r 

md_eom__8tub 

/send  CR 

call 

md_read_res 

/ get  the  result  code  in  A 

ret 

/return  to  caller 

,«*•••*••*«****•***•«•*******#*•«*•«•**••*««*••« 

; 

;  Set  the  MODEM  result  messages  to  numeric 

1 

ft********************************************** 

I*********************************************** 

md_set_re8_num: 


outstr 

md_attn_ldin 

/send  AT 

outstr 

md_resmod_ldin 

/send  V 

outst r 

md_num_8tub 

/send  0 

out  st  r 

md_eom_stub 

/send  CR 

call 

md_read_res 

/get  the  result  code  in  A 

ret 

/return  to  caller 

;  Set  the  MODEM  result  messages  to  verbal 


md_Bet_reB_ver : 


outst  r 

md_at tn_ldin 

/send  AT 

outst  r 

md_resmod_ldin 

/send  V 

outstr 

md_ver_stub 

/send  1 

outst  r 

md_eom_stub 

/  send  CR 

call 

md_read_re8 

/get  the  result  code  In  A 

ret 

/return  to  caller 

Set  the  MODEM  result  messages  to  extended  format 


(Continued  on  page  1 16) 


114 


QC^ 


Dr.  Dobb's  Journal,  November  1985 


CP/M  Exchange  Listing 

(Listing  continued,  text  begins  on  page  102) 


md_set_res_ext : 


outst r 

md_attn_ldin 

outst r 

md_re8_ldin 

out  8 1  r 

md_extres_stub 

outstr 

md_eom_stub 

call 

md_read_reB 

ret 

/ send  AT 
jsend  X 
;send  1 
/send  CR 

/get  the  result  code  in  A 
;return  to  caller 


Set  the  MODEM  result  messages  to  standard  format 


md_set_res_std  t 


outstr 

md_attn_ldin 

/ send  AT 

outstr 

md_res_ldin 

/send  X 

outstr 

md_8tdres_8tub 

/send  0 

outstr 

md_eom_8tub 

/send  CR 

call 

md_read_res 

/get  the  result  code  in  A 

ret 

/return  to  caller 

l  Reset  the  USART. 

i  This  routine  has  been  written  for  the  worst  case  reset 

/  condition.  Upon  initial  power  on  the  8251A  may  be  in 

l  an  undetermined  mode  state.  For  that  reason  the  maximum 

l  mode  setup  case  of  setting  synchronous  mode  and  two  sync 

;  characters  is  used.  Otherwise,  there  would  be  a 

I  possibility  of  not  consistently  completing  a  reset. 

j  When  exit  from  this  routine  is  taken  the  8251A  will  be 

>  ready  for  the  initial  mode  and  command  -  command  bytes. 

md_reset_usart : 


nd _ reset _ er  r  t 


xra  a 

sloout  md_8tat_port 

sloout  md_Btat_port 

sloout  md_stat_port 

mvi  a,md_cmd_ires 

sloout  md_stat_port 
ret 


Reset  the  USART  error  status 


/get  a  zero  in  A 

/output  mode  synchronous 

;output  first  sync  byte 

/output  second  sync  byte 

/8251A  reset  command 
ji68ue  the  reset  command 
/return  to  user 


Ida  save_cur_cmd 
sloout  md_8tat_port 
ret 


;get  error  reset  command 
/send  to  status  port 
/return  to  caller 


Set  the  USART  to  1200  baud 


md_setl200: 


call 

md_reset_usart 

/establish  known  initial  mode 

mvi 

a ,md_mode_xl6+md_ 

_mode_cl8+md_mode__nop+md_mode_stpl 
/set  mode  to: 

/  16X  clock 

/  8  bits  per  charactc 

/  no  parity 

/  1  stop  bit 

sta 

8ave_cur_mode 

/save  current  mode  byte 

sloout 

md_stat_port 

/send  the  mode  byte 

Dr.  Dobb's  Journal,  November  1985 


116 

865 


mvi 


at  a 

sloout 

ret 


a , md_cmd_xmt on+md_cmd_dt  ron+md_cmd_rcvon+md_cmd_r 1 sontmd_cm 
zse t  command  toi 
/  transmitter  on 

I  OTR  on  (not  used) 

l  receiver  on 

I  RTS  on 

t  RESET  errors 

save_cur_cmd  /save  current  command  byte 

md_stat_port  I  send  the  command  byte 

;return  to  caller 


Set  the  USART  to  600  baud 


md_set600 : 


md_reset_usart 


/establish  known  initial  mode 


mvi 


sta 

sloout 


a ,  md_mode_xl6+md_mode_cl  8+md_mode_nop+md_mode_stpl 
/ set  mode  to: 


Bave_cur_mode 
md_stat_por t 


jBave  current  mode  byte 
/send  the  mode  byte 


16X  clock 

8  bits  per  characta 
no  parity 
1  stop  bit 


sta 

sloout 

ret 


a ,  md_cmd_xmton+md_cmd_dt  ron+md_cmd_r cvon*md_cmd_r  taof  ♦md_cm 
/set  command  to: 

I  transmitter  on 

t  DTR  on  (not  used) 

/  receiver  on 

Z  RTS  off 

/  RESET  errors 

nave_cur_cmd  /save  current  command  byte 

md_stat_port  /send  the  command  byte 

/return  to  caller 


Set  the  USART  to  300  baud 


md_set300 : 


md_t  e8et_usa r t 


/establish  known  initial  mode 


mvi 


sta 

sloout 


mvi 


sta 

sloout 

ret 


,  md_mode_x64-»md_mode_cl  8+md_mode_nop*md_mode_stpl 
/set  mode  to: 
l 
/ 

/ 


eave_cur_mode 

md_8tat_port 


16X  clock 

8  bits  per  characte 
no  parity 

/  1  stop  bit 

/ save  current  mode  byte 
/send  the  mode  byte 


a , md_cmd_xmton+rod_cmd_dt ron+md_cmd_rcvon+md_cmd_rtson*md_cm 
/set  command  to: 

/  transmitter  on 

;  DTR  on  (not  used) 

/  receiver  on 

/  RTS  on 

/  RESET  errors 

save_cur_cmd  / save  current  command  byte 

md_stat_port  /send  the  command  byte 

/return  to  caller 


Set  the  USART  to  150  baud 


md_setl50 : 


ca  1 1 
mvi 


sta 

sloout 


md_reeet_usa r t  /establish  known  initial  mode 

a  ,  md_mode_x6  4 ♦md_mode_cl 8 ♦md_mode_nop+md_mode_s t  pi 
/set  mode  to: 

/  16X  clock 

/  8  bits  per  characte 

/  no  parity 

1  stop  bit 


8ave_cur_mode 

md_stat_port 


/6ave  current  mode  byte 
/send  the  mode  byte 


6ta 

8  J OOUt 

ret 


a , md_cmd_xmton+md_cmd_dt  ron+md_cmd_rcvon+md_cmd_r t sof +md_cm 
/set  command  to: 

/  transmitter  on 

/  DTR  on  (not  used) 

/  receiver  on 

/  RTS  off 

/  RESET  errors 

save_cur_cmd  /save  current  command  byte 

md_stat_port  /send  the  command  byte 

/return  to  caller 


(Continued  on  next  page) 


117 

866 


Dr.  Dobb’s  Journal,  November  1985 


CP/M  Exchange  Listing 

(Listing  continued,  text  begins  on  page  102) 


page 

*************************************************************** 

This  section  of  the  program  falls  after  all  other 
program  code  as  required  by  the  relocation  routine  used. 


endobj  * 

md_dis_msg  t 

db 

1 +4+ ' ,md_eom 

xdieconnect  from  phone  line 

md_repeat_msg : 

db 

’A/ 

'  ,md_eom 

/repeat  last  command 

md_attn_ldin  x 

db 

'AT 

'  ,md_eom 

/beginning  of  all  messages 

md_dial_ldin : 

db 

'D' 

,  md_eom 

/dial  number  lead-in 

md_echo_ldin : 

db 

'  E  ' 

,  md_eom 

/echo  all  commands  lead-in 

md_duplx_ldin : 

db 

•  f* 

,  md_eom 

/duplex  transmission 

md_spkr_ldinx 

db 

'M' 

,  md_eom 

/speaker  control 

md_reslt_ldin : 

db 

'O’ 

,  md_eom 

/result  message  control 

md_set reg_ldin : 

db 

•s' 

,  md_eom 

/set  registers 

md_re8mod_ldin : 

db 

•v' 

,md_eom 

/set  numeric/verbal  result  mode 

md_res_ldin : 

db 

•x' 

,  md_eom 

/set  result  mode 

md_an8_8tubx 

db 

'A' 

,  md_eom 

/answer  phone  immediately 

n>d_pulse_stub  x 

db 

'P' 

,  md_eom 

/dial  via  pulse  method 

md_tone_stub: 

db 

'T' 

,  md_eom 

/dial  via  tone  method 

md_pause_8tubx 

db 

'  ,  ' 

,  md_eom 

/delay  for  2  seconds  during 

dialing 
md_eof f_stub: 

db 

•0' 

,  md_eom 

/turn  local  echo  off 

md_eon_Btub: 

db 

'1' 

,  md_eom 

/turn  local  echo  on 

md_hdup_stub: 

db 

•0' 

,md_eom 

/half  duplex 

md_fdup_8tub: 

db 

'  1' 

,  md_eom 

/full  duplex 

md_spkof f_stub: 

db 

•0' 

,  md_eom 

/turn  speaker  off 

md_8pkdl_8tub: 

db 

•1  ' 

,  md_eom 

/turn  speaker  on  until  connect 

md_Bpkon_8tub: 

db 

'  2 ' 

,md_eom 

/turn  speaker  on 

md_re8on_8tub: 

db 

'0' 

,md_eom 

/send  result  messages 

md_re8of f_stub: 

db 

'1' 

,md_eom 

/suppress  result  messages 

md_an8cnt_8tub : 

db 

•0  = 

'  ,md_eom 

/ set  answer  count 

md_escod_8tubx 

db 

'2' 

,  md_eom 

/set  escape  code 

md_ca rwat_etub : 

db 

,  md_eom 

/set  wait  for  carrier  time 

md_nun\_8tub: 

db 

•0' 

,  md_eom 

/result  messages  are  numeric 

md_ver_stub: 

db 

'1' 

,  md_eom 

/result  messages  are  verbal 

md_8tdres_stubi 

db 

'  0  ' 

rmd_eom 

/standard  result  codes 

md_extre8_Btub: 

db 

'  i  ' 

,  md_eom 

/extended  result  codes 

md_re8et_8tub; 

db 

'Z' 

,  md_eom 

/reset  command 

md_eom_stubx 

db 

md_i 

cr rmd_eom 

/end  of  message 

md_number : 

ds 

20 

/place  phone  number  here 

8ave_re8_code s  ds 

save_cur_mode :  ds 

1 

1 

/followed  by  md_eon\_stub 

/save  arra  for  current  mode  byte 

save_cur_cmd s  ds 

1 

/save  area  for  current  command  byte 

Listing  1  -  S-100  Modem  Overlay  Program 


End  Listing 


Dr.  Dobb's  Journal.  November  1985 


118 

867 


OF  INTEREST 


by  Alex  Ragen 

CP/M 

Electronic  Business,  a  magazine  de¬ 
voted  to  the  dollars  and  cents  issues 
of  the  computer  business,  recently 
published  a  long  article  analyzing  the 
microprocessor  market.  It  carried  the 
surprising  message  that  8-bit  proces¬ 
sors  make  up  by  far  the  lion’s  share  of 
that  market,  with  16-bit  processors 
trailing  far  behind.  Moreover,  most 
industry  observers  expect  this  situa¬ 
tion  to  continue  for  many  years.  The 
reason  is  that  the  now  inexpensive  8- 
bit  processors  and  their  peripherals 
are  perfectly  adequate  for  most  appli¬ 
cations.  Where  16-bit  processors  hold 
sway  is  in  the  prestige  conscious 
world  of  the  personal  computer, 
where  only  the  latest  fashion  is  mar¬ 
ketable.  In  my  corner  of  the  world, 
everyone  talks  only  about  BMWs  and 
IBM  PCs,  but  they  drive  Toyotas  and, 
when  they  build  a  piece  of  equip¬ 
ment,  they  use  Z80s  whenever  they 
can  and  develop  the  software  for  it 
with  that  workhorse,  CP/M  80,  the 
reports  of  whose  death  have  been 
greatly  exaggerated. 

It’s  true  that  finding  a  place  to  buy 
CP/M  80  software  is  a  challenge,  but 
there  is  plenty  of  good  software  out 
there.  Those  who  persevere  will  find 
that  their  blood,  sweat  and  tears  will 
be  rewarded.  One  of  the  most  nag¬ 
ging  problems  a  dealer  has  in  stock¬ 
ing  CP/M  80  software  is  that  he  has 
to  carry  several  dozen  disk  formats. 
As  a  result  stores  are  not  a  good  place 
to  find  software.  The  best  source  is 
the  manufacturer,  who  can  whip  up  a 
disk  for  your  format  using  one  of  the 
disk  emulation  programs  now  widely 
available.  A  good  source  for  adver¬ 
tisements  is  Kaypro’s  magazine, 
PROFILES ,  which  is  available  at 
most  Kaypro  dealers. 

Echelon  Inc.  is  one  company  that 
continues  to  develop  quality  CP/M  80 


software.  Its  piece  de  resistance  is 
ZCPR3,  the  CCP  replacement  which 
gives  CP/M  80  a  number  of  Unix  like 
features.  Also  SYSLIB3,  a  macro  li¬ 
brary  with  210  subroutines,  DISCAT, 
a  disk  and  file  utility  program,  TERM 
3,  a  telecommunications  program, 
and  many  others.  All  programs  come 
with  extensive  documentation.  For  a 
catalog  and  price  list,  contact  the 
vendor  at  101  First  St.,  Los  Altos, 
CA  94022,  (415)  948-3820.  Reader 

Service  Number  101. 

The  KAMAS  “Outline  Processor” 
is  now  available  for  CP/M  80  2.2. 
KAMAS  combines  outline  processing 
and  information  retrieval  with  word 
processing  to  provide  a  complete  en¬ 
vironment  for  developing  and  con¬ 
trolling  text.  KAMAS  is  an  aid  for 
writers,  researchers  and  other  profes¬ 
sionals  who  must  organize  their  ideas 
and  categorize  information.  It  allows 
users  to  classify  information  in  a  fa¬ 
miliar  outline  structure,  and  then  to 
alter  and  access  the  information 
based  on  that  structure.  Levels  of  the 
outline  can  be  collapsed  from  the 
screen  and  hidden  from  view,  and 
then  expanded  back  into  view  for 
editing.  KAMAS  includes  a  full 
screen  editor  with  document  output 
controls  as  well  as  a  Forth-like  pro¬ 
gramming  language.  There  is  a  grow¬ 
ing  body  of  public  domain  add-ons, 
and  the  vendor  offers  three  volumes 
of  these  utilities.  The  price  is  $147 
plus  $4  shipping.  Contact  KAMA- 
SOFT  Inc.,  2525  S.W.  224th  Avenue, 
Aloha,  OR  97006,  (503)  649-3765. 

Reader  Service  Number  103. 


Alternative  Languages 

The  Forth-83  Handy  Reference  Card 
is  now  available  free  from  the  Forth 
Interest  Group  (FIG).  It  functions  as 
a  pocket  programming  aid  and  refer¬ 
ence  for  Forth  programmers.  Com¬ 


mands  are  grouped  by  function  and 
include:  stack  manipulation,  compar¬ 
ison,  arithmetic  and  others.  For  addi¬ 
tional  information  about  the  card  and 
FIG,  call  the  FIG  hot  line  at  (408) 
277-0668  or  write  FIG,  P.O.  Box 
8231,  San  Jose,  CA  95155.  Reader 

Service  Number  105. 

MacScheme  is  a  modern  imple¬ 
mentation  of  Lisp  for  the  5 1 2K  Mac¬ 
intosh.  It  offers  an  interpreter  with 
full  run  time  error  detection  and  de¬ 
bugging  and  adheres  to  the  standards 
for  Scheme,  an  influential  dialect  of 
Lisp.  It  supports  the  most  important 
features  of  Lisp:  lexically  scoped 
variables,  first  class  procedures  (clo¬ 
sures),  macros,  and  generic  arithme¬ 
tic,  including  floating  point  and  infi¬ 
nite  precision  integers.  The 
Smalltalk-like  interface  features 
multiple  scrolling  windows.  The  price 
is  $1 25  without  copy  protection.  Con¬ 
tact  Semantic  Microsystems,  1001 
Bridgeway,  Suite  543,  Sausalito,  CA 
94965.  Reader  Service  Number  111. 

UO-LISP,  which  contains  over  400 
standard  Lisp  functions  and  comes 
with  an  optimizing  compiler  that 
generates  native  8086  code,  is  now 
available  for  the  IBM  PC  and  compat¬ 
ibles.  There  is  a  multi-window  screen 
editor  that  supports  Lisp  interactions 
and  a  complete  program  development 
package.  The  entire  package  is  priced 
at  $150.  The  LEARN  LISP  system,  a 
subset  with  on  line  help,  a  special  ref¬ 
erence  manual  and  tutorial  guide,  is 
priced  at  $85.  UO-LISP2,  a  CP/M 
version,  costs  $125.  Contact  North¬ 
west  Computer  Algorithms,  P.O.  Box 
90995,  Long  Beach,  CA  90809, 
(213)  426-1  893.  Reader  Service  Number 
113. 

CLISP  is  a  Lisp  interpreter  written 
in  C  for  the  IBM  PC.  It  has  40  func¬ 
tions  for  list  manipulation,  arithme¬ 
tic,  relational  and  Boolean  opera- 


122 

868 


Dr.  Dobb's  Journal,  November  1985 


tions.  A  modest  library  of  Lisp 
functions  is  also  included.  The  price 
is  $150,  which  includes  source  code 
and  documentation.  Contact  WEST- 
COMP  Software  Development 
Group,  517  North  Mountain  Ave¬ 
nue,  Suite  229,  Upland,  CA  91786, 
(714)  982-1738.  Reader  Service  Number 
115. 

Useful  and  practical  expert  sys¬ 
tems  can  be  designed  and  implement¬ 
ed  by  users  with  no  prior  knowledge 
of  AI  programming  with  the  ES/P 
Advisor,  according  to  the  vendor. 
The  product,  written  in  Prolog  2  for 
the  IBM  PC,  gives  the  user  the  benefit 
of  general  purpose  (and  Prolog  2)  in¬ 
terfacing,  fast  execution,  windows, 
garbage  collection  and  other  fea¬ 
tures.  There  are  no  limits  in  knowl¬ 
edge  base  or  number  of  rules  that  can 
be  handled.  The  price  is  $895.  Con¬ 
tact  Expert  Systems  International, 
1 1 50  First  Avenue,  King  of  Prussia, 
PA  19406,  (215)  337-2300.  Reader 

Service  Number  117. 

STOK  PILOT-PC,  a  language  for 
the  development  of  advanced  training 
and  control  applications,  is  now  avail¬ 
able  for  the  IBM  PC.  This  is  the  only 
high  level  language  with  integrated 
instructions  for  the  storage  and  ma¬ 
nipulation  of  audio  on  a  hard  disk. 
Contact  Stok  Software,  17  West  17th 
St.,  New  York,  NY  10011,  (212) 

243-1444.  Reader  Service  Number  1 19. 

Super  Pascal,  a  complete  develop¬ 
ment  system  for  the  Commodore  64 
and  128  computers,  is  available  for 
$59.95  from  Abacus  Software,  2201 
Kalamazoo  S.E.,  P.O.  Box  7211, 
Grand  Rapids,  Ml  49510,  (616)  241- 

5510.  Reader  Service  Number  121. 


Apple 

A  transportable  300  baud  modem 
which  clips  on  to  the  back  of  the  Ap¬ 
ple  lie  and  solves  the  problem  of  sud¬ 
den  disconnect  when  a  call  comes  in 
on  a  “call  waiting”  line,  has  been  an¬ 
nounced  by  Prometheus  Products. 
The  price  is  $199.95.  Contact  the 
vendor  at  P.O.  Box  4156,  Fremont, 
CA  94539,  (415)  490-2370.  Reader 

Service  Number  123. 

The  Mac  3.5  card  provides  Apple 
II  users  with  a  3.6  MHz  65C02  pro¬ 
cessor  that  speeds  up  their  Apple  in 
some  cases  by  as  much  as  3.5  times, 


though  the  vendor  is  careful  to  point 
out  that  for  programs  that  contain 
most  of  their  code  in  the  auxiliary 
memory  of  the  Apple  lie,  e.g.  Pascal 
1.2,  the  speed  advantages  will  be  less 
impressive.  Contact  A.P.P.L.E  Co¬ 
op,  290  S.W.  43rd  St.,  Renton,  WA 
98055,  (206)  251-5222.  Reader  Service 

Number  125. 

A  RAM  disk  for  the  Macintosh  is 
available  from  Symmetry  Corpora¬ 
tion.  Quickdisk  requires  a  512K  Mac 
or  XL  and  costs  $34.  Contact  Sym¬ 
metry  at  3900  East  Camelback 
Road,  Suite  103-S,  Phoenix,  Arizona 
85018  (602)  224-5944.  Reader  Service 

Number  127. 

Hippopotamus  Software  has  an¬ 
nounced  the  availability  of  level  2  of 
its  Hippo-C  for  the  Macintosh.  Level 
1,  which  has  been  available  for  sever¬ 
al  months,  is  designed  for  use  by  edu¬ 
cational  institutions  and  the  general 
public.  It  incorporates  the  familiar 
Macintosh  interface  and  includes  an 
editor,  full  C  compiler,  linker,  sym¬ 
bolic  debugger,  standard  C  library, 
online  tutorial,  and  shell  command 
processor.  Level  2  features  an  opti¬ 
mizing  compiler,  68000  assembler, 
linker,  librarian,  full  floating-point 
support,  header  files  and  a  Unix-like 
command  shell,  access  to  over  500 
toolbox  routines,  the  sound  channels, 
and  serial  ports.  Level  1  retails  for 
$149.95,  and  level  2  retails  for 
$399.95.  An  upgrade  kit  for  level  1 
costs  $250.  There  are  no  licensing 
fees  for  software  produced  using  Hip- 
po-C,  and  nonprotected  versions  are 
available  for  $25  more.  Reader  Service 

Number  129. 

The  same  vendor  also  has  intro¬ 
duced  Hippo-Lock,  a  “business  Data 
Security  System”  for  the  Mac,  which 
scrambles  the  contents  of  a  file  once 
the  user  creates  a  keyword  phrase  us¬ 
ing  the  DES  standard.  Hippo-Lock 
works  with  microdisks,  hard  disks, 
and  over  the  AppleTalk  office  net¬ 
work,  and  it  can  be  used  to  scramble 
electronic  mail  on  MCI  mail,  Easy- 
Link,  and  CompuServe.  Contact  the 
vendor  at  985  University  Avenue, 
Suite  12,  Los  Gatos,  California  95030 
(408)  395-3190.  Reader  Service  Number 
131. 

DeskTop  Software  has  announced 
a  solution  for  the  problem  of  incom¬ 


patibility  of  copy  protection  schemes 
and  hard  disk  usage.  The  new  system, 
which  transfers  the  copy  protection 
information  to  the  hard  disk,  is  avail¬ 
able  for  all  three  of  DeskTop’s  prod¬ 
ucts:  IstBASE,  IstPORT,  and  1st- 
MERGE.  Present  users  can  obtain  the 
new  system  at  no  charge.  Contact  the 
vendor  at  244  Wall  Street,  Princeton, 
New  Jersey  08540  (609)  924-7111. 

Reader  Service  Number  133. 

IBM-PC 

The  Disc  Interchange  Service  con¬ 
verts  nles  written  in  one  format  to  a 
different  format.  They  have  added 
the  3  Vi  inch  drive  to  their  format  con¬ 
version  capabilities,  bringing  the 
number  of  formats  supported  to  near¬ 
ly  200.  The  following  formats  are 
available:  DG/One,  Kaypro  2000 
and  the  IBM  PC  2  (when  its  details 
become  known),  as  well  as  the  Hew¬ 
lett-Packard  HP- 150.  Contact  the 
vendor  at  481  Great  Road,  Acton, 
MA  01720,  (617)  263-6001.  Reader 

Service  Number  109. 


Dr.  Dobb  s  Journal,  November  1985 


125 

869 


A  software  package  which  is  de¬ 
signed  to  ensure  the  security  of  data 
in  a  shared  computer  environment 
without  the  use  of  encryption  has 
been  announced  by  AST  Research. 
The  Knight  Data  Security  Manager 
offers  passwords,  copy  protection 
utilities  and  a  variety  of  file  manage¬ 
ment  features  for  the  IBM  PC  and 
compatibles.  A  complete  menu  driv¬ 
en  DOS  interface  is  also  provided, 
though  access  to  DOS  is  still  possible 
in  the  normal  way.  Contact  the  ven¬ 
dor  at  2121  Alton  Ave.,  Irvine,  CA 
927  14,  (714)  863- 1  333.  Reader  Service 

Number  135. 

X-VIEW.86,  which  allows  hard¬ 
ware  and  software  experts  to  observe 
the  internal  operations  of  DOS  appli¬ 
cations  software,  has  been  introduced 
by  McGraw  Hill.  It  is  available  for 
immediate  shipment  and  is  priced  at 
$59.95  plus  shipping.  X-VIEW  86  is  a 
software  analyzer  and  consists  of  a 
diskette,  documentation  and  techni¬ 
cal  support  for  registered  users,  the 
product  is  available  on  a  special  10 
day  examination  basis,  and  the  price 
is  fully  refundable,  provided  the  pro¬ 
tective  seal  on  the  diskette  is  unbro¬ 
ken.  Contact  the  vendor  at  81 1 1  LBJ 
Freeway,  Dallas,  TX  75251,  (800) 
221-8439,  or  (800)  233-8439  in  Tex- 

<iS.  Reader  Service  Number  137. 

CP/EM  makes  CP/M  80  emulation 
in  software  available  for  the  IBM  PC 
and  compatibles.  It  is  priced  at 
$79.95.  Contact  the  ICU  Group,  P.O. 
Box  10118,  Rochester,  NY  14610, 
(716)  425-25  1  9.  Reader  Service  Number 
139. 


C  and  Unix 

Computer  Innovations  has  an¬ 
nounced  version  2.3  of  their  Optimiz¬ 
ing  C86  compiler,  whose  most  impor¬ 
tant  new  feature  is  source  level 
debugging  support.  This  allows  popu¬ 
lar  debuggers  such  as  Periscope,  Pfix 
Plus  and  Atron  to  be  used.  The  docu¬ 
mentation  is  completely  new.  It  is 
priced  at  $395,  or  $35  for  an  upgrade 
for  exsiting  C86  users.  Contact  the 
vendor  at  980  Shrewsbury  Avenue, 
Tinton  Falls,  NJ  07724,  (800)  922- 
0169.  Reader  Service  Number  141. 

HEXPOSE  is  a  “visual”  binary  file 
editor  for  Unix  which  can  be  used  for 
patching  object  modules,  repairing 


damaged  files  and  verifying  the  re¬ 
sults  of  I/O  operations.  For  further 
information,  contact  Specialized  Sys¬ 
tems  Consultants,  P.O.  Box  55549, 
Seattle,  WA  98125,  (206)  367- 

UNIX.  Reader  Service  Number  143. 

National  Semiconductor  has  an¬ 
nounced  the  production  release  of 
Genix  4.2,  a  port  of  Berkeley  4.2  BSD 
Unix  for  National’s  Series  32000  32 
bit  microprocessor  family.  The  pack¬ 
age  includes  a  C  compiler  and  port¬ 
ing  tools  for  a  VAX/4.2  host.  A  Pas¬ 
cal  compiler  is  available  as  well. 
National  also  offers  System  V/  Se¬ 
ries  32000,  based  on  AT&T’s  Unix 
System  V.  Contact  the  vendor  at 
2900  Semiconductor  Drive,  Santa 
Clara,  CA  95051  (408)  721-5000. 

Reader  Service  Number  145. 

Software  Architecture  and  Engi¬ 
neering  has  announced  a  line  of  embed¬ 
dable  expert  system  tools  implement¬ 
ed  in  C.  Initial  releases  of  the  Knowl¬ 
edge  Engineering  System  II  support 
the  IF  .  .  .  THEN  production  rules  for 
inferencing,  and  future  releases  will 
add  frame-based  pattern  matching 
and  statistical  inferencing  methods. 
Contact  the  vendor  at  1500  Wilson 
Boulevard,  Suite  800,  Arlington,  Vir¬ 
ginia  22209  (703)  276-7910.  Reader 

Service  Number  147. 

Software  Research  Associates  has 
introduced  its  Test  Coverage  Analy¬ 
sis  Tool  (TCAT)  for  C,  which  the  ven¬ 
dor  claims  will  improve  the  quality  of 
unit  and  system  level  testing  by  a  fac¬ 
tor  of  five  or  more.  It  identifies  which 
parts  of  a  program  have  been  tested 
and  which  have  not.  TCAT/C  is  avail¬ 
able  for  VAX/Unix,  VAX/VMS,  and 
IBM  PC  and  compatibles.  Prices 
range  from  $975  to  $9000  depending 
on  the  configuration.  SRA  is  prepared 
to  port  TCAT/C  to  almost  any  oper¬ 
ating  system  on  a  custom  basis.  For 
further  information,  contact  the  ven¬ 
dor  at  580  Market  Street,  San  Fran¬ 
cisco,  California  94104  (415)  957- 

1  44  1 .  Reader  Service  Number  149. 

A  training  package  for  C  has  been 
announced  by  Computer  Innova¬ 
tions,  the  developers  of  C-86.  “Intro¬ 
ducing  C,”  which  consists  of  a  self- 
paced  training  manual  and  C 
interpreter,  runs  on  an  IBM  PC  (DOS 
2.0)  with  192K.  The  price  is  $95. 
Contact  the  vendor  at  980  Shrews¬ 


bury  Avenue,  Tinton  Falls,  New  Jer¬ 
sey  07724.  Reader  Service  Number  151. 

Smart/C  is  an  integrated  precom¬ 
pilation  development  environment 
for  C;  it  consists  of  a  syntax-directed 
editor  integrated  with  a  screen-ori¬ 
ented  interpreter  and  the  Migrator, 
which  allows  existing  C  programs  to 
become  “amenable”  to  the  Smart/C 
environment.  It  runs  under  Unix  Sys¬ 
tem  V,  Berkeley  BSD  4.2,  Xenix,  and 
MSDOS.  Contact  AGS  Computers, 
Advanced  Products  Division,  1139 
Spruce  Drive,  Mountainside,  New 
Jersey  07092  (201)  654-4321.  Reader 

Service  Number  153. 

Interactive/C  is  a  full  K&R  devel¬ 
opment  system  that  includes  a  com¬ 
mand  processor,  full  screen  editor, 
source  level  debugger,  and  execution 
profiler.  Its  multiwindow  multi-user 
interface  permits  debugging  of  full 
screen  graphics  on  one  or  more  CRTs 
with  simultaneous  display  of  source 
code,  program  output,  and  system 
status.  It  is  compatible  with  Lattice 
C  and  lists  for  $395.  Contact  the  ven¬ 
dor,  IMPACC  Associates,  at  P.O.  Box 
93,  Gwynedd  Valley,  Pennsylvania 
19437.  Reader  Service  Number  155. 

C-LINK  is  an  application  generator 
that,  working  with  the  vendor’s  S- 
Tran  BASIC  to  C  translator,  allows  a 
programmer  to  develop  programs  in 
C  by  writing  in  BASIC.  C-LINK  runs 
under  Unix  or  Xenix  and  costs  $695. 
For  further  information,  contact  the 
vendor,  SMI,  at  20720  South  Leap- 
wood  Avenue,  Carson,  California 
90746  (213)  538-8174.  Reader  Service 

Number  1 57. 


DD| 


126 

870 


Dr.  Dobb's  Journal,  November  1985 


:  %%%%**» 


k 

\h 

] 

■  si 

I - 

u 

U.J3 

JU 

Dr.  Dobb's  Journal 


Editorial 

Editor-in-Chief  Michael  Swaine 
Managing  Editor  Frank  DeRose 
Editorial  Assistant  Sara  Noah 
Contributing  Editors  Robert  Blum. 

Dave  Cortes i. 

Ray  Duncan. 

Allen  Holub 
Copy  Editor  Vince  Leone 
Typesetter  Jean  Aring 
Production 

Production  Manager  Bob  Wynne 

Art  Director  Shelley  Rae  Doeden 
Production  Assistant  Alida  Hinton 
Cover  Artist  Tom  Upton 
Circulation 

Sub.  Fulfillment  Mgr.  Stephanie  Barber 
Subscription  Mgr.  Maureen  Kaminski 
Book  Marketing  Mgr.  Jane  Sharninghouse 
Single  Copy  Sales  Mgr.  Stephanie  Barber 
Administration 
Finance  Manager  Sandra  Dunie 
Business  Manager  Betty  Trickett 
Accounts  Payable  Supv.  Mayda  Lopez -Quintana 
Accounts  Payable  Asst.  Denise  Giannini 
Billing  Coordinator  Laura  Di  Lazzaro 
Accountant  Marilyn  Brown 
Adm.  Coordinator  Kobi  Morgan 
Advertising 
Advertising  Director 
Shawn  Horst  (415)424-0600 
Advertising  Sales 

Walter  Andrzejewski  (617)  567-8361 
Lisa  Boudreau  (415)424-0600 
BethDudas  (714)643-9439 
Michele  Beaty  (317)  875-8093 
Systems  Manager  Ron  Copeland 
Administrative  Manager  Anna  Kittleson 


M&T  Publishing.  Inc. 

Chairman  of  the  Board  Otmar  Weber 
Director  C.  F.  von  Quadt 
President  Laird  Foshay 


In  this  Issue 


This  is  our  second  annual  operating  systems  issue.  We’ve  tried  to  present  a 
variety  of  tools  for  extending  the  performance  of  several  operating  systems. 
We  looked  for  techniques  and  programs  that  would  make  software  develop¬ 
ment  easier:  a  debugging  tool,  techniques  for  expanding  the  available  mem¬ 
ory  for  programs,  a  conditional  instruction  tool  for  CP/M-68K. 

We  also  took  a  look  at  three  commercial  products  that  claim  to  extend 
the  performance  of  the  MS  DOS  operating  system:  DRI’s  GEM,  IBM’s  Top- 
View,  and  Microsoft’s  Windows.  We  talked  to  developers  of  each  product, 
asking  what  programmers  need  to  know  to  develop  applications  compatible 
with  these  operating  environments  and  why  a  programmer  might  want  to 
do  such  a  thing. 

Look  closely  at  the  issue  next  month;  it  won’t  look  quite  the  same.  Our 
art  director  has  introduced  some  changes  inside  and  outside  the  magazine 
that  we  think  you’ll  like. 

We  don’t  think  you’ll  like  hearing  that  Dr.  Dobb’s  Clinic  is  closing  with 
this  issue.  We  don’t  like  telling  you.  The  resident  intern,  Dave  Cortesi,  has 
gone  off  to  pursue  interests  having  nothing  to  do  with  software.  We’ll  miss 
Dave’s  gentle  wit.  We’d  like  to  reopen  the  Clinic,  but  we  haven’t  found 
anyone  yet  to  fill  Dave’s  shoes,  and  until  we  do,  we  won’t. 

Next  month:  68000  programming. 


Dr.  Dobb's  Journal  (USPS  307690)  is  published 
monlhly  by  M&T  Publishing,  Inc.,  2464  Embarcadero 
Way,  Palo  Alio,  CA  94303.  (415)  424-0600.  Second 
class  postage  paid  at  Palo  Alto  and  at  additional  entry 
points. 

Address  correction  requested.  Postmaster:  Send  Form 
3579  to  Dr  Dobb's  Journal,  P.O.  Box  27809,  San 
Diego,  CA  92128.  ISSN  0278-6508 

Customer  Service:  For  subscription  problems  call: 
outside  CA  800-321-3333;  within  CA  619-485-9623  or 
566-6947.  For  book,  back  issue,  or  disk  order  problems 
call  415-424-1474. 

Subscription  Rates:  $25  per  year  within  the  United 
States.  Foreign  subscription  rates:  $52.00  for  airmail, 
$42.00  for  surface  mail.  Foreign  subscriptions  must  be 
pre-paid  in  U  S.  Dollars,  drawn  on  a  U.S.  Bank. 

Foreign  Distributor:  Worldwide  Media  Service,  Inc., 
386  Park  Ave.  South.  New  York,  NY  10016,  (212) 
686-1520  TELEX'.  620430  ( WU1) 

Entire  contents  copyright  *>  \985  by  M&T 
Publishing.  Inc.  unless  otherwise  noted  on 
specific  articles.  All  rights  reserved.  SUE" 

People's  Computer  Company 

Dr.  Dobb’s  Journal  is  published  by  M&T  Publishing, 
Inc.  under  license  from  People's  Computer  Company, 
2682  Bishop  Dr..  Suite  107.  San  Ramon,  CA  94583,  a 
non-prolil,  educational  corporation. 


STATEMENT  OF  OWNERSHIP,  MANAGEMENT,  AND  CIRCULATION 

(Act  of  August  12.  1970.  Section  3685.  Title  39.  United  States  Code) 


1.  Title  of  Publication:  Dr.  Dobb's  Journal.  Publication  No. 
307690. 

2.  Dale  of  Filing:  October  12,  1985. 

3.  Frequency  of  Issue:  Monthly  ( 1 2  issues,  $25). 

4.  Location  of  Known  Office  of  Publication:  2464  Embarca- 
dero  Way,  Palo  Alto,  CA  94303. 

5.  Location  of  Headquarters  of  General  Business  Offices  of 
the  Publishers:  2464  Embarcadero  Way,  Palo  Alto,  CA 
94303. 

6.  Names  and  Addresses  of  Publisher.  Editor,  and  Managing 
Editor:  Publisher.  Laird  Foshay.  2464  Embarcadero  Way, 
Palo  Alto.  CA  94303.  Editor,  Michael  Swaine,  2464  Em¬ 
barcadero  Way.  Palo  Alto,  CA  94303.  Managing  Editor, 
Frank  DeRose,  2464  Embarcadero  Way,  Palo  Alto.  CA 
94303. 

7.  Owner:  Otmar  Weber,  2464  Embarcadero  Way.  Palo  Alto, 
CA  94303;  and  C.  F.  Von  Quadt,  2464  Embarcadero  Way, 
Palo  Alto.  CA  94303. 

8.  Known  Bondholders,  Mortgages,  and  Other  Security  Hold¬ 
ers  Owning  or  Holding  I  Percent  or  More  of  Total  Amount 
of  Bonds.  Mortgages  or  Other  Securities  Markl  &  Technik, 
Hans-Pinsel-Strasse  2  8013  Haar  bei  Munich  W.  Germany. 

9.  Extent  and  Nature  of  Circulation: 

A.  Total  number  of  copies  printed:  Average  number  of 
copies  each  issue  during  the  preceding  12  months: 
41.693.  Actual  number  of  copies  of  single  issue  pub¬ 
lished  nearest  to  filing  date:  45,037. 

B.  Paid  Circulation.  I.  Sales  through  dealers  and  carri¬ 
ers.  street  vendors,  and  counter  sales.  Average  number 
of  copies  each  issue  during  preceding  12  months: 
8.707.  Actual  number  of  copies  of  single  issue  pub¬ 
lished  nearest  to  filing  date:  7,815.  2.  Mail  subscrip¬ 


tions.  Average  number  of  copies  each  issue  during  pre¬ 
ceding  1 2  months:  26,568.  Actual  number  of  copies  of 
single  issue  published  nearest  to  filing  date:  30,4 1 7. 

C.  Total  Paid  Circulation.  Average  number  of  copies 
each  issue  during  preceding  12  months:  35,275.  Actu¬ 
al  number  of  copies  of  single  issue  published  nearest  to 
filing  date:  38,232. 

D.  Free  distribution  by  mail,  carrier,  or  other  means, 
samples,  complimentary,  and  other  free  copies.  Aver¬ 
age  number  of  copies  each  issue  during  preceding  12 
months:  2,353.  Actual  number  of  copies  of  single  issue 
published  nearest  to  filing  date:  3,305. 

E.  Total  distribution.  Average  number  of  copies  each  is¬ 
sue  during  preceding  1 2  months:  37,628.  Actual  num¬ 
ber  of  copies  of  single  issue  published  nearest  to  filing 
date:  41,537. 

F.  Copies  not  distributed.  I.  Office  use.  left  over,  unac¬ 
counted,  spoiled  after  printing.  Average  number  of 
copies  each  issue  during  preceding  12  months:  150.  Ac¬ 
tual  number  of  copies  of  single  issue  published  nearest 
to  tiling  date.  150.  2.  Returns  from  news  agents.  Aver¬ 
age  number  of  copies  each  issue  during  preceding  12 
months:  3.915.  Actual  number  copies  of  single  issue 
published  nearest  to  filing  date:  3,350. 

G.  Total.  Average  number  of  copies  each  issue  during 
preceding  12  months:  41.693.  Actual  number  of 
copies  of  single  issue  published  nearest  to  filing  date: 
45.037. 

I  certify  that  the  statements  made  by  me  above  are  correct  and 


Laird  Foshay.  Publisher 


Dr.  Dobb's  Journal,  December  1985 


December  1985 
Volume  10,  Issue  12 


Dr.  Dobb’s  Journal 


ARTICLES 


Windowing  Operating  Environments 

by  Michael  Swaine 

BANKSWAP:  A  Banked  Memory  Debugging 

Tool  for  CP/M  Plus 

by  Albert  S.  Woodhull 

Adding  a  Copy  Command  to  ProDOS 

by  Shawn  Day 

CP/M-68K  Conditional  Batch  Processing 

by  Roger  E.  Donais 


ProDOS  and  the  RAM  Disk 

by  Alfred  Steele 


We  talked  to  the  developers  behind  GEM,  TopView,  and 
Windows  to  find  out  what  these  operating  environments 
could  mean  to  software  developers.  Reader  Ballot  No.  1 92. 

CP/M  Plus  can  address  a  megabyte,  but  debugging 
beyond  the  first  64K  bank  is  a  problem.  Reader  Ballot  No. 
193. 

COPY  solves  the  problem  of  copying  files  between  volumes 
without  losing  a  BASIC  program  in  memory.  Reader  Ballot 
No.  194. 

Efficient  programming  requires  tools  for  automatically 
compiling,  assembling,  and  linking  with  termination  on 
error.  IF.68K  extends  CP/M-68K  in  that  direction.  Reader 
Ballot  No.  195. 

Here’s  how  one  programmer  developed  a  RAM-disk  driver 
for  ProDOS.  Reader  Ballot  No.  196. 


28 

34 

54 

70 

84 


COLUMNS 

Dr.  Dobb's  Clinic  14  The  Clinic  closes  its  doors.  Reader  Ballot  No.  190. 
by  D.E.  Cortesi 

C  Chest  1  6  Support  routines  for  a  new  shell  for  MS  DOS;  An  fgets 
by  Allen  Holub  that  edits.  Reader  Ballot  No.  1 91 . 

1  6- Bit  Software  Toolbox  102  Readers  flame  on  Microsoft  Assembler;  notes  on 
by  Ray  Duncan  converting  to  MS  DOS  3.0.  Reader  Ballot  No.  1 97. 

Mac  Toolbox  110  Concluding  the  MacSCSI  interface  series.  Reader  Ballot 
by  John  Bass  No.  1 98. 


CP/M  Exchange  114  In  honor  of  our  focus  on  operating  system  extensions,  our 
by  W.  E.  Wilson  guest  columnist  tells  of  a  product  that  makes  CP/M  a  big 

operating  system.  Reader  Ballot  No.  1 99. 


DEPARTMENTS 

Editorial  6 
Letters  8 
Advertiser  Index  128 


Dr.  Dobb's  Journal,  December  1985 


5 

873 


EDITORIAL 


It’s  been  a  litigious  autumn  in  the  software  industry.  At  press  time,  Apple 
had  forced  DRI  into  changing  the  GEM  Desktop;  Apple  lawyers  were 
eyeing  Atari  and  Microsoft;  and  Atari,  anxious  to  get  its  software  into 
ROM,  was  reportedly  ready  to  challenge  Apple.  Apple’s  contention  that  its 
copyright  protected  not  only  the  expression  of  algorithms  in  code  but  also 
the  visual  expression  of  the  desktop  (and  implicitly  that  it  had  invented  that 
desktop)  had  yet  to  be  tested  in  court. 

Adapso  had  been  running  ads  attempting  to  educate  software  users 
about  the  criminal  nature  of  unauthorized  copying  of  software.  Several 
software  publishers  had  brought  suit  against  customers  to  aid  in  the  peda¬ 
gogical  process — all  in  reaction  to  a  perceived  loss  of  (potential)  revenues 
and  all  very  reminiscent  of  a  campaign  by  the  author  of  MITS  BASIC  a 
decade  earlier.  It  wasn’t  a  good  tactic  then  to  call  your  (potential)  custom¬ 
ers  thieves  and  probably  isn’t  now,  but  how  are  authors’  and  publishers’ 
investments  in  software  to  be  protected  without  alienating  users? 

Vault,  a  manufacturer  of  a  copy-protection  product,  concluded  that  a 
Canadian  company  must  have  illegally  disassembled  Vault’s  code  in  order 
to  produce  its  unlocking  product;  so  Vault  found  a  state  in  which  it  thought 
it  could  get  a  decision  in  its  favor  and  sued.  Vault  lost,  but  one  decision  in  a 
Louisiana  court  hardly  dissipates  the  moral  ambiguity  that  surrounds  the 
entire  copy-protection  issue.  Do  we  really  want  this  escalating  arms  race 
between  protectors  and  crackers? 

The  Vault  case  dealt  with  trade  secrets.  Copyright  applies  to  works  that 
are  in  some  manner  made  public.  No  author  of  a  book  would  object  to 
someone  analyzing  his  work  in  order  to  understand  it — disassembling  it,  if 
you  will.  Trade  secrets,  on  the  other  hand,  are  necessarily  secret.  Whom  do 
trade  secrets  protect,  and  whom  does  copyright  protect?  Many  software 
publishers  treat  the  two  protection  methods  as  compatible,  but  does  it  real¬ 
ly  make  sense  that  you  can  protect  as  a  trade  secret  something  protected 
under  copyright  law?  As  a  book  reviewer  I  could,  under  the  fair-use  inter¬ 
pretation  of  copyright  law,  excerpt  and  analyze  a  work  of  literature.  Could 
I,  as  a  software  reviewer,  disassemble  and  publish  portions  of  commercial 
programs  to  show  the  workings  of  key  algorithms? 

These  questions  suggest,  at  the  very  least,  that  not  everyone  is  of  one 
mind  regarding  the  protection  of  intellectual  property.  They  also  suggest 
something  more  alarming;  that  precedents  are  being  set  and  opinions  are 
being  molded  by  bluster  and  threat,  by  cases  that  never  come  to  court,  and 
by  venue-choosing  ploys  that  take  advantage  of  differing  levels  of  under¬ 
standing  of  the  issues  in  different  states.  It  shouldn’t  be  that  way. 

You  could  do  something  about  it.  You  probably  know  more  about  the 
issues  than  do  most  journalists,  judges,  or  jurors.  And  I  suspect  you  may 
have  more  empathy  for  the  user  than  does  the  average  software  publisher, 
who  often  has  stockholders  to  answer  to.  I  urge  you  to  make  your  expertise 
available  to  those  making  decisions  with  long-term  implications.  Educate 
lawyers  and  editors. 

Become  an  expert  witness  for  rational  software  law. 

y/i. 


Michael  Swaine 


6 

874 


Dr.  Dobb' s  Journal,  December  1985 


LETTERS 


Small-C 

Dear  DDJ , 

The  Small-C  problem  reported  by 
Gregor  Owen  (Letters,  July  1985)  has 
already  been  identified,  fixed,  and  re¬ 
ported  to  Small-C  users  on  my  mailing 
list.  It  also  appears  in  the  “Small-C 
Update”  article  (DDJ,  August  1985). 

As  it  turns  out,  Owen’s  change 
does  not  address  the  real  problem  and 
introduces  serious  problems  of  its 
own.  It  is  generally  not  a  good  idea 
simply  to  remove  functions  from  a 
program  even  if  they  are  referenced 
only  once.  Most  programmers  have 
reasons,  other  than  the  introduction 
of  bugs,  for  placing  even  poorly  docu¬ 
mented  functions  in  programs. 

The  function  result(  )  serves  the  vi¬ 
tal  role  of  causing  the  expression  ana¬ 
lyzer  to  note  for  binary  operators 
whether  or  not  the  result  is  an  address, 
and  if  so  whether  it  is  a  character  or 
integer  address.  Removing  it  will 
cause  the  analyzer  always  to  assume 
the  attributes  of  the  left  operand  for 
the  operator  value.  For  example, 
“addrl  —  addr2”  should  yield  an  in¬ 
teger  result  indicating  the  number  of 
objects  (bytes  or  words)  between  the 
two  addresses.  Without  result(  ), 
Small-C  will  think  it  has  another  ad¬ 
dress,  and  that  has  ramifications  for 
the  further  process  of  expression 
evaluation. 

The  real  problem  was  that  the  ana¬ 
lyzer  was  losing  track  of  the  attri¬ 
butes  of  the  lvalue  for  ?=  operators 
when  the  right  side  evaluated  to  an 
address.  The  correction  was  to  make 
hier  1  (  )  (in  CC31.C)  remember  two 
of  the  lvalue  attributes  for  use  at 
store  time.  This  is  done  as  follows: 

1.  define  the  local  integer  array 
lval3[2]  after  lval2[ 8 ]. 

2.  between  “if(k  =  =0)  .  .  and 
“if(lval[  1  ]) .  .  .”  insert 


lval3[0]  =  lval[0]; 
lval3[  1  ]  =  lval[  I  ]; 

3.  change  the  call  to  store(  )  to 
read 

store(lval3); 

Owen’s  change  appeared  to  work 
because  in  his  test  case  the  error  he 
introduced  caused  the  compiler  to 
sidestep  the  original  problem.  The 
troublesome  statement  was: 

1  ptr  +  =  r  —  bytes 

where  “r”  was  an  integer  and  “bytes” 
was  an  array  name  (yields  the  array 
address).  As  mentioned  above,  the  re¬ 
moval  of  result(  )  would  cause  “r  — 
bytes”  to  yield  an  integer  result,  there¬ 
by  avoiding  the  original  problem. 

By  the  way,  one  might  question  the 
meaning  of  an  integer  minus  an  ad¬ 
dress.  K&R  point  out  on  page  99  that 
such  expressions  are  illegal. 

Jim  Hendrix 
Rt.  1,  Box  585 
Oxford,  MS  38655 

Information  Age  Issues 

Dear  DDJ , 

Please  continue  publishing  articles 
like  Dean  Gengle’s  “Information  Age 
Issues”  (June  1985).  Such  ethical  is¬ 
sues  are  too  important  to  leave  in  the 
hands  of  a  systems  designer. 

I  must,  however,  take  issue  with 
Gengle’s  comment  that  “no  one  is 
talking  about  our  rights  to  access  in¬ 
formation  that  is  not  private  and  that 
was  collected  legally  at  taxpayers’  ex¬ 
pense.  Census  data  and  Library  of 
Congress  information  come  to  mind.” 

Your  readers  should  know  that 
both  the  American  Library  Associa¬ 
tion  Government  Documents  Round¬ 
table  and  the  Special  Libraries  Asso¬ 
ciation  Government  Relations 
Committee  have  indeed  been  talking 


about  it.  In  addition,  the  Joint  Com¬ 
mittee  on  Printing  of  the  Congress, 
which  oversees  the  Government 
Printing  Office,  has  been  very  con¬ 
cerned  that  material  produced  in  ma¬ 
chine-readable  form  by  Federal 
agencies  has  not  been  made  available 
to  the  GPO  for  distribution  to  the 
public  through  the  depository  librar¬ 
ies  because  it  is  not  “printed.” 

Librarians  protested  strongly  the 
distribution  of  census  data  in  micro¬ 
fiche-only  format  because  such  data 
is  not  readily  accessible  to  the  poor  or 
illiterate.  Distribution  on  magnetic 
tape  is  a  great  service  to  those  who 
can  afford  to  have  the  tapes  pro¬ 
cessed  but  may  harm  those  who  de¬ 
pend  on  such  data  for  distribution  of 
entitlement  program  benefits. 

Write  to  your  representatives  and 
and  urge  them  to  support  the  efforts 
of  the  Joint  Committee  on  Printing, 
the  ALA,  and  SLA. 

I’m  not  sure  what  Gengle  is  refer¬ 
ring  to  by  “Library  of  Congress  in¬ 
formation.”  He  may  be  suffering 
from  the  misinformation  provided  by 
an  episode  of  the  television  program 
“Whiz  Kids”  in  which  the  elfin  he¬ 
roes  broke  into  the  Library  of  Con¬ 
gress  computers  and  did  a  homework 
assignment.  I  doubt  if  there  are  any 
files  at  the  LC,  or  anywhere  else  for 
that  matter,  that  could  produce  the 
results  obtained.  In  fact,  the  files  at 
the  LC  are  bibliographic,  containing 
references  to  literature,  but  not  full 
texts — at  least  not  yet.  Not  only  are 
these  bibliographic  files  available  to 
the  public  at  the  LC  but  they  are 
available  on  magnetic  tape  to  librar¬ 
ies  and  other  institutions  throughout 
the  world.  Many  libraries  located  in 
this  country  access  those  records 
through  OCLC,  a  bibliographic  utili¬ 
ty  with  over  1 1  million  records.  Some 
agencies  do  apparently  try  to  hide 


Dr.  Dobb’s  Journal,  December  1985 


8 

875 


their  information  from  the  public, 
but  the  Library  of  Congress  certainly 
does  not. 

Bruce  B.  Cox 
Documents  Librarian  and 
Automation  Committee 
Linda  Hall  Library 
5109  Cherry  Street 
Kansas  City,  MO  64 1 1 0 
C  Compiler  Review 
Dear  DDJ, 

I  read  with  great  interest  your  Au¬ 
gust  1985  review  of  MS  DOS  C  com¬ 
pilers.  I  have  two  comments  I  would 
like  to  interject  into  the  debate. 

First,  regarding  the  review  of  the 
documentation  —  this  is  always  a 
touchy  area,  being  rather  subjective 
in  impact — I  am  not  convinced  that  a 
C  compiler  manual  should  include 
what  is  essentially  a  rehash  of  the 
standard  K&R  syntax  chapters  nor  a 
rehash  of  the  standard  Unix  manual’s 
chapters  2  and  3.  That  is,  unless  a 
compiler  and  run-time  library  devi¬ 
ate  from  these  accepted  standards,  I 
see  no  reason  to  repeat  them.  It  only 
serves  to  confuse.  As  an  example,  I 
find  the  Lattice  C  documentation  dif¬ 
ficult  to  use  because  I  generally  ex¬ 
pect  it  to  be  similar  to  the  Unix  man¬ 
ual.  It  isn’t,  and  I  always  get  lost 
trying  to  find  something. 

It  is  much  better  simply  to  include 
a  “pointer”  to  the  appropriate  docu¬ 
mentation  with  the  caveat  that  any 
functions  and/or  syntactic  notes  in¬ 
cluded  in  the  product’s  manual  imply 
a  deviation  from  the  norm.  Sort  of 
like  a  “search  path”  if  you  will.  One 
uses  the  Unix  and  K&R  manuals  un¬ 
less  the  product’s  manual  overrides 
them. 

Second,  the  authors  did  not  include 
as  a  criterion  of  judgement  the  num¬ 
ber  of  other  products  that  support  a 
particular  compiler.  For  instance,  al¬ 
though  Lattice  C  does  not  necessarily 
produce  the  tightest  code  nor  run  the 
quickest,  it  is  important  that  one  can 
obtain  interfaces  to  major  products. 
The  Oracle  DBMS  includes  a  host- 
language  interface  (HLI)  to  Lattice 
C,  and  a  HLI  to  dBASE  II  and  III  can 
be  obtained  from  Lattice.  Such  inter¬ 
faces  are  of  great  significance,  and  in 
these  systems  the  choice  of  compiler 
will  hinge  entirely  upon  that  single 
criterion.  It  is  better,  in  other  words, 
to  put  up  with  slightly  slower  execu- 

10 


tion  than  to  have  to  generate  in  house 
an  entire  nontrivial  HLI. 

Otherwise,  I  was  pleased  with  the 
review — it  was  obviously  a  strong  ef¬ 
fort.  It  is  gratifying  to  this  C  and 
Unix  nut  to  see  the  wide  interest  in 
these  tools  for  programmers. 

Brian  Jay  Wu 

P.O.  Box  203 

Newbury  Park,  CA  91320 
Dear  DDJ, 

When  I  heard  that  your  magazine 
was  going  to  do  a  review  of  C  compil¬ 
ers  and  interpreters,  I  couldn’t  wait 
to  read  it. 

The  cover  for  the  August  1 985  issue 
claims  that  the  review  of  C  compilers 
is  definitive.  I  think  the  review  is  mar¬ 
ginally  useful  and  generally  mislead¬ 
ing.  Information  important  to  the  po¬ 
tential  user  was  omitted.  A  list  of 
considerations  relating  to  the  useful¬ 
ness  of  the  various  C  compilers 
follows: 

1.  Good  luck  to  anyone  who  pur¬ 
chases  the  Microsoft  compiler  Ver¬ 
sion  3.0  without  a  hard  disk!  The  four 
passes  of  the  compiler  total  290K,  the 
CL. EXE  driver  takes  27K,  the  linker 
takes  41  K,  and  the  libraries  needed 
total  at  least  1 20K.  Add  it  up  and  you 
get  a  whopping  478K. 

it  is  becoming  common  to  run  a 
compiler  from  inside  vour  editor. 
COMMAND.COM  needs  about  18K 
and  a  programmer’s  editor  may  need 
1 00K  or  more.  We  haven’t  even  start¬ 
ed  to  include  a  debugger,  assembler, 
other  libraries,  object  modules,  or 
header  files.  In  general,  it  would  be 
nice  to  know  the  amount  of  space 
needed  by  the  compiler,  libraries, 
linker,  and  other  assorted  software 
the  compiler  needs  to  run. 

2.  The  object-module  format  of  the 
compilers  was  not  mentioned.  Micro¬ 
soft  compatibility  may  be  an  impor¬ 
tant  issue  for  some  people,  especially 
those  who  buy  libraries. 

3.  The  assembler  that  comes  with  the 
Mark  Williams  compiler  does  not  use 
Intel  mnemonics. 

4.  The  review  mentions  that  the  large 
model  for  Lattice  allows  disabling  of 
pointer  range  checking,  which  avoids 
a  wraparound  on  a  64K  boundary 
when  doing  pointer  arithmetic.  No¬ 
where  was  it  mentioned  how  perilous  | 
such  an  undertaking  can  be  on  a  pro¬ 


ject  of  any  size! 

5.  Several  of  the  compilers  support 
in-line  assembly,  which  is  extremely 
useful.  I  did  not  see  any  mention  of 
the  DeSmet  or  DR  I  compilers  sup¬ 
porting  that  feature. 

6.  The  DeSmet  debugger,  an  excel¬ 
lent  tool,  wasn’t  even  looked  at! 

7.  The  DeSmet  compiler  doesn’t  sup¬ 
port  the  large  memory  model  but 
does  come  with  an  overlay  linker. 

8.  Although  it  can  be  useful  to  look  at 
the  size  of  the  .EXE  file  produced  by 
a  compiler  and  linker,  sometimes  you 
want  to  know  the  sizes  of  the  code 
and  data  produced  by  the  compiler  as 
well.  Will  the  compiler  tell  you  those 
statistics? 

9.  The  reviewers  didn’t  mention 
tradeoffs  between  compile/link  time 
vs.  speed  of  executable  code  (using  an 
optimizer  pass,  for  example). 

10.  The  fact  that  Lattice  and  Com¬ 
puter  Innovations  automatically  pro¬ 
vide  run-time  stack-overflow  check¬ 
ing  was  not  mentioned. 

I  would  like  to  have  seen  more  of 
an  attempt  to  separate  out  evalua¬ 
tions  of  the  compilers  from  the  asso¬ 
ciated  libraries.  I  couldn’t  care  less 
about  the  performance  of  libraries, 
since  for  applications  that  require 
speed  and  efficiency  I  write  my  own 
routines. 

I  have  a  second  reason  for  writing. 
It  seems  to  me  that  people  deciding 
which  compiler  to  choose  might  ben¬ 
efit  from  feedback  from  program¬ 
mers  actually  using  C  compilers. 
Since  C  compilers  are  used  in  a  wide 
variety  of  applications  and  environ¬ 
ments  under  MS  DOS,  readers  would 
likely  get  different  kinds  of  informa¬ 
tion  than  a  reviewer  would  provide. 
Why  not  allocate  space  for  a  running 
column  where  readers  can  sound  off 
about  their  favorite  compiler  and  give 
reasons  why  they  like  it? 

If  you  readers  like  that  idea,  write 
to  DDJ  about  the  compiler  you  like  to 
work  with,  mentioning  aspects  that 
haven’t  been  covered.  [We  would 
welcome  this  input. — Ed.]  Include 
your  work  environment  (network, 
single-user,  etc.);  machine;  any  idio- 
syncracies  or  work  requirements  on 
programming  style;  and  why  the 
compiler  you  work  with  is  the  great¬ 
est  thing  since  DDJ. 

Tom  Hogan 

Dr.  Dobh's  Journal,  December  1985 


DR.  DOBB'S  CLINIC 


Clinic  to  Close 

they  graced  by  their  presence,  in  the 

Dugan,  Dan 

4/82 

The  Clinic  opened  for  business  in 

following  list.  For  every  one  of  those 

Edgar,  Gerald 

7/81 

May  1981.  Now,  four  and  one  half 

mentioned  there  were  often  two, 

English,  Glenn 

11/85 

years  later,  career  changes  compel 

three,  or  a  dozen  more  who  had  the 

Enterline,  Tom 

8/84 

me  to  close  it  down.  The  landlord  will 

same  insight  but  wrote  later  or  in  a 

Falk,  Richard 

3/84 

soon  install  new  and  interesting  en- 

less  quotable  way. 

My  personal 

Farmer,  Wayne 

8/81, 10/81 

terprises  on  the  premises,  but  I 

thanks  to  all  who  wrote — it’s  been  a 

Fay,  Jack 

11/85 

thought  I’d  use  our  last  meeting  to 

privilege  to  associate 

with  such  a 

Feign,  David 

8/84 

look  back  and  say  thanks. 

classy  bunch.  You  folks  keep  up  the 

Floyd,  Chet 

9/84,  2/85 

Two  things  come  to  mind  when  I 

good  work,  you  hear? 

Fordham,  Malcom 

4/84 

reflect  on  the  column.  The  first  is 

Fritz,  Randolph 

5/83,  6/83 

trivial:  that  damned  editorial  We.  I 

Allison,  Dennis 

8/81, 10/81 

Gardner,  Calvin  L. 

8/84 

can’t  say  why  I  hobbled  myself  with 

Amelang,  Loren 

2/83 

Glassey,  C.  Roger 

12/81 

that  foolish  affectation  in  the  first 

Asay,  Matt 

10/81 

Goldman,  Oscar, 

5/84 

column  nor  why  I  didn’t  drop  it  in  the 

Ashley,  Allen 

7/81, 10/81 

Goodman,  Bob 

7/81 

second,  but  I  did  and  then  I  didn’t 

Backus,  Rex 

4/85 

Grant,  Steve 

8/84 

and  from  then  on  I  felt  compelled  to 

Bacon,  Phillip 

7/81 

Greenlaw,  Richard 

10/81 

continue  an  established  style. 

Baenziger,  Peter 

8/84 

Gunn,  John  Paul 

2/85 

The  strongest  impression  I  retain  is 

Barker,  L. 

7/81, 10/81, 

Plahn,  Harvey 

5/83 

of  the  quality  of  the  DDJ  readership. 

11/81, 10/82, 

Hammond,  Nick 

10/82,  6/83, 

As  originally  conceived,  this  column 

10/83 

10/83,  10/85 

was  to  be  written  mostly  by  its  read- 

Barker,  David 

11/85 

Head,  Gene 

12/81 

ers  with  the  Intern  editing  and  sup- 

Barr,  Michael 

5/84, 8/85 

Hills,  Terence 

11/81 

plying  continuity,  like  a  radio  talk 

Bates,  Dan 

9/81 

Hoats,  David  L. 

12/81 

show  in  slow  motion.  It  never  quite 

Be'cus,  Georges  A. 

9/81 

Hoffman,  Lee  H. 

2/85 

worked  out;  you  readers  never  pro- 

Behler,  Alan 

5/83, 11/83 

Hole,  Dr.  William  T. 

2/85 

vided  enough  original  material  to 

Bomberger,  Alan 

10/83 

Hoolko,  Bob 

7/81 

make  a  column. 

Brickell,  Gavin 

10/82 

Howell,  Edgar 

4/82 

But  you  responded  when  your  in- 

Briggs,  R.  C. 

8/84 

Howell,  Jim 

5/82,  2/84, 

terest  was  caught — my  goodness  how 

Brown,  Russel 

8/81 

4/85 

you  responded!  I  posed  Don  Taylor’s 

Butler,  Edmund 

7/81 

Hutchinson,  Aubrey 

2/83-6/83 

highly  abstruse  problem  in  the  math- 

Cage,  Thomas  W. 

2/85, 6/85 

Hutchinson,  W.  G. 

4/82 

ematics  of  graphics  and  a  dozen  of 

Caldwell,  Taylor 

8/81 

Irving,  Dwight 

11/83 

you  sent  detailed,  multipage  discus- 

Canniff,  Paul 

4/85 

Jackson,  Terry 

2/85 

sions  heavy  with  matrix  notation.  I 

Carr,  Thomas  E. 

9/81 

Jensen,  John  Thayer 

6/83 

put  up  David  Ross’s  BASIC  program 

Carter,  Everett 

8/85 

Johnson,  Bruce  R. 

2/85 

and  20  of  you  sent  line-by-line  discus- 

Castle,  Pat 

2/85 

Johnston,  Bill 

12/81 

sions  of  what  it  did  and  how  to  do  it 

Chrapkiewicz,  Thomas 

8/84 

Jones,  James 

4/85 

better.  Although  the  column  was  nev- 

Clark,  David 

2/84 

Kellogg,  David 

6/83 

er  written  by  readers  in  the  way  origi- 

Crowfoot,  Norman 

9/81 

Kendall,  Bruce 

8/82 

nally  planned,  you  did  supply  a  lot  of 

Dale,  Gary 

1/85 

King,  David 

2/84 

material,  correct  a  lot  of  my  mis- 

Davis,  Charles 

5/85 

Knipp,  Ernest 

10/82,  2/83 

takes,  and  lend  me  your  enthusiasm 

Davis,  M.  Patton 

10/81 

Koehler,  W.  B. 

11/83 

over  the  years. 

Dayhoff,  Edward  S. 

11/81 

Komusin,  Bruce 

4/82,  5/82 

Many  of  those  who  wrote  were 

Dempsey,  J. 

3/83 

Lane,  Tom 

7/82 

made  into  co-columnists  and  quoted 

Dinsmore,  Perry 

11/84 

Laurino,  James 

1/82,7/83 

in  the  various  issues  by  name.  They 

Dodd,  Michael  S. 

11/85 

Leonard,  M.  David 

7/81 

are  named  once  more,  with  the  issues 

Driscoll,  Michael 

9/81 

Levy,  David 

7/82 

Dr.  Dobb’s  Journal,  December  1985 


14 

877 


Lurie,  Robert 

10/81 

McDaniel,  C.  T. 

7/81 

McDermott,  Joseph 

3/84 

McKeon,  Brian 

4/85 

McLanahan,  David 

1/84, 4/84, 
8/84 

McWorter,  William  A. 

9/81, 5/83 

Manay,  Martin 

10/81 

Manns,  Larry 

11/85 

Marchand,  Fred 

1/85 

Marshall,  Charles 

3/84 

Martin,  Charles 

2/85 

Martin,  John  H. 

8/81 

Martin,  Robert 

10/81 

Medlock,  C.  W. 

5/84 

Mesirov,  Dick 

3/84 

Metzenthen,  Bill 

8/84 

Meyer,  Mike 

10/84 

Montgomery,  Owen 

5/84 

Moore,  Brian 

3/83 

Morgan,  John 

10/81 

Neuhaus,  Prof.  H.  J. 

8/83 

Newberry,  Steve 

2/84 

Norling,  Richard  D. 

11/84 

Northrup,  Mark 

11/81 

Novosielski,  Gary  P. 

11/81 

Nowell,  Scott 

11/81 

Oakland,  Richard  L. 

11/85 

Oster,  David 

4/83 

Palmer,  John 

10/82 

Pasky,  Robert 

8/82 

Pearson,  Peter  K. 

8/85 

Pierce,  John 

7/81 

Pirko,  Robert 

3/83, 6/83, 
7/83 

Poli,  Ray 

4/82 

Prince,  Dr.  Timothy  C 

2/85 

Pritchard,  Steve 

9/81 

Pulver,  Dusty 

5/82 

Purdum,  Jack 

2/84 

Rivers,  Wayne 

1/85 

Roberts,  Glenn  F. 

7/84,  8/84 

Rockwell,  Charles 

10/81 

Ross,  David 

6/85, 8/85 

Rouman,  Jay  S. 

11/85 

Rutiser,  William  A. 

8/84 

Saal,  Harry 

7/81 

Sabin,  Joseph 

3/84, 2/85 

Sackheim,  Andrew 

4/82 

Safier,  Orin 

11/85 

Salemma,  A. 

1/85 

Samuel,  Dan 

4/82 

Sand,  Paul 

1/85 

Scardelis,  Jim 

4/84 

Schafer,  Dale 

7/81 

Schemm,  John  B. 

2/85 

Schuetz,  Chuck 

4/85 

Schwab,  Leonard 

9/84 

Semos,  Steve 

11/81 

Shapin,  Ted 

4/84 

Shaw,  David  A. 

1/82,4/82 

Shaw,  Jim 

5/82 

Shochat,  David 

11/85 

Shuman,  Dave 

11/85 

Smith,  Billy 

11/81 

Smith,  Burks 

10/82,  2/83 

Smith,  Roger 

8/85 

Snow,  J.  R. 

8/81 

Stanek,  Mitchell 

9/81 

Sullivan,  Dave 

4/84 

Swearingen,  Roger  V.  2/85 

Taylor,  Bob 

6/83 

Taylor,  Don 

4/82,  8/82, 
5/83 

Thier,  U. 

7/84 

Tilton,  David  S. 

8/84,  10/84, 
1/85,4/85 

Throckmorton,  Jerry 

10/81 

Trout,  Dana 

10/82 

Wagner,  R.  C. 

7/84 

Watzman,  Barry 

8/85 

Wendel,  J.  G. 

8/81 

Wilkinson,  Bill 

7/82 

Wilson,  Ed 

7/81 

Wochnik,  Michael 

8/81 

Wright,  Don 

9/82 

Vieau,  Dean 

9/81 

DDJ 


Reader  Ballot 

Vote  for  your  favorite  feature/article. 
Circle  Reader  Service  No.  1 90. 


15 

ft  7ft 


Dr.  Dobb's  Journal,  December  1985 


C  CHEST 


A  New  Shell  for  MS  DOS,  Part  1: 

IBM  Cursor  Control  and  an  Fgets  that  Edits 

by  Allen  Holub 


Back  in  March  we  presented  a  new 
root  module  for  the  CP/M-80  version 
of  the  Aztec  C  compiler.  Since  then, 
several  people  have  written  to  say 
that  the  proper  place  for  wildcard  ex¬ 
pansion,  pipes,  redirection,  and  the 
like  is  in  the  shell  (i.e.,  the  command 
interpreter),  not  in  the  C  root  mod¬ 
ule.  There’s  no  point  in  bloating  the 
size  of  your  programs  when  you  don’t 
have  to.  I  sympathize  with  this  atti¬ 
tude,  but  frankly,  I  don’t  want  to 
write  a  new  CCP  for  CP/M.  Digital 
Research  just  makes  this  process  too 
hard. 

MS  DOS,  however,  is  another  mat¬ 
ter.  The  command  interpreter,  com¬ 
mand. com,  is  just  a  program  and  can 
be  replaced  with  any  other  program. 
Moreover,  the  DOS  interface  library 
provides  much  more  in  the  way  of 
system  level  functions  than  does  CP/ 
M.  Most  important,  DOS  Version  2 
and  higher  supports  an  exec  function 
that  allows  one  program  to  load  and 
execute  a  second  program. 

Because  most  of  my  gripes  about 
MS  DOS  are  really  aimed  at  the  be¬ 
havior  of  command.com,  it  seemed  to 
me  that  writing  a  new  shell  for  MS 
DOS  would  be  more  productive  than 
complaining  for  the  rest  of  my  life 
about  the  features  I  didn’t  have.  C  is, 
after  all,  a  language  remarkably  well 
suited  for  writing  operating  systems. 
So,  over  the  next  few  months  we  will 
look  at  a  new  shell  for  MS  DOS. 

I’ve  implemented  most  of  what  I 
like  about  the  the  Unix  C  and  Bourne 
shells:  command  line  wild  card  ex¬ 
pansion,  redirection,  history,  shell 
variables,  a  reasonable  language  for 
writing  shell  scripts  (.bat  files  under 
MS  DOS)  and  the  lot.  I’ve  also  taken 
the  opportunity  to  add  a  couple  of 
features  that  I’ve  often  wished  were 
present  in  the  Unix  shells,  such  as  an 
interactive  command  line  editor. 


String  Routines 

We’ll  start  looking  at  the  shell  itself 
next  month.  This  month  we’re  going 
to  look  at  several  support  routines.  As 
usual,  these  are  all  designed  to  be 
useful  in  their  own  right.  They  make 
up  five  sets.  The  first  three  listings 
are  routines  for  string  manipulation. 
They  are  small,  but  useful.  Next(  ) 
(Listing  One  page  19)  gets  from  a 
string  the  next  object  separated  by  a 
delimiter.  Its  calling  syntax  is: 

char  *next(  linep,  delim,  esc  ) 

char  **linep; 

int  delim,  esc; 

Linep  is  the  address  of  a  pointer  that 
points  at  the  first  character  of  the 
string  to  be  processed.  Delim  is  the 
delimiter  that  separates  objects.  Esc 
is  an  escape  character.  The  routine 
skips  leading  white-space  (a  space  or 
tab)  and  remembers  the  position  of 
the  first  non-white  character.  It  then 
looks  for  a  delimiter  not  preceded  by 
an  escape  character  and  replaces  the 
delimiter  with  a  ‘\0.’  It  returns  a 
pointer  to  the  remembered  non-white 
character  and  it  modifies  *linep  to 
point  to  the  first  character  beyond 
where  the  delimiter  used  to  be.  It 
won’t  modify  *linep  to  point  past  the 
end  of  the  string.  If  delim  is  a  white 
character,  leading  white  space  won’t 
be  skipped. 

Listing  Two  (page  19)  and  Listing 
Three  (page  19)  are  two  routines  for 
copying.  Cpy(dest,src)  is  functionally 
the  same  as  strcpy(  ).  It  copies  the 
source  string  (src)  to  the  destination 
string  (dest).  There  is,  however,  one 
difference:  whereas  strcpy  returns  its 
first  argument,  cpy  returns  a  pointer 
to  the  NULL  at  the  end  of  the  desti¬ 
nation  string.  This  allows  us  to  add 
onto  the  destination  string  without 
first  having  to  scan  through  the  string 


to  find  its  end.  The  second  copy  rou¬ 
tine  is  cptolower(dest,src).  Cptolower 
works  just  like  cpy(  ),  except  all  up¬ 
percase  characters  are  mapped  to 
lower  case  as  they  are  copied. 

Moving  the  Cursor  and  Writing 
Characters 

Listing  Four  (page  20)  is  a  collection 
of  low-level  routines  for  cursor  ma¬ 
nipulation  and  character  output  for 
the  IBM  PC.  These  routines  use  direct 
ROM  BIOS  calls,  and  so  are  much 
faster  than  the  usual  output  routines, 
which  use  the  normal  DOS  func¬ 
tions'.  Moreover,  they  let  you  move 
the  cursor  around  without  having  to 
install  the  ANSI. SYS  driver,  which  is 
excruciatingly  slow.  IBM  has  been 
very  good  about  maintaining  the 
ROM  BIOS  interface  over  different 
versions  of  the  operating  system.  As 
long  as  you  access  the  BIOS  via  the 
correct  interrupt  mechanism,  the 
routines  are  portable. 

I’ve  used  the  int86(  )  function  from 
Lattice  to  generate  the  system  video 
interrupt.  Microsoft  C  has  an  identi¬ 
cal  function,  so  these  routines  will 
port  to  Microsoft  C  without  any 
problems.  The  dos(  )  function  printed 
in  this  column  in  July  1985  can  also 
be  modified  quite  easily  to  generate  a 
video  interrupt.  Just  replace  the  INT 
21 H  instruction  on  line  68  with  an 
INT  10H  instruction. 

Listing  Four  contains  the  following 
routines: 

getpage( ) 

This  routine  gets  the  currently  active 
video  page.  When  you  are  in  text 
mode,  several  pages  are  available  for 
writing,  though  only  one  is  displayed 
at  any  given  time.  Getpage(  )  returns 
the  number  of  the  page  now  being 
displayed.  This  information  is  needed 
by  some  of  the  other  routines. 


Dr.  Dobb's  Journal,  December  1985 


16 

879 


cursize(top,  bot) 

This  routine  adjusts  the  cursor  size. 
Every  character  is  represented  on  the 
display  by  a  fixed  number  of  scan 
lines.  Cursize(  )  causes  the  cursor  to 
extend  from  the  top  scan  line  indicat¬ 
ed  to  the  bottom  line  indicated.  For 
example,  the  standard  monochrome 
display  uses  14  lines  numbered  0-13, 
the  color  adapter  uses  8,  numbered 
0-7.  So,  on  the  color  adapter,  a  nor¬ 
mal  underline  cursor  can  be  set  up 
with  the  call  cursize(6,7).  You  can 
make  a  large  block  cursor  with  the 
call  cursize(0,7).  Cursize(7,0)  cre¬ 
ates  a  two-part  cursor,  one  line  over 
the  letter  and  a  second  line  under  the 
letter.  Cursize(8,8)  will  make  the 
cursor  disappear. 

posn  =  gcur(pagenum); 
int  pagenum; 
short  posn; 

This  routine  returns  the  position  of 
the  cursor  on  the  indicated  video 
page.  The  position  is  returned  in  a 
single  16  bit  short;  the  row  number  is 
in  the  top  byte  and  the  column  num¬ 
ber  is  in  the  bottom  byte. 

scur(posn,  page) 
short  posn; 

This  modifies  the  cursor  to  rest  at 
posn,  which  may  be  a  value  returned 
from  gcur( ). 

posn  =  getcur( ); 
setcur(posn); 

These  routines  work  just  like  scurf  ) 
and  gcur(  )  except  that  they  access 
the  page  currently  being  displayed. 

wchar(c) 
int  c; 

This  routine  writes  a  single  character 
to  the  screen.  It  is  dramatically  faster 
than  putcharf  ) — at  least  the  one  that 
comes  with  the  Lattice  compiler. 
Wchar(  )  moves  the  cursor  as  it 
prints,  just  like  putcharf  ).  However, 
the  only  control  codes  it  recognizes 
are  carriage  return  (\r),  line  feed 
(\n),  bell  (\007),  and  backspace 
(\b),  so  don’t  expect  it  to  expand 
tabs.  Also,  \n  is  not  interpreted  as  a 
“newline,”  but  as  a  line  feed.  That  is, 
it  will  get  you  to  the  current  column 
on  the  next  line.  Use  \r  to  get  to  the 
left  edge  of  the  current  line. 

17 

nnn 


wstrfs,  move) 
char  *s; 
int  move; 

This  routine  prints  an  entire  string  to 
the  screen.  If  move  is  true,  the  cursor 
ends  up  positioned  just  past  the  end 
of  the  printed  string.  Otherwise,  the 
cursor  remains  over  the  first  charac¬ 
ter  of  the  string.  Wstr(  )  uses 
wcharf  )  as  its  output  function. 

An  Fgets(  )  that  Edits 

Listing  Five  (page  20)  is  an  editing 
input  function.  Let  me  point  out  at 
the  start  that  this  function  is  general 
purpose  in  nature,  even  though  it’s 
written  specifically  for  an  IBM  PC. 
It’s  a  relatively  simple  matter  to  mod¬ 
ify  the  routines  for  any  terminal  that 
has  an  addressable  cursor.  The  pri¬ 
mary  access  routine  in  this  module  is 

char  *efgets(buf,  maxline,  fp) 
char  *buf; 
int  maxline; 

FILE  *fp; 

This  is  functionally  similar  to 
fgets(  ),  though  there  are  several  ma¬ 
jor  differences.  First,  a  pointer  to  the 
end  (instead  of  to  the  start)  of  the 
loaded  buffer  is  returned  on  success. 
A  NULL  is  still  returned  on  end  of 
file.  Second,  line  continuation  is  sup¬ 
ported.  If  a  line  ends  with  a  backslash 
(\),  the  backslash  is  deleted  and  the 
line  is  concatenated  with  the  next 
line.  The  major  differences  from 
fgets(  )  are  apparent  when  fp  is  set  to 
stdin.  In  this  case  several  interactive 
editing  functions  are  supported: 

•LEFT-CURSOR  (hit  the  left  cursor 
key)  moves  the  cursor  to  the  left 
without  erasing  anything. 

•  RIGHT-CURSOR  moves  the  cursor 
to  the  right. 

•  “LEFT-CURSOR  (hold  down  the 
CTRL  key  and  hit  the  left  cursor  key) 
positions  the  cursor  at  the  beginning 
of  the  previous  word. 

•  "RIGHT-CURSOR  positions  the 
cursor  at  the  beginning  of  the  next 
word. 

•  HOME  positions  the  cursor  at  home 
position  on  the  current  line.  Home  is 
defined  as  the  position  of  the  cursor 
when  the  routine  was  entered.  For  ex¬ 
ample,  if  the  cursor  was  in  column  5 


when  efgets(  )  was  called,  HOME 
puts  it  back  in  column  5.  In  fact,  in 
this  example  there’s  no  way  to  get  the 
cursor  into  columns  1-  4  from  efgets. 
If  a  line  is  continued  with  a  \<CR> 
combination,  the  cursor  is  moved  to 
the  HOME  position  of  the  next  line, 
not  to  the  left  edge  of  the  screen. 

•  END  positions  the  cursor  just  after 
the  rightmost  character  on  the  line. 

•  ESC  is  for  an  abort.  The  buffer  is 
cleared,  but  the  characters  on  the 
screen  are  not  erased.  The  routine  re¬ 
turns  immediately.  -1  is  returned 
when  an  ESC  is  encountered. 

•  DEL  deletes  the  character  on  which 
the  cursor  is  resting  and  closes  up  the 
rest  of  the  line  to  fill  the  gap.  (i.e.,  if 
you  delete  the  X  in  the  string 
“aaaaXoooo,”  you  end  up  with 
“aaaaoooo”  on  the  screen,  rather 
than  “aaaa  oooo”). 

•  'H  is  a  destructive  backspace.  It 
moves  the  cursor  left  one  space  and 
then  deletes  that  character,  closing 
up  the  line  to  fill  the  gap. 

•  "X  erases  the  entire  line  and  clears 
the  buffer.  However,  efgets(  )  doesn’t 
return. 


Dr.  Dobb  s  Journal,  December  1 985 


•  *M  (CR)  or  Enter  (LF)  positions 
the  cursor  at  the  left  edge  (not  the 
home  position)  of  the  next  line  and 
cause  efgets(  )  to  return  to  the  caller. 
A  pointer  to  the  end  of  the  buffer  (to 
the  \0)  is  returned  on  success,  NULL 
on  end  of  file. 

Typing  any  printing  character  causes 
that  character  to  be  printed  at  the 
current  cursor  position  and  the  cursor 
to  move  right  one  space.  Typing  any¬ 
thing  else  has  no  effect.  The  cursor  is 
never  allowed  to  go  past  the  end  of 
the  buffer,  as  specified  by  the  param¬ 
eter  maxline  in  the  efgets(  )  call. 
However,  when  in  editing  mode,  the 
cursor  is  not  allowed  to  go  past  the 
end  of  the  current  line,  even  if  max¬ 
line  is  longer  (the  bell  will  ring  if  you 
try).  In  this  case,  you  can  get  to  the 
next  line  with  a  \<CR>,  but  you 
can't  edit  anything  on  the  previous 
line. 

Conclusion 

So  that’s  the  beginning.  Next  month 
we’ll  add  some  more  routines  to  the 
pile  and  incorporate  them  into  a  sim¬ 
ple  MS  DOS  shell.  In  the  following 
month  we’ll  add  various  capabilities 
to  that  shell. 

[IBM  PC  readable  versions  of  the 
listings  for  the  entire  shell  and  for 
MS  DOS  versions  of  various  Unix 
utilities  (e.g.,  grep,  Is)  will  be  made 
available  through  DDJ  in  the  next 
couple  of  months.  Watch  this  column 
for  more  details. — ed] 


Notes 

1  An  excellent  description  of  the  IBM 
BIOS  routines  and  how  to  use  them 
is  in  The  Peter  Norton  Program¬ 
mer’s  Guide  to  the  IBM  PC ,  by  Pe¬ 
ter  Norton  (Bellevue:  Microsoft 
Press:  1985). 

DD) 

(Listing  begins  at  right) 


C  Chest 


(Text  begins  on  page  16) 


Listing  One 


1 

2 

3 

4 

5 

6 

7 

8 
9 

10 

11 

12 

13 

14 

15 

16 

17 

18 

19 

20 
21 
22 

23 

24 

25 

26 

27 

28 

2  9 

30 

31 

32 

33 

34 

35 

36 

3  7 

38 

39 

40 

41 

42 

43 

44 

45 

46 

47 

48 


/*  MRXT.Cs  Skip  to  the  next  delimiter  seperated  object 

* 

*/ 

•define  iswhite(c)  (  (c)  ==  1  1  II  (c)  ==  '\t'  II  (c)  ==  '\n'  ) 


char 
cha  r 
{ 


*next(  linep,  delim,  esc  ) 
*  *1 inep; 


/*  Linep  is  the  address  of  a  character  pointer  that  points  to 

*  a  string  containing  a  series  of  delim  seperated  objects. 

*  Next  will  return  a  pointer  to  the  first  non-white  object  in 

*  *linep,  replace  the  first  delimiter  it  finds  with  a  null,  and 

*  advance  *linep  to  point  past  the  null  (provided  that  it's  not 

*  at  end  of  string).  0  is  returned  when  an  empty  string  is  passed 

*  to  next()  .  v/hite  space  may  be  used  as  a  delimiter  but 

*  in  this  case  white  space  won't  be  skipped.  A  delimiter  preceded 

*  by  "esc"  is  ignored.  Quoted  strings  are  copied  verbatim. 

V 


register  char  *start,  *end  ; 
int  inquote  =  0; 

i f (  ! *  *1 inep  ) 

return  0; 

start  -  *lincp; 

if(  !  iswhite (del im)  ) 

for(  ;  iswhite ( *star t)  ;  start++  ) 


} 


for (  end  =  start;  *end  &&  (*end  !=  delim  I  I  inquote)  ;  end++  ) 

{ 

if  (  *end  ==  esc  &&  *(end+l)  ) 
end^  + ; 


else 


} 


if (  *  end  == 
inquote  = 


II  *end 
“inquote ; 


V  ) 


i f (  *end  ) 

*end++  =  ' \0 ' ; 

♦linep  =  end; 
return  start; 


End  Listing  One 

Listing  Two 


1 

2 

3 

4 

5 

6 

7 

8 
9 

10 

11 

12 

13 


char 

char 

{ 


} 


cptolower(  dest,  src  ) 

♦dest,  *src; 

/*  Copy  src  to  dest,  mapping  all  upper  case  letters  to  lower 

*  case,  src  and  dest  may  be  the  same. 


for(;  *src  ;  src++  ) 

*dest++  =  ('A'  <=  *src  && 
? 

♦dest  =  ' \0 '  ; 
return  dest  ; 


♦src  <=  ' Z  '  ) 

♦src  +  ( 'a'-'A' ) 


♦src  ; 


End  Listing  Two 


Listing  Three 


1:  char 
2:  char 
3:  { 

4  : 

5  : 

6: 

7: 

8: 

9: 

10: 

11 : 

12: 

13:  ) 


*cpy(  dest,  src  ) 

♦dest,  *src; 

/*  Works  like  strcpy  but  returns  a  pointer  to  the  new  end 

*  of  string  (ie.  to  the  null). 

V 

while(  *src  ) 

*dest++  =  *src++  ; 

♦dest  =  0; 
return  dest; 


End  Listing  Three 


Dr.  Dobb's  Journal,  December  1985 


19 

881 


C  Chest 


(Listing  continued,  text  begins  on  page  16) 


Listing  Four 


11: 

* 

int 

getpage 

() 

Get  active  video  page  • 

12: 

* 

void 

cursize 

(top,bot ) 

Set  cursor  size 

13: 

* 

void 

scur 

(posn,  page) 

short  posn; 

Set  cursor  position 

14  : 

* 

short 

gcur 

(pagenum) 

Get  cursor  position 

15: 

* 

void 

setcur 

(posn)  short 

posn; 

Set  cur  pos  on  current  page 

16: 

* 

short 

getcur 

() 

Get  cur  pos  from  cur  page 

17: 

* 

void 

wchar 

(c) 

write  a  single  character 

18: 

* 

wst  r 

wst  r 

(s,  move)  char  *s; 

write  a  string 

19 

20 
21 
22 

23 

24 

25 

26 

27 

28 

29 

30 

31 

32 

33 

34 

35 

36 

37 

38 

39 

40 

41 

42 

43 

44 

45 

46 

47 

48 

49 

50 

51 

52 

53 

54 

55 

56 

57 

58 

59 

60 
61 
62 

63 

64 

65 

66 

67 

68 

69 

70 

71 

72 

73 

74 

75 

76 

77 

78 

79 

80 
81 
82 

83 

84 

85 

86 

87 

88 

89 

90 

91 

92 

93 


•include  <stdio.h> 
•include  "/lc/dos.h" 


VIDBIOS . C 


Various  cursor  and  i/o  routine  using 
the  bios  interrupts  (see  below  for  greater  detail): 

Copyright  (C)  1985  Allen  I.  Holub.  All  rights  reserved. 

Externally  accessable  routines: 


VIDE0_INT 

0x10 

/* 

CUR  SIZE 

0x1 

/* 

SET  POSH 

0x2 

/* 

READ  POSN 

0x3 

/* 

WRITE  TTY 

0  xe 

/* 

GET_VM0DL 

0xf 

/* 

union  REGS 

Regs ; 

Video  interrupt 


V 


Set  cursor  size  */ 

Modify  cursor  posn  */ 

Read  current  cursor  posn  */ 

Write  character  &  move  cursor  */ 
Get  current  video  mode  £>  disp  pg  */ 

- V 


int 

f 


getpage ( ) 


/* 


Returns  the  currently  active  display  page  number 


Regs. h. ah  =  GET_VMODE; 

int 86 (  VIDEO_INT,  iRegs,  iRegs  ); 

return  (int)  Regs.h.bh  ; 


cursize(  top_line,  bot_line  ) 

{ 


V 


Scan  lines  are  numberd  0  at  the  top  and  7  at  the  bottom, 
if  the  two  are  reversed  you'll  get  a  2  part  cursor. 
Top_line  determines  the  position  of  the  top  scan  line 
of  the  cursor,  bot_line  is  the  bottom.  A  normal  cursor 
can  be  created  with  cursi ze ( 6 , 7 ) .  Cursize(0,7)  will 
fill  the  entire  area  occupied  by  a  character.  Cursize(0,l 
will  put  a  line  over  the  character  rather  than  under  it. 


Regs.h.ch  =  top_line  ; 

Regs. h. cl  =  bot_line  ; 

Regs. h. ah  =  CUR_SIZE  ; 

int86  (  VIDEO_INT,  &Regs,  iRegs  ); 


scur(  posn,  pagenum  ) 
short  posn; 


V 


Modify  current  cursor  position.  The  top  byte  of 
value  holds  the  row,  the  bottom  by  the  column. 
Pagenum  is  the  video  page  number. 


short 

( 


Regs.x.dx  =  posn  ; 

Regs.h.bh  =  pagenum  ; 

Regs . h . ah  =  SET_POSN  ; 

int86 (  VI DEO_INT ,  iRegs,  &Regs  ); 


gcur (  pagenum  ) 

/* 


V 


Get  current  cursor  position.  The  top  byte  of  the  return 
value  holds  the  row,  the  bottom  by  the  column. 

Pagenum  is  the  video  page  number. 


Regs.h.bh  =  pagenum  ; 
Regs. h. ah  =  RKAD_POSN  ; 
int 86 (  VIDEO_INT,  fcRegs, 

return(  Regs.x.dx  ); 


iRegs  ) ; 


20 

882 


Dr.  Dobb  s  Journal,  December  1985 


Setcur  and  getcur  work  just  like  scur  and  gcur  except  that  they 
access  the  current  video  page. 


setcur (  posn  ) 
short  posn; 

( 

scur  (  posn,  getpage()  )? 

} 

short  qetcurO 

{ 

return  gcur(  getpageO  ); 

} 


wchar (  c  ) 

{ 

/* 


Write  a  character  to  the  screen  in  TTY  mode.  Only  normal 
printing  characters,  BS,  BEL,  CR  and  LF  are  recognized. 
The  cursor  is  automatically  advanced  and  lines  will  wrap. 


/*  Use  current  color  */ 


Regs.h.bl  =  0;  /*  Use  c 

Regs.h.al  =  c; 

Regs. n. ah  =  WRITE_TTY  ; 

int 86 (  VIDEO_INT,  &Regs,  &Regs  ) 


wstr  (  str,  move_cur  ) 


/* - 

#  i  f def 


Write  a  string  to  the  screen  in  TTY  mode.  If  move_cur  is 
true  the  cursor  is  left  at  the  end  of  string.  If  not 
the  cursor  will  be  restored  to  its  original  position 
(before  the  write). 


register  short  posn; 

i  f  (  1  ir.ove_.cu  r  ) 

posn  -  getcur ( ) ; 

while(  *str  ) 

{ 

Regs.h.bl  =  0  ; 

Regs.h.al  =  *str++  ; 

Regs. h. ah  =  WRITE_TTY  ; 
i r. t C 6  (  VI DEO_I NT ,  &Regs,  &Regs  ); 

} 

if(  lmove_cur  ) 

setcur  (  posn  )  ; 


cursize (  0 ,  7  ) ; 

wstr(  "The  large  cursor  should  be  on  the  'T'",  0  ); 
getcharO  ; 


cursize (  6,  7  ) ; 


End  Listing  Four 


Listing  Five 


1:  ^include  <stdio.h> 
2: 

3;  /*  EFGKTS.C 


An  editing  version  of  efgets.  Recognizes  \<CR> 
line  termination  and  supports  editing  when  input 
is  from  stdin. 


Copyright  (C)  1985  Allen  I.  Holub.  All  rights  reserved. 


Externally  accessable  routines: 
void  ptail  (bp,  end,  move  ) 
char  *egets  (start,  bufsize) 
char  *getl  (buf,  maxline,  fp) 
char  *efgets  (buf,  maxline,  fp) 


Print  string  from  bp  to  end.  Move 
cursor  to  end  if  move  is  true. 

Get  a  string  from  stdin  w/  editing, 
get  at  most  bufsize-1  chars, 
like  fgets  but  returns  pointer  to 
end  of  input  string  on  success, 
like  getl  but  uses  egets  for 
standard  input  rather  than  getc. 


( Continued  on  next  page ) 


Dr.  Dobb’s  Journal,  December  1985 


21 

883 


C  Chest  (Listing  continued,  text  begins  on  page  16) 

Listing  Five 


22 

* 

FILE 

‘fp; 

23 

* 

char 

*buf , 

*bp,  *end; 

24 

* 

int 

move, 

buf size 

maxi ine 

; 

25 

*/ 

26 

27 

extern 

int 

getpage 

() ; 

/* 

Sources 

in  vidbios.c 

*/ 

28 

extern 

void 

setcur 

(>; 

/* 

" 

♦/ 

29 

extern 

short 

getcur 

()  ; 

/* 

" 

*/ 

30 

extern 

void 

wchar 

(); 

/* 

* 

*/ 

31 

extern 

int 

dos 

()  ! 

/* 

Part  of 

the  Lattice  standard 

library*/ 

32 

extern 

void 

movemem 

()» 

/* 

" 

*/ 

53 

♦def ine 

INS 

0x84 

54 

♦def ine 

DEL 

0x85 

55 

♦def ine 

HOME 

0x86 

56 

♦def ine 

END 

0x87 

57 

58 

♦def ine 

BDOS, 

.IN 

8 

/*  raw  (non 

echo) 

59 

60 

♦def ine 

CNTL. 

_C 

0x03 

/* 

~c  V 

61 

♦def ine 

CNTL_ 

_z 

0xla 

/* 

~Z  */ 

62 

♦def ine 

BEL 

0x07 

/* 

"g  V 

63 

♦def ine 

ESC 

0xlb 

/* 

*(  */ 

64 

♦define 

CAN 

0x18 

/* 

-X  */ 

Values  returned  from  DOS  when  cursor  keys  etc.  are  hit: 


» define  _LEFT  75 

♦define  .RIGHT  77 

♦define  .CTL.LEFT  115 

♦define  .CTL.RIGHT  116 

♦define  _INS  82 

♦  define  _Dt'L  83 

♦define  _HOME  71 

♦define  .END  79 

/*  The  above  are  mapped  as  follows  by  getkey 

V 

♦define  LEFT  0x80 

♦define  RIGHT  0x81 

♦define  CTL_LKFT  0x82 

♦define  CTL_RIGHT  0x83 


static  int 
{ 

/* 


getkey  ( ) 

Return  a  key  from  the  keyboard.  Keys  are  gotten  in  raw 
input  mode  and  mapped  as  specified  above  if  necessary. 


register  int  c; 

static  int  ateof  *=  0; 

if(  ateof  ) 

return  EOF; 

if(  ! ( c  =  bdos (BDOS_IN) )  )  /*  Special  function  key  */ 


83 

switch (  bdos (BDOS  IN) 

84 

{ 

85 

case  _LEFT: 

return ( 

LEFT 

>  > 

86 

case  .RIGHT: 

return  ( 

RIGHT 

)  i 

87 

case  _CTL_LEFT: 

return ( 

CTL.LEFT 

)  ! 

88 

case  _CTL_RIGHT : 

r eturn ( 

CTL.RIGHT 

)  ; 

8  9 

case  _INS : 

ret  urn ( 

INS 

)  i 

90 

case  .DEL: 

return ( 

DEL 

)  ! 

91 

case  .HOME: 

return  ( 

HOME 

)  ; 

92 

case  .END: 

return ( 

END 

)  i 

93 

default : 

return ( 

NULL 

)  ; 

if  (  c  ■»=  '\r '  ) 
return (  ' \n '  ) ? 


ateof  =  1; 
return  EOF; 


/*  map  ENTER  key  to  '\n'  */ 


ptail(  bpr  end,  move  ) 


QQA 


Dr.  Dobb’s  Journal,  December  1985 


113 

114 

115 

116 

117 

118 

119 

120 
121 
122 

123 

124 

125 

126 

127 

128 

129 

130 

131 

132 

133 

134 

135 

136 

137 

138 

139 

140 

141 

142 

143 

144 

145 

146 

147 

148 

149 

150 

151 

152 

153 

154 

155 

156 

157 

158 

159 


register  char  *bp,  *end; 

{ 

/*  Print  out  all  characters  between  bp  and  end  (inclusive) 

*  without  modifying  the  current  cursor  position.  If  move 

*  is  zero  the  cursor  will  not  change  position,  otherwise 

*  .  the  cursor  will  be  left  pointing  to  the  character 

*  referenced  by  end. 

V 

register  short  posn; 

if(  lmove  ) 

posn  =  getcur ( ) ; 

for  (  ;  *bp  &&  bp  <=  end;  wchar (*bp++)  ) 


} 


if  ( 
else 


lmove  ) 

setcur  (  posn 

wchar ( ' \b' ) ; 


);  /*  put  cursor  under  first  character  */ 
/*  put  cursor  under  last  character  */ 


V 


char 

char 

{ 


*egets(  start,  bufsize  ) 
•star t ; 


/*  Get  a  string  with  editing.  If  bufsize  is  wider  than  your 

*  screen,  strange  things  will  happen  when  you  try  to  use 

*  the  editing  functions.  If  you  access  this  function 

*  via  fgetlineO  then  it  will  input  longer  lines  in  78 

*  character  chunks  and  \<CR>  can  be  used  to  extend  a  line. 


"H  or  BACKSPACE 

LEFT  CURSOR 
RIGHT  CURSOR 
"LEFT  CURSOR 
"RIGHT  CURSOR 
HOME 
END 

CR  or  LF 
"X 
ESC 
DEL 


Destructive  backspace,  close  up 
remainder  of  string  to  fill  hole. 
Non-destructive  backspace 
Move  right  one  character. 

Left  to  previous  word  or  line  start 
Right  to  next  word  or  line  end 
Left  edge  of  line 
Right  edge  of  line 
Terminate  line. 

Erase  entire  line  but  don't  return. 
Return  a  null  string  immediatly. 
Delete  a  current  cursor  position  and 
close  up  to  fill  hole. 


160 

161 

162 

163 

164 

165 

166 

167 

168 

169 

170 

171 

172 

173 

174 

175 

176 

177 

178 

179 

180 
181 
182 

183 

184 

185 

186 

187 

188 

189 

190 

191 

192 

193 

194 

195 

196 

197 

198 

199 

200 
201 
202 

203 

204 

205 

206 
207 
20fi 

209 

210 


Any  printing  character  Enter  that  char  at  cursor  posn 
Anything  else  Ring  the  bell. 

The  bell  will  also  ring  if  you  try  to  move  the  cursor 
past  either  the  left  or  right  edges  of  the  buffer. 

Return  a  pointer  to  the  end  of  string  normally,  return  0 
on  EOF  and  return  -1  when  ESC  is  encountered. 


registe 

r  char  *bp; 

/* 

Points 

at 

current  cursor 

position 

•/ 

registe 

r  char  *end; 

/* 

Points 

at 

largest  possibl 

e  cur  pos. 

*/ 

registe 

r  char  *maxbp; 

/* 

Points 

at 

rightmost  char 

on  line 

*/ 

registe 

r  int  c  ; 

/* 

Cur ren 

t  character. 

*/ 

short 

home; 

/* 

place 

to  r 

emember  the  leftmost  cursor 

* 

pos  it  i» 

on. 

*/ 

end 

*  start  +  (buf 

size 

-1)  ; 

•end-- 

=  '\0'  ; 

bp 

=  start; 

maxbp 

=  start; 

/* 

Fill  the  entir 

e  buffer  w 

ith 

spaces 

V 

home  = 

getcur ( ) ; 

/* 

Get  the  current  cursor 

*/ 

/* 

position. 

*/ 

i  f  (  *bp 

) 

/* 

If  the  buffer 

isnt  empty 

V 

{ 

/* 

print  out  its 

contents 

*/ 

while(  *bp  ) 

/* 

and  set  maxbp 

to  point 

*/ 

wchar (  *bp++  ) 

7 

/* 

at  the  previou 

is  end  of 

*/ 

maxbp  c  bp; 

/* 

string. 

*/ 

) 

setcur (  home  ) 

7 

for  (  ; 

bp  <=  end  ;  *bp++  = 

‘  '  ) 

/ 

*  and  then  fill 

.  the  rest 

*/ 

> 

/ 

•  of  it  with  spaces. 

*/ 

hp  =  start; 

/* 

bp  points  into 

the 

bufer 

at 

the  current  cur 

sor  location. 

* 

end  points  at 

the 

r ighmo! 

st  place  that  the  c 

:ursor  movement 

* 

commands  can  g 

et  u 

s .  The 

re  i 

s  actual  1 y  one 

more  place 

* 

in  the  buffer. 

Get 

the  1 

ine : 

V 

whil e ( 

(c  =  getkeyO) 

!  = 

'  \n '  i 

E.& 

c  ! =  EOF  ) 

switch(  c  ) 


(Continued  on  next  page) 


Dr.  Dobb's  Journal,  December  1985 


23 

885 


C/ieSl  (Listing  continued,  text  begins  on  page  16) 

Listing  Five 


211 : 
212: 
213: 
214  : 
215: 
216: 
217: 
218: 
219: 
220: 
221: 
222: 
223: 
224  : 
225: 
226: 
227: 
228: 
229: 
230: 
231: 
232: 

233  : 

234  : 

235  : 
236: 
237: 
238: 

239  : 

240  : 
241: 
242: 

243  : 

244  : 

245  : 
246: 

247  : 

248  : 

249  : 
250: 
251  : 
252: 
253: 
254  : 
255: 
256: 
257: 
258: 
259: 
260: 
261  : 
262  : 

263  : 

264  : 
265: 
266  : 
267: 
268: 
269: 


{ 

case  LF.FT:  /*  Non-destructive  backspace  */ 

if(  bp  >  start  ) 


wchar(  '\b'  ); 

—bp; 

break ; 


wchar(  BEL  ); 
break ; 

case  '\b':  /*  Destructive  backspace  */ 

if(  bp  <=  start  ) 

{ 

wchar(  BEL  ); 
break ; 


wchar  (  '\b'  ) ; 

— bp; 

/*  fall  through  to  delete  case  */ 


case  DEL: 


/*  Delete  character  and  close  up 


if(  bp  >=  maxbp  )  /*  nothing  to  delete  */ 

break ; 

movmem (  bp+1,  bp,  maxbp-bp  ); 

♦maxbp  =  1  ' ; 

ptail(  bp,  maxbp —  ,  0); 

break ; 

case  CTL_LEFT:  /*  Cursor  to  start  of  previous  word  */ 

if (  bp  >  start  ) 

{ 

do  { 

— bp; 

wchar (  '\b'  ) ; 


}  while(  bp  >  start  &&  *bp 


*  ); 


} 


while(  bp  >  start  &&  *bp  I«  '  '  ) 
— bp; 

wchar (  1 \b '  ) ; 

} 

i f (  *bp  ==  '  '  ) 

wchar (  *bp+  +  ); 

break ; 


wchar (  BEL  ); 


270 

271 

272 

273 

274 

275 

276 

277 

278 

279 

280 
281 
282 

283 

284 

285 

286 

287 

288 

289 

290 

291 

292 

293 

294 

295 

296 

297 

298 

299 

300 

301 

302 


break ; 

case  HOME:  /*  Cursor  to  left  extreme  */ 

bp  =  start  ; 
setcur  (  home  t • 
break ; 

case  ESC:  /*  Erase  entire  line  and  return  */ 

♦start  =  0; 
wchar  (  ' \r '  ) ; 
wchar  (  ' \n '  ) ; 
return  -1  ; 

case  CAN:  /*  Erase  entire  line  */ 

setcur (  home  ) ; 

for(  bp  =  start;  bp  <  maxbp;  *bp++  =  1  ') 
wchar ( '  ' ) ? 

setcur (  home  ) ; 
maxbp  =  bp  =  start: 
break ; 

case  RIGHT:  /*  Cursor  right  one  character  */ 

wchar (  (  bp  <  end  )  ?  *bp++  :  BEL  ); 

break ; 

case  CTL_RIGHT:  /*  Advance  to  next  word  */ 

while  (  bp  <  maxbp  &&  *bp  !=  '  '  ) 
wchar  (  *bp++  ) ; 

while (  bp  <  maxbp  &&  *bp  ==  '  '  ) 


24 


QQC 


Dr.  Dobb's  Journal,  December  1985 


303 

304 

305 

306 

307 

308 

309 

310 

311 

312 

313 

314 

315 

316 


wchar(  *bp++  ); 


break ; 


case  END: 


/*  Go  to  right  extremity  */ 


if  (  bp  <  maxbp  ) 

{ 

ptail(  bp,  maxbp,  1  ); 
bp  =  maxbp  ; 

} 

break ; 


default : 


317 

/*  If  we 

ren't  at 

the 

right-most 

extreme 

318 

*  of  the 

buffer,  move 

the  tail  over  to 

319 

*  make  room  for  the  c 

jrrent  char 

acter , 

320 

*  else  just  print 

it. 

321 

*/ 

322 

323 

if(  bp  <=  end 
[ 

&&  ('  ' 

<= 

&&  c  < 

0x7f )  ) 

324 

325 

if  (  bp 

<  maxbp 

326 

{ 

327 

if  (  maxbp  < 

end  ) 

328 

maxbp++  ; 

329 

330 

movmem ( 

bp, 

bp+1,  end- 

bp  )  ; 

331 

ptail  ( 

bp, 

maxbp,  0  ) 

? 

332 

1 

333 

334 

wchar ( 

c  )  ; 

335 

336 

if  (  bp 

<  end  ) 

337 

*bp++  = 

c; 

338 

else 

339 

t 

/*  we're 

at  the 

*/ 

340 

wchar ( 

'  \b ' 

) ;/*  right 

margin 

*/ 

341 

wchar ( 

BEL 

;  /*  back 

up  and 

V 

342 

*bp  =  c 

; 

/*  ring 

the  bell 

*/ 

343 

) 

344 

345 

if  (  bp 

>  maxbp 

) 

346 

maxbp  = 

bp; 

347  : 

348  : 

349  : 

350: 

351: 

352: 

353  : 

354  : 

355: 

356: 

357: 

358: 

359: 

360  : 

361: 

362: 

363: 

364  : 

365  : 

366: 

367: 

363:  } 

369  : 

370:  /*— 
371: 

372:  char 
373:  char 
374:  FILE 
375:  { 

376: 

377: 

370: 

379 

380 

381 

382 

383 

384 

385 

386 

387 

388 

389 

390 

391 

392 

393 

394 

395 

396 

397 

398 

399 

400 


break ; 


/• 

* 

* 

V 

for  ( 


*++end 
wchar  ( 
wchar ( 


} 

break ; 


Delete  trailing  whitespace,  terminate  the  string,  go 
to  the  next  line,  and  return  EOF  if  we're  at  end  of 
file,  the  end  pointer  otherwise. 


•end 

I 

«  '  \0 ' ; 
' \r '  ); 
' \n '  )? 


&&  end  >=  start  ;  — end  ) 


} 

/*— 

char 

char 

FILE 

{ 


return  (  c  ==  EOF  &&  start  =«=  end  )  ?  NULL  :  end  ; 


*getl(  buf,  maxline,  fp  ) 

•buf ; 

*fp; 

/*  Works  exactly  like  fgets  but  returns  a  pointer  to  the 

*  end  of  the  string  on  success. 


register  int  c; 

register  char  *bp  =  buf; 

while(  (c  =  fgetc(fp))  1=  EOF  &&  c  !=  '\n'  &&  — maxline  >  0  ) 

*bp+  +  =  c; 

*bp  *»  1  \0  '  ; 

return (  (c  ==  EOF  &&  bp  ==  buf)  ?  NULL  :  bp  ) ; 


*efgets(  buf,  maxline,  fp  ) 

•buf  ; 

*fp  ; 

/*  An  editing  version  of  fgets. 

*  Works  like  fgets  but  recognizes  a  back-slash  at  end  of  line 

*  if  fp  is  stdin  then  raw  i/o  is  used  and  various  editing 

*  functions  are  enabled  (see  egets  for  details).  A  pointer 


(Continued  on  next  page ) 


Dr.  Dobb’s  Journal,  December  1985 


25 

887 


x^nCSl  (Listing  continued,  text  begins  on  page  16) 

Listing  Five 


4U1 

402 

403 

404 

405 

406 

407 

408 

409 

410 

411 

412 

413 

414 

415 

416 

417 

418 

419 

420 

421 

422 

423 

424 

425 

426 

427 

428 

429 

430 

431 

432 

433 

434 


*  to  the  end  of  the  input  string  is  returned  on  success  or 

*  NULL  on  end  of  file. 

V 

register  char  *bp,  *start  =  buf  ; 
int  linelen,  col; 

if (  fp  ==  stdin  ) 

{ 

/*  linelen  is  the  amount  of  space  left  on  the 

*  current  input  line.  Col  is  the  column  componant 

*  of  the  current  cursor  position. 

V 

linelen  «=  80  -  (col  =  getcurO  &  0xff); 

} 

while (1) 

{ 

bp  =  (fp  stdin)  ?  egets(  buf,  mindinelen,  maxline)  ) 
:  getl  (  buf,  maxline,  fp  ) 


/*  If  egets()  found  and  ESC  (bp  ==  -1)  or  we've 

*  hit  end  of  file  (Ibp)  or  we've  seen  a 

*  blank  line  (bp  ==  buf)  the  last  character  on  the 

*  line  isn't  a  \,  break;  Note  that  in  the  first 

*  case  we  erase  then  entire  buffer. 

V 

if (  bp  “  -1  ) 

{ 

* (bp  »  start)  =  ' \0 ' ; 
break ; 

) 


435  :  else  if(  Ibp  I  I  bp  <=  buf  ||  Mbp-1)  l»  '\\'  ) 

436:  break; 

437: 

438  : 

439:  /*  Adjust  maxline  to  compensate  for  the  characters 

440:  *  already  gotten  and  decrement  bp  so  that  we'll 

441:  *  overwrite  the  \  on  the  next  pass.  Then,  if 

442:  *  we're  getting  input  from  stdin,  position  the 

443:  *  cursor  in  its  original  column  but  on  the  current 

444:  *  line. 

445  :  V 

446  : 

447:  maxline  -=  ( — bp  -  buf); 

448:  buf  *  bp  ; 

449: 

450:  if (  fp  stdin  ) 

451:  setcur(  (getcurO  &  “0xff)  I  col  ); 

452:  ) 

453: 

454 :  return  (  bp  ) ; 

455:  ) 

456: 

457:  /* - - - V 

458: 


459:  # ifdef  DEBUG 
460: 


461:  main() 
462:  { 

463: 

464  : 

465  : 

466 : 

467: 

468: 

469: 

470  : 

471: 

472: 

473: 

474  : 

475: 

476: 

477:  ) 

478: 

479:  #endif 


static  char  buf[80]  ; 


print f  ( " 
printf  (" 
printf  (" 


123  4\n" ) ; 
12 34 5678901 234 567 8901 234 5678901 2 34 56789 0\n") ; 
•); 


while(  efgets(buf, 

{ 

printf  ( " 
printf  ( " 
printf ( " 
printf  ( " 
*buf  =  0; 

} 


40,  stdin)  >  0  ) 

%s< - \n\n",  buf  ); 

123  4\n" ) ; 

1234 5678901 234 567 8901 234 5678901 234 567890\n")  ; 
")l 


End  Listings 


26 


ooo 


Dr.  Dobb’s  Journal.  December  1985 


Windowing 

Operating 

Environments 

TopView,  GEM, 
and  Windows 


by  Michael  Swaine 

What's  really  going  on  inside  GEM , 
beneath  TopView,  behind  Windows ? 


It’s  a  good  thing  Bill  Gates  isn’t  thin-skinned.  Otherwise 
he  might  take  offense  at  the  way  so  many  programmers 
are  spending  so  much  time  enhancing,  augmenting,  by¬ 
passing,  subverting,  taming,  masking,  and  hiding  MS  DOS. 
And  now  Microsoft  is  getting  into  the  act  with  Windows. 
This  article  examines  Windows  and  two  other  “windowing, 
multitasking  programming  environments”  for  MS  or  PC 
DOS  machines:  IBM’s  TopView  and  DRl’s  GEM. 

You  could  argue  that  the  seeds  were  planted  by  no  less 
a  gardener  than  the  Jolly  Blue  Giant  when  IBM  released 
its  PC  with  no  operating  system  but  with  three  operating 
system  options:  PC  (a.k.a.  MS)  DOS;  CP/M-86;  and  Sof- 
Tech’s  UCSD  p-System.  Although  IBM’s  subsequent  pric¬ 
ing  strategy  strongly  hinted  that  PC  DOS  was  the  PC’s 
DOS,  the  other  operating  systems  never  entirely  took  the 
hint  and  went  away.  Nor  did  they  even  get  humble:  Con¬ 
sider  NCI’s  recent  ads  for  its  p-System  that  announce  a 
new  release  for  the  whole  PC  family  (except  for  jr,  whom 
the  family  never  mentions).  The  ads,  directed  at  program¬ 
mers,  suggest  that  with  the  p-System  you  can  have  com¬ 
patibility  with  the  operating  system  that  your  customers, 
in  their  ignorance,  want  (DOS),  but  can  still  work  in  a 
good  programming  environment  (not  DOS). 

Although  there  were  precursors,  DRI  should  probably 
get  the  credit  for  bringing  concurrent  processing  to  per¬ 
sonal  computers  with  its  concurrent  version  of  CP/M.  It 
soon  became  clear  to  DRI  that  to  sell  concurrency  on  IBM 
PCs  it  had  to  develop  a  concurrent  PC  DOS,  acknowledg¬ 
ing  Microsoft’s  control  of  the  PC  operating  system  area. 
But  DRI  had  raised  the  issue  of  concurrency  on  a  single- 
user  computer. 

After  DRI  opened  the  concurrency  window,  there  came 
the  pop-ups,  an  undisciplined  horde  of  dwarf  programs 
that  demonstrated  even  to  ordinary  mortals  the  benefits 
of  some  approximation  of  concurrency.  They  presented 
object  lessons  in  the  hazards  of  operating  system  anarchy 
as  well.  It  was  cute  when  Eastman  Kodak  put  a  package 
under  the  tree  labeled  “Open  me  first”;  it  was  less  appeal¬ 
ing  to  confront  a  dozen  software  packages  all  clamoring 
“Load  me  first.” 

What  allowed  pop-ups  to  show  off  their  concurrency 
was  windows,  an  architectural  feature  opened  by  Xerox 
and  polished  by  Apple.  The  success  of  the  concept  of  win¬ 
dows  is  exemplified  by  its  employment  in  dWindow,  a 
non-operating  system  application.  For  years,  Ashton¬ 
Tate’s  dBASE  II  set  the  standard  for  austerity  in  user  in¬ 
terfaces — you  can’t  get  much  simpler  than  a  single  period 
prompt.  Liberty-Bell  Software’s  dWindow  does  a  daz¬ 
zling  cathedral  window  treatment  on  dBASE  II  and  III 
that  makes  them  look  like  entirely  different  products.  But 
it  is  at  the  operating  system  level  that  the  fenestration  of 
the  interface  has  become  obligatory,  and  it  was  Apple’s 
Macintosh  that  made  it  so. 

Bill  Gates  and  his  Microsoft  programmers  were  among 
the  first  developers  to  peek  behind  the  blinds  at  Apple,  and 
they  were  not  slow  to  announce  Microsoft’s  own  window- 
oriented  product,  which  they  called  Windows.  Sometime 
between  the  announcement  of  Windows  and  its  release,  the 
Korean  War  ended,  Alaska  and  Hawaii  attained  state- 


Dr.  Dobb’s  Journal,  December  1985 


28 

Rfi9 


hood,  and  Digital  Research  programmers  conceived  and 
brought  out  a  windowing  product  called  GEM,  whose  visu¬ 
al  display,  Apple  recently  noticed,  looks  rather  like  the 
Mac’s.  Meanwhile,  IBM  caught  the  drift,  fumbled  through 
its  pockets,  and  pulled  out  a  windowing,  multitasking  oper¬ 
ating  environment  that  it  christened  TopView. 

With  at  least  three  windowing,  multitasking  operating 
environments  to  load  on  top  of,  stack  next  to,  or  wrap 
around  DOS,  the  archetypal  “fully  loaded  PC”  was  begin¬ 
ning  to  look  like  a  pop-up  on  steroids.  In  fact,  or  at  least  in 
PC  Week ,  Peter  Norton  described  a  dream  he  had  in 
which  he  tried  to  run  TopView,  GEM,  and  Windows  si¬ 
multaneously.  His  hard  disk  flew  to  Poland. 

Smart  Money  Talks  a  Lot 

A  programmer  must  face  more  serious  questions  of  com¬ 
patibility.  How  does  each  product  work  with  DOS?  How 
will  existing  and  future  programs  run  in  each  of  these  envi¬ 
ronments?  How  well  will  they  run?  What  are  the  costs  of 
laying  another  sheet  of  software  between  programmer  and 
machine?  We  asked  such  questions  of  development-team 
members  for  each  of  the  three  products.  But  as  the  answers 
came  back,  industry  analyses  were  circulating  that  ques¬ 
tioned  the  viability  of  at  least  two  of  the  products  (the  two 
that  had  actually  been  released).  Were  we  wasting  our 
time  asking  questions  about  these  products? 

Although  some  developers  are  producing  TopView- 
compatible  applications,  the  product  has  clearly  not  cap¬ 
tured  the  imagination  of  users  or  developers.  The  recent 
agreement  between  IBM  and  Microsoft  has  fueled  much 
speculation  about  the  viability  of  TopView,  and  many  ob¬ 
servers  seem  to  be  betting  on  Microsoft.  One  popular  ar¬ 
gument  is  that  IBM  will  create  two  systems:  one  using 
Windows  and  Microsoft’s  DOS,  the  other  using  a  DOS- 
free  descendant  or  unrelated  successor  to  TopView  in 
any  case,  a  proprietary  operating  system.  Some,  including 
IBM  watcher  Andy  Seybold,  speculate  that  the  IBM- 
Microsoft  agreement  may  have  little  to  do  with  IBM’s 
long-term  goals  and  that  IBM  will  use  TopView  as  the 
centerpiece  of  its  future  operating  systems.  At  least  one 
analyst  insists  that  IBM’s  history  demonstrates  that  it  will 
eventually  have  a  proprietary  operating  system  on  the  PC. 
But  IBM  has  broken  tradition  several  times  in  the  short 
history  of  the  PC,  and  the  venerable  wisdom  about  what 
IBM  always  does  may  not  be  as  wise  now  as  once  it  was.  It 
does  seem  that  it  is  getting  harder  to  second-guess  IBM, 
and  that  the  benefits  from  succeeding  therein  may  be 
even  more  dubious  today  than  in  mainframe  days. 

The  smart  money  is  writing  off  GEM  in  the  IBM-com¬ 
patible  market,  reasoning  that  if  IBM  owns  the  hardware 
and  Microsoft  owns  DOS,  where  does  that  leave  a  compa¬ 
ny  that  tries  to  compete  with  them  on  their  turf? 

Perhaps  DRI’s  success  with  GEM  will  have  to  come  in 
the  other  end  of  the  dumbbell.  Lee  Felsenstein,  in  prosely¬ 
tizing  for  his  Hacker’s  Mac  project,  describes  the  person¬ 
al  computer  market  as  an  asymmetric  dumbbell  with  one 
globe  of  IBM-compatibles  and  a  smaller  globe  of  Maca- 
likes.  He  argues  that  the  smaller  globe  can  grow  and  pros¬ 
per  only  with  compatibility  and  proposes  a  radical  strate¬ 


gy  for  forcing  compatibility  on  unwilling  Apple,  Commo¬ 
dore,  and  Atari.  Such  a  development  would  likely  benefit 
DRI  and  GEM,  but  is  it  likely? 

Perhaps  not,  given  the  fact  that  Apple  has  pressured 
DRI  into  changing  GEM  to  decrease  its  similarity  to  the 
Macintosh’s  visual  interface.  GEM  as  originally  released 
had  to  be  terminated  by  November  1 5,  that  is,  DRI  had  to 
stop  supporting  and  advertising  it.  The  new  version  will 
look  less  Macintoshish.  Apple  is  also  talking  with  Micro¬ 
soft  about  Windows,  but  in  softer  tones. 

Smart  money  and  dream-machine  designers  aside, 
GEM  provides  MS  DOS  users  and  programmers  with  ca¬ 
pabilities  that  Windows  and  TopView  lack.  But  TopView 
and  Windows  have  their  own  distinct,  desirable  features. 

Three  facts  argue  against  accepting  the  judgment  of 
the  smart  money  too  hastily.  First,  the  three  operating 
environments  offer  three  different  sets  of  capabilities  to 
users  and  software  developers.  Second,  no  one  needs  any 
of  these  products;  they  are  all  frosting  on  the  DOS,  and 
users  may  decide  among  them — or  against  all  of  them — 
on  grounds  that  smart  money  would  consider  dumb. 
Third,  as  Bob  Frankston  pointed  out  in  InfoWorld,  writ¬ 
ing  for  TopView  (or  Windows  or  GEM)  limits  your  mar¬ 
ket.  In  any  case,  we  assume  there  is  merit  in  understand¬ 
ing  these  products  from  a  programmer’s  point  of  view. 

Inside  GEM 

At  one  level,  getting  started  developing  an  application 
that  is  compatible  with  GEM  is  simple.  You  get  the  Pro¬ 
grammer’s  Toolkit  and  start  writing.  In  terms  of  hard¬ 
ware,  you  need  a  PC  with  half  a  megabyte  of  memory  and 
a  color-graphics  adapter.  You  should  also  have  a  hard 
disk  and  a  mouse,  although  you  can  do  development  work 
without  them. 

Beyond  these  elementary  requirements,  one  comes  up 
against  the  fact  that  GEM  is  a  message-passing  program. 
Years  of  single-thread  procedural  programming  experi¬ 
ence  will  not  prepare  you  adequately  for  the  different  pro¬ 
gramming  model  that  GEM  employs.  According  to  one 
GEM  programmer,  the  learning  curve  within  DRI  during 
GEM  development  was  two  months.  Programmers  who 
had  never  worked  with  any  windowing  system  took  two 
months  to  get  up  to  speed  in  the  GEM  programming  envi¬ 
ronment.  Programmers  who  had  had  some  windowing  ex¬ 
perience  learned  faster  but  had  to  unlearn  some  details 
that  did  not  transfer. 

GEM  itself  does  not  communicate  directly  with  DOS; 
that  is,  the  Virtual  Device  Interface  and  Window  Services 
don’t.  The  GEM  Desktop  does.  File  manipulation  is  han¬ 
dled  through  DOS  calls.  GEM  supports  a  variety  of  de¬ 
vices  and  is  expected  to  support  the  AST/Ashton-Tate/ 
DRI/Quadram  expanded-memory  specification,  which 
will  become  particularly  significant  when  the  multitask¬ 
ing  version  of  GEM  is  released. 

GEM  compares  more  directly  with  Windows  than  with 
TopView;  TopView  is  character-oriented  and  truly  multi¬ 
tasking  and  GEM  and  Windows  are  neither.  In  comparing 
GEM  and  Windows  as  programming  environments,  Win¬ 
dows  developers  point  to  powerful  features,  and  GEM  de- 


29 


onn 


Dr.  Dobb's  Journal,  December  1985 


velopers  talk  about  a  clean  programmer’s  interface.  But 
the  clearest  advantages  GEM  has  over  Windows  at  the  mo¬ 
ment  seem  to  be  that  GEM  has  been  out  long  enough  to 
land  significant  committed  and  producing  OEMs,  includ¬ 
ing  Atari  and  Apricot,  and  the  leverage  that  this  gives  the 
developer  in  porting  an  application  to  different 
environments. 

What  does  the  Apple-DRI  settlement  mean  to  program¬ 
mers  who  have  developed  GEM-compatible  software  or 
who  are  thinking  about  doing  so?  Perhaps  not  much.  The 
changes  in  GEM  forced  by  Apple  appear  to  be  essentially 
cosmetic,  and  though  the  impact  on  the  GEM  Desktop  will 
be  significant,  the  programming  impact  may  be  minimal. 

Beneath  TopView 

The  chief  difference  between  TopView  on  the  one  hand 
and  GEM  and  Windows  on  the  other  is  that  TopView  is 
truly  multitasking.  You  can  see  multitasking  in  action  if 
you  bring  up  a  visually  active  BASICA  program  in  two 
TopView  windows  simultaneously.  You’ll  see  the  pro¬ 
gram  doing  its  thing  in  parallel  with  itself. 

Because  it  is  a  multitasking  operating  environment, 
TopView  permits  the  development  of  multitasking  appli¬ 
cations.  The  application  developer  can  produce  a  task 
with  its  own  subtasks.  Then,  in  addition  to  running  the 
application  in  multitasking  mode  with  other  tasks,  Top- 
View  will  multitask  the  application’s  subtasks.  Tasks  can 
communicate;  if  you  get  the  object  handle  for  a  task  you 
can  send  it  a  message. 

Memory  management  under  TopView  is  as  simple  as  it 
is  under  DOS.  When  a  program  begins,  it  is  assigned 
memory  according  to  the  demands  of  its  fixed  Program 
Information  File.  Henceforth  it  can  get  no  more  nor  less. 
On  a  program’s  termination,  its  memory  is  freed. 

TopView  does  not  replace  DOS.  It  sits  atop  DOS,  hand¬ 
ing  off  file  I/O  and  other  system  functions  to  the  system. 
It  is  at  the  character  I/O  level  that  TopView  butts  in, 
redirecting  all  character  I/O.  Among  other  things,  this 
means  that  you  can  freely  mix  TopView  and  DOS  calls. 
You  can  put  the  user  in  DOS  under  TopView  and  have  the 
fact  that  TopView  is  active  be  unknown  to  them.  (A  Get- 
version  call  will  show  the  user  that  TopView  is  active.) 

Although  TopView  is  character-oriented  rather  than 
graphics-oriented,  it  will  work  with  a  standard  mono¬ 
chrome  or  color  screen  and  anything  up  to  the  EGA.  Top- 
View  will  work  with  the  EGA  but  won’t  take  advantage  of 
its  extended  graphics  capabilities. 

TopView  developers  are  trying  to  woo  other  developers, 
arguing  that  you  can  bring  a  more  powerful  application  to 
market  more  quickly  if  you  write  for  the  TopView  envi¬ 
ronment  you  can  take  advantage  of  a  standardized  dis¬ 
play  style,  a  toolkit  of  window  design  aids,  and  other  de¬ 
velopment  tools.  You  can  also  use  what  TopView 
designers  call  full-screen  input.  This  wooing  of  developers 
has  not  been  entirely  unsuccessful;  the  Trio  micro-to- 
mainframe  product  and  the  Lattice  Topview  Toolbasket 
are  significant  TopView-compatible  products. 

For  existing  programs,  you  can  make  use  of  as  much  of 
the  windowing  capability  as  makes  sense.  For  some  appli¬ 


cations  this  may  be  useless,  but  the  capability  is  there. 
The  implementation  of  DOS  services  shows  what  IBM 
projects  for  future  applications-  making  logical  use  of 
several  windows  rather  than  just  throwing  a  frame  around 
a  full-screen  display. 

Behind  Windows 

Because  Microsoft  has  good  sources  on  what  future  ver¬ 
sions  of  DOS  will  require,  its  programmers  can  do  what 
they  admonish  independent  developers  not  to  do — work 
around  DOS.  Windows  runs  “side  by  side  with  DOS.” 
When  Windows  comes  up,  its  DOS  executive  replaces 
command.com,  which  is  no  longer  needed. 

Windows  is  made  up  of  three  pieces:  two  are  the  exter¬ 
nal  pieces  that  everyone  sees,  the  user  interface  and  the 
graphics  device  interface  (GDI).  The  third  is  the  kernel, 
which  interfaces  to  whatever  kind  of  MS  DOS  is  on  the 
machine.  This  interface  changes  considerably  with  differ¬ 
ent  versions  of  DOS  and  hides  the  operating  environment 
from  Windows. 

The  user  interface  differs  from  the  Mac/GEM/Top- 
View  approach  in  tiling  the  screen  with  windows  that 
don’t  overlap  other  windows. 

At  the  GDI  level  is  an  interrogating  interface:  if  a  de¬ 
vice  says  that  it  can  only  do  bitblt,  the  interface  will  simu¬ 
late  everything  else  in  the  software  using  bitblt;  if  the 
device  says  it  can  do  complex  polygons  with  hash  filling, 
GDI  hands  off  to  the  device.  The  device  manufacturer  fills 
in  capabilities  up  to  some  level  and  Windows  simulates 
the  rest.  The  goal  at  the  GDI  level  was  device  indepen¬ 
dence — the  ability  to  change  one  line  of  code  and  have 
output  go  to  a  different  device. 

At  the  level  of  the  kernel,  Windows  has  true  compact¬ 
ing  global  memory  management;  it  does  not  have  true 
preemptive  multitasking.  It  can  allocate,  reallocate,  dy¬ 
namically  free  and  restore  data.  Its  task  handling  is  round 
robin  nonpreemptive  multitasking — the  application  must 
yield  control.  There  are  mechanisms  for  implicit  yielding 
when  the  application  is  waiting  for  something. 

The  reason  Microsoft  gives  for  not  implementing  true 
preemptive  multitasking  is  suggestive;  DOS  is  not  reen¬ 
trant.  If  you  preempt  a  task  and  it’s  in  DOS  at  the  time, 
you’d  best  get  back  to  DOS  quickly.  So  a  good  chunk  of 
the  time  slicing  would  not  be  beneficial.  That,  Windows 
developers  claim,  is  why  TopView  is  slow.  Rick  Dill  of  the 
Windows  design  team  says,  “We  think  multitasking  real¬ 
ly  belongs  at  the  operating  system  level,  and  it  will  get 
there  eventually.”  He  means  in  MS  DOS. 

Windows  started  from  the  Smalltalk  push  model  and 
changed  to  a  more  procedural  approach  only  when  Micro¬ 
soft  found  that  programmers  didn’t  work  well  with  its 
implementation  of  the  push  model.  The  general  structure 
of  a  Windows  application  is:  initialization;  Windows-re¬ 
quired  initializations  (because  Windows  does  not  require 
that  an  application  be  installed  it  does  require  that  the 
application  register  itself  with  Windows  when  it  starts); 
creating  a  window;  and  the  main  program  while  loop — 
Getmessage,  Translatemessage,  Dispatchmessage. 

One  of  the  hidden  features  is  the  ability  for  applications 

30 


Dr.  Dobb's  Journal.  December  1985 


RQ1 


written  by  different  vendors  to  be  integrated  via  cut  and 
paste  as  well  as  the  via  the  messaging  mechanism.  For 
example.  Multiplan  could  register  a  message  that  says 
“cell  changed.”  A  presentation-graphics  program  could 
know  that  that  message  means  it  can  look  in  the  clipboard 
for  the  contents  of  the  cell  and  automatically  update  the 
chart  being  displayed. 

Windows  is  85  percent  written  in  C;  the  low-level  part 
is  written  in  assembly.  Windows  supports  Pascal,  FOR¬ 
TRAN,  C,  or  Microsoft’s  macro  assembler.  Because  of  the 
new  .exe  format,  programmers  will  have  to  use  Micro¬ 
soft’s  new  linker. 

According  to  Dill,  Microsoft  hopes  to  entice  software 
developers  with  the  knowledge  that,  in  conforming  with 
Windows,  they’re  building  a  future  DOS-conforming  ap¬ 
plication.  Windows  is  a  sneak  peek  into  DOS.  (On  the 
other  hand.  Dill  admits  there  are  software  developers  who 
have  disassembled  the  .exe  file  and  know  more  about  the 
innards  of  DOS  than  he  does.) 

But  does  all  this  just  get  in  the  software  developer’s  way? 
Most  serious  software  developers  find  that  they  must  work 
around  DOS  to  produce  a  competitive  product.  Won’t  soft¬ 
ware  developers  find  Windows  just  as  encumbering? 

“Our  bitbit,”  Dill  responds,  “is  faster  than  yours.”  He's 
serious:  Microsoft’s  bitblt  is  a  general-purpose  source- 
pattern-destination  function  supporting  four  operators. 
The  result  is  that  there  are  256  different  operations  avail¬ 


able:  others  implement  some  subset,  often  16,  of  those. 
The  Windows  graphics  routines  can  move  a  graphics 
block  around  the  screen  almost  as  fast  as  the  data  can  be 
moved  through  memory.  Whether  that  answers  the  ques¬ 
tion  is  moot. 

One  significant  change  Microsoft  is  introducing  is  a  new 
.exe  file  format.  Future  versions  of  DOS  (of  which  Win¬ 
dows  is  a  hint)  will  need  more  information  in  the  .exe  file. 
To  maintain  some  compatibility,  Microsoft  has  grafted  the 
old  .exe  header,  code,  and  data  on  top  of  the  new  .exe 
header.  The  old  tells  where  the  new  begins,  and  the  new 
has  its  own  code  and  data.  It  also  has  something  totally 
new  called  resources.  Resources  (like  menus  and  dialogue 
boxes)  can  be  changed  without  messing  with  the  .exe  file. 
Thus,  one  can  have  one  binary  that  works  worldwide,  with 
the  resources  supplying  the  language-specific  information. 

What,  then,  of  the  .com  files?  In  the  next  version  of 
DOS,  .com  files  go  away.  DOS  3  is  the  last  Microsoft  oper¬ 
ating  system  in  which  memory-image  programs  will  be 
supported  under  the  operating  system  itself.  “We  just  need 
the  information,”  Dill  explains,  “to  be  able  to  do  things  like 
running  that  application  up  in  high  memory  and  being  able 
to  segment  it  correctly  and  deal  with  running  an  old  style 
8088  unprotected  application  under  a  286.”  ddj 

Reader  Ballot 

Vote  for  your  favorite  feature/article. 

Circle  Reader  Service  No.  1 92. 


33 


Dr.  Dobb’s  Journal,  December  1985 


BANKSWAP 

A  Banked  Memory  Debugging  Tool  for  CP/M  Plus 


by  Albert  S.  Woodhull 


Dr.  Dobb’s  Journal,  December  1985 


By  making  extended  memory  use¬ 
ful  to  8-bit  processors,  CP/M 
Plus  can  help  to  keep  8080-, 
8085-,  and  Z80-based  microcomput¬ 
ers  in  competition  with  newer  16-bit 
systems.  Although  the  8-bit  proces¬ 
sors  can  directly  address  only  64K  of 
memory,  under  CP/M  Plus  a  mega¬ 
byte  can  be  efficiently  used. 

For  the  programmer  there  is  a 
catch.  Transient  programs  must  re¬ 
side  in  a  single  64K  bank.  They  have 
indirect  access  to  alternate  banks 
through  the  operating  system.  A  user 
running  purchased  software  will  not 
be  bothered  by  this.  Developers  and 
others  who  need  to  write  or  modify 
code  that  uses  the  alternate  banks 
have  a  serious  problem,  however.  CP/ 
M  Plus  comes  with  development  tools, 
such  as  the  RMAC  assembler  and 


ability  for  a  user  program  even 
though  the  program  resides  entirely 
in  the  main  bank.  The  speed  increase 
comes  from  using  the  extra  memory 
to  buffer  disk  data  and  disk  directo¬ 
ries.  The  space  increase  comes  from 
putting  part  of  the  operating  system 
in  an  alternate  memory  bank,  making 
almost  all  of  the  64K  of  memory  the 
processor  can  address  available  to  the 
user’s  program.  For  instance,  on  an 
Apple  with  the  Advanced  Logic  Sys¬ 
tems  CP/M  Card  the  transient  pro¬ 
gram  area  (TPA)  is  60K,  even  though 
the  CP/M  Plus  system  itself  (exclu¬ 
sive  of  the  CCP)  takes  up  1 7K. 

User  programs  cannot  directly  ac¬ 
cess  data  or  executable  code  in  alter¬ 
nate  banks.  The  point  can  be  argued, 
but  I  believe  Digital  Research  made  a 
wise  choice  in  imposing  these  restric- 


Apple's  Filer  is  fine  until  you  want  to  copy  files 
between  volumes  without  losing  a  BASIC  program 

in  memory. 


LINK,  that  enable  one  to  write  code 
for  use  in  any  bank  of  memory,  but 
the  standard  CP/M  Plus  debugging 
tool,  SID,  is  an  ordinary  transient  pro¬ 
gram,  incapable  of  examining  or 
changing  the  alternate  banks.  The 
programmer  needs  new  tools  or  a  way 
to  extend  the  power  of  the  old  ones. 

My  response  to  this  need  was 
BANKSWAP,  an  extension  for  SID  or 
DDT  that  provides  access  to  the  alter¬ 
nate  banks.  I  will  describe  this  tool  af¬ 
ter  a  brief  discussion  of  how  CP/M 
Plus  uses  banked  memory. 

Banked  Memory  in  CP/M  Plus 

Additional  banks  of  memory  can  im¬ 
prove  the  speed  and  memory  avail- 


Albert  S.  Woodhull,  Hampshire 
College,  Amherst,  MA  01002 


tions.  Most  programs  that  use  a  lot  of 
memory  use  it  to  hold  data,  and  disk 
buffering  can  provide  an  improve¬ 
ment  in  data  access  comparable  to 
that  of  extended  memory.  Given  the 
inherent  limitations  of  8-bit  proces¬ 
sors,  it  is  not  clear  that  allowing  user 
programs  to  execute  code  resident  in 
extended  memory  would  be  much 
more  efficient  than  using  code  over¬ 
lays  swapped  in  from  disk  buffers. 

Under  CP/M  Plus,  programs  must 
be  written  as  if  the  extra  memory  is 
not  there  at  all.  As  long  as  the  mini¬ 
mum  amount  of  memory  needed  for 
an  application  is  present  in  the  TPA 
bank,  a  program  will  be  able  to  run  on 
any  CP/M  system.  This  continues  the 
CP/M  tradition  of  providing  portabil¬ 
ity  for  programs  by  using  the  operat¬ 
ing  system  to  make  details  of  hard¬ 
ware  irrelevant  to  user  programs. 


34 

893 


The  BANKSWAP  Program 

BANKS  WAP  is  not  a  stand-alone  pro¬ 
gram;  it  is  an  enhancement  to  SID  or 
DDT  that  provides  additional  com¬ 
mands  to  copy  blocks  of  memory  from 
bank  to  bank.  The  normal  functions  of 
the  debugger  can  be  used  on  a  copy  of 
memory  from  another  bank  that  has 
been  brought  to  the  TPA  bank. 

BANKSWAP  is  relocatable  and  is 
not  necessarily  loaded  to  the  same  lo¬ 
cation  each  time  it  is  used.  For  ease  of 
use,  BANKSWAP  installs  a  vector  to 
its  own  entry  point  at  the  RST  5  loca¬ 
tion  (28H)  during  installation.  Typ¬ 
ing  G28  from  the  SID  or  DDT  prompt 
brings  up  the  BANKSWAP  menu. 
The  initialization  process  also  dis-~ 
plays  a  message  to  remind  the  user  of 
the  presence  of  BANKSWAP  and  the 
command  to  access  it. 

The  BANKSWAP  menu  allows  the 
user  to  choose  the  direction  of  the 
move,  the  memory  addresses  for  the 
source  and  destination,  and  the  length 
of  the  block  to  be  moved.  The  menu 
also  provides  for  easy  return  to  DDT 
or  SID  and  for  the  eventual  removal  of 
BANKSWAP.  Copying  is  done  in  two 
steps  through  a  buffer  that  is  also  in 
common  memory.  I  chose  to  use  a  rel¬ 
atively  small  buffer  and  repeat  the 
process  several  times  in  order  to  make 
the  best  use  of  memory  space. 

Listing  One  (page  38)  for  BANKS- 
WAP.ASM  contains  comments  that 
explain  the  operation  of  BANK- 
SWAP,  but  I  will  emphasize  a  few 
points  I  found  important  in  working 
with  banked  memory.  Although  I 
wrote  this  program  for  use  on  an  Ap¬ 
ple  with  the  Advanced  Logic  Systems 
CP/M  Card,  there  should  be  no  prob¬ 
lems  in  making  BANKSWAP  work  on 
other  implementations  of  CP/M  Plus. 
The  most  critical  point  is  to  be  sure 
that  control  is  not  lost  while  bank  1  is 
deselected.  This  means  ensuring  that 
BANKSWAP  itself,  the  stack,  and  all 
data  areas  used  are  located  in  com¬ 
mon  memory.  It  is  possible  for  a  CP/ 
M  Plus  system  to  be  constructed  so 
that  interrupts  and  system  calls  can 
be  handled  while  alternate  banks  are 
selected.  With  insurance  in  mind,  I 
thought  it  best  to  disable  interrupts 
and  avoid  calls  to  the  standard  BDOS 
entry  point  while  bank  1  is  deselected 
because  the  vectors  on  page  0  of  bank 

35 

QQyf 


1  are  then  inaccessible. 

I  used  a  direct  call  to  a  BIOS  rou¬ 
tine  to  select  the  bank  of  memory. 
There  are  several  points  to  mention  in 
regard  to  that.  First,  the  CP/M  Plus 
documentation  is  emphatic  in  stating 
that  direct  BIOS  calls  must  not  be 
made  by  application  programs;  the 
reason  for  this  is  that  under  CP/M 
Plus  some  BIOS  routines  are  always 
called  from  the  bank  0  portion  of  the 
BDOS  and  will  not  return  to  a  pro¬ 
gram  located  in  bank  1.  In  fact,  a  sep¬ 
arate  BDOS  function  is  provided  for 
gaining  direct  access  to  the  BIOS.  The 
catch  is  that  this  BDOS  function  pro¬ 
hibits  access  to  one  BIOS  routine 
you  guessed  it — the  one  we  need  for 
BANKSWAP.  Digital  Research  really 
doesn’t  want  user  programs  to  try  to 
access  other  memory  banks. 

Having  decided  to  take  the  law 
into  your  own  hands  and  call  the 
BIOS  bank-select  routine  directly, 
you  can’t  do  it  by  accessing  the  BIOS 
vector  in  low  memory  just  any  old 
time  for  the  same  reason  you  can’t 
use  BDOS  routines  any  old  time — 
some  of  the  time  the  low  memory  the 
program  sees  will  be  in  bank  0.  For 
this  reason,  I  made  the  initialization 
portion  of  BANKSWAP  fetch  the 
BIOS  vector  so  the  necessary  entry 
point  could  be  calculated  and  stored 
locally.  I  did  this  only  for  the  BIOS 
SELMEM  routine;  the  program  can 
be  simplified  if  a  BIOS  XMOVE  rou¬ 
tine  has  also  been  implemented  (this 
is  not  provided  in  the  Advanced  Log¬ 
ic  Systems  release  of  CP/M  Plus). 
Resident  System  Extensions 
BANKSWAP  must  run  in  common 
memory.  This  is  most  easily  accom¬ 
plished  by  creating. a  relocatable  file 
to  be  loaded  to  the  top  of  the  TPA. 
CP/M  Plus  has  another  feature,  the 
Resident  System  Extension  (RSX),  to 
simplify  this  task. 

An  RSX  is  a  page-relocatable  pro¬ 
gram  segment  that  CP/M  Plus  loads 
to  the  highest  available  address,  just 
as  DDT  or  SID  are  handled  under  old¬ 
er  versions  of  CP/M.  Each  RSX  mod¬ 
ule  has  a  prefix,  as  shown  in  Listing 
One.  The  prefix  includes  a  jump  to 
the  BDOS  entry  point  in  high  memory 
(for  the  first  RSX  installed)  or  to  the 
last  previously  installed  RSX.  When¬ 
ever  an  RSX  is  installed,  the  JMP  in¬ 


struction  at  location  5  is  modified  to 
point  to  it,  so  a  chain  of  JMPs  is  tra¬ 
versed  whenever  a  BDOS  call  is  made. 
The  address  portion  of  the  JMP  at  lo¬ 
cation  5  is  also  used  by  programs  to 
detect  the  decreased  size  of  the  TPA, 
protecting  the  RSX  from  being 
overwritten. 

Because  all  BDOS  calls  pass 
through  the  RSX  JMP  chain,  RSXs 
can  be  written  to  intercept  BDOS  calls 
to  customize  BDOS  performance. 
BDOS  interception  is  not  necessary, 
however.  BANKSWAP  intercepts  only 
the  very  first  BDOS  call  after  it  is 
loaded — this  is  a  convenient  way  to 
force  execution  of  the  BANKSWAP 
initialization  phase.  The  initialization 
code  saves  and  restores  whatever  in¬ 
formation  is  being  passed  to  the  BDOS 
and  modifies  the  JMP  chain  to  prevent 
reinitialization. 

An  RSX  must  be  connected  to  a 
normal  .COM  program  to  be  in¬ 
stalled.  CP/M  Plus  provides  a  utility, 
called  GENCOM,  to  do  this  and  also 
provides  the  RMAC  and  LINK  pro¬ 
grams  needed  to  produce  the  reloca¬ 
table  program  and  its  relocation  bit 
map.  The  process  is  considerably 
more  complicated  than  assembling 
and  loading  a  program  under  CP/M 
2,  but  it  is  easy  to  use  the  CP/M  SUB¬ 
MIT  program  to  direct  the  process. 
An  RSX  can  be  attached  to  any  .COM 
file.  I  have  attached  BANKSWAP  to 
DDT.COM  and  SID.COM  and  it 
works  with  both.  Listing  Two  (page 
52)  shows  the  RSXM AKER. SUB  file 
that  can  create  BANKSWAP.RSX 
and  connect  it  to  SID.COM. 

When  BANKSWAP  is  to  be  at¬ 
tached  to  SID  or  DDT  it  is  assembled 
with  a  REMOVE  flag  set  in  the  RSX 
prefix.  This  ensures  that  when  a 
warm  boot  occurs  upon  exiting  from 
SID  the  space  occupied  by  the  RSX 
will  be  freed.  BANKSWAP  can  also 
be  assembled  to  be  loaded  indepen¬ 
dently  of  SID  by  setting  the  ALONE 
equate  true.  In  this  case  it  is  still  nec¬ 
essary  to  attach  the  RSX  code  to  a 
.COM  file,  which  could  be  nothing 
more  than  a  jump  to  location  zero.  As 
the  Listing  One  shows,  setting 
ALONE  true  resets  the  REMOVE  flag 
in  the  RSX  header,  and  it  also  adds  an 
option  to  the  BANKSWAP  menu  to 
allow  later  removal. 

Dr.  Dobb’s  Journal,  December  1985 


There  is  a  potential  problem  with 
the  version  of  BANKSWAP  shown  in 
Listing  One,  but  I’m  leaving  it  for 
someone  else  to  fix.  As  noted  above, 
BANKSWAP  uses  the  RSX  technique 
in  order  to  be  located  at  the  highest 
available  memory  address.  The  actual 
amount  of  high  memory  that  is  com¬ 
mon  depends  upon  the  hardware  used, 
and  I  didn’t  figure  out  a  way  for  a  pro¬ 
gram  to  determine  this.  If  too  many 
RSXs  are  installed,  the  top  of  avail¬ 
able  memory  can  be  below  the  com¬ 
mon  region.  In  this  case  BANKSWAP 
will  probably  cause  a  crash.  On  my 
Rev.  A  CP/M  card  the  common-mem¬ 
ory  limit  is  at  8000H,  which  leaves 
room  for  a  lot  of  RSXs,  so  I  have  never 
given  the  problem  a  high  priority. 

Using  BANKSWAP 

Once  BANKSWAP  has  been  attached 
to  a  copy  of  SID.COM  it  will  be  in¬ 
stalled  whenever  SID  is  run.  Listing 
Three  (page  52)  shows  part  of  a  typi¬ 
cal  session  with  enhanced  SID.  The 
initialization  process  tells  the  opera¬ 
tor  that  BANKSWAP  is  available.  A 
G28  command  enters  BANKSWAP, 
which  displays  a  menu.  I  usually 
bring  part  of  an  alternate  bank  over 
to  bank  1  first,  then  return  to  the  de¬ 
bugger  to  examine,  disassemble,  or 
alter  the  copy.  In  fact,  if  executable 
code  from  some  portion  of  bank  0  is 
brought  over  to  the  corresponding  ad¬ 
dress  range  in  bank  1  the  debugger 
can  be  used  to  trace  through  it — pro¬ 
viding  that  the  code  doesn’t  switch 
banks  or  access  I/O  or  storage  ad¬ 
dresses  in  an  alternate  bank. 

In  the  Apple  environment  there  is 
one  limitation:  an  Apple’s  I/O  is  all 
memory  mapped  in  bank  0.  If  an  at¬ 
tempt  is  made  to  access  bank  0  in  the 
range  6000H  to  67FFH  it  may  crash 
the  system  because  some  addresses  in 
this  range  activate  switches  on  the 
Apple  main  board  or  peripheral  cards, 
including  the  CP/M  Card  itself. 

Conclusion 

One  of  my  first  reactions  to  CP/M 
Plus  was  a  helpless  feeling.  I  had 
written  my  own  BIOS  for  CP/M  2.2 
on  my  S-100  system,  and  I  was  accus¬ 
tomed  to  being  able  to  explore  how 
the  system  worked  on  any  CP/M  ma¬ 
chine.  With  CP/M  Plus,  portions  of 

Dr.  Dobb’s  Journal,  December  1985 


the  operating  system  were  totally  in¬ 
accessible  to  the  tools  I  had  on  hand. 

Most  users  don’t  need  the  kind  of 
access  I  wanted.  However,  the  initial 
version  of  CP/M  Plus  I  received  did 
not  support  my  printer  interface 
properly.  I  also  had  proposed  to  de¬ 
velop  CP/M  Plus  driver  software  for 
companies  that  manufactured  large- 
format  disks  and  RAM-disk  add-ons 
for  Apples.  The  usability  of  my  sys¬ 
tem  as  well  as  potential  income  de¬ 
pended  upon  my  ability  to  patch  vari¬ 
ous  devices  into  CP/M  Plus. 

BANKSWAP  solved  my  problems. 
It  gave  me  a  way  to  satisfy  my  curios¬ 


ity  about  how  the  Advanced  Logic  1 
Systems  CP/M  Plus  BIOS  worked, 
and  I  was  able  to  figure  out  how  to 
patch  some  of  my  peripheral  drivers 
into  CP/M  Plus.  Finally,  I  now  had 
the  tool  I  needed  to  begin  to  develop 
and  debug  enhancements  to  the  sup¬ 
plied  BIOS.  In  the  process  of  develop¬ 
ing  BANKSWAP  I  also  learned  how 
to  create  and  use  RSXs,  which  are 
very  useful  CP/M  Plus  features. 

DD| 

(Listing  begins  on  next  page) 

Reader  Ballot 

Vote  for  your  favorite  feature/article. 

Circle  Reader  Service  No.  1 93. 


895 


BANKSWAP  (Text  begins  on  page  34) 

Listing  One 


BANKSWAP. ASM 

A.  S.  Woodhull  28  June  83 

rev  1  July  85  —  minor  editing 

20  Oct  83 

This  is  designed  to  be  run  as  a  subprogram  under  DDT 
or  SID,  in  a  banked  version  of  CP/M  3.0  The  function 
of  BANKSWAP  is  to  copy  blocks  of  memory  from  other 
banks  to  and  from  bank  1,  using  a  buffer  in  the  common 
area.  Normal  DDT  or  SID  functions  can  then  be 
performed,  using  the  copy  of  the  code  in  bank  1. 

(Of  course,  you  cannot  trace  through  program  segments 
that  switch  banks  or  access  memory  mapped  I/O  in 
another  bank.) 

BANKSWAP  must  reside  in  the  common  area  of  memory,  and 
a  buffer  area  through  which  data  can  be  copied  must 
also  be  present  in  the  common  area.  BANKSWAP  is  to  be 
assembled  as  a  Resident  System  Extension  (RSX),  which 
will  automatically  be  relocated  to  the  top  of  the  TPA. 

It  is  assumed  that  the  common  area  is  large  enough  to 
allow  an  RSX  to  fit — if  this  is  not  true  BANKSWAP  will 
not  work  in  its  present  form. 

Because  the  location  of  BANKSWAP  in  memory  is  not  fixed 
a  jump  through  a  fixed  location  is  set  up  when  the  BANK- 
SWAP  code  is  installed.  In  this  version  the  RST  5  vector 
at  28H  is  used,  but  any  convenient  location  on  page  zero 
may  be  used. 

Under  CP/M  3  direct  access  of  BIOS  routines  is  generally 
to  be  avoided  by  user  programs,  since  BIOS  routines  may 
have  expectations  about  which  bank  is  selected  when  they 
are  called.  We  will,  however,  do  bank  switching  through 
the  BIOS  selmem  routine.  For  generality  we  will  use  the 
BIOS  vector  at  location  1. 


FFFF 

= 

true : 

equ 

0ffffh 

0000 

= 

false : 

equ 

not  true 

0000 

= 

7 

alone : 

equ 

false  ;make  false  if  attached  to  DDT/SID 

0001 

ss 

7 

biosv : 

equ 

1  yaddress  of  wboot  in  BIOS  found  here 

004E 

= 

selmem: 

equ 

4eh  yoffset  from  wboot 

0100 

= 

7 

buf len : 

equ 

100h  ymove  block  size 

/ 

;  default  parameters 

1000 

c 

movent : 

equ 

1000h  ;to  move  16  pages  (4K)  at  a  time 

0100 

= 

b0df t : 

equ 

100h  ystart  of  block  in  bank  0 

0100 

= 

bldft: 

7 

equ 

100h  ystart  of  block  in  bank  1 

;  zero 

page 

addresses 

0005 

= 

bdos : 

equ 

5  ;bdos  entry  point 

0028 

= 

rstv: 

equ 

28h  ;RST  5  used  as  entry  vector 

y  BDOS 

functions  used 

0001 

= 

conin : 

equ 

1  ?get  a  char 

0009 

= 

pr intf : 

equ 

9  ;print  a  string 

000A 

= 

rdbuf : 

equ 

10  ;read  a  line 

7 

?  This 

is  standard  prefix  for  a  Resident  System  Extension 

;  see 

CP/M 

3  Programmer's  Guide,  1st  ed. ,  sec.  4.4,  p.168 

0000 

i 

0000000000serial: 

db 

0,0, 0,0, 0,0 

0006 

C3A203 

start: 

jmp 

install  ;one-time  routine 

0009 

C30000 

next : 

jmp 

0  yaltered  at  installation 

(Continued  on  page  40) 

38 


one 


Dr.  Dobb’s  Journal,  December  1 985 


BA  N KS  WA  P  (Listing  continued,  text  begins  on  page  34) 

Listing  One 

000C 

0  00  0 

prev: 

dw 

0 

/ 

if 

alone 

rmvf lg : 

db 

0  ;keep  this  in  memory 

else 

000E 

FF 

rmvflg : 

db 

0ffh  ; remove  when  main  program  ends 

endif 

000F 

00 

7 

nonbnk : 

db 

0  ; banked  system 

0010 

4  241 4E4B53 

db 

' BANKSWAP ' 

0018 

00 

loader : 

db 

0 

0019 

0  0  0  0 

db 

0,0 

;  ***  note:  If  BANKSWAP  is  to  be  attached  directly  to  SID.COM 

;  or  DDT.COM 

then  make  rmvflg  0ffh  to  force  removal 

7 

bankexam: 

001B 

210000 

lxi 

h,0  ?get  stack  pointer 

001E 

39 

dad 

sp  ;...and  hold  it  for  return 

001F 

220F04 

shld 

holdsp  ;... reset  SP  to  a  location 

0022 

310804 

lxi 

sp,locstk  ;...in  common  memory 

7 

:  Main 

loop — 

exit  by  Quit  or  Remove  command 

bankex2 

: 

0025 

CD2E00 

call 

prompt  ;this  returns  address  of  sub 

0028 

CD6D00 

call 

doit  ;do  subroutine  addressed  by  HL 

002B 

C32500 

jmp 

bankex2 

002E 

111F02 

7 

prompt : 

lxi 

d,menu  ;get  ready  for  menu 

0031 

3A9103 

Ida 

quietf lag 

0034 

B7 

ora 

a  ;suppress  menu? 

0035 

CA3B00 

jz 

prmpt2 

0038 

11F402 

prmptl : 

lxi 

d, query  ;set  for  prompt  only 

003B 

0E09 

prmpt2 : 

mvi 

c,printf 

003D 

CD0500 

call 

bdos 

;  Get  a 

character 

0040 

0E01 

mvi 

c, conin 

0042 

CD0500 

call 

bdos 

0045 

CD7001 

call 

crlf 

;  make 

upper 

case,  reject  non-alpha 

0048 

E65F 

ani 

5fh 

004A 

FE4 1 

cpi 

'A' 

004C 

DA3800 

jc 

prmptl  ;ask  again  if  invalid 

004F 

FE5B 

cpi 

'Z'+l 

0051 

D23800 

jnc 

prmptl  ?ask  again  if  invalid 

;  find 

match 

in  alphtbl 

0054 

010700 

lxi 

b,altblen  ;length  of  table 

0057 

211102 

lxi 

h,alphtbl+altblen  ;work  back 

005A 

BE 

try: 

cmp 

m 

0  05B 

CA6300 

jz 

match 

005E 

2B 

dcx 

h 

0  05F 

0D 

dcr 

c  ;  count  down 

0060 

C25A00 

jnz 

try 

;  if  c= 

0  no 

match  found.  Now  form  address 

0063 

211102 

match : 

lxi 

h , addrtbl 

0066 

09 

dad 

b  jadd  offset 

0067 

09 

dad 

b  ;again,  2  bytes  per  table  entry 

;  get  the  command  address 

0068 

7E 

mov 

a  ,m 

0069 

23 

inx 

h 

006A 

66 

mov 

h,m 

006B 

6F 

mov 

1 ,  a 

006C 

C9 

ret 

;HL  has  action  address 

006D 

E9 

doit : 

pchl 

;call  here  to  use  action  address 

7 

getbank 

: 

006E 

2A9C03 

lhld 

bOstart  ;setup  addresses 

(Continued  on  page  42)  | 

Dr.  Dobb’s  Journal.  December  1985 

40 

897 

B/ 4  NKS  WA  P  (Listing  continued,  text  begins  on  page  34) 

Listing  One 

0071 

220A04 

shld 

source 

0074 

2A9E03 

lhld 

blstart 

0077 

220C04 

shld 

dest 

0  07A 

3AA103 

Ida 

length+1  ;low 

byte  ignored 

007D 

320E04 

sta 

count 

;  page  by  page 

take  a  chunk  of  bank 

0  to  buffer,  then 

move 

;  to  bank  1  destination 

get2 : 

0080 

F3 

di 

;be  sure  no 

interrupts 

0081 

3E00 

mvi 

a,  0 

0083 

2A0804 

lhld 

selmv 

0086 

CD6D00 

call 

doit 

0089 

CDF400 

call 

getbuf 

008C 

3E01 

mvi 

a  ,  1 

008E 

2A0804 

lhld 

selmv  ;point  to  BIOS  selmem  routine 

0091 

CD6D00 

call 

doit 

0094 

FB 

ei 

; interrupts 

safe  again 

0095 

CDFD00 

call 

putbuf 

0098 

110001 

lxi 

d, buf len 

0  09B 

2A0A04 

lhld 

source 

009E 

19 

dad 

d 

009F 

220A04 

shld 

source 

00A2 

2A0C04 

lhld 

dest 

00A5 

19 

dad 

d 

0  0A6 

220C04 

shld 

dest 

00A9 

210E04 

lxi 

h,  count  ; repeat  for 

required  #  of  pages 

00AC 

35 

dcr 

m 

0  0AD 

C28000 

jnz 

get2 

00B0 

C9 

ret 

t 

putbank : 

00B1 

2A9E03 

lhld 

blstart 

0  0B4 

220A04 

shld 

source 

00B7 

2A9C03 

lhld 

b0start 

00BA 

220C04 

shld 

dest 

00BD 

3AA103 

Ida 

length+1  ;low 

byte  ignored 

00C0 

320E04 

sta 

count 

;  page  by  page 
;  to  bank  0 
put2 : 

,  move  bank  1  data  to 

buffer,  then  move 

it 

00C3 

CDF400 

call 

getbuf 

00C6 

F3 

di 

;be  sure  no 

interrupts 

00C7 

3E00 

mvi 

a ,  0 

00C9 

2A0804 

lhld 

selmv 

00CC 

CD6D00 

call 

doit 

00CF 

CDFD00 

call 

putbuf 

0  0D2 

3E01 

mvi 

a,l 

00D4 

2A0804 

lhld 

selmv 

00D7 

CD6D00 

call 

doit 

00DA 

FB 

ei 

;  interrupts 

safe  again 

00DB 

110001 

lxi 

d,buflen 

00DE 

2A0A04 

lhld 

source 

00E1 

19 

dad 

d 

00E2 

220A04 

shld 

source 

00E5 

2A0C04 

lhld 

dest 

00E8 

19 

dad 

d 

00E9 

220C04 

shld 

dest 

00EC 

2.10E04 

lxi 

h, count 

00EF 

35 

dcr 

m 

00F0 

C2C300 

jnz 

put2 

00F3 

C9 

ret 

;  source  to  buffer 

getbuf : 

00F4 

2A0A04 

lhld 

source 

00F7 

111104 

lxi 

d,buf fer 

0  0FA 

C30401 

jmp 

pbl 

;  buffer  to  dest 

putbuf : 

(Continued  on  page  44) 

42 

Dr.  Dobb’s  Journal,  December  /  985 

898 

1  BANKS\A/AP  (Listing  continued,  text  begins  on  page  34) 

Listing  One 

0  0FD 

2A0C04 

lhld 

dest 

0100 

111104 

lxi 

d , buffer 

0103 

EB 

xchg 

7 

common  code 

for  getbuf  and  putbuf 

0104 

010001 

pbl :  lxi 

b,buflen 

/ 

move  (BC)  bytes  from  (HL)  to  (DE)  (could  use  BIOS  move 

/ 

routine  for 

this ) 

0107 

7E 

move:  mov 

a,m 

0108 

12 

stax 

d 

0109 

23 

inx 

h 

010A 

13 

inx 

d 

010B 

0B 

dcx 

b 

010C 

78 

mov 

a ,  b 

010D 

Bl 

ora 

c 

010E 

C20701 

jnz 

move 

0111 

C9 

ret 

? 

7 

Go  back  to  SID/DDT 

0112 

116403 

quit:  lxi 

d,qmsg  ?say  how  to  get  back 

0115 

0E09 

mvi 

c,printf 

0117 

CD0500 

call 

bdos 

011A 

2A0F04 

lhld 

holdsp  ;restore  stack  pointer 

0 1  id 

F9 

sphl 

011E 

FF 

rst 

7  ;back  to  DDT  or  SID 

7 

if 

alone 

7 

Set  for  removal  on  termination  of  SID  or  DDT 

remove:  lxi 

h,rmvflg  ;set  the  remove  flag 

mvi 

m,0ffh  ;...in  the  RSX  prefix 

lxi 

h,rstv  ;then  wipe  out  the  entry 

mvi 

m,0ffh  ; . . . JMP  with  an  RST  7 

rst 

7  ;leave  via  the  debugger 

endif 

; alone 

7 

7 

Set  up  addresses  for  move,  also  set  up  length 

011F 

111703 

adset:  lxi 

d,b0id  ;tell  current  bank  0  addr 

0122 

0E09 

mvi 

c, printf 

0124 

CD0500 

call 

bdos 

0127 

2A9C03 

lhld 

b0start  ;get  the  address 

012A 

CDDA01 

call 

addro  ;and  print  it 

012D 

219C03 

lxi 

h, b0start 

0130 

CD8101 

call 

update  ;enter  hex  to  (HL) 

0133 

112503 

lxi 

d,blid  ;do  it  again  for  bank  1 

0136 

0E09 

mvi 

c, printf 

0138 

CD0500 

call 

bdos  ;tell 

013B 

2A9E03 

lhld 

blstart 

013E 

CDDA01 

call 

addro 

0141 

219E03 

lxi 

h, blstart 

0144 

CD8101 

call 

update  ;get  new  address,  if  any 

7 

Can  fall  through  from  adset  or  enter  directly  here  to  set 

7 

length  of  block  moved 

0147 

113303 

Inset:  lxi 

d,lnmsg  ;tell  current  length 

014A 

0E09 

mvi 

c, printf 

014C 

CD0500 

call 

bdos 

0 1  4  F 

2AA003 

lhld 

length 

0152 

CDDA01 

call 

addro 

0155 

21A003 

lxi 

h, length 

0158 

CD8101 

call 

update  ;offer  to  change  it 

015B 

CD7001 

call 

crlf 

015E 

C9 

ret 

7 

7 

Toggle  menu 

of  f/on 

015F 

3A9103 

Xpert:  Ida 

quietf lag 

0162 

2F 

cm  a 

jtoggle 

0163 

329103 

sta 

quietf lag 

0166 

C9 

ret 

7 

7 

Get  here  on 

invalid  command 

1 

(Continued  on  page  46)  | 

Dr.  Dobb's  Journal.  December  1 985 

44 

899 

BANKS H'/4 P  (Listing  continued,  text  begins  on  page  34) 

Listing  One 

0167  11FA02 

na : 

lxi 

d,namsg  ;say  can't  do  it 

016A  0E09 

mvi 

c,printf 

016C  CD0500 

call 

bdos 

016F  C9 

ret 

0170  C5 

crlf  s 

push 

b 

0171  D5 

push 

d 

0172  E5 

push 

h 

0173  F5 

push 

psw 

0174  111403 

lxi 

d, crlf string 

0177  0E09 

mvi 

c,printf 

0179  CD0500 

call 

bdos 

017C  FI 

pop 

psw 

017D  El 

pop 

h 

017E  D1 

pop 

d 

017F  Cl 

pop 

b 

0180  C9 

ret 

i 

;  get  string, 

do  nothing  if  null,  else  convert,  store  at  (HL) 

update : 

0181  E5 

push 

h  ;save  the  location 

;  loop 

back  here  if  input  is  not  valid 

0182  210000 

updl : 

lxi 

h,0  jinitial  buffer 

0185  229303 

shld 

inbuf+1 

0188  114203 

lxi 

d,chquery  ;say  what's  up 

018B  0E09 

mvi 

c,printf 

018D  CD0500 

call 

bdos 

0190  119203 

lxi 

d, inbuf 

0193  0E0A 

mvi 

c,rdbuf  ;read  console  until  <ret> 

0195  CD0500 

call 

bdos 

0198  CD7001 

call 

crlf 

019B  3A9303 

Ida 

inbuf+1  ;get  length  of  hex  string 

019E  B7 

ora 

a  ;check  for  0  length  input 

019F  C2A401 

jnz 

convert 

;  null 

string, 

go  back 

01A2  El 

pop 

h  ;retrieve  value  at  entry 

01A3  C9 

ret 

/ 

;  Convert  the 

hex  string  in  the  buffer  to  binary 

convert 

: 

01A4  210000 

lxi 

h,0  ;start  with  a  zero 

01A7  47 

mov 

b,a  ;hold  length  in  B 

01A8  119403 

lxi 

d , inbuf +2 

01AB  1A 

conv2 : 

ldax 

d  ;get  first  (or  next)  char 

01AC  13 

inx 

cl 

0 lAD  FE60 

cpi 

60h 

01AF  DAB401 

jc 

conv3 

01B2  E65F 

ani 

5fh  ;make  lower  case  if  necessary 

01B4  D630 

conv3  s 

sui 

'  0 ' 

01B6  FA8201 

jm 

updl  jmust  be  valid  hex,  0..9,  A..F 

01B9  FE0A 

cpi 

0ah 

01BB  DACA01 

jc 

num  7 jump  if  a  good  numeric 

0 lBE  D607 

sui 

7 

01C0  FE0A 

cpi 

0ah 

01C2  DA8201 

jc 

updl  ;error  if  not  good  alpha 

01C5  FE10 

cpi 

10h 

01C7  D28201 

jnc 

updl  ;error  if  not  good  alpha 

01CA  29 

num: 

dad 

h  ;multiply  current  val  by  16 

01CB  29 

dad 

h 

01CC  29 

dad 

h 

01CD  29 

dad 

h 

01CE  85 

add 

1  ;add  new  least  significant  digit 

01CF  6F 

mov 

1 ,  a 

01D0  05 

dcr 

b  jcountdown  the  digits 

01D1  C2AB01 

jnz 

conv2 

01D4  EB 

xchg 

;result  to  DE 

0 1D5  El 

pop 

h  ;HL  at  entry  says  where  to  it 

(Continued  on  page  48) 

46 

Dr.  Dobb's  Journal,  December  1 985 

900 

BA  NKS  WA  P  (Listing  continued,  text  begins  on  page  34) 

1  Listing  One 

01D6 

73 

mov 

m,  e 

01D7 

23 

inx 

h 

01D8 

72 

mov 

m,  d 

0 1D9 

C9 

ret 

7 

7 

Print  HL  as  hex 

addro : 

0  Ida 

D5 

push 

d 

01DB 

E5 

push 

h 

01  DC 

EB 

xchg 

01DD 

215C03 

lxi 

h,hexstr  jwhere  to  build 

string 

01E0 

7A 

mov 

a ,  d 

01E1 

CDF301 

call 

byte  ;get  A  as  2  ASCII  chars 

at  (HL) 

01E4 

7B 

mov 

a ,  e 

01E5 

CDF301 

call 

byte  ;again,  low  byte 

01E8 

115C03 

lxi 

d,hexstr 

01EB 

0E09 

mvi 

c, printf 

01ED 

CD0500 

call 

bdos  ;print  it 

01F0 

El 

pop 

h 

01F1 

Dl 

pop 

d 

01F2 

C9 

ret 

7 

7 

Convert  byte 

to  hex  ASCII  chars,  put  at  (HL) 

01F3 

F5 

byte:  push 

psw 

01F4 

IF 

rar 

;get  high  nybble 

01F5 

IF 

rar 

01F6 

IF 

rar 

01F7 

IF 

rar 

01 F8 

CDFC01 

call 

nybble 

0  lFB 

Fl 

pop 

psw  ;fall  through  for  low  nybble 

7 

nybble  makes 

1  char,  advances  output  pointer 

01FC 

E60F 

nybble:  ani 

0fh 

01FE 

C630 

adi 

i  0  i 

0200 

FE3A 

cpi 

3ah 

0202 

DA0702 

jc 

nput 

0205 

C607 

adi 

7 

0207 

77 

nput:  mov 

m,  a 

0208 

23 

inx 

h 

0209 

C9 

ret 

7 

7 

Acceptable  command  inputs  go  in  this  table 

alphtbl : 

020A 

00 

db 

0  ;dummy  for  no  match 

020B 

41 

db 

'A' 

020C 

47 

db 

'G' 

020D 

4C 

db 

'L' 

020E 

50 

db 

ipI 

020F 

51 

db 

’  Q ' 

7 

if 

alone 

db 

'R' 

endif 

;alone 

0210 

58 

7 

db 

•x' 

0007 

= 

altblen : 

equ  $-alphtbl 

7 

addresses  of 

action  routines,  same  order  as  alphtabl 

addrtbl : 

0211 

6701 

dw 

na  ;not  available 

0213 

1F01 

dw 

adset  jaddress  set 

0215 

6E00 

dw 

getbank 

0217 

4701 

dw 

Inset  ;length  set 

0219 

B100 

dw 

putbank 

0  2lB 

1201 

dw 

quit 

7 

if 

alone 

dw 

remove 

endif 

(Continued  on  page  50) 

Dr.  Dobb's  Journal,  December  1 985 

48 

901 

BA  NKS  WA  P  (Lifting  continued,  text  begins  on  page  34) 

Listing  One 

021D 

5F01 

aw 

xpert  ;expert  mode,  no  menu 

021F 

4261CE6B73menu : 

db 

'Bankswap  1.0  by  A.  S.  Woodhull 

10/20/83' ,0dh,0ah 

0248 

66756E6374 

db 

'functions  available :', 0dh, 0ah 

025E 

09412E2E2E 

db 

'  A... set  move  Addresses ', 0dh , 0ah 

0277 

09472E2E2E 

db 

'  G...Get  alternate  bank ' , 0dh , 0ah 

0  29  0 

09  4C2E2E2E 

db 

'  L...set  move  Length ', 0dh, 0ah 

02A6 

09502E2E2E 

db 

'  P. . .Put  alternate  bank ' , 0dh, 0ah 

02BF 

09512E2E2E 

db 

'  Q. . .Quit  to  SID  or  DDT',0dh,0ah 

7 

if 

alone 

db 

'  R. . .Remove  BANKSWAP' , 0dh, 0ah 

endif 

02D8 

09582E2E2E* 

db 

'  X... expert  mode  (no  menu) ' , 0dh, 0ah 

0dh, 0ah, '??  $' 

02F4 

0D0A3F3F20query: 

db 

02  FA 

2E2E2E6675namsg : 

db 

'...function  not  available.' 

crlfstring: 

0314 

0D0A24 

db 

0dh , 0ah, ' $' 

0317 

42616E6B20b0id: 

db 

'Bank  0  addr:  $' 

0325 

42616E6B20blid: 

db 

'Bank  1  addr:  $' 

0333 

4C656E67741nmsg: 

db 

'Length  is  now  $' 

chquery 

: 

0342 

4368616E67 

db 

'Change  to?  (CR  to  keep):  $' 

0350 

hexst  r 

ds 

4 

0360 

480D0A24 

db 

'H' , 0dh, 0ah, '$' 

0364 

52652D656Eqmsg : 

db 

'Re-enter  BANKSWAP  from  DDT  or  SID  by  MG28'" 

038E 

0D0A24 

db 

0dh, 0ah, ' $' 

t 

quietf lag : 

0391 

00 

db 

0  ;initialized  off 

0392 

08  inbuf : 

db 

8  ;max  length  of  buffer 

0393 

ds 

9 

1 

;  default  parameters:  alter  by  Set  and  Length  commands 

039C 

0001  b0start 

: 

dw  b0dft 

039E 

0001  blstart 

: 

dw  bldft 

03A0 

0010  length: 

dw  movent 

1 

;  One-time  routine,  on  1st  BDOS  call  intercepted 

install 

: 

03A2 

C5 

push 

b  ;keep  everything  as  it  was 

03a3 

D5 

push 

d  ;...so  BDOS  function  can  be 

03A4 

E5 

push 

h  ; . . . completed 

03A5 

F5 

push 

psw 

;  set  up  restart  vector  for  re-entry 

03A6 

3EC3 

mvi 

a,0c3h  ;a  JMP  instruction 

03A8 

322800 

sta 

rstv 

03AB 

211B00 

lxi 

h,bankexam 

03AE 

222900 

shld 

rstv+1 

;  set  up  address  of  BIOS  routine  accessed  directly 

03Bl 

2A0100 

lhld 

biosv  ;find  where  BIOS  is 

03B4 

114E00 

lxi 

d,selmem  ;...and  add  offset 

03B7 

19 

dad 

d 

03B8 

220804 

shld 

selmv 

;  then 

patch 

the  RSX  prefix  to  prevent  reinstallation 

03BB 

2A0A00 

lhld 

next+1 

03BE 

220700 

shld 

start+1 

;  tell 

'  em  we 

're  ready 

03C1 

0E09 

mvi 

c, printf 

03C3 

11D003 

lxi 

d, imsg 

03C6 

CD0900 

call 

next 

03C9 

Fl 

pop 

psw  ;continue  with  the  task  that 

03CA 

El 

pop 

h  ; . . .was  so  rudely  interrupted 

03CB 

Dl 

pop 

d 

03CC 

Cl 

pop 

b 

03CD 

C30900 

jmp 

next 

(Continued  on  page  52) 

50 

Dr.  Dobb's  Journal,  December  1985 

Q  09 

D/\  f\l\,  3  rr/ll  (Listing  continued,  text  begins  on  page  34) 

Listing  One 


03D0 

7 

42414E4B53imsg : 

db 

'BANKSWAP  loaded.  To  access  from  DDT  or 

03F7 

5349442074 

db 

'SID  type  "G28 " ' 

0405 

0D0A24 

db 

0dh, 0ah, ' $' 

;  reuse  installation  code  area  for  stack  space 
locstk : 


;  uninitialized  storage 


0408 

selmv : 

ds 

2 

;used  for  BIOS  call  to  selmem 

040A 

source : 

ds 

2 

;for  moves  to  buffer 

040C 

dest : 

ds 

2 

;for  moves  from  buffer 

040E 

count : 

ds 

1 

;blocks  to  move 

040F 

holdsp: 

ds 

2 

;stack  pointer  from  DDT  or  SID 

7 

buffer : 

0411 

ds 

buflen 

0511 

end 

End  Listing  One 


Listing  Two 

;  RSXMAKER . SUB  assemble  RSX  and  attach  to  existing  COM  file 
;  ASW  21  Oct  83 

;  Usage:  A>SUBMIT  RSXMAKER  <comfile>  <asmfile> 

7 

rmac  $2 

link  $2  [op] 

ren  $2 . rsx=$2 . prl 

gencom  $1  $2  End  Listing  Two 


Listing  Three 

Listing  Three.  An  example  of  the  use  of  BANKSWAP  to  allow  examination  of  a 
portion  of  a  custom  disk  drive  routine  installed  in  bank  0. 
(Comments  added  in  parentheses). 


(go  to  BANKSWAP) 


D>sid 

BANKSWAP  loaded.  To  access  from  DDT  or  SID  type  "G28" 
CP/M  3  SID  -  Version  3.0 
#g28 

Bankswap  1.0  by  A.  S.  Woodhull  10/20/83 
functions  available: 

A... set  move  Addresses 
G...Get  alternate  bank 
L...set  move  Length 

P.  . .Put  alternate  bank 

Q. ..Quit  to  SID  or  DDT 
X... expert  mode  (no  menu) 


??  x 
??  a 
Bank  0 
Change 
Bank  1 
Change 
Length 
Change 
??  g 
??  q 

Re-enter  BANKSWAP  from  DDT  or 
*D6lE 

#d7c00  7c3f 


addr:  0100H 
to?  (CR  to  keep) 
addr:  0100H 
to?  (CR  to  keep) 
is  now  1000H 
to?  (CR  to  keep) 


7000 


7000 


<CR> 


7C00: 

C3 

09 

7C 

C3 

0F 

7C 

C3 

15 

7C 

21 

94 

0C 

C3 

00 

73 

21  . 

7C1 0  : 

74 

0C 

C3 

00 

73 

21 

IB 

0C 

C3 

00 

73 

AE 

Fl 

0C 

A9 

04  t 

7C20: 

D0 

2D 

AE 

Fl 

0C 

AC 

DF 

0C 

B9 

BA 

0C 

9D 

8E 

C0 

AD 

DE  . 

7C30: 

0C 

4A 

48 

A9 

00 

2A 

0D 

EF 

0C 

0D 

Fl 

0C 

A8 

B9 

80 

C0  ., 

(remove  the  menu) 

(set  up  range) 

(don't  accept  default) 


(length  is  OK) 

(get  the  data  from  bank  0) 
(back  to  SID) 

SID  by  "G28" 

(we're  back) 

(examine  the  data  from  bank  0) 

..  I..  I..  I  1  . . . . S  ! 
s  !  .  .  .  .  s . 


(Tc 

D> 


(return  to 


.  JH. 

CP/M) 


End  Listings 


Dr.  Dobb’s  Journal,  December  1985 


52 

90S 


Adding  a  COPY  Command  to 
ProDOS 


hy  Shawn  Day  1  f  you  use  ProDOS  on  an  Apple  or 

the  same  volume  or  to  copy  files  from 

I  Apple-compatible  system,  you  may 

one  volume  to  another.  If  you  are  in 

1  have  noticed  that  you  cannot  copy 

the  process  of  copying  files  from  one 

files  from  one  volume  to  another 

volume  to  another  and  no  ProDOS 

without  running  FILER  and  losing 

prefix  has  been  set,  you  must  specify 

any  BASIC  program  you  have  in 

the  complete  path  name  for  both 

memory.  I  ran  across  this  problem 

files,  e.g., 

when  I  tried  to  copy  files  to  the  Pro- 

DOS  RAM  disk  from  within  a  BASIC 

COPY  /MYDISK/HELLO, 

program:  I  wanted  my  startup  pro- 

/RAM/STARTUP 

gram  to  load  all  of  the  ProDOS  editor 

and  assembler  files  into  the  RAM 

If  a  ProDOS  prefix  has  been  set,  it 

disk  so  that  I  could  have  the  maxi- 

will  automatically  be  prefixed  to  ei- 

mum  amount  of  storage  space  on  my 

ther  path  name  if  required.  For  ex- 

single  disk  drive  and  speed  up  the 

ample,  if  the  prefix  is  /RAM/,  the 

process  of  assembling  long  source 

command 

files.  To  solve  this  problem,  I  wrote  a 

program  that  adds  a  COPY  command 

COPY  /MYDISK/HELLO, 

to  ProDOS. 

STARTUP 

Additional  banks  of  memory  can  buy  you  both 

speed  and  more  usable  memory  even  though  your 

program  must  reside  entirely  in  one  bank. 

Using  the  Program 

will  copy  the  file  /MYDISK/HELLO 

To  add  the  COPY  command  to  Pro- 

to  the  file  /RAM/STARTUP.  If  the 

DOS,  simply  type  BRUN  COPY  or  use 

destination  file  name  already  exists 

the  general-purpose  “run”  command, 

on  the  destination  volume,  the  com- 

-COPY.  The  code  will  be  installed  in 

mand  exits  with  the  DUPLICATE  FI- 

memory  above  the  ProDOS  file  buff- 

LENAME  error.  This  prevents  acci- 

ers  and  protected  in  the  system  bit 

dental  erasure  of  files.  If  you  want  to 

map.  To  execute  the  command,  use 

copy  a  file  over  an  already  existing 

the  following  syntax: 

file,  use  the  DELETE  command  to  de- 

stroy  the  existing  file  and  then  use  the 

COPY  pathnamel,pathname2 

COPY  command. 

COPY  cannot  be  used  to  copy  files 

A  new  file  (pathname2)  will  be 

directly  from  one  disk  to  another  if 

created,  and  the  contents  of  the  file 

you  have  only  one  disk  drive.  Howev- 

indicated  by  pathname  1  will  be  cop- 

er,  if  you  have  a  ProDOS  RAM  disk 

ied  into  it.  You  can  use  this  command 

available  to  you  (that  is,  you  are  using 

to  make  backup  copies  of  any  file  on 

an  Apple  I Ic  or  a  He  with  an  extended 

80-column  card)  you  have  the  capa- 

bility  to  copy  the  file  into  the  RAM 

Shawn  Day,  724  Glenburn  St.,  Ke- 

disk  and  then  copy  the  file  again  from 

lowna,  British  Columbia  VI V  4G6 

the  RAM  disk  over  to  the  destination 

Canada 

disk. 

54 

on  a 

Dr.  Dobb's  Journal,  December  1985 

Entering  the  Program 

The  source  code  for  COPY  is  shown  in 
Listing  One  (page  58).  To  type  in  the 
program,  you  can  either  use  an  assem¬ 
bler  or  type  the  hexadecimal  numbers 
in  from  the  monitor.  When  you  are 
finished,  save  the  program  with 

BSAVE  COPY,A$6000,LS2B6 


How  it  Works 

The  COPY  code  is  divided  logically 
into  three  parts:  installation,  first- 
stage  processing,  and  second-stage 
processing.  I  will  describe  the  opera¬ 
tion  of  each  in  turn. 

Installation 

When  COPY  is  invoked,  it  relocates 
itself  to  reside  between  the  ProDOS 
BASIC. SYSTEM  and  its  file  buffers. 
A  call  to  GETBUFR  with  the  accu¬ 
mulator  containing  the  value  $02 
(Listing  One,  lines  69  and  70)  re¬ 
serves  the  required  two  pages  of 
memory.  The  high  byte  of  the  start¬ 
ing  address  of  the  reserved  space  is 
returned  in  the  accumulator.  Line  71 
checks  to  see  if  the  call  to  GETBUFR 
was  successful.  In  ProDOS  Version 
1 .0. 1  there  is  a  bug  in  GETBUFR  that 
causes  it  to  return  a  successful  error 
code  whether  or  not  an  error  occurs. 
Due  to  this  bug,  COPY  will  only  work 
with  ProDOS  Version  1.1.1  and  later. 
If  you  have  an  earlier  version  of  Pro¬ 
DOS,  see  your  dealer  to  get  an  up¬ 
date.  If  GETBUFR  is  unsuccessful, 
the  program  exits  with  an  appropri¬ 
ate  error  message  (line  72). 

Assuming  the  GETBUFR  call  was 
successful,  program  control  is  trans¬ 
ferred  to  GOTSPACE  (lines  73-83), 
which  protects  the  acquired  memory 
space  via  the  system  bit  map  and  up¬ 
dates  RSHIMEM.  The  routine  BIT¬ 
MAPS  (lines  135-157)  is  called 
twice  —once  to  protect  each  page. 
Then  the  value  of  RSHIMEM  in  the 
BASIC. SYSTEM’S  global  page  is  de¬ 
creased  by  2  (lines  80-83).  This  pro¬ 
tects  the  acquired  memory  from  a 
call  to  FREBUFR.  FREBUFR  resets 
HIMEM  to  the  value  contained  in 
RSHIMEM.  Because  COPY  dynami¬ 
cally  allocates  buffer  space  during 
execution  via  GETBUFR  and  FRE¬ 
BUFR,  it  is  necessary  to  perform  this 


step.  If  you  wish  to  have  any  other 
added  ProDOS  commands  resident  in 
memory  during  the  execution  of 
COPY,  they  must  each  be  protected 
from  FREBUFR  by  this  method. 

The  next  program  segment  (lines 
87-95)  hooks  the  COPY  command 
into  BASIC. SYSTEM’S  external  com¬ 
mand  link.  Any  previously  installed 
external  commands  are  daisy  chained 
by  the  method  described  in  the  Pro¬ 
DOS  Technical  Reference  Manual. 
Lines  (99-115)  update  all  internal, 
nonrelocatable  references  so  that  the 
program  will  execute  properly  in  its 
final  location.  They  make  use  of  the 
table  of  nonrelocatable  references  in 
lines  161-188. 

After  all  the  references  are  adjust¬ 
ed,  the  code  is  moved  to  its  final  loca¬ 
tion  by  the  monitor  MOVE  routine 
(lines  1 19-131).  This  finishes  the  in¬ 
stallation  of  COPY,  and  control  is 
passed  back  to  the  system. 

First-Stage  Processing 

When  ProDOS  runs  across  a  com¬ 
mand  that  it  does  not  recognize,  it 
passes  control  to  EXTRNCMD  in  the 
BASIC. SYSTEM  global  page.  With 
COPY  installed,  this  location  con¬ 
tains  a  jump  to  the  relocated  copy 
code  (starting  at  line  203).  Here  the 
path-name  buffer  is  checked  to  see  if 
it  contains  the  COPY  command  in  up¬ 
per  or  lower  case.  If  not,  the  carry 
flag  is  set  to  indicate  no  match,  and 
control  passes  to  the  next  user-in¬ 
stalled  external  command  (lines  214- 
215  and  193).  If  there  are  no  more 
user-installed  commands,  line  193 
will  contain  a  jump  to  an  RTS  in¬ 
struction.  Assuming  the  COPY  com¬ 
mand  was  found,  execution  continues 
at  line  218,  where  the  command¬ 
string  length  is  decremented  and 
stored  in  XLEN.  Lines  220  and  221 
store  a  0  in  XCNUM  to  indicate  to 
BASIC. SYSTEM  that  the  command  is 
external,  and  lines  222—225  store  the 
address  where  second-stage  process¬ 
ing  is  to  be  resumed  after  the  input 
string  has  been  parsed  by  BASIC- 
. SYSTEM.  Next,  PBITS  and 
PBITS+  1  are  set  to  indicate  that  file 
creation  is  allowed,  that  the  com¬ 
mand  requires  two  path  names,  and 
that  the  current  prefix  should  be 
fetched  if  none  is  specified  in  the 


command  string.  Finally,  the  pro¬ 
gram  exits  with  the  carry  flag  cleared 
to  tell  BASIC. SYSTEM  that  the  com¬ 
mand  has  been  identified. 

Second-Stage  Processing 

After  BASIC. SYSTEM  has  parsed  the 
command  string,  control  is  passed  to 
line  236.  At  this  point,  FBITS  and 
FBITS+1  have  been  set  by  BASIC 
.SYSTEM  to  indicate  which  parame¬ 
ters  were  present  in  the  command 
string.  Lines  236-243  check  that  two 
path  names  were  given  and  exit  with 
a  syntax  error  if  not. 

Assuming  both  path  names  were 
present,  execution  continues  at 
PARMSOK  (line  247),  where  a  GET 
_FILE_INFO  call  is  made  to  the  MLI 
(machine-language  interface) 
through  the  GOSYSTEM  vector.  This 
call  gets  the  file  information  for  the 
source  file  (the  first  path  name  speci¬ 
fied  in  the  command  string).  The  file 
type  is  checked  to  make  sure  it  is  not 
a  directory  or  bad  block  file.  (Copy¬ 
ing  these  file  types  would  be  mean¬ 
ingless.)  If  the  file  is  either  of  these 
two  types,  the  program  exits  with  a 
FILE  TYPE  MISMATCH  error. 

Next,  a  new  file  is  created  with  the 
file  name  specified  by  the  second 
path  name  in  the  command  line  (lines 
262-277).  If  an  error  occurs  during 
the  CREATE  attempt,  control  passes 
to  BADCALL  (line  277),  where  the 
MLI  error  is  translated  to  a  BASIC 
.SYSTEM  error,  the  carry  flag  is  set, 
and  an  RTS  instruction  transfers  con¬ 
trol  back  to  BASIC. SYSTEM.  If  the 
CREATE  is  successful,  lines  281-289 
attempt  to  obtain  a  IK  file  buffer 
from  the  system  for  use  by  the  desti¬ 
nation  file.  If  the  attempt  is  unsuc¬ 
cessful,  the  NO  BUFFERS  AVAIL¬ 
ABLE  error  is  generated  (lines 
284-286). 

Finally,  the  rest  of  free  memory 
(excluding  any  resident  BASIC  pro¬ 
grams  and  any  variables  that  may 
have  values  assigned  to  them)  is  re¬ 
served  for  the  file-transfer  buffer 
(lines  294-316).  It  is  desirable  to 
have  as  large  a  buffer  as  possible  in 
order  to  speed  up  the  copy.  If  a  very 
large  BASIC  program  is  in  memory  at 
the  time  the  COPY  command  is  exe¬ 
cuted,  the  file  will  be  copied  in  sever¬ 
al  passes  because  only  a  small 


Dr.  Dobb's  Journal.  December  1985 


55 

905 


amount  of  it  can  be  read  into  memory 
at  one  time.  If  there  is  no  BASIC  pro¬ 
gram  in  memory,  even  very  large  files 
can  be  copied  in  one  or  two  passes.  If 
there  is  less  than  one  free  page  of 
memory,  all  of  the  buffer  space  ob¬ 
tained  so  far  is  returned  to  the  sys¬ 
tem,  and  the  program  exits  with  the 
NO  BUFFERS  AVAILABLE  error 
(lines  302-305). 

Now  that  all  the  buffers  are  set  up, 
lines  320-331  open  the  destination 
file,  and  lines  335-337  open  the 
source  file.  Any  errors  detected  dur¬ 
ing  this  process  cause  the  buffer 
space  to  be  returned  to  the  system 
and  an  appropriate  error  message  to 
be  generated.  Lines  346-351  call 
NEWLINE  to  indicate  that  no  NEW- 
LINE  character  is  to  be  recognized. 
The  NEWLINE  character  is  used  by 
ProDOS  when  reading  a  file  to  signify 
the  end  of  a  line  or  record.  For  exam¬ 
ple,  when  reading  lines  from  a  text 
file,  the  NEWLINE  character  should 
be  set  to  $8D  (carriage  return).  This 
allows  processing  of  the  file  line  by 
line.  Because  it  is  desirable  to  read  in 
as  much  of  the  file  as  possible  during 
each  pass,  the  NEWLINE  character  is 
disabled  by  setting  the  mask  byte  to  0 
(lines  346  and  347). 

Lines  361-378  perform  the  actual 
transfer.  Lines  363  and  364  call  the 
MLI  READ  routine  to  read  in  as  much 
of  the  file  as  possible.  An  error  during 
the  read  causes  program  control  to 
pass  to  line  366,  where  the  end-of-file 
condition  is  checked.  If  the  end  of  file 
has  been  reached,  lines  382-401  close 
both  files,  free  the  allocated  buffer 
space,  and  return  control  to  the  sys¬ 
tem.  Otherwise,  lines  369-375  write 
the  buffer  contents  out  to  the  new  file 
and  then  (assuming  no  errors  were  de¬ 
tected  during  the  WRITE)  go  back  for 
more  (line  376).  Lines  402-432  are 
the  parameter  lists  used  during  the 
calls  to  the  MLI. 

Improvements  and  Modifications 

The  most  obvious  improvement  is  to 
allow  the  use  of  disk-to-disk  copies 
with  a  single  disk  drive.  You  might 
also  find  it  useful  to  allow  COPYing  a 
text  file  to  the  screen  or  a  printer. 
Then  you  would  have  a  general-pur¬ 
pose  copy  utility,  such  as  CP/M’s  PIP 
command.  ..... 


ProDos  Copy  Listing  (Text  begins  on  page  54) 


0093 

0000 

0000 

0000 

0000 

0000 

0000 

0000 

0000 

0000 

0000 

0000 

0000 

0000 

0000 

0000 

0000 


1  1ST  GEN,VSYM 

2  I**.*.***...**.*.****..*.**.***.*..****....*.*.. 

3  * 

4  *  COPY  Command  for  ProOOS  BASIC. SYSTEM 

5  *  by  Shawn  Day 

6  * 

7  *  Syntax:  COPY  patlinamel , pathname2 

8  * 

9  *  This  comnand  copies  the  file  specified  by 

10  *  pathnamel,  to  the  file  specified  by  pritlukune2. 

11  *  If  iiathnaine2  does  not  exist,  it  is  created. 

12  *  If  pathname2  does  exist,  the  convand  exits 

13  *  with  a  DUPLICATE  FILENAME  error. 

14  * 

15  •  A|»ple  ProOOS  Assembler  version  1.0 

16  * 

17 


0000 

00 3C 

19  AIL 

EQU 

$3C 

; Start  address  for  MOVE 

0000 

00 3D 

20  Alii 

BQU 

S3D 

0000 

003E 

21  A2L 

DQU 

S3E 

;End  address  for  MOVE 

0000 

00  3F 

22  A2i! 

EQU 

53F 

0000 

0042 

23  A4L 

DQU 

$42 

; Destination  for  MOVE 

0000 

0043 

24  A41I 

DQU 

$43 

0000 

0073 

25  IIIMEM 

EQU 

$73 

; IIIMEM  pointer 

0000 

00PC 

26  TEMP 

EQU 

$FC 

/Temporary  storage 

0000 

00FD 

27  OFFSET 

EQU 

$FD 

/Offset  for  relocation  code 

0000 

0UFE 

28  PTR 

BQU 

$FE 

zTemjJorary  address  pointer 

0000 

00FE 

29  COUNT 

EQU 

$FE 

/Temporary  storage 

0000 

0200 

30  INOUF 

EQU 

$200 

/Keyboard  input  buffer 

0000 

UE06 

31  L'XTUIJCMD 

EQU 

8BE06 

/External  and  jmp  vector 

0000 

BE09 

32  ERiO/r 

EQU 

SBP.09 

/Print  error  message 

0000 

UE50 

33  XTRNADDR 

EQU 

$BG50 

/External  cmd  address 

0000 

UE52 

34  XLKN 

EQU 

$UE52 

/Length  of  cmd  string-1 

0000 

BE53 

35  XCNUM 

DQU 

$BE53 

/and  no.  (extrn.  end  -  0) 

0000 

BE54 

36  POITS 

EQU 

SBE54 

/Coui.und  parameter  Igits 

0000 

BE56 

37  FBITS 

BQU 

$BE56 

/Command  parameter  bits  found 

0000 

BE6C 

38  VPAT1I1 

EQU 

$BE6C 

/Pathname  1  buffer  pointer 

0000 

BE6E 

39  VPATH2 

EQU 

$BE6E 

jPatJuu/nc  2  buffer 

0000 

BE70 

40  GOSYSTEM 

DQU 

SBE70 

/Perform  MLI  call 

0000 

BE8B 

41  BADCALL 

BQU 

5UE80 

/Translate  MLI  error  to  BI  error 

0000 

BEB4 

42  SSGINFO 

EQU 

$UEB4 

/GET_FILE_INFO  MLI  buffer 

0000 

BF037 

43  FIACE3S 

EQU 

5BLB7 

/Access  used  by  lock/ unlock 

0000 

bEB8 

44  FIFILID 

EQU 

$UCB8 

/File  id  of  disk  file 

0000 

BEB9 

45  FIAUXID 

DQU 

$BEB9 

/Aux  id 

0000 

BDCE 

46  OSYSBUF 

EQU 

SBECE 

/Buffer  for  MLI  OPEN 

0000 

BED0 

47  OREFNUM 

EQU 

SliEDO 

/Reference  number  of  opened  file 

0000 

BED2 

43  NEWU<EF 

EQU 

SUED2 

/NEWLINE  file  reference  number 

0000 

BED3 

49  NLINENBL 

EQU 

$BED3 

/NEWLINE  enable  mask  and  char 

0000 

BED6 

50  I6VKEFNUM 

EQU 

SBED6 

/Read  file  reference  number 

0000 

13ED7 

51  RWDATA 

BQU 

$BED7 

/Read  butfer 

0000 

BED9 

52  14  COUNT 

BQU 

$BED9 

/Read  (Number  of  cliar  requested) 

0000 

BEDB 

53  IWrRANS 

EQU 

$BEDU 

/Read  (Number  of  char  transferred) 

0000 

BEDE 

54  CFREFNUM 

DQU 

5  BEDE 

/Close  file  reference  number 

0000 

BEF5 

55  GETBUFR 

EQU 

5BEF5 

/Get  buffer  from  system 

0000 

BEF8 

56  FKEOUFR 

EQU 

$BEF8 

/Give  buffer  buck  to  system 

0000 

BEFB 

57  RSHIMEM 

EQU 

$B EFB 

/IIIMEM  reset  value  for  FREEBUFR 

0000 

UFOO 

58  MLI 

EQU 

$BF00 

/MLI  entry  [joint 

0000 

BF53 

59  BITMAP 

BQU 

$BF58 

/System  bit  map  table 

0000 

FE2C 

60  MOVE 

DQU 

$FE2C 

/Monitor  MOVE  routine 

— 

NEXT  OBJECT 

FILE  NAME  IS  COPY 

6000 

6000 

61 

OfW 

$6000 

6000 

6000 

63  a  Installation 

6000 

6000 

65  * 

6000 

66  * 

Get  some  space  for 

the  COPY  code 

6000 

67  • 

and  protect  it  by  updating  the  system  bit  map 

6000 

68  • 

6000 

AD 

03 

61 

69 

LOA 

PAGES 

/Number  of  pages  needed 

6003 

20 

F5 

BE 

70 

JSR 

GETBUFR 

/Ask  the  BI  for  soiie  space 

6006 

90 

03 

6000 

71 

OCC 

GOTSPACE 

/We  got  the  space 

6008 

4C 

09 

BE 

72 

JMP 

ERIOUT 

/Couldn't  find  space 

600B 

48 

73  GOTSPACE 

PIIA 

/Save  start  address 

600C:AE 

03 

61 

74 

LDX 

PAGES 

/Number  of  pages  to  protect 

600F 

20 

73 

60 

75  PROTECT 

JSR 

BITMAPS 

/Protect  a  page 

6012 

18 

76 

CLC 

6013 

69 

01 

77 

ADC 

«$01 

/Point  to  next  page 

6015 

CA 

78 

DEX 

/Are  we  done? 

6016 

DO 

F7 

600F 

79 

ONE 

PROTECT 

tlC  not 

6018 

AD 

FB 

BE 

80 

LOA 

RSI  II  MEM 

/Protect  code  from  FREEBUFR 

601B 

38 

81 

SEC 

60 1C:  ED 

03 

61 

82 

SBC 

PAGES 

601F 

8D 

FB 

BE 

33 

STA 

RSHIMLM 

6022 

84  * 

6022 

85  * 

Hook 

routine  into 

the  ProOOS  external  comnand  vector 

6022 

86  * 

6022 

AE 

07 

BE 

87 

LDX 

EXTRNCMD*1 

/Set  link  to  next  external  comnund 

6025 

AC 

08 

BE 

88 

LDY 

EXTRNCMD+2 

/or  RTS  (whicltever  was  already  tliere) 

6028 

3E 

01 

61 

89 

STX 

LINK ♦ 1 

602B 

8C 

02 

61 

90 

STY 

LINK* 2 

602E 

A9 

0E 

91 

LOA 

I >START 

/Install  the  address  of  the 

6030 

8D 

07 

BE 

92 

STA 

LXTRNCMD*  1 

; COPY  comnand  handler  in  the 

6033 

68 

93 

PLA 

/external  comnand  jump  vector 

6034 

48 

94 

PHA 

/Save  start  address  for  MOVE 

6035 

8D 

08 

BE 

95 

STA 

LXTHNCMD*  2 

6038 

96  * 

6038 

97  * 

Uixlate  all  non-reloca table  references  in  the  COPY  code 

6038 

98  * 

6038 

38 

99 

SEC 

/Find  offset  to  be  added  to  all 

6039 

E9 

61 

100 

SBC 

KLINK 

/non- relocatable  references 

603B 

85 

FD 

101 

STA 

OFFSET 

603D: A2 

00 

102 

LDX 

#$00 

/Index  start  of  table 

603F:A0 

00 

103 

lOY 

#$00 

6041 

18 

104 

CLC 

(Continued  on  page  60) 


58 

906 


Dr.  Dobb’s  Journal,  December  1985 


ProDos  Copy  Listing 

(Listing  continued,  text  begins  on  page  54) 


6042:UO 

91  60 

105  RELOCATE 

LDA 

RELIABLE,  X  ;l\3int  to  non-relocatable  reference 

6045:d5 

FE 

106 

STA 

PrR 

604 7: HD 

92  60 

107 

LDA 

RELIABLE* 1 , X 

604A:85 

FF 

103 

STA 

PTR*  1 

604C:B1 

FE 

109 

LDA 

(PI-R).Y  ;Acld  offset  to  the  reference 

604E:65 

FD 

110 

ADC 

OFFSET 

6050:91 

FE 

111 

STA 

(PTR) , Y 

6052 :E8 

112 

I  NX 

; Index  next  reference 

6053 :E8 

113 

I  NX 

6054 :UC  C7  60 

114 

CPX 

TABLES I  ZE  ; Number  of  references  times  2 

6057:90 

E9  6042 

115 

DCC 

RELOCATE 

6059: 

116  * 

6059: 

117  * 

Move 

the  code  to  its  final  none 

6059: 

118  * 

6059 :A2 

00 

119 

LDX 

«>LINK  ; Start 

605B: AO 

61 

120 

LDY 

# CLINK 

605D: 86 

3C 

121 

STX 

AIL 

605F: 84 

3D 

122 

STY 

Alii 

6061  :A2 

AB 

123 

LDX 

#>END  ;End 

606 3: AO 

62 

124 

LDY 

IK  END 

6065:86 

3E 

125 

STX 

A2L 

6067:34 

3F 

126 

STY 

A2H 

6069 : AO 

00 

127 

LDY 

#$00  ; Destination  (Always  on  a  jiage  boundary) 

606B: 84 

42 

128 

STY 

A4L 

606D:63 

129 

PLA 

;We  saved  tlie  hijh  byte  on  the  stack 

606E:05 

43 

130 

STA 

A4U 

6070 :4C 

2C  FE 

131 

JNP 

MOVE  ;Y-reg=$00 

6073: 

132  * 

6073: 

133  * 

Subroutine  to  update  system  bit  map 

6073: 

134  * 

6073 :A8 

135  BITMAPS 

TAY 

;Save  page  address 

6074 : 48 

136 

PilA 

6075 :8A 

137 

TXA 

;Save  X-reg 

6076:43 

138 

PilA 

6077:90 

139 

TYA 

.•Restore  |iage  address 

6073 :4A 

140 

LSR 

A  ; Divide  page  address  by  8 

6079 :4A 

141 

LSR 

A 

607A: 4A 

142 

LSR 

A 

607U: AA 

143 

TAX 

;Load  X-reg  with  page  address/ 8 

607C:9U 

144 

TYA 

.•Restore  (>age  address 

6070:29 

07 

145 

AND 

#$07  ; Ki 1 1  off  the  high  order  bits 

607F.-A8 

146 

TAY 

;and  put  it  in  the  Y-reg 

6080  :A9 

00 

147 

LDA 

#$00  ;Zero  tiie  mask 

6082:38 

143 

SDC 

.•Prepare  to  put  a  1  into  mask 

6083:6A 

149  UITMAPS1 

RDR 

A  ; Rotate  carry  into  mask 

6084:83 

150 

DEY 

6005:10 

FC  6083 

151 

BPL 

BITMAPS 1  ; Rotate  nask  to  proper  position 

6087: ID 

58  BF 

152 

ORA 

BI'I-MAP, X  ; Update  tlie  actual  bitmap 

608A: 9D 

58  BF 

153 

STA 

BITMAP, X 

608D.-68 

154 

PLA 

; Restore  X-reg  and  A-reg 

603K: AA 

155 

TAX 

608F : 68 

156 

PLA 

6090:60 

157 

RTS 

6091: 

150  * 

6091: 

159  * 

Table 

of  non-relocatable  references 

6091: 

160  * 

6091: IF 

61 

161  KELT ABLE 

DW 

RELOCl*2 

6093:24 

61 

162 

DW 

KELOC2*2 

6095 :2C 

61 

163 

DW 

NXTCI1R2+2 

6097: 3F 

61 

164 

DW 

RELGC3*  1 

6099:78 

61 

165 

DW 

FI LOOK* 2 

6090: 7E 

61 

166 

DW 

RFLOC4  ♦  2 

609D: 84 

61 

167 

DW 

RELOC5*2 

609F :89 

61 

168 

DW 

KEL0C6*2 

60A1 : 8F 

61 

169 

DW 

RELOC7*2 

60A3:95 

61 

170 

DW 

RELOC8*2 

60A5:9B 

61 

171 

DW 

REL0C9*1 

60A7 : AE 

61 

172 

DW 

RELLC10*2 

60A9:B3 

61 

173 

DW 

RELX11  +  2 

60AB:Do 

61 

174 

DW 

RELDC12+2 

60AD:DF 

61 

175 

DW 

RELXT3+2 

60AF:E7 

61 

176 

DW 

RELOC14*2 

60iil  :EO 

61 

177 

DW 

RELOC15*2 

6003 :F3 

61 

170 

DW 

RELDC16*2 

60U5:F9 

61 

179 

DW 

KEUXT17*1 

60B7 : 3B 

62 

130 

DW 

TRANSFER* 2 

60B9 : 3E 

62 

181 

DW 

RELOC18+2 

60OB: 51 

62 

132 

LAV 

RELOC19*2 

60BD: 57 

62 

183 

DW 

RELOC20*2 

60BF : 5D 

62 

184 

DW 

RELOC21*l 

60C1 : 74 

62 

185 

DW 

RELOC22*2 

60C3:77 

62 

106 

DW 

RELDC23*2 

60C5:7D 

62 

187 

DW 

RELDC24*1 

60C7:36 

188  TABLES I ZE 

DFB 

TABLES  I  ZE-  RELIABLE 

60C8: 

6100: 

0038 

189  FILLPAGE 

US 

>0-F I LEPAGE  ;Fill  to  next  page  bouiviary 

6100: 

191  *  The  following 

is  the  COPY  cxxnmand  code. 

6100: 4C 

00  00 

193  LINK 

JMP 

$0000  ; Address  planted  to  next  coiimand 

6103:02 

194  PAGES 

DFB 

< END-LINK* 256  ; Number  of  pages  occupied 

6104:04 

6108:59 

43  4F  50 

195  CMDSTRU 

STR 

"COPY"  ;  Til  is  is  the  comirand  string 

6109:04 

63  6F  70 

196  G4DSTRL 

STR 

"copy"  ;  Accept  up(ier  or  lower  case 

610E: 

197  * - 

— 

610E: 

198  *  Check  to  see  if  the  command  is  COPY.  If  not. 

610E: 

199  *  then  check  for  next  user  connund,  or  tell  ProDQS 

610E: 

200  *  it  is  not  a  user  command.  If  it  is  the  COPY 

blOE: 

201  *  canuund, 

i  then 

set  up  for  first  stage  processing. 

610E: AD 

6C  BE 

203  START 

LDA 

VPATII1  ;Get  command  line  pointer 

6111:85 

FE 

204 

STA 

PTR 

6113:AD 

6D  BE 

205 

LDA 

VPATH1*1 

6116:85 

FF 

206 

STA 

PTR*1 

(Continued  on  page  62) 


Dr.  Dobb’s  Journal.  December  1985 


QD7 


ProDos  Copy  Listing 

(Listing  continued,  text  begins  on  page  54) 


6113:A0 

00 

207 

LDY 

#$00 

/Index  command  string 

611A:C8 

208 

NXTCHR 

INY 

;  Increment  index 

611B:B1 

FE 

209 

LOA 

(PTR) ,  Y 

;Get  character  from  input  string 

61 ID: 09 

04 

61 

210 

RELOCI 

CMP 

CMDSTRU, Y 

;Does  it  natch  upper  case? 

6120:F0 

08 

612A 

211 

BEQ 

.MXTCHR2 

;If  yes 

6122:09 

09 

61 

212 

RELOC2 

CMP 

CMDGTRL, Y 

.-Does  it  natch  lower  case? 

6125 :P0 

03 

612A 

213 

BBQ 

NXTC!IK2 

/If  yes 

6127:38 

214 

SBC 

;Flag  match  failure 

6128:U0 

D6 

6100 

215 

BOS 

LINK 

;Go  to  next  conifund  in  the  chain 

612A:CC 

04 

61 

216 

NXTCHH2 

CPY 

CMDSTRU 

; Checked  all  diaracters? 

6120:00 

ED 

611A 

217 

BNE 

NXTQIR 

; No,  so  check  the  next  one 

612F.-88 

218 

DEY 

;  Put  ooronand  length-1  in  XU29 

6130:8C 

52  BE 

219 

STY 

XLEN 

6133: A9 

00 

220 

IDA 

•500 

;Mark  command  as  external 

6135:00 

53 

BE 

221 

STA 

XCNUM 

6138: A9 

4E 

222 

IDA 

•>SBC0ND 

;  Point  to  command  handler 

613A: 80 

50  D£ 

223 

STA 

XTRNADOR 

6130:A9 

61 

224 

RELOC3 

IDA 

«<  SECOND 

613F:8D 

51 

BE 

225 

STA 

XTRNADOR* 1 

6142: A9 

OB 

226 

IDA 

•  $0B 

; Allow  CREATE,  require  two  pathnames 

6144:80 

54 

BE 

227 

STA 

PBITS 

6147:A9 

04 

228 

IDA 

#$04 

/Fetch  prefix,  if  required 

6149:80 

55 

BE 

229 

STA 

PliITS+1 

614C:18 

230 

CLC 

jNo  errors  so  far 

6140:60 

231 

RTS 

; Return  to  BASIC. SYSTEM 

614E: 

232 

* - - 

— 

614E: 

233 

*  COPY  secoivl  stage  processing.  First  dieck  FBITS 

614E: 

234 

*  to  see 

if  the  proper  parameters  were  present. 

614C: 

235 

* - - 

6140: AD 

56 

BE 

236 

SECOND 

IDA 

FBITS 

; Parameters  found 

6151.-4A 

237 

LSR 

A 

.•Filename  found? 

6152:90 

03 

6157 

238 

BCC 

SYNERROR 

;No,  so  give  error  message 

6154 :4A 

239 

I  SR 

A 

; Second  filename  found? 

6155:B0 

04 

615B 

240 

DCS 

PARMSOK 

;  Yes ,  so  go  do  ooiMiand 

6157 :A9 

10 

241 

SYNEKHOR 

IDA 

l$10 

/Syntax  error 

6159:38 

242 

ML I EUR 

SEC 

;Set  carry  to  sliow  error 

615A.-60 

243 

HETUHN 

RTS 

/Return  to  system 

615B: 

244 

• 

615B: 

245 

• 

Both 

filenames  were  given,  so  execute  COPY 

615U: 

246 

* 

615U:A9 

OA 

24.7 

PAKMSOK 

IDA 

«$0A 

/ Set  up  G£T_F I LB_ INFO  call 

6150:80 

B4 

BE 

248 

STA 

S5GINFO 

6160:A9 

C4 

249 

IDA 

l$C4 

;MLI  GET_FILE_INFO  code 

6162:20 

70 

BE 

250 

JSR 

GOSYSTEJ4 

/ Perform  the  MLI  call 

6165:B0 

F3 

615A 

251 

DCS 

RETURN 

/If  error,  return  carry  set 

6167: AO 

B8 

BE 

252 

IDA 

FIFILID 

/Get  file  type 

616A:C9 

OF 

253 

CMP 

#$0F 

/Can't  copy  a  directory  file 

616C:F0 

04 

6172 

254 

BEQ 

MISMATCH 

616E:C9 

01 

255 

CMP 

#$01 

/Can't  copy  a  bad  block  file 

6170:00 

04 

6176 

256 

BNE 

FILBOK 

6172:A9 

OD 

257 

MISMATCH 

IDA 

#13 

/FILE  TYPE  MISMATCH  error 

6174:00 

E3 

6159 

258 

BNE 

ML1ERR 

/Always 

6176: 

259 

* 

6176: 

260 

* 

File 

type  is  OK 

6176: 

261 

* 

6176:80 

93 

62 

262 

FILBOK 

STA 

CRFILID 

/File  type  for  CREATE 

6179:AD 

B9 

BE 

263 

IDA 

FIAUXID 

/File  aux  type  for  CREATE 

617C:8D 

94 

62 

264 

HELOC4 

STA 

CRAUXID 

617F: AO 

BA 

BE 

265 

IDA 

FIAUXID* 1 

6182:80 

95 

62 

266 

RELOC5 

STA 

CRAUXID* 1 

6185: A9 

03 

267 

IDA 

•  $C3 

/File  access  (UNLOCKED) 

6187:80 

92 

62 

268 

KELOC6 

STA 

CRACCESS 

/We  have  to  be  able  to  write! 

618A: AD 

6E 

BE 

269 

IDA 

VPATH2 

/Pathname  for  CREATE 

6180:80 

90 

62 

270 

RELOC7 

STA 

CRPATH 

ol90:AD 

6F 

BE 

271 

IDA 

VPATH2*1 

6193:80 

91 

62 

272 

REUX8 

STA 

CRPATH* 1 

6196:20 

00 

BF 

273  CREATE 

JSR 

ML! 

/Create  the  new  file 

6199:00 

274 

DFB 

$C0 

/MLI  CREATE  connand  code 

619A: 8F 

62 

275 

REL0C9 

DW 

CRPARMS 

/CREATE  parameter  list 

619C:90 

03 

61A1 

276 

BCC 

SUCCESS 

/CREATE  successful 

619E:4C 

80  BE 

277 

JMP 

BADCALL 

/Translate  to  BI  error  code  and  exit 

61A1: 

278 

• 

61A1 : 

279 

* 

Create  a  file  buffer  for  the  destination  file 

61A1 : 

280 

* 

61A1 : A9 

04 

281 

SUCCESS 

IDA 

•  $04 

/IK  buffer  required 

61A3: 20 

F5 

BE 

282 

JSR 

GETUUFR 

/Get  space 

61A6 : 90 

04 

61  AC 

283 

BCC 

REIDC10 

61A8: A9 

OC 

284 

IDA 

•  $0C 

/NO  BUFFERS  AVAILABLE  error 

61AA: 38 

285 

SEC 

61AB:60 

286 

RTS 

61AC:SO 

9F 

62 

287 

HELOCIO 

STA 

OPEN2BUF*l 

61AF: A9 

00 

288 

IDA 

•  $00 

/Buffer  always  on  page  boundary 

61B1 :8D 

9E 

62 

289 

REL0C11 

STA 

OPU42BUF 

61B4: 

290 

• 

61B4: 

291 

* 

Create  a  transfer 

buffer  for  the  file 

61B4: 

292 

* 

Use 

as  tiuch  free 

memory  as  possible 

61134: 

293 

* 

61B4:A9 

FF 

294 

TRANS BUF 

IDA 

#$FF 

/Count  l»ow  many  pages  we  can  get 

6106:85 

FE 

295 

STA 

COUNT 

/Initialize  with  -1 

61B8:E6 

FE 

296 

GETM3RE 

INC 

COUNT 

/Count  !k>w  many  pages 

61BA: A9 

01 

297 

IDA 

l$01 

/Get  one  page  at  a  time 

61bC: 20 

F5 

BE 

298 

JSR 

GETUUFR 

/ Get  space 

610F:90 

F7 

61B8 

299 

BCC 

GETUORE 

/Keep  going  until  we  can't  get  more 

61C1 :A5 

FE 

300 

IDA 

COUNT 

/Check  lkow  many  pages  we  got 

6103: DO 

07 

6100 

301 

BNE 

GOl’ENOUGH 

/Any  non- zero  number  is  okay 

6105:20 

F8 

BE 

302 

JSR 

FREBUFR 

/Give  back  the  allocated  memory 

6 1C8 : A9 

OC 

303 

IDA 

#$0C 

/NO  BUFFERS  AVAILABLE  error 

61CA: 38 

304 

SBC 

6103:60 

305 

RTS 

6100:80 

DA 

BE 

306 

GOTEHOUGH  STA 

RWCOUNT* 1 

/Save  buffer  size 

61CF:A9 

00 

307 

IDA 

l$00 

/Length  is  always  an  even  number  of  page 

6101:80 

D9 

BE 

308 

STA 

RECOUNT 

6104  :AO 

9F 

62 

309 

RELOCI 2 

IDA 

OPQ92BUF* 1 

/Get  start  address  of  buffer 

6107:38 

310 

SBC 

(Continued  on  page  66) 


62 


nno 


Dr.  Dobb’s  Journal,  December  1985 


ProDos  Copy  Listing 

(Listing  continued,  text  begins  on  page  54) 


61D8.-E5  FE 

311 

SBC 

COUNT 

61DA: 8D  D8 

BE 

312 

STA 

RWDATA* 1 

;Save  buffer  start  address 

6100:80  A4 

62 

313  RELCC13 

STA 

WR2BUF ♦ 1 

61E0 : A9  00 

314 

LOA 

1500 

;  Buffer  starts  on  a  page  boundary 

61E2:8D  07 

BE 

315 

STA 

RWDATA 

61E5:8D  A3 

62 

316  RELOC14 

STA 

WR2BUF 

61E8: 

317  • 

61E8: 

318  * 

CXx?n  the  destination  file 

61E8: 

319  * 

61E8: AD  6E  BE 

320 

LEA 

VPATH2 

.•Pathnane  pointer 

61EB.-8D  9C 

62 

321  RELOC15 

STA 

OPEN 2 PATH 

61 EE: AD  6F 

BE 

322 

LDA 

VPATH2* 1 

6 1 FI : 80  90 

62 

323  RELOC16 

STA 

OPU42PATH+1 

61F4 : 20  00 

BF 

324 

JSR 

MLI 

; Perform  MLI  call 

61F7.-C8 

325 

DFB 

5C8 

;  MLI  OP114  command  code 

61F8-.9B  62 

326  RELOC17 

CM 

OPEN2PARM 

61FA:90  08 

6204 

327 

BCC 

OP124SRC 

;No  errors,  so  open  the  source  file 

61FC:48 

328 

PHA 

;Save  error  code 

61FD.-20  F8 

BE 

329 

JSR 

FREBUFR 

.•Release  buffer 

6200:68 

330 

PLA 

j Restore  error  code 

62U1:4C  8B 

BE 

331 

JMP 

BADCALL 

; Trems late  error  code  and  exit 

6204: 

332  * 

6204: 

333  * 

Open  source  file 

6204: 

334  * 

6204 :A5  74 

335  OPQ4SRC 

LDA 

1IIMEM+1 

;Get  buffer  start 

6206:80  CF 

BE 

336 

STA 

GSYSBUF*1 

; (use  general  purpose  buffer) 

6209 :A9  00 

337 

LDA 

t$oo 

,-Buffer  always  on  a  page  boundary 

620B:8D  CE 

BE 

330 

STA 

QSYSBUF 

620E: A9  C8 

339 

LDA 

•  5C8 

;MLI  open  code 

6210:20  70 

BE 

340 

JSR 

GOSYSTEM 

.•Perform  the  MLI  call 

621 3: BO  ID 

6232 

341 

DCS 

ERJWRET 

/Exit  if  error  detected 

6215: AO  DO 

BE 

342 

LDA 

OREFNUM 

;Set  up  file  reference  number 

6218 :8D  D2 

BE 

343 

STA 

NEWLREf 

;For  NEWLINE  call 

6210:80  06 

BE 

344 

STA 

RWREFNUM 

;For  READ  call 

621E: 80  DE  BE 

345 

STA 

CFREFNUM 

;For  CLOSE  call 

6221 :A9  00 

346 

IDA 

•  500 

jSet  up  NEWLINE  call 

6223:80  03 

BE 

347 

STA 

NLINLMiL 

/ to  disable  NEWLINE  diar 

6226 :A9  00 

340 

IDA 

1500 

; Non-zero 

6228:80  D4 

BE 

349 

STA 

NLINENBL* 1 

622B: A9  C9 

350 

LDA 

I5C9 

;MLI  NEWLINE  code 

6220: 20  70 

BE 

351 

JSR 

GOSYSTEM 

.•Perform  tie  MLI  call 

6230:90  07 

6239 

352 

BCC 

TRANSFER 

;If  no  errors  detected 

6232:48 

353  ERRORET 

PIIA 

.•Save  error  code 

6233:20  65 

62 

354 

JSR 

CLEANUP 

;Close  files  and  free  buffer 

6236:68 

355 

PLA 

.•Restore  error  code 

6237:38 

356 

SEC 

;Flag  error 

6238:60 

357 

RTS 

.•Return  to  system 

6239: 

358  * 

6239: 

359  * 

Bo  til 

files  are  new  open  so  perform  transfer 

0239: 

360  • 

6239: AD  AO 

62 

361  THANSFEH 

IDA 

OPEN2REF 

;Set  up  WRITE  file  reference  nunber 

623C.-80  A 2 

62 

3t>2  RELOC18 

STA 

WR2REF 

623F: A9  CA 

363  XFERLOOP 

IDA 

•  5CA 

;MLI  READ  code 

6241:20  70 

BE 

364 

JSR 

GOSYSTEM 

.•Perform  the  MLI  call 

6244:90  06 

624C 

365 

BCC 

XFEROK 

;No  errors 

6246 :C9  05 

366 

CMP 

•  505 

;End  of  file? 

6248:F0  IB 

6265 

367 

BEQ 

CLEANUP 

;Yes,  so  clean  up  and  exit 

624A:D0  E6 

6232 

368 

BNE 

ERROKET 

.•Exit  with  appropriate  error 

624C: AD  DB 

BE 

369  XFEROK 

IDA 

RNTRANS 

.•Number  of  bytes  transferred 

624F: 80  A5 

62 

370  RELGC19 

STA 

WR2REQ 

; Reguest  to  write  these  bytes 

6252: AD  DC 

BE 

371 

LDA 

RWTRANS*1 

6255:80  A6 

62 

372  RELOC20 

STA 

WR2REQ* 1 

6258:20  00 

BF 

373 

JSR 

MLI 

; Write  the  data  to  the  destination  file 

625U:CU 

374 

DFB 

OCB 

JMLI  WRITE  code 

625C: A1  62 

375  RELOC21 

CM 

WR2PARMS 

625E:90  DF 

623F 

376 

BCC 

XFERLOOP 

6260:20  8B 

BE 

377 

JSR 

BADCALL 

; Translate  to  BI  error  code 

6263: BO  CD 

6232 

378 

DCS 

ERRORET 

; Always 

6265: 

380  *  Close  open  files  and  free 

6265 :A9  00 

382  CLEANUP 

IDA 

•  500 

.•Default  to  no  errors  detected 

6267:85  PC 

383 

STA 

TEMP 

6269 :A9  OC 

384 

LDA 

•  5CC 

;MLI  CLOSE  code 

6260:20  70  BE 

305 

JSR 

G0SYST131 

;Close  source  file 

626E:90  02 

6272 

386 

BCC 

REIDC22 

;  If  no  error  detected 

6270:85  FC 

387 

STA 

TEMP 

.•Update  error 

6272: AD  AO 

62 

388  RELOC22 

IDA 

OPEN2REF 

6275:80  AA 

62 

389  RELOC23 

STA 

CLOSE 2 REF 

6278:20  00 

BF 

390 

JSR 

MLI 

.•Perform  tJie  MLI  call 

627B:CC 

391 

DFB 

5CC 

;MLI  CLOSE  code 

627C: A9  62 

392  RELOC24 

CM 

CL2PARMS 

.•CLOSE  parameter  list 

627E:90  05 

6285 

393 

BCC 

FREE 

; If  no  error  detected 

6280:20  8B 

BE 

394 

JSR 

BADCALL 

; Trans late  to  BI  error 

6283:85  PC 

395 

STA 

TEMP 

.•Update  error 

0205:20  F8 

UE 

396  FREE 

JSR 

FREBUFR 

; Deal  locate  file  buffer 

6288:18 

397 

CLC 

; Default  to  no  errors  detected 

6289: A5  FC 

398 

LDA 

TEMP 

; Error  code  in  A 

628B:F0  01 

628E 

399 

BEQ 

RET 

;2ero  if  no  errors 

6280:38 

400 

SBC 

/Flag  error 

628E:60 

401  RET 

RTS 

628F: 

403  *  Parameter  list  for  CREATE 

628F: 07 

405  CRPARMS 

DFB 

507 

/Parameter  count 

6290: 

0002 

406  CRPATU 

DS 

2 

/Pointer  to  pathname 

6292: 

0001 

407  CRACCESS 

06 

1 

/Access  permitted  byte 

6293: 

0001 

408  CUE I LID 

DS 

1 

/File  tyi>e 

6294: 

0002 

409  CRAUXID 

DS 

2 

/File  uux  type 

6296:01 

410 

DFB 

501 

/Standard  file  (Not  directory) 

6297:00  00 

411  CRDATE 

DFB 

500,500 

/Create  date 

6299:00  00 

412  CRT1ME 

DFB 

500,500 

/Create  tine 

629B: 

413  * - 

— 

_  _ 

(Continued  on  page  68) 


Dr.  Dobb’s  Journal,  December  1985 


onn 


ProDos  Copy  Listing 

(Listing  continued,  text  begins  on  page  54) 


629B; 
629C; 
629E 
6  2  AO 
62A1 
62A1 
62A1 


62A9 
62A9 
62A9 
62A9 
62AA 
6  2  All: 


0002 

0002 

0001 


416  OPEN2PARM  DFB  $03 

417  OPEN2PATH  DS  2 

413  OPEN2BUF  DS  2 

419  OPQJ2REF  DS  1 

420  * - 

421  *  Parameter  list  for  WRITE 

422  * - 


/Parameter  count 
/Pointer  to  pathname 
/Pointer  to  I/O  buffer 
/File  reference  number 


62A1:04 

423 

WH 2 FARMS 

DFB 

$04 

/Parameter  count 

62A2: 

0001 

424 

WR2REF 

DS 

1 

/File  reference  number 

62A3: 

0002 

425 

WR2BUF 

DS 

2 

/File  transfer  buffer 

62AS: 

0002 

426 

WR2REQ 

DS 

2 

/Number  of  bytes  to  transfer 

62A7: 

0002 

427 

WR2TRANS 

DS 

2 

/Number  of  bytes  transferred 

0001 
6  2  Ail 


429  *  Parameter  list  for  CLOSE 

430  * - 


431  CL2PAKMS  DFii 

432  CIOSE2KEF  DS 

433  END  ECJU 


$01 

1 


/Parai.ieter  count 
/File  reference  nuirtoer 


3D 

A1H 

3C 

AIL 

43 

A4II 

42 

A4L 

6073 

BITMAPS 

BF58 

BITMAP 

6265 

CLEANUP 

62AA 

CIOSE2REF 

FE 

COUNT 

6292 

CRACCESS 

76196 

CREATE 

6293 

CRFILID 

76299 

CRTIME 

62AB 

END 

BE06 

EXTRNCMD 

BE56 

FBITS 

BEB8 

FIFILID 

6176 

FILBOK 

6285 

FREE 

BEF5 

GETBUFR 

6  ICC 

G0TB40UGH 

600B 

GOTSPACE 

6100 

LINK 

6172 

MISMATCH 

FE2C 

MOVE 

BED2 

NEWLREF 

612A 

NXTCHR2 

FD 

OFFSET  v 

629C 

OPEN 2 PATH 

6  2  A0 

OPEN  2 REF 

BECE 

OSYSBUF 

6103 

PAGES 

600F 

PROTECT 

FE 

PTR 

61D4 

REL0C12 

61E5 

RELOC14 

61EB 

REL0C15 

61F1 

RELOCI  6 

624F 

REirciy 

6255 

KEIOC20 

6275 

RELOC23 

6122 

KELOC2 

617C 

RELOC4 

6182 

RELOC5 

6193 

RELOCH 

619A 

REIOC9 

628E 

RET 

61 5A 

RETURN 

BED7 

RWDATA 

BED6 

RWREFNUM 

BEU4 

SSG1NFO 

610E 

START 

60C  7 

TABU -SIZE 

FC 

TEMP 

BE6C 

VPAT1I1 

BE6E 

VPATH2 

62A2 

WR2KEF 

62A5 

WR2REQ 

623F 

XFERLOOP 

624C 

XFENOK 

3C 

AIL 

3D 

A1H 

42 

A4L 

43 

A4H 

FD 

OFFSET 

FE 

PTR 

600B 

GOTSPACE 

600F 

PROTECT 

6083 

BITMAPS1 

6091 

RELTABLE 

6100 

LINK 

6103 

PAGES 

610E 

START 

611A 

NXTC11R 

612A 

NXTCIIR2 

61 3D 

REL0C3 

6159 

MLIERR 

615A 

RETURN 

6176 

FI  LOOK 

617C 

REIOC4 

610D 

RKDX7 

6193 

REIOC8 

61A1 

SUCCESS 

61  AC 

RELOC10 

61B8 

GETMORE 

6  ICC 

GOTENOUGH 

61E5 

REL0C14 

61EB 

REIOC15 

6204 

OPENSRC 

6232 

ERRORET 

623F 

XFERLOOP 

624C 

XFEROK 

625C 

RELGC21 

6265 

CLEANUP 

627C 

RELOC24 

6285 

FREE 

6290 

CRPATH 

6292 

CRACCESS 

76297 

CRDATE 

76299 

CRTIME 

629E 

OPEN2BUF 

6  2  A0 

OPEN2REF 

62A3 

WR2BUF 

62A5 

WR2REQ 

62AA 

CLOSE2REF 

62AB 

END 

BE50 

XTRNADDR 

BE52 

XLUJ 

BE56 

FBITS 

BE6C 

VPATlll 

BE93 

BADCALL 

BEB4 

SSGINFO 

BKB9 

FIAUXID 

BECE 

OSYSBUF 

BED3 

NLINENBL 

BED6 

RWREFNUM 

BEDB 

Ri/TRANS 

BEDE 

CFREFNUM 

BEFB 

RSI  II  MEM 

BF00 

MLI 

**  SUCCESSFUL  ASSEMBLY  := 

NO  ERRORS 

**  ASSEMBLER  CREATED  UN  15- JAN-84  21: 
**  TOTAL  LINES  ASSEMBLED  433 

**  FREE  SPACE  PAGE  COUNT  79 


3F 

A2H 

3E 

A2L 

DE8B 

BADCALL 

6083 

bitmaps 1 

BEDE 

CFREFNUM 

62A9 

CL2PARMS 

6109 

CMDSTRL 

6104 

CMDSTRU 

6294 

CRAUXID 

76297 

CRDATE 

628F 

CRPARMS 

6290 

CHI' ATI  I 

6232 

ERRORET 

BE09 

ERROUT 

7BEB7 

FIACESS 

BEB9 

FIAUXID 

60C8 

FI LLP AGE 

BEF8 

FREBUFR 

61B8 

GETMORE 

BE70 

GOSYSTEM 

73 

IIIMEM 

70200 

INBUF 

BF00 

MLI 

6159 

MLIERR 

BED3 

NLINL24BL 

61 1A 

NXICilR 

629E 

OPEN2BUF 

6290 

OPQJ2PARM 

6204 

OPENSRC 

BED0 

OREFNUM 

61 5B 

PARMSOK 

BE54 

PBITS 

61  AC 

RELOC10 

61B1 

HEL0C11 

611D 

RELOCI 

61DD 

RELOC13 

61F8 

RELOC17 

623C 

RELOC18 

625C 

REL0C21 

6272 

RELOC22 

627C 

REL0C24 

613D 

RELOC3 

6187 

RELOC6 

618D 

RELOC7 

6042 

RELOCATE 

6091 

RELTABLE 

BEFB 

RSI  II  MEM 

BED9 

KNCOUMT 

BEDB 

RWTRANS 

614E 

SECOND 

61A1 

SUCCESS 

6157 

SYNERKOR 

761B4 

TRANS BUF 

6239 

TRANSFER 

62A3 

WR2BUF 

62A1 

WR2PARMS 

762A7 

WR2TRAN5 

BE53 

XCNUM 

BE52 

XLEN 

BE50 

XTRNADDR 

3E 

A2L 

3F 

A2li 

73 

HIMLM 

FC 

TEMP 

FE 

COUNT 

70200 

INBUF 

6042 

RELOCATE 

6073 

BITMAPS 

60C7 

TABIESIZE 

60CU 

FI LLP AGE 

6104 

CMDSTRU 

6109 

CMDSTRL 

611D 

REL0C1 

6122 

RELOC2 

614E 

SECOND 

6157 

SYNERROR 

615B 

PARMSOK 

6172 

MISMATCH 

6182 

RELOC5 

6187 

RELOC6 

76196 

CREATE 

619A 

RELOC9 

61B1 

RELOC11 

761B4 

TRANSBUF 

61D4 

RELOC12 

61DD 

REL0C13 

61F1 

RELOC16 

61F8 

RELOC17 

6239 

TRANSFER 

623C 

RELOC18 

624F 

RELOCI 9 

6255 

RELDC20 

6272 

RELOC22 

6275 

REUJC23 

628E 

RET 

620F 

CRPARMS 

6293 

CRFILID 

6294 

CRAUXID 

629B 

0PO42PARM 

629C 

OPEN2PATH 

62A1 

WR2PARMS 

62A2 

WR2REF 

762A7 

WR2TRANS 

62A9 

CL2PARMS 

BE06 

EXTRNCMD 

BE09 

ERROUT 

BE53 

XCNUM 

BE54 

PBITS 

BE6E 

VPATH2 

BE70 

GOSYSTEM 

7BEB7 

FIACESS 

li£B8 

FIFILID 

BED0 

OREFNUM 

BED2 

NBVUOiF 

BED7 

RWDATA 

BED9 

RW0OUNT 

BEF5 

GETBUFR 

BEF8 

FREBUFR 

BF58 

BITMAP 

FE2C 

MOVE 

End  Listing 


Dr.  Dobb’s  Journal,  December  1985 


CP/M-68K  Conditional  Batch 
Processing 


by  Roger  E.  Donais 


Anyone  who  uses  batch  process¬ 
ing  is  certain  to  recognize  the 
value  of  conditional  instruc¬ 
tions.  A  few  basic  submit  statements, 
such  as  SKIPIF,  PAUSE1F,  and 
ABORTIF  have  been  added  to  CP/M- 
80  in  the  past.  These  instructions 
make  it  possible  for  compiling,  as¬ 
sembling,  and  linking  to  be  handled 
by  a  single  submit  file  that  is  smart 
enough  to  stop  the  process  if  any 
phase  should  fail. 

CP/M-68K  and  CP/M-80  are  simi¬ 
lar  in  many  ways.  In  fact,  files  and 
programs  can  share  the  same  disk 
without  adverse  consequences.  They 
are,  however,  as  different  as  the  pro¬ 
cessors  they  support.  Because  CP/M- 
80  creates  a  working  copy  of  a  submit 
file,  that  file  becomes  the  natural  tar¬ 
get  for  implementing  batch  control 


gramming  effort  by  taking  advantage 
of  the  parsing  done  by  the  CCP,  and 
yet  present  a  recognizable  form  of 
conditional  statement. 

IF  <parameter>  <condition> 

.<action>  /$$$$/  <statement> 

Condition:  The  first  four  characters 
taken  from  the  file-name  field  of  the 
file  control  block  created  by  the  CCP 
are  used  to  identify  the  condition  to 
be  tested.  An  asterisk  may  be  ap¬ 
pended  to  indicate  that  the  comple¬ 
mentary  condition  is  to  be  used. 

AM  BIG  tests  for  an  ambiguous  pa¬ 
rameter  (wildcards). 

EXISTS  tests  for  an  existing  file. 

EMPTY  tests  for  an  empty  file  or 
nonexistent  file. 


A  simple  "if"  can  greatly  speed  the  software 
development  process. 


functions.  On  the  other  hand,  CP/M- 
68K  stores  the  expanded  file  in  mem¬ 
ory.  The  fact  is  that  a  direct  approach 
would  require  more  knowledge  about 
the  undocumented  aspects  of  CP/M- 
68K  than  can  be  acquired  in  an 
evening! 

IF.68K  (Listing  One,  page  72)  be¬ 
gan  as  two  outlines.  The  first  listed 
what  was  wanted  and  the  other  de¬ 
scribed  how  it  would  be  accom¬ 
plished.  One  pencd  later  the  follow¬ 
ing  compromise  was  reached: 

Syntax:  The  conditional  statement  is 
structured  so  as  to  minimize  pro- 


Roger  E.  Donais,  7506  Republic  Ct., 
Alexandria,  VA  22306 


NULL  tests  for  an  undefined 
parameter. 

Actions:  The  first  character  taken 
from  the  file-type  field  of  the  same  file 
control  block  is  used  to  identify  the 
desired  action.  An  asterisk  may  again 
be  appended  to  indicate  that  the  com¬ 
plementary  action  is  to  be  taken. 

S(kip)  ignores  the  statement 
following  /$$$$/. 

Q(uit)  aborts  submit  file 
processing. 

P(ause)  prompts  for  operator 
assistance. 

The  double-slash  quad-dollar-sign 
token  separates  the  conditional  state¬ 
ment  from  the  executable  CP/M  com- 


Dr.  Dobb’s  Journal,  December  1 985 


Q1 1 


mand  tail.  When  the  submit  file  is  ex¬ 
panded,  a  dollar  sign  is  used  as  the 
leading  character  to  signal  formal  pa¬ 
rameter  replacement.  If  the  next  char¬ 
acter  is  not  an  ASCII  digit,  the  charac¬ 
ter  is  written  to  the  expanded  output 
and  no  replacement  is  performed. 
This  simply  means  that  we  use  four 
dollar  signs  in  the  submit  file  but  have 
the  program  search  for  only  two. 

One  disadvantage  with  this  com¬ 
promise  approach  is  that  CP/M-68K 
will  pass  only  one  command.  This  lim¬ 
its  the  range  of  the  conditional  state¬ 
ment  to  one  CP/M-68K  command. 
It’s  bad  enough  that  the  same  condi¬ 
tional  statement  has  to  be  entered 
over  and  over  again  in  order  to  control 
a  range  of  commands,  but  the  aggra¬ 
vation  of  watching  it  reload  each  time 
is  just  too  much! 

With  a  little  camouflage,  CP/M 
can  be  tricked  into  passing  more  than 
one  command.  The  best  character 
seems  to  be  a  semicolon.  Very  few 
programs  dare  use  this  character  be¬ 
cause  it  marks  the  beginning  of  a 
comment.  This  makes  it  a  prime  can¬ 
didate  for  an  alternate  command  sep¬ 
arator.  It  affects  very  few  programs 
and  conceals  its  true  intent  from  CP/ 
M,  and  our  program  need  only  re¬ 
place  these  bogus  separators  with  the 
exclamation  marks  that  CP/M-68K 
expects  to  find. 

The  problem  with  this  wonderful 
logic  is  that  CP/M-68K  seems  to  stop 
building  the  command  tail  after  it 
finds  a  semicolon.  Because  the  basic 
technique  could  use  any  character,  a 
backslash  became  the  next  choice. 
Although  the  entire  command  tail  is 
passed,  the  chain  function  does  not 
protect  the  referenced  line  but  leaves 
it  where  it  is.  It  doesn’t  take  much 
imagination  to  realize  what  happens 
when  the  default  buffer  is  overwrit¬ 
ten.  So  we  are  limited  to  using  it  with 
built-in  commands  and  the  aggrava¬ 
tion  of  using  only  one  external 
command. 

Let’s  look  at  the  resulting  kludge. 
The  program  starts  by  looking  for  an 
action  character  in  the  second  file 
control  block.  If  it  fails  to  find  a 
match,  as  would  be  the  case  with  a 
null  parameter,  it  flips  a  switch  be¬ 
fore  testing  the  first  file  control 
block.  Control  is  then  transferred  ac¬ 


cording  to  the  indicated  action. 

Each  action  begins  by  making  a 
call  to  evaluate  the  condition.  This 
evaluation  ends  with  a  jump  that 
complements  the  original  condition 
according  to  a  NOT  condition  and  a 
NOT  action  request.  This  is  definitely 
a  kludge  that  capitalizes  upon  the 
parsing  and  setup  done  by  the  CCP. 
The  file-name  and  file-type  fields  are 
normally  padded  with  spaces  (an 
even  $20).  The  presence  of  an  aster¬ 
isk  (CP/M  wildcard)  changes  the 
padding  character  to  a  question  mark 
(an  odd  $3F).  The  code  uses  this 
even/odd  fill  character  to  blindly  ob¬ 
tain  the  desired  state. 

The  two  complementary  labels, 
TRUE  and  FALSE,  are  truly  illusion¬ 
ary.  What  began  as  a  simple  matter 
of  going  to  one  or  the  other,  based 
upon  the  resulting  condition,  turned 
into  quite  a  nightmare.  At  times  it 
seemed  that  neither  one  was  right. 
Had  they  been  ZIP  and  ZAP  they 
might  not  have  confused  the  issue  so 
much.  Debugging  eventually  reached 
a  point  where  I  would  no  longer  look 
at  them  or  at  the  remarks.  It  became, 
“Well,  it’s  BEQ  now,  so  let’s  try 
BNE”  or  “target  is  TRUE,  so  try 
FALSE!”  It  became  worse  regardless 
of  what  I  tried.  Finally,  everything 
was  wrong.  That  made  it  easy.  TRUE 
became  FALSE  and  FALSE  became 
TRUE.  I  no  longer  care  how  it  looks 
nor  if  it  makes  sense.  It  works  and 
that  is  the  end  of  it! 

A  skip  is  performed  by  merely  end¬ 
ing  the  program  and  returning  to  the 
CCP.  The  command  is  executed  by 
invoking  the  CP/M-68K  chain  func¬ 
tion  after  replacing  all  semicolons 

Listing  One 


with  exclamation  marks. 

There  may  be  a  more  direct  way  to 
abort  than  the  method  used.  Pro¬ 
grams,  however,  evolve  according  to 
a  programmer’s  knowledge  and 
imagination.  My  68000  and  CP/M- 
68 K  knowledge  is  very  limited,  so 
that  left  only  imagination.  The  CP/M 
User's  Guide  says  that  a  submit 
statement  contained  in  a  submit  file 
would  transfer  control  if  the  refer¬ 
enced  file  exists.  Otherwise  the  state¬ 
ment  would  be  ignored  and  process¬ 
ing  would  resume  with  the  next 
statement  in  the  current  file. 

The  solution  is  obvious:  invoke  an 
existing,  empty  submit  file  et  voilal 
The  actual  code  goes  one  step  more. 
It  creates  a  submit  file  with  a  single 
instruction  that  subsequently  erases 
itself.  The  result  may  not  be  fast,  but 
it  does  abort  without  having  to  keep 
more  junk  on  the  disk. 

Yes,  the  whole  thing  has  the  poten¬ 
tial  to  be  fooled  and  foiled.  But  if  you 
remain  within  the  guidelines  and  don’t 
wander  beyond  its  incomplete  parsing 
and  blind  processing,  it  performs  ex¬ 
actly  as  intended.  The  crazy  little  sub¬ 
mit  file  in  Listing  Two  (page  82)  may 
well  be  the  world’s  slowest  ERAQ,  but 
it  does  provide  an  example  to  demon¬ 
strate  the  performance  of  IF.68K. 
The  additional  example  in  Listing 
Three  (page  82)  not  only  conditional¬ 
ly  assembles  and  links  the  modules  of 
an  assembly  project  but  also  copies 
the  changed  source  files  from  a  RAM 
disk  to  floppy.  Naturally,  execution 
speed  is  improved  by  loading  IF.68K 
from  a  RAM  disk  rather  than  floppy. 

DD| 


* 

IF. 68K  LAST  UPDATE:  10  Mar  85 


*  PROGRAM 

* 

*  PURPOSE 

* 

*  AUTHOR: 


* 
* 
* 

PROVIDE  BASIC  CONTROL  FOR  CP/M-68k  SUBMIT  FACILITY  * 

* 


Roger  E.  Donais 
7506  Republic  Ct. 
Alexandria,  VA  22306 


TEL:  (703)  765-0615 


*  UPDATE  LOG:  * 

*  * 

*  1.2  10  Mar  85  Replaced  non-working  semi-colon  with  * 

*  back-slash  as  command  separator  and  * 

*  added  ~Z  check  for  empty  file  ...red  * 

*1.1  14  Nov  84  Added  semi-colon  as  muliple  command  * 

*  separator  ...red  * 

*  1.0  11  Jul  84  * 

*  * 
**************************************************************** 


71 


Dr.  Dobb's  Journal,  December  1985 


*  SYNTAX: 


IF  <Filename>  <Condition>. <Action>  /$$$$/  <statement>  * 

* 

CONDITION  IS:  AMBIG  -  TO  TEST  FOR  AMBIGIOUS  FILESPEC  * 

EXIST  -  TO  TEST  FOR  AN  EXISTING  FILE  * 

EMPTY  -  TO  TEST  FOR  AN  EMPTY  FILE  * 

NULL  -  TO  TEST  FOR  A  NULL  PARAMETER  * 

* 

ACTION  IS:  S  -  TO  SKIP  STATMENT  PART  OF  COMMAND  LINE  * 

Q  -  TO  QUIT  SUBMIT  FILE  PROCESS  * 

P  -  TO  PAUSE  FOR  OPERATOR  ASSISTANCE  * 

* 

STATEMENT  IS  ANY  VALID  CP/M  COMMAND  * 

* 

NOTE:  AN  ASTERISK  (*)  MAY  BE  APPENDED  TO  THE  CONDITION  * 

AND/OR  THE  ACTION  TO  INDICATE  NEGATION.  AMBIG*  * 
WOULD  THUS  TRANSLATE  "NOT  AMBIGIOUS"  AND  Q*  WOULD  * 
TRANSLATE  "DO  NOT  QUIT".  * 

* 

MULTIPLE  COMMANDS  MAY  BE  SEPARATED  BY  SEMI-COLONS,  AN  * 
EXCLAIMATION  MARK  (!)  OR  END  OF  LINE  ENDS  STATEMENT.  * 


*  CP/M  FUNCTION 

bdos 

fnABORT 

fnCINP 

fnCOUT 

fnPRINT 

fnRESET 

fnOPEN 

fnCLOSE 

f nDELETE 

fnREAD 

fnWRITE 

f nCREATE 

f  nDMA 

fnCHAIN 


DEFINITIONS 
equ  2 
equ  0 
equ  1 
equ  2 
equ  9 
equ  13 
equ  15 
equ  16 
equ  19 
equ  20 
equ  21 
equ  22 
equ  26 
equ  47 


ISC  CONSTANTS 

1  equ  $5C 

2  equ  $38 

1  equ  FCBl+1 

2  equ  FCB2+1 

1  equ  NAMl+8 

2  equ  NAM2+8 

F  equ  $80 

E  equ  $0F+FCBl 


1st  CP/M  File  Control  Block 

2nd  CP/M  File  Control  Block 

1st  FCB  File  Name  field 

2nd  FCB  File  Name  field 

1st  FCB  File  Type  field 

2nd  FCB  File  Type  field 

Default  CP/M  Buffer  /  Command  Line 

CP/M  File  Size  (Sector  Count)  FOR  FCBl 


*************************************************************** 

*  PHASE-1  -  PROCESS  (P)AUSE),  (S)KIP  and  (Q)UIT  PARAMETERS 


ERROR: 
ERRORl : 


ERROR2 : 


MOVE . L  4(A7),A6 

LEA  128 (A6) ,A3 

MOVE . B  TYP2(A6),D0 

CMP.B  #,S',D0 

BEQ  SKIP 

CMP.B  #  ' Q 1 , D0 

BEQ  QUIT 

CMP.B  #’P',D0 

BEQ  PAUSE 

MOVE . B  TYPl ( A6 ) , D0 
EOR  #1 ,NULLFLG 
BNE  RETRY 

MOVE . L  #MESSAGE , Al 

MOVE . B  (Al)+,Dl 
BEQ  ERROR2 
EXT  Dl 

MOVE  #f nCOUT, D0 
TRAP  #bdos 
BRA  ERRORl 

MOVE  #f nABORT, D0 
TRAP  #bdos 


A6  Contains  Base  Page 
DEFAULT  BUFFER 

Get  Action  Character  -  2nd  FCB 


Get  Action  Character  -  1st  FCB 


Dr.  Dobb’s  Journal,  December  1985 


74 

SIR 


CP/M-68K 

Listing  One 


(Listing  continued,  text  begins  on  page  70) 


NULLFLG : 

DC.W  0000 


.page 


****************  ******************************  **************** 

*  PHASE 

-2  -  PROCESS  AMBIG 

,  EXISTS,  EMPTY  and  NULL  PARAMETERS 

COMPARE 

MOVE  #4-l,d0 

4  Character  token  count 

LEA  NAM2 (A6 ) , A0 

TST  NULLFLG 

Get  Token  address 

LI: 

BEQ  Ll 

LEA  NAMl (A6 ) ,A0 
LSL.I.  #8 ,  D2 

Make  room  for  next  character 

MOVE . B  (A0)+,D2 

and  load  it 

DBRA  D0,L1 

Repeat  for  all  4-characters 

MOVE . L  STABLE, A0 

Point  to  Function  Table 

MOVE  #4-1, D0 

Number  of  Entries 

L2 : 

MOVE . L  (A0)+,D1 

CMP. L  D2,Dl 

Get  Current  Entry 

BEQ  JUMP 

Exit  on  Match 

ADDA  #4 , A0 

Else  Step  to  Next  Entry 

DBRA  D0,L2 

and  Repeat  for  ALL  Entries 

L3  : 

ADDA  #4 , A7 

Pop  callers  from  stack 

BRA  ERROR 

and  abort 

JUMP: 

TST  NULLFLG 

BEQ  JMPl 

TST  D0 

EVERYTHING  GOOD  ON  2ND  FCB 

BNE  L3 

AND  ONLY  NULL  ON  1ST  FCB 

JMPl : 

MOVE . L  (A0),A0 

JMP  ( A0 ) 

JUMP  TO  FUNCTION 

.page 

**************************************************************1 

SKIP: 

*  SKIP  COMMAND  TAIL 
BSR  COMPARE 

IF  CONDITION  TRUE 

BNE  CHAIN 

Execute  Command  Tail  if  FALSE 

EXIT: 

RTS 

and  Ignore  if  TRUE 

QUIT: 

*  QUIT 

(ABORT)  SUBMIT 

IF  CONDITION  TRUE 

BSR  COMPARE 

BNE  CHAIN 

Ignore  Command  Tail  if  FALSE 

ABORT: 

MOVE . L 

#ABORTMSG , Dl 

MOVE 

#f nPRINT, D0 

TRAP 

#bdos 

MOVE 

#f nRESET, D0 

TRAP 

#bdos 

Make  Certain  Drive  A:  is  R/W 

MOVE . L 

SABORTREC, Dl 

MOVE 

#f nDMA, D0 

Setup  for  File  Output 

TRAP 

#bdos 

MOVE . L 

#ABORTFCB , Dl 

MOVE 

#fnDELETE,D0 

Delete  any  1 $$$ABORT.SUB'  File 

TRAP 

#bdos 

MOVE 

#  f nCREATE , D0 

Then  make  a  new  one 

TRAP 

#bdos 

MOVE 

#fnWRITE,D0 

Fill  it 

TRAP 

#bdos 

MOVE 

#  f nCLOSE , D0 

Close  it 

TRAP 

#bdos 

MOVE . L 

#ABORTSUB , Dl 

and  Chain  to  it 

BRA 

CHAINl 

************** 

************************************************ 

PAUSE: 

*  PAUSE  IF  CONDITION 

TRUE 

BSR  COMPARE 

BNE  CHAIN  EXECUTE  W/O  STOPPING  IF  FALSE 

X9 : 


MOVE . L  # PROMPT, D1 
MOVE  #  f nPRINT , D0 
TRAP  #bdos 
MOVE  #f nCINP, D0 


/6 


Dr.  Dobb's  Journal,  December  1985 


CP/M-68K  (Listing  continued,  text  begins  on  page  70) 

Listing  One 


.page 

CHAIN: 


TRAP  #bdos 
AND  #$5F,D0 
CMP.B  #'Q',D0 

BEQ  ABORT 

CMP.B  #'S',D0 

BEQ  EXIT 

CMP.B  #'C',D0 

BNE  X9 

*  CHAIN  TO  COMMAND  TAIL 

ADD  #BUFF,A6 

MOVE . B  (A6)+,D0 

EXT  D0 


DBRA  D0 , X6 
RTS 

CMP.B  #'/',( A6)+ 
BNE  X5 

CMP.B  #'  '  2 (A6) 

BEQ  X7 

CMP.B  #9,-2 ( A6 ) 
BNE  X5 

CMP.B  # ' , (A6)+ 
BNE  X5 

CMP.B  #  '  $' ,  (A6)  + 
BNE  X5 


Force  upper  case 
Quit  on  "Q" 

Skip  on  "S" 

And  continue  on  "C" 


Point  to  command  line 
Get  command  length 


Ignore  Statement  if  Command 
Tail  cannot  be  found 


Look  for  leading  slash 

Insure  that  the  previous 
character  was  either 
a  space  or  tab 


The  Original  four  dollar 
signs  will  appear 
as  only  two,  so 
check  for  2  S's 


CMP.B  #'/' , (A6)+ 
BNE  X5 

CMP.B  # '  ' , (A6)+ 

BEQ  X8 

CMP.B  #9,-l(A6) 
BNE  X5 


Check  for  trailing  slash 


And  finally  delimiting 
space  or  tab 


*  So  we  got  the  command  tail.  Let's  take  a  quick  break 

*  and  repalce  all  semicolons  with  exclaimation  marks. 


CHAINl : 


move.l  A6,Al 
move  D0,Dl 
bra  Y10 

move . b  # 1 ! 1 , -1 ( Al ) 

cmp. b  #  '  V  ,  ( Al )  + 
dbeq  Dl,Yl0 
beq  Y9 

SUB  #1,A6 
ADD  #1,D0 
MOVE . B  D0, (A6) 

MOVE.L  A6,Dl 

MOVE  # f nDMA , D0 
TRAP  #bdos 
MOVE  #f nCHAIN , D0 
TRAP  #bdos 


copy  pointer  and 
character  count, 
then  begin  search 


search  until  eol  or  '\' 
substitute  &  continue  if  'V 

Step  back  to  new  len  position 
adjust  remaining  command  length 
and  punch  it 

Put  the  command  tail  in  Dl 

Make  addr  in  Dl 
the  Default  Buffer 

and  Chain  it 


MOVE.L  A6 , Dl 
ADD. L  #FCBl ,Dl 
MOVE  #  f nOPEN, D0 
TRAP  #bdos 
CMP.B  #$FF,D0 
RTS 

.page 

NULLl :  CMP.B  * $20 , NAMl ( A6 ) 

BNE  TRUE 


Base  page  to  Dl 
and  step  to  fcb 


Set  flags  on  open  file 


Test  for  no  file  name 


Dr.  Dobb's  Journal,  December  1985 


78 

Q1R 


CP/M-68K  (Listing  continued,  text  begins  on  page  70) 

Listing  One 


FALSE:  MOVE  #0,D0 

BRA  TOGGLE 


Start  with  FALSE 


TRUE:  MOVE  #1,D0 

TOGGLE:  EOR.B  D0 , TYP2-1 ( A6 ) 
MOVE . B  TYP2-1 (A6) , D0 
EOR.B  D0 , TYP2+2 ( A6 ) 
BTST  # 0 , TYP2+2 ( A6 ) 
RTS 


Start  with  TRUE 
TOGGLE  "NOT  CONDITION" 

TOGGLE  "NOT  ACTION" 
FLAG  RESULT 


*************************************************************** 

NULL:  *  TEST  FIRST  FCB  FOR  NULL  PARAMETER 


TST  NULLFLG 
BEQ  NULLl 

EOR.B  D0 , TYPl-1 ( A6 ) 
MOVE . B  TYPl-I (A6) ,D0 
EOR.B  D0 , TYPl+2 ( A6 ) 
BTST  #  0 , TYPl  +  2 ( A6 ) 
RTS 


If  FCB2  is  in  use  the 
Condition  is  FCBl  Filename 
else  TOGGLE 

using  FCBl  Pad  Characters 


EMPTY:  *  TEST  FIRST  FCB  FOR  NULL,  NON-EXISTANT  OR  EMPTY  FILE 


CMP.B  #20, NAMl ( A6 ) 

BEQ  FALSE 

BSR  OPEN 

BEQ  FALSE 

MOVEQ  #f nREAD, D0 

TRAP  #2 

TST  D0 


NO  FILE  NAME  IS  EMPTY  FILE 
NON-EXISTANT  IS  ALSO  EMPTY 


BNE  FALSE 
CMP.B  #$1A,(A3) 
BEQ  FALSE 
BRA  TRUE 


NO  RECORD  ==  EMPTY  FILE 
“Z  MUST  BE  EMPTY  ASCII  FILE 


EXIST:  *  TEST  FIRST  FCB  FOR  EXISTING  FILE 


BSR  OPEN 
BNE  FALSE 
BRA  TRUE 


File  not  Found  Therefore  Non-Existant 
otherwise  it's  naturally  there 


*************************************************************** 

AMBIG:  *  TEST  FIRST  FCB  FOR  AMBIGIOUS  NAME  OR  TYPE 


MOVE  #11—1, D0 
X4  : 

CMP.B  # ' ?' ,NAMl (A6,D0) 
DBEQ  D0,X4 


Test  8-Char  NAME  +  3-Char  TYPE 


BNE  TRUE 
BRA  FALSE 


Contains  "?"  Therefore  Ambigious 


.page 

*************************************************************** 
********************  DATA  STORAGE  AREA  ************************ 
TABLE 

*  NOTE  * 

*  * 

*  NULL  MUST  BE  THE  LAST  TABLE  * 

*  ENTRY.  SEE  COMPARE  SUBROUTINE  * 

*  * 


DC .  B 

' AMBI  1 

DC .  L 

AMBIG 

DC .  B 

' EMPT 1 

DC .  L 

EMPTY 

DC .  B 

'  EXIS 1 

DC .  L 

EXIST 

DC  •  B 

'NULL' 

DC .  L 

NULL 

): 

DC  •  B 

10, 'A: 

DC .  B 

'ERA  A 

80 


Dr.  Dobb's  Journal.  December  1985 


CP/M-68K 

Listing  One 


(Listing  continued,  text  begins  on  page  70) 


DC.L  ', -SUBMIT  FILE  PROCESSING  ABORTED ', $D, $A, $1A 
ABORTFCB : 

DC.B  1,  '  $$$ABORTSUB' 

DC.B  0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0 

ABORTMSG 

DC.B  $D, $A, 'ABORTING  ...$' 


PROMPT: 

DC.B  $7, $D,$A,$D,$A, 'SUBMIT  FILE  PROCESSING  PAUSED. ', $D, $A, $D, $A 

DC.B  $7,'  PRESS:  (0)  TO  QUIT,  (S)  TO  SKIP  -or-  (C)  TO  CONTINUE  $' 

MESSAGE: 

DC.B  7,$D,$A,$A 

DC.B  ' IF. 68K  Ver  1.2  CONDITIONAL  SUBMIT  COMMAND  FORMAT: ', $D, $A, ?A 
DC.B  '  IF  <filespec>  AMBIGx.yx  /$$$$/  Ccommand  tail>',$D,$A 

DC.B  '  IF  <filespec>  EXISTx.yx  /$$$$/  Ccommand  tail>',$D,$A 

DC.B  ’  IF  <filespec>  EMPTY x.yx  /$$$$/  Ccommand  tail>',$D,$A 

DC.B  '  IF  <parameter>  NULLx.yx  /$$$$/  Ccommand  tail> ' , $D, $A, $A 

DC.B  '  WHERE:  x  is  an  optional  "*”  to  indicate  negation ', $D, $A 
DC.B  '  and  y  is  (Q)uit,  (S)kip  or  (P) ause ' , $D, $A, $A 

DC.B  '  NOTE:  Command  tail  may  consist  of  multiple  commands ', $D, $A 
DC.B  '  separated  by  backslashes  (\)  and  ends  at  an',$D,$A 

DC.B  '  exclamation  mark  (!)  or  physical  end  of  line. ' , $D, $A, $A 

DC.B  '  EXAMPLE:  IF  A:JUNK.TYP  /$$$$/  EMPTY. S  CMDl\CMD2 !CMD3 ' , $D, ?A, $A 
DC.B  '  Neither  CMDl  nor  CMD2  will  execute  if  A:JUNK.TYP' , $D, ?A 
DC.B  '  is  empty.  Regardless,  CMD3  will  always  execute.' 

DC.B  $D, SA , $A , 0 


END 


Listing  Two 


ERAQ.SUB  -  File  Deletion  with  Query 


DIR  $1 

IF  $1  EXISTS. P  /$$$$/  ERA  $1 
IF  $2  NULL.Q  /$$$$/ 

SUBMIT  ERAQ  $2  $3  $4  $5  $6  $7  $8  $9 


Listing  Three 


ASM. SUB  -  Simple  68K  Assemble  and  Link 


IF  $1  NULL . S 
IF  FILEl.BAK 
IF  FILE2.BAK 
IF  FILE3.BAK 
IF  FILE4.BAK 
ERA  * . BAK 
IF  FILE1.0 
IF  FILE2.0 
IF  FILE3.0 
IF  FILE4.0 


/$$?$/  ERA  $1.0 
EXISTS. S*  /$$$$/ 
EXISTS. S*  /$$$$/ 
EXISTS. S*  /$$$$/ 
EXISTS. S*  /$$$$/ 


ERA  FILEl 
ERA  FILE2 
ERA  FILE 3 
ERA  FILE4 


EXISTS. S  /$$$$/ 
EXISTS. S  /$$$$/ 
EXISTS. S  /$$$$/ 
EXISTS. S  /$$$$/ 


A : AS68  -S 
A : AS6 8  -S 
A: AS68  -S 
A  :AS68  -S 

A : L068  -R  FILEl. 0  FILE2.0  FILE3.0  FILE4 


.0\PIP 

.0\PIP 

.0\PIP 

.0\PIP 

A:  -L 
A:  -L 
A:  -L 
A:  -L 
.0 


B : =FILEl . S 
B : =FILE2 . S 
B : =FILE3 . S 
B:=FILE4.S 

FILEl. S 
FILEl. S 
FILEl. S 
FILEl .S 


End  Listing  One 


End  Listing  Two 


End  Listings 


Dr.  Dobb's  Journal,  December  1985 


81 

917 


ProDOS  and  the  RAM  Disk 


by  Alfred  Steele 


I  have  a  Synetix  Flashcard  with 
288K  of  RAM  that  I  use  with  Ap¬ 
ple’s  CP/M,  Pascal,  and  DOS  3.3. 
When  I  got  my  card  more  than  two 
years  ago,  there  was  no  ProDOS.  Af¬ 
ter  using  ProDOS  several  times,  I 
knew  that  I  could  not  live  without  my 
RAM  disk,  so  I  decided  to  roll  my 
own  driver.  1  did  several  things  to 
speed  up  the  process  of  writing  the 
driver.  I  used  only  256K  of  the  288K 
memory  on  the  Flashcard.  The 
Flashcard  uses  six  banks  of  memory. 
Four  banks  have  64K  and  the  other 
two  have  only  16K  each.  I  chose  to 
use  only  the  four  64K  banks  of  mem¬ 
ory  to  save  development  time  and  re¬ 
duce  code  size.  The  loss  of  32K  of 
storage  space  is  not  a  problem  at  this 
time.  With  the  256K  of  RAM  in  use,  1 
get  a  RAM  disk  with  512  blocks  of 
storage  space 


disk  driver  can  be  installed  anywhere 
in  memory,  but  only  three  options  are 
given  at  run  time.  You  may  place  the 
driver  inside  a  free  space  in  ProDOS 
at  SFFOO.  This  space  may  only  be 
free  in  ProDOS  Version  1.01.  Later 
versions  of  ProDOS  may  not  support 
the  driver  at  that  address.  You  may 
also  place  the  driver  under  the  BASIC 
interpreter  or  at  $300  hex  page  3. 
The  reason  I  advise  you  to  install  the 
driver  inside  of  ProDOS  is  to  support 
MousePaint,  which  uses  page  3  of 
memory.  Information  on  how  to  use 
the  interpreter  can  be  found  in  the 
ProDOS  Technical  Reference  Man¬ 
ual  and  Beneath  Apple  ProDOS. 
Both  books  are  really  necessary. 

The  code  for  the  RAM-disk  driver  is 
not  hard  to  follow.  The  RAM-disk 
driver  code  that  will  be  relocated  to 
any  address  starts  at  label  SSDSTART 


Using  a  RAM  disk  speeds  hi-res  display  by  a  factor 

of  four. 


This  is  Version  1.3  of  the  RAM-disk 
driver  (see  Listing,  page  85).  Version 
1 .0  was  larger  and  faster  but  not  relo¬ 
catable.  It  had  to  be  loaded  in  page  3 
of  memory.  Version  1.1  fixed  bugs  in 
the  installation  program.  Version  1.2 
is  the  first  version  inich  the  RAM-disk 
driver  code  is  small  enough  to  fit  in¬ 
side  of  a  free  space  in  ProDOS.  The 
program  is  completely  relocatable 
anywhere  in  memory.  One  side  effect 
of  making  the  driver  smaller  and  relo¬ 
catable  is  that  it  is  slower  than  in  the 
other  versions. 

Version  1 .3  fixes  some  errors  in  the 
installation  code  and  adds  more  user 
prompts.  This  version  of  the  RAM- 


Alfred  Steele.  Box  6296,  APO  NY 
09012 


and  ends  at  ENDSSD.  The  routine  is 
only  8E  hex  (142  decimal)  bytes  long. 
The  main  part  of  the  installation  code 
starts  at  label  MAIN.  This  is  a  series 
of  subroutines  to  check  if  the  RAM 
disk  has  already  been  formatted.  If 
this  is  a  first  time  installation,  the  pro¬ 
gram  title  and  the  volume  name  of  the 
RAM  disk  are  displayed.  The  driver 
code  is  relocated  to  the  address  of 
your  choice.  The  ProDOS  active  de¬ 
vices  are  listed  and  the  count  is  updat¬ 
ed.  The  address  of  the  device  driver  is 
placed  in  the  vector  table  for  Slot  5, 
Drive  1.  The  RAM-disk  volume  direc¬ 
tory  information  is  written  to  the 
RAM  disk  and  the  volume  bit  map  is 
written  to  disk.  Information  on  the 
diskette  volume  can  be  found  in  Chap¬ 
ter  4  of  Beneath  Apple  ProDOS  and 
Appendix  B  of  the  ProDOS  Technical 


84 

Qlft 


Dr.  Dobb’s  Journal.  December  1985 


Reference  Manual. 

The  program  has  been  written  to  be 
easy  to  use.  If  you  try  to  install  the 
driver  more  than  once,  the  program 
will  display  a  message  that  a  driver 
has  already  been  installed  and  abort. 
If  the  driver  finds  a  formatted  volume 
directory  header  on  the  RAM  disk  it 
will  ask  you  if  you  want  to  clear  it  or 
not.  If  you  type  N,  the  program  will 
install  the  driver  but  won’t  clear  the 
RAM-disk  directory.  If  you  type  Y, 
the  program  will  install  the  driver  and 
reformat  the  RAM  disk.  If  any  other 
driver,  such  as  a  hard  disk  driver,  is 
already  installed  in  the  active  device 
vector  for  Slot  5,  Drive  1 ,  the  program 
will  abort  with  an  error  message. 

Once  the  RAM-disk  driver  is  in¬ 
stalled  you  can  access  the  RAM  disk 
using  standard  ProDOS  commands 
and  utilities.  You  can  catalog  the 
RAM  disk  using  either  the  command 
CAT  /RAM  or  CAT,S5,D1.  If  you 


want  to  copy  files  to  the  RAM  disk 
use  your  copy  of  ProDOS  FILER  [or 
see  the  article  “Adding  a  COPY 
Command  to  ProDOS"  on  page  54  of 
this  issue — ed].  The  system  intergra¬ 
dation  is  clean  and  efficient  and  pro¬ 
vides  super  fast  loading  and  storing  of 
any  type  of  file. 

One  benchmark  that  I  ran  using 
the  RAM  disk  consisted  of  loading 
high-resolution  pictures  to  the  screen. 

I  made  up  a  ProDOS  test  disk  with  a 
short  BASIC  program  to  load  16  dif¬ 
ferent  hi-res  pictures  as  fast  as  possi¬ 
ble.  Using  Apple’s  Disk  II  it  took 
about  35  seconds  to  display  all  1 6  pic¬ 
tures.  Using  the  same  program  and 
pictures  loaded  to  the  RAM  disk  it 
only  took  8.3  seconds  to  display  all  16 
pictures.  The  program  runs  about  4.2 
times  faster  using  the  RAM  disk  for 
an  effective  I/O  transfer  speed  of 
15.9K  per  second  vs.  the  Disk  II 
transfer  speed  of  3.8K  per  second. 


For  those  who  don’t  want  to  type  in  ' 
more  than  900  lines  of  code  and  com¬ 
ments,  I  will  send  you  a  copy  of  the 
source  and  object  code  on  one  Apple 
ProDOS  disk  for  SI 5  postpaid  any¬ 
where  in  the  United  States.  Please 
send  a  money  order  because  a  person¬ 
al  check  is  difficult  to  process  in  West 
Germany.  To  assemble  the  code,  you 
need  a  copy  of  the  ProDOS  assem¬ 
bler/editor,  or  you  can  assemble  the 
code  under  a  DOS  3.3  assember  and 
move  the  object  code  to  a  ProDOS 
disk.  My  address  is: 


Alfred  Steele 
Box  6296 
APO  NY  09012 


DD| 


Reader  Ballot 

Vote  for  your  favorite  feature/article. 
Circle  Reader  Service  No.  1 96. 


Ram  Disk  Listing  (Text  begins  on  page  84) 


SOURC 
0  0  0  0 
0000 
0000 
00  0  0 
0  000 
00  00  . 
0000 
00  00 
0000 
00  00 
0  000  . 
00  00 
0  0  0  0 
00  00 
0000 


E 


FILE  »01  ■ >  / R AMD  I SK  SOURCE/SSD3 


2 

3 

4 
3 
6 
7 
6 
9 

1  0 
1  1 
I  2 
1  3 
1  4 
1  5 


FLASHCARD  RAMDISK  DRIVER 

VERSION  13 
JULY  1985 

COPYRICHT  1985 
ALFRED  STEELE 

THIS  PROCRAM  MAY  BE  COPIED 
FOR  PERSONAL,  NON-PROFIT  USE 


00  00 


20  00 
2000 
2000  : 
2  0  0  0 
20  00 
2  0  0  0 
2000 
2  0  00 
2  0  00 
2  000 
2  0  0  0 
2  0  0  0 
2000 
2000  : 
20  00 
2  0  0  0 
2  0  0  0 
2  0  0  0 
20  00 
2000 
2000  : 
2  0  0  0 
20  00 
2  0  0  0 
20  00 
2  000: 
2000 
2  00  0 
2  0  00 
2  0  0  0 
20  0  0  : 
2  0  0  0: 
20  00: 
2  00  0 
20  00 
2000  . 
2  0  0  0: 
2  0  0  0: 
2  0  0  0 
2000 
2  0  00 
2  0  0  0. 
2000  : 
2  000  : 
2  0  00  : 
2  0  0  0: 
20  00: 
2000 
20  00 


NEXT  OBJECT 
2  0  0  0 


00  00 
0  0  8D 


0  0  0  0 
0  0  0  2 


00  0  1 
0  00  2 
00  06 
0008 


0  0  4  2 
0  0  4  3 
0  0  4  4 
0  0  4  6 
00  4  8 
0  0  4  A 


0  0  D  2 
0  0  D  3 
0  0D4 
0  0  D  5 
0  0D6 
0  0  D  7 


C0D0 
COD  1 


BF00 
BF  3  1 
BF  3  2 
BF  l  A 


16  L  ST  ON 

FILE  NAME  IS  /RAMDISK  SOURCE/SSD3  0 

1 7  ORC  12000 

1  8  « 

IV  «  TEXT  STUFF 

2  0  * 

21  EOL  EQU  tOO 

22  CR  EQU  t  8  D 

2  3  • 

24  »  ADDRESS  OF  SSD  DRIVER 

2  5  • 

26  TEMP2ERO  EQU  *00 

27  SSDCODE  EQU  *02 

2  8  » 

29  •  ML  I  DISK  DRIVE  ZERO  PACE 

3  0  • 

31  READ  EQU  *01 

32  WRITE  EQU  *02 

33  DIRBLOCKS  EQU  *06 

34  SAVEBYTES  EQU  *08 

35  » 

36  «  DEVICE  DRIVER  PARAMETERS 

3  7  * 

38  COMMAND  EQU  *42 

39  UN1TNUMB  EQU  *43 

40  BUFFADR  EQU  *44 

41  BLOCKNUM  EQU  *46 

42  RAMBANK  EQU  *48 

43  TEMP  EQU  *4A 

4  4  « 

45  «  LOW  BYTE  ADORESS  OF  BLOCK 

46  "  NUMBERS  IN  SSD  RAM  DISK 


48  BLOCK1  EQU 

49  BLOCK2  EOU 

50  B  LOCK  3  EQU 

51  B  LOCK  4  EQU 

52  BLOCKS  EQU 

53  B  LOCK  6  EQU 

5  4  ■ 

55  •  SSD  ADDRESS 

5  6  • 

57  S  S  D  LOW  EQU 

58  SSDH1  EQU 

5  9  ■ 

60  CETBUFF  EQU 

61  MLI  EQU 

62  DEVCNT  EQU 

63  DEVLST  EQU 

64  DEVADRS 1  EQU 

65  « 


*  D  2 
»  D  3 
(  D  4 

*  D5 

*  D6 

*  D  7 

FOR  CARD  IN  SLOT  5 

tCODO 

tCODl 

(BEF5 
(BF00 
t  B  F  3  1 
IBF32 

*  B  F  1  A 


2  0  00  : 

6  6 

*  VALUE  OF  DEVICE  DRIVER  POINTER 

2000 

6  7 

•  IF  THE  SLOT  AND  DRIVE  IS  NOT 

20  00 

6  8 

•  BEINC  USED 

2  u  0  0  : 

6  9 

* 

2  0  00 

D  0  A  2 

7  0 

NODEV  EQU 

f  D  0  A  2 

2  0  0  0 

7  1 

« 

2  0  0  0 

7  2 

*  B  I  CLOBAL  PACE  ADDRESS 

2  0  0  0 

7  3 

* 

2  0  0  0 

BE00 

7  4 

WARMDOS  EQU 

*  B  E  0  0 

2  00  0 

75 

» 

2  0  0  0 

76 

«  HARDWARE  LOCATIONS 

2  00  0 

7  7 

« 

2  0  00 

cooo 

7  8 

KEY  EQU 

tcooo 

2  0  0  0 

CO  1  0 

7  9 

STROBE  EQU 

tco  1  0 

2  0  0  0 

8  0 

■ 

2  0  00 

8  1 

»  MONITOR  SUBROUTINES 

2000 

8  2 

« 

2  0  0  0 

FC  5  8 

8  3 

HOME  EQU 

*  FC  5  8 

2  0  00 

FD8E 

8  4 

CROUT  EQU 

*  F  D  8  E 

2  0  0  0 

FDED 

8  5 

COUT  EQU 

»  FDED 

2  0  00 

F  F  3  A 

8  6 

BELL  EQU 

*  F  F  3  A 

2  0  0  0 

8  7 

« 

2  0  00 

8  8 

2  0  00 

8  9 

« 

20  00  4  C 

9  0 

2  1 

9  0 

JMP 

MA  I  N 

2  00  3 

9  l 

« 

2  0  0  3 

92 

2  00  3 

9  3 

* 

2  0  0  3 

9  4 

«  THIS  IS  THE  SSD  RAMDISK  DRIVER 

2  0  0  3 

95 

•  CODE  THAT  IS 

lOVED  TO  SSDCODE 

2  0  0  3 

9  6 

•  AT  THE  START 

OF  THIS  PROGRAM 

200  3: 

97 

»  THIS  ROUTINE 

MUST  START  ON  A 

2  0  0  3 

9  8 

»  PACE  BOUNDARY 

I  USE  THE  DUMMY 

2  003 

9  9 

•  DS  STATEMENT 

BELOW  SO  THAT  THE 

20  0  3 

1  0  0 

■  ROUTINE  WILL 

START  ON  A  PAGE 

2  0  0  3 

1  0  1 

*  BOUNDARY  IF 

YOU  CHANCE  ANY 

2  0  0  3 

1  02 

»  CODE  ABOVE  BE 

SURE  TO  ADJUST 

2  00  3 

1  0  3 

■  THE  DS  STATEMENT  BEFORE  YOU 

2  0  0  3 

1  0  4 

•  ASSEMBLE  AND 

RUN  THE  PROGRAM 

200  3 

1  05 

20  0  3 

1  06 

2  0  0  3 

00FD 

1  0  7 

DS 

»  FD 

2  10  0 

1  0  8 

2  10  0 

1  0  9 

2  10  0  D  8 

1  1  0 

SSDSTART  CLD 

2  10  1 

1  1  1 

2  10  1 

1  1  2 

•  SAVE  ALL  MLI 

ZERO  PACE  LOC 

2  10  1 

1  1  3 

«  PLUS  TWO  NEEDED  BY  DRIVER 

2  10  1. 

1  1  4 

« 

2101  AD 

D  3 

CO 

1  1  5 

LDA 

(  C  0  D  3 

2  10  4  A  2 

0  8 

1  1  6 

LDX 

•SAVEBYTES 

2106  85 

4  2 

1  1  7 

S 1  LDA 

COMMAND , X 

210848 

1  1  8 

PHA 

2109  CA 

i  i  y 

DEX 

2  1  0  A  10 

FA 

2  10  6 

1  2  0 

BPL 

SI 

2  1  0C  : 

1  2  1 

» 

2  1  0C  : 

I  2  2 

•  COMPUTE  WHICH  64K  BLOCK  WE 

2  1  0C 

1  2  3 

•  NEED  TO  READ 

OR  WRITE  TO 

2  1  0C 

1  2  4 

»  EACH  6  4  K  BLOCK  OF  RAM  HAS  128 

2  1  0C  : 

1  2  5 

«  PRODOS  512  BYTES  BLOCKS  IN  IT. 

2  1  0C 

1  2  6 

»  WE  ARE  ONLY  USING  THE  64K 

2  1  0C 

1  2  7 

*  BANKS  SO  ONLY 

BLOCKS 

2  1  0C 

1  2  8 

2  1  0C  : 

1  2  9 

•  BLOCK1 

BLOCKS  0-127 

2  1  0C  : 

1  30 

•  BLOCK2 

BLOCKS  128-255 

2  l  0C  . 

1  3  1 

*  B  LOCK  4 

BLOCKS  256-383 

2  1  0C  : 

1  32 

•  BLOCKS 

BLOCKS  384-511 

(Continued  on  next  page) 


85 

919 


Dr.  Dobb’s  Journal,  December  1985 


Ram  Disk  Listing 


(Listing  continued,  text  begins  on  page  84) 


HOC 

133 

2  1  0C 

1  34 

ARE  BEINC  USED.  WHEN  WE  KNOW 

2  1  0C 

1  35 

WHAT  BANK  WE  WILL  USE  WE  MOVE 

2  1  OC 

1  3  6 

THAT  BANKS  LOW  BYTE  FOR  THE 

2  1  OC 

1  37 

SSD  RAM 

CARD  BANK  ADDRESS  INTO 

2  1  OC 

1  3  8 

THE  LOW 

-BYTE  OF  THE  ZERO  PAGE 

2  1  OC 

1  3  9 

POINTER 

RAMBANK  THE  H I -BYTE 

2  1  OC 

1  4  0 

I  S  SET 

TO  •  tCO  IF  BLOCKNUM*  1 

2  1  OC 

1  4  1 

IS  NOT 

ZERO  WE  NEED  TO  ACCESS 

2  1  OC 

1  42 

BLOCKS 

2  5  6-  38  3  OR  384-5  1  1 

2  1  OC 

1  4  3 

2  1  OC 

AS 

47 

1  4  4 

COMPUTE 

L  D A  B  LOCKNUM* 1 

2  1  OE 

DO 

OC 

2  1  1C 

1  45 

BNE  C  2 

2  110 

1  4  6 

a 

2  110 

1  4  7 

a 

BLOCKS 

0-127  OR  128-255 

2  110 

1  4  8 

• 

2  110 

A  5 

4  6 

l  4  9 

LDA  BLOCKNUM 

2  112 

30 

04 

2  118 

1  S  0 

BMI  Cl 

2  114 

1  5  1 

* 

2  114 

15  2 

* 

BLOCKS 

0-127 

2  114 

1  S  3 

* 

2  114 

A  9 

D  2 

1  5  4 

LDA  • B  LOCK  1 

2  114 

DO 

OC 

2  126 

1  55 

BNE  C  4 

2  1  1  a 

1  5  6 

a 

2  118 

1  5  7 

a 

BLOCKS 

128-255 

2  118 

1  5  8 

a 

2  118 

A  9 

D  3 

1  5  9 

Cl 

LDA  8BLOCK2 

2  1  1A 

DO 

OA 

2  126 

1  6  0 

BNE  C  4 

2  l  1C 

1  6  1 

a 

2  1  1C 

1  6  2 

a 

BLOCKS 

256-383  OR  384-511 

2  1  1C 

1  6  3 

a 

2  1  1C 

AS 

46 

1  6  4 

C  2 

LDA  BLOCKNUM 

2  1  1  E 

30 

04 

2  12  4 

1  6  5 

BMI  C  3 

2  120 

1  6  6 

a 

2  12  0 

1  6  7 

a 

BLOCKS 

256-383 

2  120 

1  6  8 

a 

2  12  0 

A9 

DS 

1  6  9 

LDA  M  B  LOCK  4 

2  12  2 

DO 

02 

2  124 

1  70 

BNE  C  4 

2  12  4 

1  7  1 

a 

2  124 

1  7  2 

a 

BLOCKS 

384-5  1  1 

2  12  4 

l  7  3 

a 

2  124 

A  V 

D  6 

1  7  4 

C  3 

LDA  • B  LOCKS 

2  12  4 

8  S 

4  8 

1  75 

C 

STA  RAMBANK 

2  12  8 

176 

a 

2)28 

177 

a 

SET  UP 

H 1  -  BYTE  OF  SSD  CARD 

2  12  8 

1  78 

a 

2  12  8 

A  9 

CO 

1  7  9 

LDA  »IC0 

2  1  2  A 

as 

4  9 

180 

STA  RAMBANK ♦ 1 

2  1  2C 

1  8  1 

a 

2  1  2  C 

1  8  2 

a 

DECODE 

TYPE  OF  COMMAND  FOR 

2  1  2  C 

1  8  3 

* 

RAM  DISK  DRIVER  TO  DO 

2  1  2  C  : 

1  8  4 

a 

2 1 2C : AS 

4  2 

185 

LDA  COMMAND 

212E83 

4  A 

1  86 

STA  TEMP 

2 1 30 . FQ 

48 

2  1  7  A 

187 

BEQ  EXIT 

2  132: 

1  88 

a 

2  13  2 

1  8  9 

a 

SAVE  COMMAND  IN  Y-REC  AND 

2  132 

1  90 

a 

MASK  OUT  BITS  1  AND  0. 

2  132 

1  9  1 

a 

IF  A-REC  •  ZERO  AFTER  MASK 

2  132 

1  92 

• 

THEN  THIS  IS  A  VALID  READ 

2  132: 

1  9  3 

■ 

OR  WRITE  COMMAND 

2  132: 

1  9  4 

a 

2  1  3  2  2  9 

FC 

195 

AND  • 1 FC 

2134F0 

0  6 

2  1  3  C 

1  96 

BEQ  SETBLOCK 

2  13  6 

1  9  7 

a 

2  136 

1  98 

a 

ERROR  COMMAND  HANDLER 

2  136 

1  9  9 

a 

INVALID  COMMAND  SENT 

2  136 

200 

a 

2 1 36  A9 

27 

20  1 

LDA  »»27 

2  1  3  8  8  5 

4  A 

202 

STA  TEMP 

2 1 3A  DO 

3  E 

2  1  7  A 

203 

BNE  EXIT 

2  1  3C 

204 

a 

2  1  3C 

205 

a 

READ  OR  WRITE  COMMAND  SET  UP 

2  1  3  C 

2  06 

a 

BLOCK  NUMBER  AND  BRANCH  TO 

2  1  3C  : 

207 

a 

READBYTE  OR  WRITEBYTE. 

2  1  3C  : 

208 

2  1  3  C  : 

2  0  9 

EACH  6  4  K  BANK  HOLDS  128  PRODOS 

2  1  3C 

2  1  0 

a 

512  BYTE  BLOCKS  GET  THE  LOWER 

2  1  3  C 

2  1  1 

a 

7  BITS  AND  SHIFT  ONCE  LEFT. 

2  1  3C  : 

2  1  2 

a 

THIS  WILL  MULTPLY  THE  NUMBER 

2  1  3  C  . 

2  1  3 

a 

0-127  TO  0-254  EVEN  VALUES 

2  1  3  C  : 

2  1  4 

a 

ONLY.  THIS  ADJUSTED  BLOCK 

2  1  3C  : 

2  1  5 

a 

NUMBER  IS  USED  AS  THE  HI- BYTE 

2  1  3  C  : 

2  1  6 

a 

OF  THE  ADDRESS  IN  THE  64K  BANK 

2  1  3C  : 

2  1  7 

a 

THE  LOW-BYTE  IS  SET  TO  ZERO 

2  1  3C  : 

2  1  8 

a 

AND  INC  FROM  0  TO  IFF. 

2  1  3  C  : 

2  1  9 

a 

2 1 3C : AS 

4  6 

220 

SETBLOCK  LDA  BLOCKNUM 

2  1  3E  :  29 

7  F 

2  2  1 

AND  • I  7  F 

21400A 

2  2  2 

ASL 

2141  85 

47 

2  2  3 

STA  B  LOCKNUMt 1 

2  1  4  3  8D 

D  1 

CO 

2  2  4 

STA  SSDH1 

2  146 

225 

2  14  6 

226 

MOVE  COMMAND  TO  A-REG  AND 

2  146: 

2  2  7 

SET  UP  LOOP  COUNT  TO  RUN 

2  146 

2  28 

THRU  READ  OR  WRITE  TWICE 

2146: 

2  2  9 

EACH  LOOP  THRU  READ  OR  WRITE 

2  146 

2  30 

WILL  MOVE  256  BYTES 

2  146: 

23  1 

2  1  46  :  A0 

00 

232 

LDY  iO 

2148  84 

4  6 

2  33 

STY  BLOCKNUM 

2  1  4  A  .  A  2 

02 

234 

LUX 

21  4C  : 

235 

2  1  4  C  : 

2  36 

a 

READ 

OR  WRITE  ONE  512  BYTE 

2  1  4  C  : 

2  3  7 

a 

PRODOS  BLOCK 

2  1  4  C  : 

2  38 

a 

2 l 4C  8C 

DO 

CO 

239 

SSD  IO 

STY 

SSDLOW 

21 4F  84 

4  A 

2  4  0 

STY 

TEMP 

2  15  1 

24  l 

a 

2  15  1 

2  4  2 

a 

CHECK 

A-REG 

TO  SEE  IF  WE  NEED 

2  15  1: 

2  4  3 

a 

TO  DO 

A  READ 

OR  WRITE 

2  15  1: 

2  4  4 

a 

2  15  1: 

2  45 

a 

A-REC  -  101  FOR  READ 

2  15  1 

2  4  6 

a 

A-REC  -  102  FOR  WRITE 

2  15  1 

2  4  7 

a 

2151  : 

248 

a 

ROTATE  A-REG 

THRU  CARRY  AND 

2  15  1: 

2  4  9 

a 

CHECK 

I F  CARRY  SET  OR  CLEAR 

2  15  1: 

250 

a 

IF  CARRY  SET 

THEN  READ.  IF 

2  15  1: 

25  1 

a 

CARRY 

CLEAR 

THEN  WRITE 

2  15  1 

2  5  2 

a 

2  15  1  :  A  5 

42 

25  3 

LDA 

COMMAND 

2 1  S3  4A 

2  5  4 

L  SR 

215490 

0B 

2  16  1 

255 

BCC 

WR 1TEBYTE 

2  156 

256 

a 

2  1  5  6  A0 

00 

257 

READBYTE  LDY 

*0 

2 1 58  8 1 

4  8 

2  5  8 

LDA 

( RAMBANK  )  ,  Y 

2  1  5  A  :  A  4 

4  A 

2  5  9 

LDY 

TEMP 

2 1  SC  :  9  1 

44 

2  6  0 

STA 

<  BUFFADR  )  .  Y 

2  1  5E  18 

2  6  1 

CLC 

2 1 5F : 90 

08 

2  169 

262 

BCC 

CONT 

2  16  1 

26  3 

a 

2  16  1 

2  6  4 

a 

WRITE 

BYTE  TO  RAMDISK 

2  16  1 

265 

a 

2  16  1  :  B 1 

4  4 

2  6  6 

WRITEBYTE  LDA 

( BUFFADR  )  ,  Y 

2  1  6  3  :  A0 

00 

2  6  7 

LDY 

«  0 

216591 

4  8 

2  6  8 

STA 

( RAMBANK ) . Y 

2  1  67  A4 

4  A 

2  6  9 

LDY 

TEMP 

2  169 

270 

a 

2  16  9: 

27  1 

a 

COMMON  CODE 

TO  INC  TO  NEXT 

2  169: 

2  7  2 

a 

BYTE 

TO  READ 

OR  WRITE 

2  16  9 

273 

a 

2169CI 

2  7  4 

CONT 

INY 

2 1 6A  DO 

E0 

2  1  4  C 

275 

BNE 

SSDIO 

2  1  6  C  : 

2  76 

a 

2  1  6  C  :  E  6 

45 

277 

INC 

BUFF  ADR ♦ 1 

2  1  6  E  E  6 

47 

2  7  8 

INC 

BLOCKNUM* 1 

2  l  70  AS 

4  7 

2  7  9 

LDA 

BLOCKNUM* 1 

2  1  72  8D 

D  1 

CO 

2  8  0 

STA 

SSDH  I 

2  1  75  :  CA 

28  1 

DEX 

2176:00 

D  4 

2  1  4  C 

282 

BNE 

SSDIO 

2  178 

283 

a 

217884 

4  A 

284 

STY 

TEMP 

2  1  7A 

285 

a 

2  1  7A 

2  8  6 

a 

TH  I  S 

PART  OF 

THE  ROUTINE  IS 

2  1  7A 

2  8  7 

a 

ENTERED  IF  THE  STATUS.  READ 

2  1  7  A 

2  8  8 

a 

OR  WRITE  I/O 

IS  COMPLETE 

2  1  7A 

28  9 

a 

2  1  7A 

290 

a 

SSD  EXIT  RETURN 

2  1  7A 

29  1 

a 

2  1  7  A  A  4 

4  A 

292 

EXIT 

LDY 

TEMP 

2  1  7  C  :  A  2 

00 

2  9  3 

LDX 

•  0 

2  1  7E  68 

2  9  4 

EX  1 

PLA 

2  1  7  F  9  5 

4  2 

295 

STA 

COMMAND , X 

21 81  .  E8 

296 

1  NX 

2 1 82E0 

09 

2  9  7 

CPX 

•SAVEBYTES* 1 

2 1  8  4  DO 

t  8 

2  1  7E 

2  9  8 

2  18  6 

2  9  9 

a 

2 1 86 : AD 

D  2 

CO 

3  0  0 

LDA 

2  18  9  18 

30  1 

CLC 

2 i 8  A :  98 

302 

T  Y  A 

2 1 8B : F0 

0  1 

2  1  8E 

303 

BEQ 

E  X  2 

2 1 8D  38 

304 

SEC 

2  1  8  E 

305 

a 

2 1 8E  :  60 

306 

E  X  2 

RTS 

216F  EA 

307 

END3SD 

NOP 

2  190: 

308 

a 

2  190 

309 

a  a 

2  190 

3  1  0 

a 

2190 

3  1  1 

a 

THIS 

IS  THE 

MAIN  SET  OF  JSR 

2  190: 

3  1  2 

a 

STATEMENTS  TO  INSTALL  AND 

2  190 

3  1  3 

a 

I  NIT 

THE  RAMDISK 

2  19  0 

3  1  4 

a 

2190:20 

50 

22 

3  1  5 

MA  IN 

J  SR 

CHECKDEV 

219320 

97 

23 

3  1  6 

JSR 

WHERETO 

2196  20 

4  F 

25 

3  1  7 

J  SR 

TITLE 

219920 

6  4 

2  5 

3  1  8 

JSR 

D I SP  VOL 

2  1  9C  : 

3  1  9 

a 

2 1 9C  2  0 

B  4 

2  1 

3  2  0 

JSR 

RELOCATE 

2 1 9F  20 

C  1 

2  1 

32  1 

JSR 

SETDEV 

2 1 A2  20 

D  7 

2  1 

322 

JSR 

S  ETV  ECTOR 

2  1  AS 

3  2  3 

a 

2  1  AS  : 

32  4 

a 

CHECK 

CLEAR 

DIRECTORY  FLAC 

2  1  AS 

325 

a 

IF  CLEARDIR 

-  1  THEN  SKIP 

2  1  AS 

326 

a 

THE  ROUTINES 

TO  WRITE  A  NEW 

2  1  A  5 

3  2  7 

a 

DIRECTORY  TO 

THE  RAMDISK  AND 

2  1  AS 

328 

a 

SK  I  P 

THE  ROUTINE  TO  WRITE  A 

2  IAS 

32  9 

a 

NEW  BITMAP  TO  THE  RAMDISK 

2  1  A5 

330 

a 

2 1  A  5  AD 

4  A 

2  6 

3  3  1 

LDA 

CLEARDIR 

2  1  A8  DO 

0  9 

2  1  B3 

332 

BNE 

MN  1 

2  1  AA 

3  3  3 

a 

2  l  AA 

334 

a 

< 

O 

Q 

NORMAL 

F I R ST  TIME  INIT 

2  1  AA 

3  35 

a 

2 1 AA  20 

E  2 

2  1 

336 

JSR 

CLEAREND 

2 1  A  D  20 

EF 

2  1 

337 

JSR 

WR ITEDIR 

2 1 B0  20 

35 

2  2 

3  38 

JSR 

B 1TMAP 

2  1  B  3  6  0 

3  39 

MN  1 

RTS 

2  1  B4 

340 

2  1  B4 

3  4  1 

2  1  B  4 

342 

a 

2  1  B4 

3  4  3 

a 

THIS 

ROUTINE 

IS  USED  TO  MOVE 

3  4  4 

a 

THE  SSD  RAM 

DISK  DRIVER  TO 

2  1  B4 

3  4  5 

THE  ADDRESS 

OF  SSDCODE 

2  1  B4 

346 

THE  ROUTINE 

IS  COMPLETELY 

2  1  B4 

3  4  7 

RELOCATABLE 

AND  DOES  NOT  HAVE 

2  1  B  4 

3  4  8 

a 

TO  BE 

ON  A  PAGE  BOUNDARY 

2  1  B4 

3  4  9 

a 

2 1 B  4  A0 

00 

350 

RELOCATE  LDY 

«  0 

2  1  B6  B  9 

00 

2  1 

35  1 

R  1 

LDA 

SSDSTART . Y 

2 1 B9  91 

02 

352 

STA 

( SSDCODE ) , Y 

21BB.C8 

353 

INY 

2 1 BC  CO 

8  F 

3  5  4 

CPY 

•  ENDS SD- SSDSTART 

2 1  BE  DO 

F  6 

2  1  B6 

355 

BNE 

R  1 

2  ICO  60 

356 

RTS 

2  1C  1  : 

357 

* 

86 

920 


Dr.  Dobb's  Journal,  December  1985 


Ram  Disk  Listing  (Listing  continued,  text  begins  on  page  84) 


2  1  C  l 
2  1C  1 
2  1  C  1 
2  1C  1 
2  1  C  1 
2  1C  1 
2  1  C  1 
2  1C  1 
2  1  C4 
2  1  C  5 
2  1  C8 
2  1  C  8 
2  1  CB 
2  ICE 
2  1  CF 


AE 
E  8 

8  E 

BD 

9  D 
CA 
DO 


3  1 

3  1 

3  1 
32 

F  7 


BF 

BF 

BF 

BF 


2  1  C8 


358  *««««*««**•«»****«*««**«***•«* 

359  - 

360  *  THIS  ROUTINE  IS  USED  TO  MAKE 

361  »  ROOM  IN  THE  DEVICE  LIST  OF 

362  *  ACTIVE  DEVICES  FOH  THE  RAM 

363  *  DISK  DRIVE  IDENTIFICATION 


36  4 
365 

SETDEV 

LDX 

DEVCNT 

3  6  6 
36  7 

I  NX 
STX 

DEVCNT 

3  6  8 
369 

* 

SD  1 

LDA 

DEVLST- 1 , X 

3  7  0 

STA 

DEVLST , X 

37  1 
372 

DEX 

BNE 

SD  1 

37  3 

* 

2  1  D  1 

3  7  4 

* 

STORE  THE  RAM 

DISK  IN  LIST 

2  1  D  1 

375 

* 

A  9 

5  0 

3  7  6 

LDA 

2  1  D  3 

8  D 

3  2 

BF 

3  7  7 

STA 

DEVLST 

2  1  D  6 

60 

3  7  8 

RTS 

2  1  D7 

379 

* 

2  1  D7 

380 

3  8  1 

2  1  D7 

3  8  2 

INSTALL 

RAMDISK  DRIVER  VECTOR 

38  3 

3  8  4 

S  ETV  ECTOR 

LDA 

SSDCODE 

8  D 

1  A 

BF 

3  8  5 

STA 

D  E V ADR  5 1 

2  1  DC 

A  5 

0  3 

3  8  6 

LDA 

SSDCODE ♦ 1 

2  IDE 

8  D 

1  B 

BF 

387 

STA 

DEVADR5 1 ♦ 1 

2  1  E  1 

60 

3  8  8 

RTS 

2  1  E2 

3  8  9 

2  1  E2 

3  9  0 

39  l 

39  2 

CLEAR  BUFFER 

FROM  END  OF  DISK 

2  1  E2 

39  3 

6 

DATA  TO 

END  OF  DISK  BUFFER 

2  1  E2 

3  9  4 

ft 

2  1  E2 

A0 

00 

3  9  5 

CLEAREND 

LDY 

2  1  E4 
2  1  E5 
2  1  E8 
2  1  EB 
2  1  EC 
2  1  EE  6  0 


99  82  26 
99  82  27 
C  8 

DO  F7  2 


3  9  6 

3  9  7  CE 
3  9  8 

3  9  9 
400 
40  1 

4  0  2 


TY  A 
STA 
STA 
I  NY 
BNE 
RTS 


MIDBUFF , Y 
MIDBUFF  » I  100, 


2  1  EF 

4  0  3 

4  0  4 

4  0  5 

CALL  ML  I  TO  WRITE  OUT  THE 

2  1  EF 

4  0  6 

• 

SSD  RAM  DISK 

VOLUME  DIRECTORY 

2  1  EF 

4  0  7 

• 

2  l  EF 

A  9 

0  6 

4  0  8 

WRITEDIR 

LDA 

HD  I RB  LOCKS 

2  1  F  1 

8  D 

7  E 

2  6 

4  0  9 

STA 

BUFFER*  39 

2  1  F  4 

A  9 

0  2 

4  1  0 

LDA 

•  *  0  2 

2  1  F6 

8  D 

4  F 

2  6 

4  1  1 

STA 

WR ITEBLOCK 

2  1  F  9 

4  1  2 

* 

2  1  F9 

20 

00 

BF 

4  1  3 

JSR 

ML  I 

2  1  FC 

8  1 

4  1  4 

DB 

t  8  1 

2  1  FD 

4  B 

2  6 

4  1  5 

DW 

ML  I WR ITE 

2  1  FF 

4  1  6 

2  l  FF 

4  1  7 

CLEAR 

AND  SET 

BLOCKS 

2  IFF 

4  1  8 

3  THRU 

D1RBLOCKS  OF 

2  1  FF 

4  I  9 

« 

VOLUME 

DIRECTORY  WE  HAVE  TO 

2  1  FF 

4  2  0 

• 

SET  UP 

THE  LAST/NEXT  BLOCK 

2  1  FF 

4  2  I 

* 

NUMBERS  IN  THE  FIRST  FOUR  (4) 

2  1  FF 

4  2  2 

BYTES 

OF  EACH 

BLOCK 

2  1  FF 

4  2  3 

2  1  FF 

A  2 

0  3 

4  2  4 

LDX 

•  3 

2  2  0  1 

2  0 

2  8 

2  2 

4  2  5 

CB  1 

JSR 

CLEARBUFF 

2  2  0  4 

4  2  6 

« 

2  2  0  4 

CA 

4  2  7 

DEX 

2  2  0  5 

8  E 

5  7 

2  6 

4  2  8 

STX 

BUFFER 

22  08 

E  8 

4  2  9 

I  N  X 

2  2  0  9 

E  8 

4  3  0 

I  NX 

2  2  0A 

4  3  1 

• 

2  2  0A 

E0 

06 

4  3  2 

CPX 

•D IRBLOCKS 

2  2  0  C 

DO 

07 

22  15 

4  3  3 

BNE 

C  B  2 

2  2  0  E 

4  3  4 

ft 

2  2  0  E 

A  9 

00 

4  35 

LDA 

•  0 

22  10 

8  D 

5  9 

2  6 

4  3  6 

STA 

BUFFER*  2 

22  13 

F0 

0  3 

2  2  18 

4  3  7 

BEQ 

C  B  3 

22  15 

4  3  8 

ft 

2  2  15 

8  E 

5  9 

2  6 

4  3  9 

CB2 

STX 

B  U  F  F  ER  ♦  2 

22  18 

4  4  0 

« 

22  18 

4  4  1 

• 

WRITE 

VOLUME 

DIRECTORY  BLOCK 

2  2  18 

4  4  2 

* 

TO  SSC 

RAM  DISK 

22  18 

4  4  3 

* 

22  18 

CA 

4  4  4 

CB  3 

DEX 

22  19 

8  E 

4  F 

2  6 

4  4  5 

STX 

WR ITEBLOCK 

2  2  1C 

4  4  6 

* 

22  1C 

20 

00 

BF 

4  4  7 

JSR 

ML  I 

22  1  F 

8  1 

4  4  8 

DB 

%  8  l 

2  2  2  0 

4  B 

2  6 

4  4  9 

DW 

ML IWR ITE 

2  2  2  2 

4  5  0 

• 

2  2  2  2 

E  8 

4  5  1 

I  NX 

2  2  2  3 

E0 

0  6 

4  5  2 

CPX 

»  D I R  8  LOCK  S 

2  2  2  5 

DO 

DA 

2  2  0  1 

4  5  3 

BNE 

CB  1 

2  2  2  7 

6  0 

4  5  4 

RTS 

2  2  2  8 

4  5  5 

* 

2  2  2  8 

4  5  6 

■ 

2  2  2  8 

45  7 

■ 

2  2  2  8 

4  5  8 

« 

CLEAR 

ENTIRE 

FILE  BUFFER 

2  2  2  8 

4  5  9 

* 

2  2  2  8 

A0 

00 

4  6  0 

CLEARBUFF  LDY  «0 

2  2  2A 

V  8 

4  6  1 

TY  A 

2  2  2  B 

99 

57 

2  6 

4  6  2 

CL1  STA  BUFFER, Y 

2  2  2  E 

9  9 

5  7 

2  7 

4  6  3 

STA  BUFFER**  100  ,  Y 

22  3  1 

C  8 

4  6  4 

INY 

2  2  3  2 

DO 

F  7 

2  2  2  B 

4  6  5 

BNE  CLl 

2  2  3  4 

6  0 

4  6  6 

RTS 

2  2  35 

467 

2  2  3  5 

4  6  8 

2  2  3  5 

469 

2  2  3  5 

4  7  0 

« 

SET  ALL  BLOCKS  NOT  USED  IN 

2  2  35 

47  1 

a 

RAM  DISK  TO  HEX  IFF  TO  SHOW 

2  2  3  5 

4  7  2 

THAT  THOSE  BLOCKS  ARE  FREE 

2  2  35 

4  7  3 

SET  FIRST  BYTE  TO  SHOW  THAT 

22  35 

4  7  4 

« 

BLOCKS  0  THRU  DIRBLOCKS  ARE 

2  2  35 

475 

• 

IN  USE 

2  2  3  5 

4  7  6 

« 

2  2  35 

A  9 

0  1 

477 

BITMAP  LDA  »«01 

2  2  3  7 

8  D 

57 

2  6 

4  7  8 

STA  BUFFER 

2  2  3  A 

A0 

3  F 

479 

LDY  *63 

2  2  3  C 

A  9 

FF 

4  8  0 

LDA  M  FF 

2  2  3  E 

4  8  1 

• 

2  2  3  E 

9  9 

5  7 

2  6 

4  8  2 

BM2  STA  BUFFER, Y 

2  2  4  1 

8  8 

4  8  3 

DEY 

2  2  4  2 

DO 

FA 

2  2  3E 

4  8  4 

BNE  BM2 

2  2  4  4 

4  8  5 

» 

2  2  4  4 

A  9 

06 

4  8  6 

LDA  IDIRBLOCKS 

2  2  4  4 

BD 

4  F 

2  6 

4  8  7 

STA  WRITEBLOCK 

2  2  4  9 

4  8  8 

• 

2  2  4  9 

4  8  9 

• 

WRITE  OUT  BIT  MAP  TO  SSD 

2  2  4  9 

4  9  0 

■ 

RAM  DISK 

2  2  4  9 

49  1 

* 

2  2  4  9 

20 

00 

BF 

4  9  2 

JSR  MLI 

2  2  4  C 

8  1 

4  9  3 

DB  *81 

2  2  4D 

4  B 

2  6 

4  9  4 

DW  ML  I WR ITE 

2  2  4  F 

4  9  5 

* 

2  2  4  F 

6  0 

4  9  6 

RTS 

2  2  5  0 

49  7 

22  5  0 

4  9  8 

2  2  5  0 

4  9  9 

• 

2  2  5  0 

5  00 

• 

THIS  ROUTINE  IS  USED  TO  CHECK 

2  2  5  0 

5  0  1 

• 

FOR  A  DEVICE  DRIVER  ALREADY 

2  2  5  0 

502 

* 

INSTALLED  FOR  SLOT  AND  DRIVE 

2  2  5  0 

5  0  3 

• 

WE  WANT  TO  USE  I  USE  SLOT  5 

22  50 

5  0  4 

DRIVE  1  FOR  THE  RAMDISK  DRIVER 

2  2  5  0 

505 

IF  THE  ADDRESS  AT  SLOT  5  DRIVE 

2  2  5  0 

5  06 

1  DOES  NOT  POINT  TO  »D0A2  THE 

2  2  5  0 

507 

* 

PROCRAM  WILL  ABORT  WITH  AN 

2  2  5  0 

5  0  8 

« 

ERROR  MESSAGE  IF  WE  FIND  NO 

2  2  5  0 

5  0  9 

* 

DRIVER  INSTALL  I  READ  BLOCK  2 

2  2  5  0 

5  1  0 

« 

OF  THE  RAMDISK  AND  COMPARE  IT 

2  2  5  0 

5  1  1 

* 

WITH  THE  VOLUME  DIRECTORY 

2  2  5  0 

5  1  2 

■ 

HEADER  INFORMATION  AT  BUFFER 

2  2  5  0 

5  1  3 

• 

IF  THE  INFORMATION  IS  THE  SAME 

2  2  5  0 

5  1  4 

* 

A  MESSACE  IS  PRINTED  ASKING 

2  2  5  0 

5  1  5 

* 

THE  USER  IF  HE  WANTS  TO  ZERO 

22  50 

5  1  6 

THE  DIRECTORY  ON  THE  RAMDISK 

2  2  5  0 

5  1  7 

* 

OR  NOT  IF  WE  WANT  TO  CLEAR 

2  2  50 

5  1  8 

« 

THE  DIRECTORY  I  SET  CLEARDIR 

2  2  5  0 

5  1  9 

• 

TO  ZERO  IF  WE  DON'T  WANT  TO 

2  2  5  0 
2  2  5  0 
22  50 
2  2  5  0 
22  5  3 
2  2  5  5 
2  2  57 
2257 
2  2  3  A 
2  2  5  C 
22  SE 
2  2  5  E 
2  2  5  E 
22  SE 
2  2  5  E 
22  SE 
2  2  5  E 
2  2  5  E 
2  2  5  E 
22  60 
2  26  2 
2262 
2  2  6  2 
22  42 
2244 
22  64 
2  24  8 
2  2  4A 
2  2  4  A 
2  2  4A 
2  2  6  A 
2  2  4  C 
2  2  6  E 
22  70 
2  2  7  2 
22  72 
2  272 
2  2  72 
2  2  7  2 
22  73 
2  2  7  3 
2  2  7  5 
22  75 
2  2  7  5 
22  75 
2  2  75 
2  2  7  5 
2  2  75 
2  2  75 
2  2  75 
2  2  75 
2  2  75 
22  75 
2  2  7  7 
22  7A 
22  7D 


0  1 
42 


520 
52  1 
522 


CLEARDIR  TO  ONE . 


BF 

523 

5  2  4 

CHECKDEV 

LDA 

CMP 

DEVADR5 1 
•  >  NODEV 

2  2D  1 

525 

526 

BNE 

DEVABORT 

BF 

527 

528 

LDA 

CMP 

DEVADR5 1 ♦ 
» (NODEV 

2  2  D  1 

5  2  9 

5  30 

• 

BNE 

DEVABORT 

5  3  1 
5  32 
5  3  3 

534 

535 

536 

537 

538 

539 

540 


LETS  TRY  AND  READ  THE  RAM  DISK 
DIRECTORY  AT  BLOCK  2  AND 
COMP  IT  TO  SOME  OF  THE  BYTES 
AT  ADDR  BUFFER  TO  SEE  IF  THE 
RAMDISK  MIGHT  HAVE  A  FORMATTED 
DIRECTORY  ON  IT. 


LDA 

STA 


•  READ 
COMMAND 


8  2 
44 
26 
4  5 


02 

44 

00 

47 


54  1 

542 

543 
5  4  4 
545 
5  46 
547 
3  4  8 
5  4  9 
550 
35  1 
352 
5  5  3 

354 

355 


ADDRESS  OF  READ  BUFFER 

LDA  •>MIDBUFF 
STA  BUFFADR 
LDA  i<MIDBUFF 
STA  BUFF  ADR ♦ 1 

BLOCK  NUMBER  TO  READ 

LDA  tt«02 

STA  BLOCKNUM 

LDA  •400 

STA  B  LOCKNUMt 1 

READ  THE  RAMDISK  USING  DRIVER 


00  2  1 


00 

5  7  2  6 
8  2  2  6 
4  C  2  2  C  B 


556 
5  5  7 
5  5  8 
5  5  9 
5  6  0 
5  6  1 
5  6  2 
5  6  3 
5  6  4 
5  65 
5  6  6 
5  6  7 
5  6  8 
5  6  9 
5  70 
57  1 
5  7  2 
5  7  3 
5  7  4 
575 


«  AT  SSDSTART  IN  THIS  PROGRAM 


JSR  SSDSTART 


WE  WILL  NOW  ASSUME  AT  THERE 
IS  THE  INFORMATION  IN  BLOCK  2 
OF  THE  RAMDISK  AT  ADDRESS 
MIDBUFF  COMPARE  THREE  SETS  OF 
BYTES  TO  SEE  IF  IT  MATCHS  A 
VOLUMN  DIRECTORY  HEADER  BLOCK 
IF  ANY  BYTE  DOES  NOT  MATCH 
THE  ROUTINE  JUMPS  TO  NODIR 
IF  ALL  THREE  SETS  OF  BYTES 
MATCH  I  ASK  USER  IF  HE  WANTS 
TO  CLEAR  THE  DIR  OR  NOT 


CD0 


LDX  M00 
LDA  BUFFER, X 
CMP  MIDBUFF,  X 
BNE  NODIR 


Dr.  Dobb’s  Journal,  December  1985 


87 

921 


Ram  Disk  Listing  (Listing  continued,  text  begins  on  page  84) 


22  7F 
2  2  7  F 
22  SO 
2  2  8  2 
22  8  4 
2  2  8  4 
2  2  8  6 
2  2  8  9 
2  2  8  C 
2  2  8  E 
2  2  8  E 
22  8  F 
2  2  9  1 
2  2  9  3 
22  9  3 
2  2  9  5 
22  98 
2  2  9B 
2  2  9D 
2  2  90 
2  2  9  E 
2  2  A  0 
2  2  A  2 
2  2  A  2 
2  2  A  2 
2  2  A  2 
2  2  A  2 
22  A2 
2  2  A  2 
2  2  A  2 


EO  04 
DO  F  3 

A  2  14 

BD  57 
DD  8  2 
DO  3D 

E  8 

EO  25 
DO  F  3 

A  2  27 
BD  5  7 
DD  8  2 
DO  2  E 


EO  2  B 
DO  F  3 


5  7  6 
5  7  7 
5  78 

579 

580 

58  1 
5  8  2 
5  8  3 
5  8  4 
585 
5  8  6 

587 

588 

589 
5  9  0 

59  1 
5  9  2 
5  9  3 
5  9  4 
595 
5  9  6 

59  7 
5  9  8 

5  9  9 

6  0  0 

60  1 
602 
6  0  3 
604 
6  0  5 


I  NX 
CPX 
BNE 

LDX 

LDA 

CMP 

BNE 

1  NX 
CPX 
BNE 

LDX 

LDA 

CMP 

BNE 

I  NX 
CPX 
BNE 


•  •04 

CDO 

•  614 

BUFFER , X 
MIDBUFF , X 
NOD  1  R 


•  •25 
CD  1 

•  •27 

BUFFER , X 
MIDBUFF , X 
NOD  IR 


i  •  2B 

CD2 


ASK  USER  IF  HE  WANT  TO  ZERO 
DIRECTORY  OR  NOT  IF  THE  USER 
WANTS  TO  ZERO  THE  DIRECTORY 
JUST  CONTINUE  WITH  THE  INSTALL 
PROCRAM  IF  THE  USER  DOES  NOT 
WANT  TO  ZERO  THE  DIRECTORY 
DO  A  JUMP  TO  THE  DOS  WARMSTAR1 


2  2  A  2  : 

6  0  6 

• 

2  3  B  E  C  9 

5  1 

733 

CMP 

•  ’O’ 

2  2  A 2 :  20 

58 

FC 

6  0  7 

JSR 

HOME 

2  3C0  DO 

EA 

2  SAC 

7  3  4 

BNE 

WT  1 

22A5  20 

3  A 

FF 

6  0  8 

JSR 

BELL 

2  3  C  2 

7  35 

• 

2  2  A  8  : 

6  0  9 

» 

23C2  20 

5  8 

FC 

736 

JSR 

HOME 

2  2  A  8  : A0 

0  0 

6  1  0 

LDY 

•  0 

2  3  C  5  :  20 

3  A 

FF 

7  3  7 

JSR 

BELL 

2  2  A A  :  B9 

EF 

2  2 

6  1  1 

C  D  3 

LDA 

D  I  R  FOUND . Y 

23C8.4C 

00 

BE 

738 

JMP 

WARMDOS 

2  2  AD : C  9 

00 

6  1  2 

CMP 

•  EOL 

2  3  C  B 

7  3  9 

* 

2  2  A  F :  F0 

o  y 

2  2  BA 

6  1  3 

BEQ 

C  D  4 

2  3  C  B 

7  4  0 

•  MOVE 

CODE  INSIDE  PRODOS 

2  2  B  1  : 

6  1  4 

• 

2  3CB 

7  4  1 

« 

22B1 : 09 

0  0 

6  1  5 

ORA 

*  *  80 

2  3  C  B  A  9 

00 

742 

WT  2 

LDA 

•  too 

22B3 : 20 

ED 

FD 

6  1  6 

JSR 

COUT 

2  3  CD  85 

0  2 

7  4  3 

STA 

SSDCODE 

2  2  B  6  .  C  8 

6  1  7 

INY 

2  3  C  F  A  9 

FF 

7  4  4 

LDA 

•  »  FF 

22B74C 

AA 

22 

6  1  8 

JMP 

CDS 

2  3  D 1  :  8  5 

0  3 

745 

STA 

SSDCODE  *  1 

2  2  BA 

6  1  9 

2  3D3  AD 

8  1 

CO 

7  4  6 

LDA 

t  C  0  8  1 

2  2  B A  :  20 

4  1 

25 

6  20 

CD  4 

JSR 

CETKEY 

2  3 D6  : AD 

8  1 

CO 

7  4  7 

LDA 

•  C  0  8  1 

2  2  BD : C  9 

5  9 

6  2  1 

CMP 

•  ’  Y  ’ 

2  3  D  9  .  6  0 

7  4  8 

RTS 

2  2  B  F : F0 

0A 

2  2  C  B 

622 

BEQ 

NODIR 

2  3  DA 

749 

• 

2  2  C  1  : 

6  2  3 

* 

2  3  DA 

750 

»  MOVE 

CODE  UNDER  B1 

2  2  C  1  :  C  9 

4  E 

6  2  4 

CMP 

•  ’  N  ‘ 

2  3  DA  : 

75  1 

• 

22C3 : DO 

F  5 

2  2  BA 

6  2  5 

BNE 

CD4 

2  3 DA  A  9 

0  1 

75  2 

WT  3 

LDA 

•  1 

2  2  C  5  : 

2  2  C  5  : 

2  2  C  5  : 

2  2  C  5  : 

2  2  C  5 : A  9  0  1 

2  2  C  7 :  8  D  4A 
2  2  C  A  :  60 
2  2CB 
2 2 CB  : 

2  2  C  B  : 

2  2  C  B 
2  2  C  B  : 

22CBA9  00 
2  2  C  D :  BD  4A 
2  2  D  0  :  60 
2  2  D  1  : 

2  2  D  1  : 

2  2D  1  ; 

2  2  01: 

2  2D  1  : 

2  2  D  1  : 

2  2  D  1  : 

2201  : 

2  2  D  1  : 

2  2  D  1  . 

2  2  D  1  : 

2  2  D  1  : 
2201:20 
2  2D4  :  20 
2  2  D  7  : 

22D7 : AO 
2  2  D  9  :  B  9 
2  2  DC  : C  9 
2  2  D  E  F  0 
2  2  E  0  : 

22E0  09 

22  E2  :  20 
2  2  E  5  C  8 
2  2  E  6  4  C 
2  2  E  9  : 
22E9.20 
2  2  E  C  .  4  C 
22  EF 
22EF 
2  2  E  F  : 

22  EF 
2  2  E  F  . 

2  2  EF  :  8D 
2  2  F  3  :  BD 
22  F7  20 
2  30  3.4  4 
2  3  1  2  80 
2  3  1  4  2  0 


58  FC 
3  A  FF 


00 

4  B  2  3 


ED  FD 
D  9  22 


3  A  FF 
00  BE 


8  D  8  D  8  D 
BD  8  D  8  D 
20  20  20 
49  52  45 
8  D 

20  20  20 


6  2  6 
627 
6  2  8 
62  9 
630 

6  3  1 
6  32 
6  3  3 
6  3  4 
635 
6  3  6 
637 
6  3  8 
6  3  9 
6  4  0 
6  4  1 
6  4  2 
6  4  3 
6  4  4 
6  4  5 
6  4  6 
6  4  7 
6  4  8 
6  4  9 
6  5  0 
6  5  1 

652 

653 
65  4 
6  5  5 

65  6 
657 
6  5  8 
659 
6  6  0 
6  6  1 
6  6  2 

66  3 
6  6  4 
6  6  5 
6  6  6 
6  6  7 
6  6  8 
6  6  9 
6  7  0 
6  J  1 
6  7  2 
6  7  3 
6  7  4 
6  7  5 
6  7  6 
6  7  7 
6  7  8 


THE  USER  DOES  NOT  WANT  TO 
CLEAR  THE  DIRECTORY. 


STA 

RTS 


CLEARD  IR 


«  WE  WANT  TO  CLEAR  THE  DIRECTORY 
«  ON  THE  RAMDISK  SO  SET  FLAG  FOR 
«  CLEAR  AND  RETURN  TO  ROUTINE. 


LDA 

STA 

RTS 


N  *  00 

CLEARD1R 


THIS  PART  OF  THE  ROUTINE  IS 
ENTERED  IF  WE  FIND  A  DEVICE 
DRIVER  ADDRESS  THAT  DOES  NOT 
POINT  TO  THE  NO  DEVICE  DRIVER 
ACTIVE  ADDRESS  OR  TO  THE 
ADDRESS  WHERE  WE  WANT  TO  PUT 
OUR  CODE.  THERE  COULD  BE  A 
HARD  DISK  OR  OTHER  STORACE 
DEVICE  ALREADY  INSTALL  FOR 
THAT  SLOT/DR  1 VE 


2  3  9  7 
2  3  9  7 
2  3  9  7 
2  3  9  7: 
2  3  9  7 
2  3  9  7 
2  3  9  7 
2  3  9  7 
2  3  9A 
2  3  9C 
2  3  9  F 
2  3  A  1 
2  3  A  3 
2  3  A  3 
2  3  A  5 
2  3  A  8 
2  3  A  9 
2  3  A  C 
2  3  A  C 
2  3  AF 
2  3  B  2  . 
2  3  B  4 
2  3  B  6 
2  3  B  6 
2  3  B  8 
2  3  B  A 
2  3  B  A 
2  3BC 
2  3  B  E 


20  58  FC 
A0  0  0 
B9  OF  24 
C  9  00 

F0  09  2  3 AC 

0  9  80 
20  ED  FD 
C  8 

4  C  9  C  23 

2  0  3  A  FF 
20  41  25 

C  9  3  1 

F0  1  5  2  3 C B 

C  9  3  2 

F0  20  2  3 DA 

C  9  33 

F0  2  A  2  3  E  8 


7  0  4  « 

705  »  LOC  1 

706  *  LOC  2 

707  » 

7  0  8  * 

709  •  LOC  3 

7  10  • 

7 1 1  WHERETO 
7  1  2 

7  13  WTO 
7  1  4 
7  1  5 
7  16  * 

7  1  7 
7  1  8 
7  1  9 
7  2  0 

72  1  * 

722  WT 1 
7  2  3 

724 
7  25 
7  2  6  * 

7  2  7 
7  2  8 
7  2  9  * 

730 

73  1 

7  3  2  * 


INSIDE  PRODOS  AT  *F000 
BETWEEN  THE  BASIC 
INTERPRETER  AND  THE 
FILE  BUFFERS 
AT  *300 


J  SR 
LDY 
LDA 
CMP 
BEQ 

ORA 

JSR 

INY 

JMP 

JSR 

JSR 

CMP 

BEQ 

CMP 

BEQ 

CMP 
B  EQ 


HOME 

•  0 

WHEREMSC , Y 
•  EOL 
WT  1 


BELL 

CETKEY 


DEVABORT 


JSR 

JSR 

LDY 

LDA 

CMP 

BEQ 

ORA 

JSR 

INY 

JMP 

JSR 

JMP 


HOME 

BELL 


A  BORTMSC . Y 
•  EOL 
DA  2 


•  *  80 

COUT 


DIRECTORY  FOUND  MESSACE 


D I R  FOUND 


DFB 

DFB 

ASC 

ASC 

DFB 

ASC 


CR  .  CR  .  CR , CR 
CR , CR . CR . CR 


•  DIRECTORY 
CR  .  CR 


2 3 DC : 20 
2  3  D  F  B0 
2  3  E  1  : 

2  3E  1  :  8  5 
2  3  E  3  A  9 
23E5  :  85 
23E7 : 60 
2  3  E  8  . 

2  3  E  8  : 

2  3  E  8  : 

2  3  E  8  A  9 
2  3EA  :  85 
2  3  EC  . A9 
23EE  85 
23F060 
2  3  F  l  : 

2  3  F  1  : 

2  3  F  1 
23  F  1  20 

23F4  :  20 
23F7 : A0 
2  3  F  9  :  B  9 
2  3  F  C  :  C  9 
2  3  F  E  :  F  0 
2400  : 
2400:09 
240220 
2  4  05  :  CB 
2  4  0  6  :  4C 
2  4  0  9 
240920 
2  4  0  C  4  C 
2  4  0  F  : 

2  4  OF  : 


JSR 

BCS 


CETBUFF 

BUFFERROR 


3  A  FF 
5  8  FC 


8  0 

ED  FD 


3  A  FF 
00  BE 


75  3 
75  4 
7  5  5  ■ 

75  6 
757 

75  8 
759 

76  0  * 

761  •  MOVE  CODE  TO  »300 

762  » 

7  6  3  WT  4 
76  4 

765 

766 

767 
7  6  8  * 

769  »  ERROR  CETTINC  BUFFER  FROM  SYS 
7  7  0  • 

771  BUFFERROR  JSR 

772  JSR 


STA  SSDCODE ♦ 
LDA  MOO 
STA  SSDCODE 
RTS 


LDA  ••00 
STA  SSDCODE 
LDA  ••03 
STA  SSDCODE* 
RTS 


BE  0 


773 

774 

775 

77  6 

7  7  7  » 

7  78 
7  7  9 
7  80 

78  1 
78  2  • 

7  8  3  BE  1 
784 
7  8  5  * 

7  8  6  •  ■ « • 


LDY 

LDA 

CMP 

BEQ 

ORA 
JSR 
I  NY 
JMP 

JSR 

JMP 


BELL 

HOME 

•  0 

NOBUFF  , 
•  EOL 
B  E  1 


BELL 

WARMDOS 


2  3  1  A 
2  3  3  4 
2  3  3  6 
2  3  4A 
2  3  4B 
2  3  4  B 
2  3  4  B 
2  3  4  B 
2  3  4  F 
2  3  5  3 
2  3  5  7  : 
2  3  7  6 
2  3  7  8 
2  3  8  5 
2  3  9  2 
2  3  9  6 
2  3  9  7 
2  3  9  7 
2  3  9  7 
2  3  9  7 
2  3  9  7 
2  3  9  7 
2  3  9  7 
2  3  9  7 
2  3  9  7 


4  3  4  C  4  5  4  1 
8  D  6  D 

20  20  20  20 


8  D  8  D  8  D  0  D 
8  D  8  D  8  D  8  D 
20  20  20  20 


20  20  20  20 
50  52  4  F  47 
8  D  8  D  80  8  D 
00 


679  ASC  ’CLEAR 

680  DFB  CR . CR 

681  ASC 

682  DFB  EOL 

6  8  3  « 

684  «  ABORT  MESSACE 


6  8  5 

6  8  6  A  BORTMSC 


68  7 
6  8  8 
6  8  9 
6  9  0 
6  9  1 
6  9  2 
6  9  3 
6  9  4 
6  9  5 
6  9  6 
6  9  7 
6  9  8 

6  9  9 
700 

7  0  1 
7  0  2 
70  3 


DFB 

DFB 

ASC 

ASC 

DFB 

ASC 

ASC 

DFB 

DFB 


CR . CR , CR , CR 
CR  ,  CR  ,  CR  . CR 

’DEVICE 
CR  .  CR 

’  PROCRAM 
CR  .  CR  ,  CR  ,  CR 
EOL 


D  I  RECTORY 


DRIVER  ALREADY  INSTALLED’ 


THIS  ROUTINE  IS  USED  TO  ASK 
THE  USER  WHERE  HE/SHE  WOULD 
LIKE  TO  MOVE  THE  RAMDISK 
DRIVER  CODE  TO  THERE  ARE 
THREE  LOCATIONS  TO  MOVE  THE 
CODE  TO  OR  THE  USER  CAN  QUIT 


90  Dr.  Dobb’s  Journal,  December  1985 

922 


2  4  0  F  : 

7  8  7 

* 

2  4  OF  : 

7  8  8 

«  MESSACE 

ASKINC  USER  WHERE 

2  4  OF 

7  8  9 

*  TO  PUT 

* AMD  I SK  DRIVER  CODE 

2  4  OF 

7  9  0 

a 

2  4  OF  8 D 

8  D 

8  D 

8  D 

7  9  1 

WHEREMSG 

DFB 

CR  ,  CR , CR . CR 

241320 

2  0 

2  0 

2  0 

7  9  2 

ASC 

2418:52 

4  1 

4  D 

4  4 

7  9  3 

ASC 

'  R AMD  I SK 

DRIVER  ADDRESS 

2  4  2  F  4  F 

5  0 

5  4 

4  9 

79  4 

ASC 

•  OPTIONS ' 

24  36  .  8D 

8  D 

80 

7  9  5 

DFB 

CR . CR , CR 

2439:20 

2  0 

20 

20 

796 

ASC 

2  4  3  F  5  B 

3  1 

5  D 

2  0 

7  9  7 

ASC 

•  t  1  ] 

INSIDE  PRODOS  AT  1FFOO ‘ 

2459:80 

8  D 

7  9  8 

DFB 

CR  .  CR 

2  4  5  B  20 

20 

2  0 

2  0 

7  9  9 

ASC 

2  4  6  1  5  B 

3  2 

5  D 

2  0 

8  0  0 

ASC 

•12) 

UNDER  BASIC  INTERPRETER' 

2  4  7  C  8  D 

80 

8  0  1 

DFB 

CR  .  CR 

2  4  7  E  2  0 

2  0 

2  0 

2  0 

80  2 

ASC 

2  4  8  4  3  B 

3  3 

SD 

2  0 

8  0  3 

ASC 

•  C  3  ) 

AT  *  3  0  u 

2  4  8  F  8  D 

8  D 

8  0  4 

DFB 

CR  .  CR 

2491  20 

2  0 

2  0 

2  0 

8  0  5 

ASC 

2497.58 

5  l 

3D 

55 

8  0  6 

ASC 

*  C  Q  )  U  I  T  ' 

2  4  9  D  .  8  D 

8  D 

8  0  7 

DFB 

CR  ,  CR 

2  4  9  F  2  0 

2  0 

2  0 

2  0 

8  0  8 

ASC 

2  4  A  9  2  0 

2  0 

2  0 

2  0 

8  0  9 

ASC 

2  4  B  2  00 

8  1  0 

DFB 

EOL 

2  4  B  3 

8  1  1 

• 

2  4  B  3 

8  1  2 

a  a 

24B3 

8  1  3 

2  4  B  3 

8  1  4 

*  NO  DOS 

BUFFER 

FOUND  UNDER  BI 

2  4B  3 

6  1  3 

2  4  B  3  8  D 

8  D 

8  D 

8  D 

8  1  6 

NOBUFF 

DFB 

CR , CR , CR . CR 

2  4  B  7  8  D 

3D 

8  D 

8  D 

8  1  7 

DFB 

CR  .  CR  .  CR  .  CR 

2  4  BB  2  0 

20 

2  0 

2  0 

8  1  8 

ASC 

2  4C  9  4  6 

4  1 

5  4 

4  1 

8  1  9 

ASC 

•  FATAL 

ERROR  1 

2404  80 

8  D 

8  2  0 

DFB 

CR  .  CR 

2  4  D  6  20 

2  0 

2  0 

8  2  1 

ASC 

2409-43 

4  F 

55 

4  C 

8  2  2 

ASC 

•  COULD 

NOT  ALLOCATE  BUFFER  BETWEEN 

2  4  F  A  8  0 

80 

823 

DFB 

CR  .  CR 

2  i  F  C  2  0 

2  0 

20 

20 

8  2  4 

ASC 

2506  42 

4  9 

2  0 

4  1 

8  2  5 

ASC 

■  B  I 

AND  FILE  BUFFERS’ 

2519:80 

8  D 

8  2  6 

DFB 

CR  .  CR 

25 1 H  20 

2  0 

2  0 

2  0 

8  2  7 

ASC 

2520:46 

4  C 

4  1 

3  3 

8  2  8 

ASC 

1  FLASHCARD 

DRIVER  NOT  INSTALLED' 

2  5  3  E  8  D 

80 

8  2  9 

DFB 

CR  .  CR 

254000 

8  30 

DFB 

EOL 

2  5  4  1 

8  3  1 

» 

2  5  4  1 

8  3  2 

a 

2  5  4  1: 

833 

a 

2  5  4  1 

8  3  4 

■  THIS  ROUTINE 

IS  USED  TO  CET 

2  5  4  1 

8  35 

«  A  SINCLE  KEYPRESS  FROM  THE 

2  5  4  1 

8  3  6 

*  KEYBOARD  AND 

RETURN  IT  IN 

2  5  4  1: 

837 

«  THE  A-REC  WITH  THE  MSB  OFF 

2  5  4  1 

8  3  8 

» 

2541:80 

1  0 

CO 

8  3  9 

CETKEY 

STA 

STROBE 

2544  AD 

00 

CO 

8  4  0 

CK  1 

LDA 

KEY 

2547  10 

FB 

2  5  4  4 

8  4  l 

BPL 

CK  1 

2  5  4  9  8  D 

1  0 

CO 

8  4  2 

STA 

STROBE 

2  5  4C  2  9 

7  F 

8  4  3 

AND 

•  S  7  F 

2  5  4  E  60 

8  4  4 

RTS 

2  5  4  F 

845 

* 

2  5  4  F 

8  4  6 

a 

2  5  4  F 

8  4  7 

* 

2  5  4  F 

8  4  8 

«  PRINT  PROCRAP 

TITLE 

2  5  4  F 

8  4  9 

« 

2  5  4  F  2  0 

5  8 

FC 

850 

TITLE 

JSR 

HOME 

2552  AO 

00 

85  1 

LDY 

•  0 

2  5  5  4  B  9 

9  B 

2  5 

85  2 

T  I  1 

LDA 

MSC  ,  Y 

2  5  5  7  C  9 

00 

85  3 

CMP 

*  EOL 

2  5  5  9  FO 

08 

2  5  6  3 

8  5  4 

BEQ 

T  I  2 

255B 

855 

a 

2  5  5  B  0  9 

80 

85  6 

ORA 

N  «  8  0 

2  3  5D  2  0 

ED 

FD 

8  5  7 

JSR 

COUT 

25  6  0  ce 

8  5  8 

1  NY 

2561  DO 

F  1 

2  5  5  4 

8  5  9 

BNE 

T  I  1 

2  5  6  3  6  0 

8  6  0 

T  I  2 

RTS 

2  5  6  4 

8  6  1 

* 

2  5  6  4 

8  6  2 

a  a 

2  5  6  4 

8  6  3 

* 

2  5  6  4 

8  6  4 

*  THIS  ROUTINE 

WILL  DISPLAY  THE 

2  5  6  4 

8  6  5 

*  RAM  DISK  VOLUMN  NAME  IF  THE 

25  6  4 

8  6  6 

*  INSTALL 

WAS  SUCCESSFULL. 

25  6  4 

86  7 

2564  AO 

00 

8  6  8 

D  I  SPVOL 

LDY 

2566  B9 

3  1 

2  6 

8  6  9 

DVO 

LDA 

2  5  6  9  C9 

00 

8  7  0 

CMP 

2  5  6  B  FO 

0  9 

2  5  7  6 

8  7  1 

BEQ 

2560  09 

80 

8  7  2 

ORA 

2  5  6  F  20 

EO 

FD 

8  7  3 

JSR 

COUT 

25  7  2  :  C8 

8  7  4 

INY 

2  5  7  3  :  4C 

6  6 

2  5 

8  7  5 

JMP 

DVO 

2  5  7  6 

8  7  6 

a 

2576  AO 

0  0 

8  7  7 

DV  1 

LDY 

2578B9 

5  C 

2  6 

8  7  8 

D  V  2 

LDA 

257B  C9 

00 

8  7  9 

CMP 

25  70  -  FO 

OC 

2  3  8  B 

8  8  0 

257F  CO 

0  E 

88  1 

2  3  8  1  FO 

0  8 

2  3  8  B 

8  8  2 

BEQ 

DV3 

2  5  8  3: 

8  8  3 

* 

2583  09 

80 

8  8  4 

ORA 

2585  20 

ED 

FD 

8  8  5 

JSR 

COUT 

25  88  C8 

8  8  6 

2589  DO 

ED 

2  5  7  8 

8  8  7 

BNE 

D  V  2 

2  5  8  B 

8  8  8 

a 

238B  20 

8  E 

FD 

8  8  9 

D  V  3 

JSR 

2  5  8E  20 

8  E 

FD 

8  9  0 

JSR 

2591:20 

8  E 

FD 

8  9  1 

JSR 

2394:20 

8  E 

FD 

8  9  2 

JSR 

259720 

3  A 

FF 

8  9  3 

JSR 

BELL 

25  9A  6  0 

8  9  4 

RTS 

2  5  9B 

8  9  5 

a 

25  9  B 

8  9  6 

a  a 

2  5  9B 

8  9  7 

23  9B 

8  9  8 

■  PROCRAM 

TITLE 

2  5  9  B 

8  9  9 

a 

2  5  9  B  8  D 

6  D 

8  D 

9  0  0 

MSC 

2  5  9  E  :  8  D 

8  D 

80 

9  0  1 

DFB 

CR . CR . CR 

2  3  A  1  20 

2  0 

2  0 

2  0 

902 

2  3 AA  5  0 

2  3  B  F  8  0 

5  2 

8  D 

4  F 

4  4 

9  0  3 

9  0  4 

ASC 

• PRODOS 

CR  ,  CR 

RAMDISK  DRIVER' 

23C 1  20 

2  0 

20 

2  0 

905 

2  3  C  E  46 

4  F 

5  2 

2  0 

9  0  6 

ASC 

'  FOR 

FLASHCARD • 

9  0  7 

DFB 

CR  .  CR 

2  0 

2  0 

2  0 

9  0  8 

23  EB  5  6 

43 

5  2 

5  3 

9  0  9 

ASC 

2  5  F  6  8  D 

8  D 

9  1  0 

DFB 

CR  ,  CR 

2  5  F  8  2  0 

20 

2  0 

2  0 

9  1  1 

260541 

4  C 

4  6 

32 

9  1  2 

ASC 

' ALFRED 

STEELE ' 

8  D 

9  1  3 

DFB 

CR  ,  CR 

2614  20 

20 

20 

2  0 

9  1  4 

ASC 

2  6  2  3  4  A 

3  5 

4  C 

5  9 

9  1  5 

ASC 

1  JULY 

1  98  5  ' 

8  D 

8  D 

8  D 

9  1  6 

DFB 

263000 

9  1  7 

DFB 

EOL 

2  6  3  1 

9  1  8 

a 

2  6  3  1 

9  1  9 

a 

2  6  3  1 

920 

a 

2631:20 

2649:00 

20 

20 

52 

92  1 

9  2  2 

VOLNAME 

ASC 

DFB 

EOL 

RAMDISK  VOLUME  NAME  /• 

2  6  4  A 

9  2  3 

26  4A  : 

924 

• 

Dr.  Dobb's  Journal,  December  1985 


96 

923 


Ram  Disk  Listing 


(Listing  continued,  text  begins  on  page  84) 


24  4A 

926 

•  CLEAR  DIRECTORY  FLAC 

2  6  4A 

927 

*  SET  FLAC  FOR  NO  CLEAR 

24  4A 

9  2  8 

• 

24  4A 

0  1 

929 

CLEARDIR  DB  tOl 

2  4  4  B 

9  30 

« 

2  4  4  B 

9  3  1 

26  4  B 

932 

* 

2  6  4  B 

933 

»  ML  I  WRITE  PARAMETER  LIST 

2  6  4  B 

9  3  4 

2  4  4  8 

03 

9  35 

ML  1 WR ITE  DB  t03 

2  6  4  C 

50 

9  34 

DB  ISO 

2  4  4D 

57 

2  6 

937 

DW  BUFFER 

2  4  4  F 

02 

00 

9  3  8 

WRITEBLOCK  DW  t0002 

26  5  1 

9  39 

• 

24  5  1 

9  4  0 

245  1 

94  l 

• 

2  6  5  1 

9  4  2 

«  ML  I  READ  PARAMETER  LIST 

2  4  5  1 

9  4  3 

26  5  1 

0  3 

9  4  4 

MLIREAD  DB  t03 

2  6  5  2 

5  0 

9  4  5 

DB  t  50 

2  6  5  3 

00 

20 

9  4  6 

READBUFF  DW  t2000 

2  6  5  5 

0  2 

00 

9  4  7 

READBLOCK  DW  t0002 

2  4  5  7 

9  4  8 

* 

26  5  7 

9  4  9 

2  6  5  7 

950 

ft 

2  6  5  7 

9  5  1 

*  ML  I  READ  BUFFER 

2  6  5  7 

952 

a 

26  5  7 

953 

«  THE  BYTES  FROM  BUFFER  TO 

24  5  7 

95  4 

•  MIDBUFF  IS  THE  RAMDISK  VOLUME 

24  5  7 

955 

*  DIRECTORY  HEADER  THESE  BYTES 

2  6  5  7 

956 

*  HAVE  TO  BE  WRITTEN  TO  THE 

95  7 

•  RAMDISK  AT  BLOCK  2  BEFORE  THE 

2  4  5  7 

958 

»  RAMDISK  CAN  BE  USED  BY  PRODOS 

26  5  7 

95  9 

* 

4  1  4  D 

00  00  00 
00  00  00 
0C  00  00 

00  00  00 
00  00  00 


•  WE  WILL  ALSO  USE  PARTS  OF 

•  THIS  VOLUME  DIRECTORY  HEADER 

•  AS  A  CHECK  TO  SEE  IF  THE 

•  RAMDISK  IS  ALREADY  FORMATTED 

■  I F  SO  THE  USER  CAN  REFORMAT 

•  THE  RAMDISK  OR  LEAVE  IT  ALONE 

BUFFER  DB  100, *00 

•  LAST  BLOCK 

DB  103,100 

■  STORACE  TYPE/NAME  LENCTH 

DB  I  F  3 

•  VOLUME  NAME 

VOLUME  ASC  'RAM' 

DB  t  00  ,  *  00  .  100  ,  • 00 

DB  100,  100,100,100 

DB  100 ,  »00  ,  »00.  tOO 

•  RESERVED 

DB  $00 ,  100  ,  100  .  tOO 

DB  tOO .  100 ,  tOO  ,  tOO 

•  CREATION  DATE/TIME 

DB  $00  ,  tOO  ,  100  ,  tOO 

•  V  ERS 1  ON /MIN  VERSION 

DB  tOO . tOO 

•  ACCESS  BYTE 

DB  t  43 

•  ENTERY  LENCTH 

DB  12  7 

•  ENTER  I E S  PER  BLOCK 

DB  tOD 

•  FILE  COUNT  ON  DISK 

DW  t 0000 

•  BLOCK  NUMBER  OF  BIT  MAP 

DW  t00  06 

•  TOTAL  NUMBER  BLOCKS  ON  DISK 

DW  1 02  00 


794  *  REST  OF  BUFF  MORE  THEN  NEEDED 
0200  997  MIDBUFF  DS  512 


2  3  4  B  A  BORTMSC  23F9 

2235  BITMAP  D2 

D 5  B  LOCK  4  D6 

2  2  3  E  BM  2  44 

2  I  1 8  C 1  2  1  1C 

2  2  0  1  C  B  1  2215 

2284  CD1  2295 

21ESCE  2250 

2  4  4A  CLEARD I R  2 1 E2 

2169  CONT  FDED 

2  2  D  9  DAI  2  2  E  9 

B  F  3 1  DEVCNT  BF32 

2544  D\S?VOL  2544 

2  5  8  B  D  V  3  2  1  8  F 

218EEX2  21 7A 

2544  CK1  FCS8 

2482  MIDBUFF  244B 

21B3MN1  2  5  9  B 

2  2  C  B  NODIR  21B4 

?  2  4  5  3  READBUFF  ’2156 

2  1  0  4  S  1  0  8 

2  1  C 1  SETDEV  2 1 D  7 

2 1 4  C  SSDIO  C0D0 

4  A  TEMP  »  00 

2  5  4  F  TITLE  ?  4  3 

BEOO  WARMDOS  240F 

2  6  4  F  WR ITEBLOCK  2  16  1 

2 3 AC  WT 1  23CB 

»«  SUCCESSFUL  ASSEMBLY  « 
••  ASSEMBLER  CREATED  ON  1 
••  TOTAL  LINES  ASSEMBLED 
••  FREE  SPACE  PACE  COUNT 


BE0 

BLOCK  1 
BLOCKS 
BUFFADR 
C  2 
C  B  2 
CD2 

CHECKDEV 
CLEAREND 
COUT 
DA  2 

DEVLST 

DV0 

ENDSSD 

EXIT 

HOME 

ML IWR ITE 
MSC 
R  1 

READBYTE 
SAVEBYTES 
S  ETV  ECTOR 
SSDLOW 
TEMP  Z  ERO 
UN  1 TNUMB 
WHEREMSC 
WR ITEBYTE 
WT2 

NO  ERRORS 
4  - JUN-83  09  : 
9  9  7 


BE  1 

B  LOCK  2 

B  LOCK 6 

BUFFERROR 

C  3 

C  B  3 

CD3 

CL  1 

COMMAND 

CROUT 

DEVABORT 

DIRBLOCKS 

D  V  1 

EOL 

CETBUFF 
KEY 
ML  I 

NOBUFF 

RAMBANK 

READ 

SD1 

SSDCODE ■ 
SSDSTART 
T I  1 

VOLNAME 
WHERETO 
WR ITED I R 
WT  3 


BELL 

BLOCKS 

BLOCKNUM 

BUFFER 

C  4 

CD0 

CD  4 

CLEARBUFF 

COMPUTE 

CR 

DEVADR5 1 
DIRFOUND 
D  V  2 
EX  1 

CETKEY 

MAIN 

ML  I  R  E AD 

NODEV 

READBLOCK 

RELOCATE 

SETBLOCK 

S SDH  I 

STROBE 

T I  2 

VOLUME 
WRITE 
WTO 
WT  4 


End  Listing 


Q  DA 


Dr.  Dobb’s  Journal,  December  1985 


The  programs  published  in  this 
month’s  column  are  available  for 
downloading  from  the  Laboratory 
Microsystems  RBBS  at  (213)  306- 
3530  (300  baud  or  1200  bps). 

Microsoft  Assembler 

I  never  cease  to  be  astonished  at  the 
flurry  of  letters  that  results  every 
time  we  publish  something  about  the 
Microsoft  Macro  Assembler. 

J.  C.  Hoisington  of  Morgan  Com¬ 
puting  (publisher  of  Trace86  and 
Professional  Basic)  writes:  “We 
switched  from  IBM  to  Microsoft  at 
Version  2.0.  That  means  we  didn’t 
get  SALUT  but  we  did  get  to  upgrade 
to  Version  3.0  early  this  year  for 
$75.00.  Version  3.0  finally  corrects 
many  of  the  errors  (and  ridiculous  re¬ 
strictions)  of  earlier  versions.  The 
most  useful  to  us  is  that  they  finally 
allow 

lods  byte  ptr  es:[si  +  0] 

“which  assembles  as  26AC.  I  still 
found  many  bugs  in  Version  2.0,  but 
3.0  seems  to  have  very  few  glitches.” 

David  L.  Rabbers,  of  Seattle, 
Washington,  writes:  “Regarding  the 
errors  [published  in  previous  col¬ 
umns]  two  of  them  are  yours,  not  the 
assembler’s.  Specifically,  any  con¬ 
stant  ending  in  b  or  B  is  always  treat¬ 
ed  as  binary,  and  any  constant  ending 
in  d  or  D  is  always  treated  as  decimal. 
This  is  true  regardless  of  the  current 
radix  setting.  So,  if  you  set  the  radix 
to  1 6,  you  must  see  to  it  that  hex  con¬ 
stants  ending  in  b,  B,  d,  or  D  are  fol¬ 
lowed  by  an  h  or  H.  A  little  inconve¬ 
nient,  but  how  else  could  you  specify 
binary  or  decimal  constants  in  the 
midst  of  radix  16? 

“One  feature  of  Microsoft  MASM 
Version  3.0  is  a  new  linker  that  sup¬ 
posedly  supports  simple  overlays. 


Unfortunately,  there  is  no  hint  that  I 
can  find  on  how  to  use  this  feature.  If 
you  know  how,  or  discover  how, 
please  publish  it.”  Good  point.  My 
manuals  don’t  breath  a  word  about 
the  construction  of  overlays.  Anyone 
out  there  have  some  inside  informa¬ 
tion  on  this  subject? 

Robert  A.  Blair,  of  Walnut,  Cali¬ 
fornia,  made  the  same  point  about  ra¬ 
dix  overrides  and  went  on  to  say: 
“This  assembler  should  be  used  in 
Computer  Science  courses  in  college 
as  an  example  of  the  wrong  way  to 
design  a  compiler;  it  is  the  worst  I 
have  ever  seen.  If  you  thought  that 
Version  1  had  a  zillion  bugs,  wait  un¬ 
til  you  use  Version  2  for  a  while. 

“I  have  to  define  my  data  before  I 
use  it  (I  thought  I  only  had  to  do  that 
in  Pascal)  or  explicitly  tell  the  com¬ 
piler  where  it  is  to  avoid  phase  errors. 
A  multi-pass  compiler  should  be  able 
to  figure  out  where  the  data  is  located 
without  my  having  to  tell  it.  The  ac¬ 
companying  assembly  listing  shows  a 
few  errors  that  I  know  about.  The 
80286  PUSH  immediate  instruction 
generates  the  wrong  length  for  some 
values.  It  will  generate  an  instruction 
that  requires  two  bytes  of  immediate 
data  and  only  inserts  one  byte,  or 
generates  an  instruction  that  requires 
one  byte  of  data  and  inserts  two 
bytes.  The  OFFSET  operator  causes 
link  errors  occasionally.  The  MASK 
used  with  HIGH  does  not  work  .  . . 
These  are  just  the  tip  of  the  iceberg.” 
Robert’s  contribution  accompanies 
the  column  as  the  listing  (page  106). 

Converting  to  DOS  3 

Dan  Daetwyler  is  a  regular  corre¬ 
spondent  to  Doctor  Dobb's  and  al¬ 
ways  has  something  interesting  to 
say.  This  month  he  shared  some  of  his 
experiences  with  the  latest  version  of 
DOS:  “When  DOS  2.0  introduced  the 


concept  of  a  file  handle,  I  looked  it 
over  fairly  carefully,  and  then  decid¬ 
ed  we  were  being  shown  ‘the  way  to 
go.’  I  went.  It  was  a  nice  concept,  and 
effectively  the  number  of  file  handles 
that  could  be  used  at  one  time  was 
unlimited.  They  made  it  so  you  could 
not  use  more  than  99  handles  at 
once — a  good,  typical  effort  by  pro¬ 
gramming  to  cope  with  the  concept  of 
infinity  on  a  finite  machine.  This  was 
all  well  and  good,  since  no  one  was 
going  to  have  more  than  99  files  go¬ 
ing  on  a  PC. 

“Then  along  came  the  new  Ver¬ 
sion  3.x  DOS.  Gee,  it  just  had  to  be 
better  (and  bigger),  and  all  that  good 
stuff.  I  read  the  new  features  section, 
and  there  were  a  couple  of  goodies.  I 
devoured  them  carefully,  and  put 
them  to  immediate  use.  I  bought  Ver¬ 
sion  3.0,  even  though  each  time  I 
swear  I’ll  wait  for  the  x.  1  release — I 
get  so  tired  of  fighting  MS  bugs.  Still, 
I  bought  it,  and  the  Technical  Refer¬ 
ence,  and  really  thought  I  read  the 
documentation  thoroughly.  Along 
came  Version  3.1,  for  which  I 
breathed  a  large  sigh  of  relief,  since  I 
could  now  stop  fighting  DOS  bugs 
and  worry  about  my  own.  And  then  I 
hit  the  thing  with  one  of  my  moder¬ 
ately  large  complex  systems.  And 
bounced  like  a  rubber  ball. 

“Buried  in  the  details  of  the  FILE 
statement  in  the  CONFIG  disserta¬ 
tion  is  a  neat  little  statement  that 
they’ve  arbitrarily  reduced  the  num¬ 
ber  of  active  handles  to  20!  The  con¬ 
straint  is  for  a  process,  which  is  inter¬ 
estingly  undefined,  but  you  can 
assume  it’s  a  DOS  job  or  task.  The 
horrible  part  is,  they  meant  it! 

“So  20  isn’t  too  bad  a  constraint. 
There  won’t  be  too  many  people  who 
want  to  exceed  that  limit.  Well,  on 
the  same  page  there’s  a  note  that  five 
of  the  handles  are  grabbed  by  DOS. 


Dr.  Dobb's  Journal,  December  1985 


102 

925 


That’s  very  true.  So  now  you’re  down 
to  1 5.  Still  a  reasonable  number?  Not 
in  my  estimation.  Let’s  follow 
through  on  a  reasonably  complex 
data  base  problem.  We’ve  got  sever¬ 
al — let’s  say  five — master  files,  each 
of  which  is  indexed.  The  three  prima¬ 
ry  files  have  double  indices,  while  the 
less  important  two  have  only  a  single 
index.  Five  plus  three  times  two  plus 
two  gives  us  thirteen.  Still  safe?  Not 
really.  As  you’re  aware,  the  only  way 
you  can  force  a  buffer  purge  in  DOS 
is  to  pseudo-close  a  file.  Doing  this 
with  a  duplicate  handle  permits  you 
to  avoid  the  killing  performance  of  a 
re-open.  Let’s  be  stingy.  We’ll  only 
use  one  handle  for  this  purpose. 
We’ve  still  got  one  left.  However,  I’ve 
yet  to  write  a  system  that  doesn’t 
have  at  least  three  and  sometimes 
half  a  dozen  supporting  files  for  ar¬ 
chives,  output,  etc.  The  net  result  is 
that  you  end  up  short  about  five  han¬ 
dles,  even  on  a  relatively  small 
system.” 

Dan’s  idea  of  a  “relatively  small 
system”  would  give  many  of  us  night¬ 
mares.  Note  how  Dan  offhandedly 
supplies  the  reason  for  the  existence 
of  that  mysterious  DOS  function  45H 
called  DUP,  whose  “explanation”  in 
the  DOS  Technical  Manual  is  “Pur¬ 
pose:  returns  a  new  file  handle  for  an 
open  file  that  refers  to  the  same  file  at 
the  same  position.”  I  had  always  been 
perplexed  by  this  function,  and  the 
Unix  manuals  weren’t  any  more  help¬ 
ful  than  the  DOS  manual.  By  the 
way,  the  5  handles  preassigned  by 
DOS  to  the  Standard  Devices  aren’t 
really  lost.  You  can  always  close 
them,  and  then  reuse  them  for  some 
other  file  or  device. 

Dan  writes  on:  “My  current  effort 
involves  a  minimum  of  33  handles, 
and  even  that  makes  me  uncomfort¬ 
able.  Goodbye  to  DOS  Version  3.x. 
Except  I  can’t!  I  can’t  really  lock  a 
customer  into  the  old  DOS.  Especial¬ 
ly  with  multi-tasking  ‘right  around 
the  corner.’ 

“If  you  think  this  tale  has  a  happy 
ending,  you’re  wrong.  Let  me  tell  you 
some  of  the  things  I’ve  tried.  You  can 
set  up  a  resident  task,  split  the  data 
base,  and  let  the  resident  task  own 
part  of  it.  The  catch  is  that  if  you  try 
to  communicate  with  the  resident 


task,  you’re  running  under  the  main 
task  control  structure,  and  DOS  will 
tell  you  that  the  access  isn’t  autho¬ 
rized.  I  haven’t  figured  out  how  DOS 
knows,  as  yet,  so  I  can’t  figure  out 
how  to  switch  task  structures.  The 
traditional  use  of  a  user  interrupt 
vector  doesn’t  work.  DOS  senses  this 
and  tells  you  again  that  the  access  is 
not  authorized.  Setting  the  file  up 
with  the  proper  attributes  for  a 
shared  resource,  and  loading 
SHARE,  doesn’t  help.  I  guess  I  could 
fight  through  a  disassembly  of  DOS 
and  find  out  what  they’re  doing,  but  I 
don’t  want  to  spend  the  rest  of  the 
summer  on  this. 

“I  started  to  drop  back  to  my  own 
FCBs,  and  drop  the  handle  concept. 
There’s  no  limit  to  the  number  of  files 
I  can  open,  then,  but  then  I’m  back  to 
the  problems  of  the  pre-handle  DOS, 
and  I  can’t  see  any  way  to  purge  the 
buffers.  I  can’t  really  see  exposing  my 
customer  to  loss  of  data  base  when 
the  power  drops  unexpectedly.  I  can’t 
actually  give  him  total  integrity,  but 
using  the  pseudo-close  purge  gets  the 
risk  factor  down  to  the  noise  level. 
I’ve  three  of  the  complex  database 
systems  out  now,  and  in  use  on  about 
fifty  machines.  We’re  semi-rural,  and 
the  power  fluctuation  during  thun¬ 
derstorms  has  to  be  seen  to  be  be¬ 
lieved,  yet  I’ve  never  (knock  wood) 
lost  a  database  from  that  source, 
since  I  started  using  the  pseudo-close. 

“So  right  now,  I’m  stuck.  I  can’t 
move  two  of  my  systems  to  DOS  3.x, 
and  the  new  one  isn’t  even  in  the  ball 
park  ...  So  this  tale  ends  with  ques¬ 
tions.  Do  you  know  anything  of  help 
in  this  area?” 

Actually,  even  the  DOS  2.1  manual 
(pages  4-10)  said  that  the  maximum 
number  of  handles  that  a  process 
could  have  concurrently  open  was  20, 
but  that  the  maximum  for  the  system 
was  99.  This  apparently  includes 
background  tasks  such  as  the  print 
spooler.  Perhaps  they  just  didn’t  en¬ 
force  the  process  maximum.  The 
manual  for  DOS  3.1  specifies  the 
maximum  for  the  system  can  be  in 
the  range  8-255,  but  again  the  maxi¬ 
mum  per  process  is  20.  For  those  of 
you  who  are  still  using  FCBs  for 
whatever  reason,  be  warned  that  DOS 
3.1  puts  some  new  and  fairly  strict 


restrictions  on  the  use  of  FCBs  when 
the  file  sharing  module  is  also  loaded. 
The  default  maximum  number  of 
FCBs  that  can  be  simultaneously 
open  is  4.  If  you  try  to  open  more 
than  your  maximum,  DOS  unilateral¬ 
ly  closes  the  least  recently  used  FCB. 
You  can  increase  the  maximum  al¬ 
lowed  number  of  simultaneously 
open  FCBs  by  a  command  FCBS  =  nn 
in  the  CONFIG.SYS  file,  just  as  FI¬ 
LES  =nn  works  for  handles.  This  is 
an  omen,  I  believe,  that  FCBs  are  as 
doomed  as  the  dinosaurs. 

Microsoft  and  IBM  have  issued 
particular  warnings  about  use  of 
FCBs  in  the  networking  environment. 
If  you  use  an  FCB  to  access  a  file  and 
fail  to  close  it  (a  very  common  thing 
to  do  when  you  are  using  the  file  for 
read  only),  the  system  can  bog  down 
due  to  excessive  connections  accumu¬ 
lating  across  the  network.  Also,  they 
remark  that  moving  FCBs  around  in 
memory  after  they  are  opened  can 
cause  problems. 

Dan  proceeds:  “Tale  two  is  slightly 
more  amusing.  When  I  got  my  AT, 
and  then  started  hearing  all  the  terror 
stories  about  the  hard  disk,  I  consid¬ 
ered  myself  very  fortunate  when  I 
didn’t  encounter  any  ...  for  a  while. 
Then  I  had  to  load  a  5  Mbyte  data¬ 
base,  and  my  trouble  started.  After 
several  days  of  frustration,  I  nailed 
my  friendly  dealer  to  the  wall,  and 
IBM  emergency  shipped  a  replace¬ 
ment  hard  disk,  which  made  life  a  lot 
better.  In  the  interim,  I  did  several 
interesting  things.  I  put  a  trap  in  the 
program  so  that  any  time  the  I/O 
failure  occurred,  I’d  get  control.  I 
was  running  under  DOS  3.0,  of 
course.  The  first  thing  I  discovered 
was  that  when  the  disk  glitched,  the 
error  code  that  DOS  gave  me  back 
had  no  relation  to  disk  I/O  at  all.  In 
fact,  DOS  kept  insisting  that  I  was 
‘out  of  memory.’  On  a  half  megabyte 
machine  with  one  small  applica¬ 
tion? — not  bloody  likely. 

“The  next  thing  I  discovered  was 
that  if  I  simply  tried  the  operation 
again,  by  looping  back  to  the  inter¬ 
rupt,  99%  of  the  time  the  thing 
worked  happily  on  the  second  try. 
Very  interesting!  Then  I  discovered 
by  accident  that  I  was  running  with 
VERIFY  =  OFF.  So  I  changed  to 


104 

926 


Dr.  Dobb's  Journal,  December  1 985 


VERIFY  =  ON,  and  got  a  whole  new 
set  of  behavior  patterns.  Different  er¬ 
ror  codes,  and  the  fail  ratio  went  way 
down.  Running  in  this  mode,  the 
problem  seemed  to  be  occurring 
when  DOS  encountered  something  it 
thought  was  a  bad  track,  so  I  off¬ 
loaded  my  20  Mbyte  disk,  and  refor¬ 
matted.  Oh  boy.  The  plot  thickens 
like  glue.  I  could  run  FORMAT  repet¬ 
itively,  and  every  time  I  ran  it  I  would 
find  a  different  set  of  ‘bad  tracks.’ 
There  didn’t  even  seem  to  be  a  consis¬ 
tent  pattern.  1  believe  it  did  have  a 
relationship  to  something,  but  as  near 
as  I  could  detect,  it  was  the  phases  of 
the  moon,  or  the  position  of  Jupiter. 

“1  won’t  bore  you  with  more  of  the 
experiments  that  cost  me  about  ten 


days  of  time,  but  I  will  tell  you  the 
punch  line.  We  all  heard  about  the 
hard  disk  problems  in  all  of  the  trade 
journals.  IBM  replaced  hard  disks  left 
and  right,  and  the  disk  vendor  was 
the  bad  guy.  Very  interesting,  since, 
when  I  got  Version  3.1,  all  the  prob¬ 
lems  disappeared.  Now  DOS  gives 
back  the  correct  error  codes.  Now 
FORMAT  is  at  least  consistent.” 

Dan’s  second  tale  is  the  latest  of  a 
raft  of  mutually  exclusive  explana¬ 
tions  of  the  mysterious  AT  hard  disk 
problems.  We  have  heard  that  the 
original  hard  disk  was  no  good  be¬ 
cause  the  vendor  tried  to  ramp  up 
production  too  fast  (and  a  recent  re¬ 
view  of  the  CM  I  hard  disk  showed  it 
to  be  much  more  shock-sensitive  and 


error-prone  than  its  competitors); 
that  the  FORMAT  program  had  a 
bug  that  prevented  it  from  actually 
checking  the  second  half  of  the  20 
Mbyte  hard  disk;  that  the  disk  con¬ 
troller  had  design  or  component 
problems;  and  so  on.  Throughout 
most  of  this  time,  the  official  IBM 
press  flacks  insisted  that  everything 
was  wonderful  and  there  was  no  AT 
disk  problem  at  all.  It  certainly 
makes  one  apprehensive  about  the 
position  we’ll  all  be  in  if  IBM  man¬ 
ages  to  knock  off  all  its  competitors  in 
the  PC  market. 

DD| 


16-Bit  Listing  (Text  begins  on  page  102) 


1 

2 

3 

4 

5 

6 

7 

8 

9 

10 
11 
12 

13 

14 

15 

16 

17 

18 

19 

20 
21 
22 

23 

24 

25 

26 

27 

28 

29 

30 

31 

32 

33 

34 

35 

36 

37 

38 


page 

title 

.286C 


55,132 

Microsoft  Assembler  Bugs 


Some  IBM  PC  Macro  Assembler  2.0  "features" 
contributed  by  Robert  Blair 


0000 

0000 

0000  E9  010B  R 


foo  segment  para  public 
assume  cs:foo 
foos  proc  far 

jmp  foobar 


'CODE1 


0003  02  [ 


0007  02  t 


000B  0100  [ 


01 0B  CB 


01 0B  R 


010B  R 


00 


dw 


dw 


db 


foobar:  ret 


this  bug  causes  link  errors  if 
the  offset  is  larger  than  256. 
the  error  is 

'fixup  offset  exceeds  field  width' 
2  dup  (offset  foobar) 


2  dup  (foobar) 


256  dup  (0) 


this  will  work  ok 


it  appears  that  if  any  calculations  are  required,  the  compiler 
truncates  after  the  first  two  bytes.  The  assembler  reference 
manual  p.  2-15  states  this  is  done  for  the  small  assembler  but  it 
does  it  for  both  assemblers. 


39 

010C  80  51  00  00 

dd 

60*60*24  ;  incorrect 

40 

0110  80  51  01  00 

dd 

86400  ;  correct 

41 

42 

43 

;  Now 

try  HIGH 

and  MASK  operators  together 

44 

45 

fooi 

record 

f 1 : 1 , f2: 1 , f3:3, f4: 1 , f5: 1 ,f6: 1 , f7:8 

46 

=  0200 

fcl 

equ 

0200h 

47 

=  4000 

fc2 

equ 

4000h 

48 


Dr.  Dobb’s  Journal,  December  1985 


105 

Q?7 


49 

;  immediate  data 

should  be 

50 

0114 

25 

0200 

and 

ax, mask  f5 

0200h 

51 

0117 

25 

4000 

and 

ax, mask  f2 

4000h 

52 

01 1 A 

25 

0040 

and 

ax, high  (mask  f2 

) 

0040h 

53 

01  ID 

25 

0042 

and 

ax, mask  f5+high(mask  f2) 

0240h 

54 

0120 

25  4200 

and 

ax,(high(mask  f2))+(mask  f5) 

0240h 

55 

0123 

25 

0200 

and 

ax.fcl 

0200h 

56 

0126 

25 

4000 

and 

ax,fc2 

4000h 

57 

0129 

25 

0040 

and 

ax, high  fc2 

0040h 

58 

012C 

25 

0042 

and 

ax, fcl+high  fc2 

0240h 

59 

012F 

25 

4200 

and 

ax, (high(fc2))+(fc1 ) 

0240h 

60 

0132 

25 

4200 

and 

ax.fcl  or  high  fc2 

0240h 

O  1 

62 

63 

;  80286  PUSH 

IMMEDIATE  instruction  bugs 

64 

;  an  op  code 

of  6A  requires  one  byte  of  immediate 

data 

65 

;  an  op  code 

of  68  requires  two  bytes  of  immediate  data 

66 

67 

0135 

6A 

00 

push 

0 

6a00  correct 

68 

0137 

6A 

7F 

push 

127 

6a7f  correct 

69 

0139 

6A 

FF 

push 

-1 

6aff  correct 

70 

013B 

68 

80 

push 

128 

should  be  680080 

71 

013D 

68 

0080 

push 

word  ptr  128 

generates  correct  code 

72 

0140 

68 

FF 

push 

255 

should  be  6800 FF 

73 

0142 

68 

00FF 

push 

word  ptr  255 

generates  correct  code 

74 

0145 

6A 

FFFF 

push 

Offffh 

should  be  6aFF 

or  68FFFF 

75 

0148 

6A 

34 

push 

byte  ptr  1234h 

lost  data  with 

no  error 

76 

77 

78 

;  I  have  to  define  my  data  befor 

»  use  or  tel  l  the 

assembler 

79 

;  where  it  is 

.  Multi-pass  compilers  should  be  smarter 

80 

;  than  this 

81 

82 

014A 

2E 

D7 

xlat 

fred 

causes  (spurious)  phase  error 

83 

message  but  generates  right  code 

84 

85 

014C 

2E 

D7 

xlat 

cs:f red 

works  ok 

86 

87 

FF  C 

fred  db 

255  dup  (0) 

E  r 

r  o  r 

-- 

6:Phase  error  between  passes 

;  This  bug  is  not  very  serious  because  the  only  thing  wrong  is 
;  the  assembly  tisting.  The  generated  code  is  correct  but  the 
;  listing  of  the  generated  code  is  not  very  neat. 


OD  3D  [D  OA 
2D 


13,10,13,10,61  dup  13, 10 


foos  endp 

foo  ends 

end 


End  Listing 


OOH 


Dr.  Dobb's  Journal.  December  1985 


MAC  TOOLBOX 


Introduction  to  Using  Your  MacSCSI  Host  Adapter 


by  John  Bass 


The  MacSCSI  host  adapter  was  de¬ 
signed  to  work  with  most  SASI-  and 
SCSI-compatible  controller  cards.  It 
is  also  a  reasonable  starting  point  for 
other  custom  Mac  interfaces.  Source 
software  written  in  Aztec  C  is  provid¬ 
ed  for  custom  configuring  your  sys¬ 
tem.  If  your  controller  is  Xebec 
SI 410  compatible  you  should  be  up 
and  running  with  little  trouble. 

Changing  the  Parameters  for  the 
Drive  and  Controller 

For  controllers  other  than  the  Xebec 
S1410,  you  may  need  to  modify  the 
last  byte  of  the  format,  read,  and  write 
commands  to  reflect  the  vendor-spe¬ 
cific  requirements.  The  Xebec  S1410 
uses  this  byte  to  encode  the  step  rate 
for  the  drive  as  shipped,  the  code  is 
set  up  to  use  a  Seagate  ST506  drive 
with  the  half-step  strap  enabled.  If 
you  are  using  a  Xebec  S1410  with  an¬ 
other  drive,  consult  your  controller 
manual  for  the  correct  drive  code  to 
use.  Then  change  the  cmd.sc_vendor 
assignment  from  1  to  the  correct 
drive-type  code. 

Because  the  Xebec  defaults  to  an 
ST506-drive  configuration  we  didn’t 
need  to  issue  any  additional  com¬ 
mands  to  define  the  drive  configura¬ 
tion  to  the  controller.  This  is  an  area 
that  is  very  vendor  specific — read 
your  controller  documentation  very 
carefully.  You  will  want  to  read  the 
descriptions  for  the  format,  read,  and 
write  commands  closely.  Also  check 
the  error-code  definitions  carefully 
because  they  vary  greatly  between 
some  controllers. 

You  may  need  to  issue  a  Set  Media 
or  Set  Mode  command  that  defines 
the  drive  parameters  for  number  of 
heads,  cylinders,  write  precomp,  write 
reduce,  and  so  on.  Some  drives,  such 
as  the  Xebec  Owl,  have  an  embedded 
controller  and  don’t  need  such  a  setup 


parameter.  Some  controllers  encode 
the  parameters  at  format  time  and 
write  them  on  the  disk  for  future  use. 
Other  controllers  require  a  Mode  Se¬ 
lect  or  other  command  to  set  the  drive 
parameters  prior  to  the  first  format, 
read,  or  write  command. 

The  long  evolution  from  early 
SASI  controllers  to  the  current  pro¬ 
posed  SCSI  specification  has  left  a  lot 
of  nonconforming  controllers  around 
the  marketplace,  and  many  more 
nonconforming  controllers  are  likely 
to  be  produced  before  the  standard  is 
finished  and  adopted.  Until  then,  the 
software  drivers  for  each  host  adapt¬ 
er  will  need  to  be  customized  per  con¬ 
troller. 

A  copy  of  the  current  SCSI  (ANSC 
X3T9.2)  specification  is  available 
from  the  American  National  Stan¬ 
dard  publications  office.  For  NCR- 
5380  information  contact  your  local 
NCR  Microelectronics  representative 
or  write  to  the  NCR  Microelectronics 
Division,  1635  Aeroplaza  Dr.,  Colo¬ 
rado  Springs,  CO  80916;  (800)  525- 
2252. 

Mounting  Drives  Inside  or 
Outside  the  Mac 

Several  5-,  10-,  and  20-megabyte  3V4- 
inch  drives  are  now  on  the  market, 
and  controllers  in  the  same  form  fac¬ 
tor  are  available  as  well.  The  easiest 
way  to  mount  a  3 '/2-inch  drive,  con¬ 
troller,  and  power  supply  internally  is 
to  fabricate  a  sheet  metal  and/or 
Plexiglass  mount  and  screw  it  inside 
the  plastic  back  of  the  Mac.  To  get 
AC  for  your  power  supply,  install  a 
power  cable  to  the  video  board 
after  the  switch  and  AC  filters.  Be 
sure  to  leave  a  long  enough  service 
loop  on  both  the  power  and  SCSI 
cables  to  make  getting  the  back  off 
easy. 

Other  internal  mounting  configu¬ 


rations  require  more  attention  to 
sheet  metal  design  and  choice  of  con¬ 
troller,  drive,  and  power  supply.  Con¬ 
vection  cooling  can  be  enhanced  by 
mounting  everything  on  the  right  side 
and  using  the  sheet  metal  and  Plexi¬ 
glass  to  form  a  chimney  extending 
from  the  bottom  to  the  vents  at  the 
top  of  the  case.  EMI  fields  from  the 
power  supplies,  flyback,  and  yoke 
coils  present  some  problems  in  get¬ 
ting  the  disk  to  operate  without  er¬ 
rors — proper  selection  of  shielding 
material  and  position  is  critical. 

The  easiest  way  to  interface  drives 
to  the  MacSCSI  is  to  mount  them  ex¬ 
ternally  in  a  separate  box,  so  that 
physical  mounting,  heat,  and  EMI 
problems  are  less  severe.  Several  man¬ 
ufacturers  have  standard  line  enclo¬ 
sures.  Some  of  these  enclosures  are 
complete  with  power  supply,  cables, 
fans,  and  maybe  a  drive  of  your 
choice. 

External  drives  require  an  external 
cable.  The  SCSI  standard  specifies  an 
EMI  shielded  cable,  plug,  and  header 
assembly.  We  use  AMP  part  number 
1-499977-0  for  the  panel-mounted 
connector  and  AMP  part  number 
102793-4  for  the  ground  plane 
(shield  assembly)  that  mates  with  the 
connector.  Use  the  ground  plane  only 
if  you  plan  to  use  a  SCSI  standard 
shielded  cable — it  will  interfere  with 
a  standard  nonshielded  IDC  ribbon- 
cable  connector. 

Installing  the  MacSCSI  Board 

Getting  the  back  off  a  Mac  requires  a 
Torx  5  screwdriver  with  a  long  shaft. 
There  are  five  screws:  two  are  located 
at  the  top  in  holes  under  the  handle, 
two  are  on  the  back  toward  the  bot¬ 
tom  above  the  connectors,  and  one  is 
located  behind  the  battery  cover. 
Carefully  use  a  wooden  ruler  or  the 
battery  cover  to  ease  the  crack  along 


Dr.  Dobb’s  Journal,  December  1985 


110 

929 


the  front  seam  apart — do  not  use  a 
screwdriver  as  a  pry  because  the  case 
plastic  is  very  soft  and  will  show  pry 
marks. 

Remove  the  power  and  floppy  ca¬ 
bles  from  the  digital  board,  then 
gently  slide  it  out  of  the  sheet  metal 
frame.  Place  the  board  on  a  table 
with  the  keyboard  connector  toward 
the  back  and  the  other  connector 
near  you.  Lay  the  MacSCSl  board 
near  the  ROMs  with  the  SCSI  header 
near  you.  Remove  and  transfer  both 
of  the  Mac  ROMs  onto  the  MacSCSl 
board,  then  plug  the  MacSCSl  board 
back  into  the  ROM  sockets. 

Now  clip  the  miniplug  onto  pin  8  of 
the  TSM  PAL  located  at  Dl  on  the 
right  edge  of  the  board.  Lay  the  clip 
flat  against  the  board  alongside  the 
15.667200  crystal  and  secure  it  with 
a  piece  of  masking  tape  across  the 
crystal  and  over  the  edge  of  the 
board.  Route  the  wire  between  the 
switches  and  toroid  coil  and  continue 
alongside  the  metal  shield  to  the 
MacSCSl  board.  Replace  the  digital 
board  in  the  frame,  flexing  slightly  to 
pass  the  MacSCSl  header  past  the 
edges  of  the  frame.  Be  careful  not  to 
pull  the  board  out  of  the  ROM  sock¬ 
ets.  Cable  the  SCSI  port  as  required, 
reconnect  the  power  and  floppy  ca¬ 
bles,  and  reassemble  the  case. 

Bringing  Up  the  System  for  the 
First  Time 

Once  you  have  the  system  cabled  up 
and  have  made  any  required  software 
changes,  try  formatting  the  drive. 
Watch  for  the  drive  to  be  selected. 
On  some  drives  you  can  see  the  posi¬ 


tioner  step  from  track  to  track,  and 
on  others  you  may  be  able  to  feel  the 
drive  stepping.  Formatting  takes  a 
while — for  5-  and  10-megabyte 
drives  it  may  take  several  minutes, 
and  100-megabyte  drives  may  need  a 
half  hour.  The  format  routine  initial¬ 
izes  the  Mac  file  system  by  writing  a 
volume  header  and  clearing  all  other 
blocks  and  then  rereads  all  blocks  to 
check  for  errors. 

If  you  have  problems  with  Aztec  C 
you  can  add  printfs  to  the  C  I/O  rou¬ 
tines  and  follow  the  progress.  Once 
you  have  the  format  routine  and  I /O 
routines  running,  compile  and  link  the 
driver  with  the  I/O  routines. 

If  you  are  connecting  to  something 
other  than  a  disk  you  can  simply  in¬ 
clude  the  I/O  routines  in  your  appli¬ 
cation  and  drive  the  SCSI  bus  directly. 

Miscellaneous  Comments 

We  have  used  MacSCSl  under  Finder 
4.1  and  Aztec  C  Shell  I.06D  and  F 
with  a  nonpartitioned  5-megabyte 
drive.  Aztec  C  Shell  and  environment 
seem  to  run  well  even  with  a  very 
large  number  of  files.  Finder  has 
some  limits  you  should  keep  in  mind. 
On  large  volumes  it  gets  pretty  slow 
after  150  files.  Finder  also  crashes 
sometimes  when  too  many  files  get 
created.  Deleting  some  files  or  the 
desktop  under  Aztec  C  sometimes 
makes  it  happy;  at  other  times  it 
seems  that  you  need  to  back  up  the 
disk  under  Aztec  C  Shell  and  refor¬ 
mat  the  volume.  We  are  working  with 
Apple  to  find  the  solutions  to  these 
problems. 

We  have  used  the  driver  with  and 


without  a  cache  with  some  interesting 
results.  For  small  cache  sizes  the  over¬ 
head  of  maintaining  the  cache  slows 
the  overall  operation  down.  For  the 
Aztec  C  environment  the  minimum 
size  is  roughly  the  sum  of  all  the  pro¬ 
grams,  libraries,  and  include  files  in 
use — it  is  easier  to  start  up  the  RAM 
disk  and  copy  everything  to  it.  For 
large  cache  sizes  it  is  a  pain  to  run 
large  programs  or  the  switcher  be¬ 
cause  you  need  to  restart  the  driver 
with  a  smaller  cache  size.  The  overall 
conclusion  for  our  working  environ¬ 
ment  is  that,  although  the  cache  can 
create  RAM-disk  speeds,  it  sometimes 
creates  floppy  speeds.  The  MacSCSl 
hard  disk  is  nearly  as  fast  as  the  RAM 
disk  anyway,  and  maintaining  the 
cache  just  becomes  a  headache  when 
switching  environments. 

Another  minor  annoyance  is  that 
you  can’t  remount  the  MacSCSl  vol¬ 
ume  if  you  eject  it  from  an  open 
box — never  do  so.  We  are  working  on 
adding  the  code  to  trap  the  ejects  and 
ignore  them.  The  eject  solution,  mul¬ 
tiple  volumes  via  partitions,  and 
other  improvements  will  be  available 
in  our  2.0  software  release  within  a 
few  weeks. 

We  are  collecting  changes  for 
other  controller-and-drive  combina¬ 
tions  to  be  available  on  our  update 
disk.  If  you  would  like  to  share  your 
changes,  send  us  a  disk  with  your 
modifications  and  we  will  return  it 
with  the  current  updates.  Mail  your 
changes  to  Fastime,  P.O.  Box  12508, 
San  Luis  Obispo,  CA  93406. 

DD| 


112 

930 


Dr.  Dobb  s  Journal,  December  1985 


Software  running  on  a  Z80-based  sys¬ 
tem  under  CP/M-80  will  frequently 
perform  better  than  comparable  soft¬ 
ware  running  on  an  IBM  PC  under  MS 
DOS.  For  example,  Microsoft  BASIC- 
86  is  actually  slower  on  the  IBM  PC 
than  BASIC-80  on  a  Z80-based  sys¬ 
tem.  Programs  running  under  CP/M- 
80  also  are  frequently  smaller  than 
their  counterparts  on  MS  DOS.  Trans¬ 
late  a  Z80  assembly-language  pro¬ 
gram  into  8086  assembly  language 
and  watch  the  code  expand. 

Sixteen-bit  systems  do  have  a  num¬ 
ber  of  advantages  over  their  8-bit  pre¬ 
decessors.  You  can  address  more  mem¬ 
ory  and  can  have  better,  even  color, 
graphics.  The  PC  can  communicate 
easily  with  mainframe  systems  and  will 
probably  be  more  compatible  with  fu¬ 
ture  developments  than  the  older  8-bit 
systems.  On  the  other  hand,  there  are 
still  many  more  software  packages 
available  for  CP/M  than  there  are  for 
MS  DOS,  some  quite  good  and  very 
reasonably  priced. 

When  comparing  Z80-based  sys¬ 
tems  to  the  IBM  PC,  there  are  some 
basic  facts  that  you  should  keep  in 
mind.  First  of  all,  the  limited  ad¬ 
dressable  memory  space  of  8-bit  sys¬ 
tems  used  to  force  programmers  to 
write  tight,  rapidly  executing  code. 
With  the  advent  of  16-bit  systems 
and  more  addressable  memory  space, 
software  developers  were  no  longer 
compelled  to  keep  code  size  small, 
and  they  shifted  to  less  efficient  pro¬ 
gramming  techniques,  including 
writing  in  system-independent,  high- 
level  languages.  Second,  most  popu¬ 
lar  16-bit  programs  were  originally 
written  for  8-bit  systems.  Developers 


W.  E.  Wilson,  Washington  State 
University,  Nuclear  Radiation  Cen¬ 
ter,  Pullman,  WA  99164 


have  often  moved  these  programs  to 
the  PC  by  translating  the  8-bit  code 
line  by  line  into  the  equivalent  16-bit 
code.  The  result  is  that  much  of  the 
potential  efficiency  of  the  16-bit  sys¬ 
tems  has  been  lost.  For  example, 
many  of  the  spreadsheets  that  run 
without  any  problems  on  a  64K  8-bit 
system  require  more  than  128K  on  a 
16-bit  system.  Third,  the  memory 
overhead  for  the  MS  DOS  operating 
system  is  so  much  greater  than  the 
overhead  for  CP/M-80  that  a  128K 
PC  is  approximately  equivalent  to  a 
64K  CP/M-80  system.  Finally,  the 
5'/4-inch  floppy  disk  drives  on  a  PC 
are  as  slow  as  molasses  in  January 
when  compared  to  the  typical  8-inch 
drives  on  the  older  CP/M-80  systems. 

Mini  System  Software  for  a 
Micro 

Back  in  the  days  before  the  invention 
of  the  processor  on  a  chip  and  the  de¬ 
velopment  of  modern  micro  systems, 
memory  was  very  expensive.  Mini¬ 
computer  companies  such  as  Digital 
Equipment  Company  provided  over¬ 
laying  linkers  for  use  with  their  com¬ 
puters.  In  the  early  ’70’s  the  institu¬ 
tion  where  I  worked  had  a  DEC 
PDP-15  with  64K  of  memory.  DEC 
provided  a  software  package  called 
Chain  and  Execute  that  allowed  you 
to  run  a  very  large  program  on  the 
PDP-15  by  segmenting  it  into  a  series 
of  overlays.  I  used  Chain  and  Execute 
to  run  some  huge  programs  written  in 
FORTRAN  for  the  IBM  360  on  the 
little  DEC  system.  Not  very  much  is 
written  on  the  subject  of  overlays', 
but  this  technique  is  used  extensively 
in  mainframe  systems  and  in  mul¬ 
tiuser  operating  systems. 

The  Linking  Process 

The  creation  of  an  executable  pro¬ 
gram  from  a  number  of  source  files 


involves  several  steps.  First,  the 
source  files  are  individually  read  by 
the  compiler  and  converted  into  one 
or  more  object  modules.  Some  com¬ 
pilers  produce  relocatable  object 
code  directly.  Small-C  and  its  deriva¬ 
tives  generate  assembly-language 
output  that  must  be  assembled.  The 
following  discussion  applies  to  com¬ 
pilers  of  the  latter  type  only. 

It  is  not  practical  to  have  the  com¬ 
piler  generate  all  the  code  needed  for 
each  module.  Instead,  code  is  gener¬ 
ated  only  for  operations  specific  to 
the  particular  module.  General-pur¬ 
pose  functions  and  subroutines  from 
the  run-time  library  are  merely  refer¬ 
enced  by  the  compiler  and  only  later 
linked  into  the  program.  These  exter¬ 
nal  references  are  “place  holders” 
into  which  absolute  addresses  are 
eventually  placed.  For  functions  and 
variables  defined  within  the  module, 
the  compiler  generates  references  rel¬ 
ative  to  the  beginning  of  the  module. 
This  means  that  if  the  module  is 
eventually  loaded  at  address  L,  then 
all  references  will  be  correct  if  L  is 
added  to  them. 

An  assembler  then  reads  each  indi¬ 
vidual  module  created  by  the  compil¬ 
er  and  generates  for  each  one  a  relo¬ 
catable  object  module.  This  module 
is  called  relocatable  because  it  can  be 
relocated  anywhere  in  memory. 

The  final  stage  is  linking.  The  link¬ 
er  searches  the  run-time  library  for 
any  referenced  subroutines  and  func¬ 
tions.  It  loads  these  subroutines  and 
functions  along  with  the  relocatable 
object  modules  into  their  final  posi¬ 
tions  in  contiguous  blocks  of  memory. 
Finally,  it  fills  in  the  absolute  ad¬ 
dresses  for  externals  and  adjusts  the 
relative  addresses  for  internals. 

A  standard  linker  builds  programs 
in  memory.  Thus,  the  maximum  pro¬ 
gram  size  is  equal  to  the  total  memo- 


Dr.  Dobb’s  Journal,  December  1985 


114 

931 


0100-0102  JP  ENTRY 
0103-0107  Identification  Code 

0108-0109  End  +  1  of  Last  Overlay  or  first  free  memory  location  above  overlay 

system 

010A-010B  End  +  1  of  Root  Segment  or  the  start  of  first  overlay 
010C-0114  Eight  Character  Root  File  Name 


Byte  0-1  Overlay  Transfer  Address  (Load  Address) 

Byte  2-3  End  +  1  of  Segment 

Byte  4-11  Eight  Character  Segment  File  Name 


Figure  1 

Root  and  Overlay  Segment  Headers 


Root  (LO) 

/  I  \ 

LI  A  LIB  L1C 


L2A 

Figure  2 

Structure  of  the  test  overlay  system 


ROOT(M  AIN);OVINIT,  OVRLAY,  LO; 

END; 

END, 

OVL(1,L1C); 

SYMFIL(CTEST); 

INCL(LIC); 

0VL(1,L1A); 

END; 

INCL(LIA); 

OVL(2,L2A); 

END; 

INCL(L2A); 

OVL(1,L1B); 

END; 

INCL(LIB); 

DONE; 

Figure  3 

ZLKO  Command  File  Used  to  Create  Overlay  System 

ZLKO  Command  Summary 

CMND 

=  Take  Command  Input  From  Specified  File 

DCOM 

=  Display  Common  Block  Information 

DMAP 

=  Display  Global  and  External  Symbols 

DONE 

=  Complete  Current  Segment  and  Quit 

DOVL 

=  Display  Overlay  Structure 

DSEG 

=  Display  Information  On  Current  Overlay  Segment 

DUND 

=  Display  Undefined  Symbols 

END 

=  Complete  Current  Segment,  forces  a  SRCH 

HELP 

=  Display  Command  List 

INCL 

=  Include  The  Listed  REL  Files  in  The  Current  Segment 

OVL 

=  Build  Overlay  (  OVL)  File 

PBSE 

=  Set  Program  Code  Base  Location 

QUIT 

=  Quit  and  Return  to  CP/M 

RELDMP 

=  Dump  Relocatable  (  REL)  File 

ROOT 

=  Build  ROOT  (  COM)  File 

SRCH 

=  Search  Specified  (  REL)  Library 

STOP 

=  Quit  Building  Current  Segment  and  Save  The  Link  Table 

SYMFIL 

=  Generate  Symbol  File  For  Symbolic  Debugger 

?SYM 

=  Suppress  Symbols  Starting  with  ? 

TCSW 

=  Turn  Off  Module  Counting 

Figure  4 

ZLKO  Help  Command  Output 

ry  space  minus  the  space  used  by  the 
operating  system  and  the  linker.  An 
overlaying  linking  loader  links  the 
program  on  disk  rather  than  in  mem¬ 
ory  and  thus  can  link  a  program  that 
will  fill  the  entire  available  memory 
space  or  more.  There  is,  however  a 
penalty  paid  in  speed.  An  overlaying 
linking  loader  must  read  every  file 
twice — once  to  locate  the  globals  and 
determine  the  size  of  the  module  and 
once  to  do  the  actual  linking. 

ZLKO 

ZLKO  from  ZEE  MICROWARE  is  an 
overlaying  linking  loader  for  Z80- 
based  systems.  The  most  important 
feature  of  ZLKO  comes  into  play 
when  a  program  is  too  big  to  fit  into 
available  memory  space.  The  stan¬ 
dard  solution  to  this  problem  is  to  use 
the  call-next-program  technique.  This 
technique  involves  dividing  the  pro¬ 
gram  into  a  selected  number  of  seg¬ 
ments,  each  small  enough  to  fit  indi¬ 
vidually  into  memory.  Each 
sequential  segment  except  the  last 
must  load  and  execute  the  next  seg¬ 
ment.  This  method  is  only  supported 
by  a  few  compilers  and  has  a  number 
of  disadvantages: 

•  It  is  not  possible  to  return  to  a  call¬ 
ing  segment 

•  All  segment  intercommunication 
must  be  via  disk  files 

•  Selective  segment  execution  is  not 
possible 

All  of  these  weaknesses  can  be  over¬ 
come  by  using  the  overlaying  capabil¬ 
ities  of  ZLKO. 

The  user  segments  the  program 
into  a  small  root  (main)  module  and 
one  or  more  overlays,  each  of  which  is 
composed  of  a  number  of  subroutines 
and  functions.  The  subroutines  are 
grouped  into  overlays  according  to 
criteria  selected  by  the  user.  ZLKO 
then  constructs  a  multilevel  “tree”  of 
overlays  in  which  the  overlays  at  any 
given  level  can  call  overlays  at  the 
next  lower  level.  The  execution  of  the 
segmented  program  begins  when  the 
root  module  is  executed.  The  root  will 
then  activate  other  overlays  by  call¬ 
ing  the  subroutine  “ovrlay”  that  has 
been  linked  with  the  root  segment. 
The  root  segment  will  stay  in  memory 


116 


Dr.  Dubb's  Journal.  December  1985 


the  entire  time.  The  overlays  will  be 
loaded  into  memory  and  control 
passed  to  each  as  program  execution 
proceeds.  When  an  overlay  returns 
control  to  the  higher  level  that  acti¬ 
vated  it,  the  higher  level  may  then  ac¬ 
tivate  another  overlay  beneath  itself 
or  pass  control  down  one  level.  Each 
overlay  on  the  same  level  in  the  tree  is 
loaded  into  memory  starting  at  the 
same  location. 

Routines  in  an  overlay  can  refer¬ 
ence  any  symbol  defined  within  that 
overlay  and  all  global  symbols  locat¬ 
ed  in  any  active  higher-level  overlay 
up  to  and  including  the  root  segment. 
Global  symbols  and  routines  in  the 
root  segment  may  be  referenced  from 
any  segment.  Frequently  needed  rou¬ 
tines  and  functions  should  be  includ¬ 
ed  in  the  root  segment.  Routines  in  an 
overlay  cannot,  however,  reference 
symbols  or  routines  defined  in  an 
overlay  on  the  same  level  or  in  a  seg¬ 
ment  lower  down  the  tree. 

ZLKO  Usage 

ZLKO  places  a  2 1  -byte  header  on  the 
root-segment  file  and  a  12-byte  head¬ 
er  on  each  overlay-segment  file  (see 
Figure  1,  page  1 16).  The  headers  in¬ 
clude  the  primary  CP/M  file  name  of 
the  segment  as  well  as  address  infor¬ 
mation.  In  the  case  of  an  overlay  seg¬ 
ment,  the  overlay  name  and  transfer 
address  are  used  by  the  ovrlay  sub¬ 
routine  to  load  the  overlay.  The  exe¬ 
cution  process  starts  at  the  root  seg¬ 
ment  and  proceeds  by  descending 
down  and  climbing  back  up  each 
branch  of  the  overlay  tree  structure 
one  branch  at  a  time. 

Using  ZLKO  with  C  offers  one  sig¬ 
nificant  advantage  over  using  it  with  a 
language  like  FORTRAN.  FORTRAN 
employs  the  call-by-reference  tech¬ 
nique.  Hence,  arguments  cannot  be 
passed  to  a  called  overlay  except  by 
means  of  the  COMMON  block  in  the 
root  segment.  On  the  other  hand,  C 
uses  the  call-by-value  technique. 
Thus,  values  can  easily  be  passed  to  a 
function  in  an  overlay.  C  passes  the 
arguments  by  pushing  them  on  the 
stack,  and  the  function  in  the  called 
overlay  simply  pops  them  off  the 
stack. 

ZLKO  does  not  automatically  insert 
code  into  the  root  segment  to  set  the 


stack  pointer  because  the  required  call 
to  main(  )  in  the  first  routine  in  the 
root  segment  will  take  care  of  setting 
the  stack  pointer.  That  is,  this  opera¬ 
tion  is  handled  by  the  C  compiler, 
which  expands  the  call  to  main  to  in¬ 
clude  setting  the  stack  pointer. 

Most  Small-C  compilers  use  the 
symbol  _memptr  to  designate  the 
first  free  memory  location  above  the 
program  code  space.  The  Q/C  com¬ 
piler  I  used  to  test  ZLKO  uses  the 
symbol  SMEMRY  for  compatibility 
with  the  L80  linking  loader.  Under 
ZLKO,  the  value  to  be  used  for 
SMEMRY  or  _memptr  is  stored  in 
the  8th  and  9th  bytes  of  the  root-seg¬ 
ment  header.  This  value  must  be  ex¬ 
tracted  from  the  header  and  stored  in 
location  SMEMRY  or  _memptr  in 
the  compiled  C  program  before  pro¬ 
gram  execution  commences. 

This  operation  is  readily  accom¬ 
plished  by  using  the  ovinit  function 
shown  in  Listing  One  (page  1 19). 
Ovinit  must  be  linked  into  the  root 
segment  and  must  be  the  first  func¬ 
tion  called  from  within  main(  ).  The 
ovrlay  function  supplied  with  ZLKO 
must  be  linked  into  the  root  segment 
following  ovinit.  Each  time  an  over¬ 
lay  is  to  be  loaded,  the  user  program 
must  make  a  call  to  the  ovrlay  func¬ 
tion  using  a  pointer  to  the  overlay 
name  as  the  last  argument  of  the  ovr¬ 
lay  function  call.  The  overlay  test 
(see  next  section)  demonstrates  the 
use  of  the  ovrlay  function.  Ovrlay  will 
load  the  specified  overlay  at  the 
transfer  address  specified  in  the 
header  and  then  execute  the  overlay. 

ZLKO  Test 

I  tested  the  ZLKO  overlaying  linking 
loader  with  a  simple  overlay  system 
written  in  C.  The  test  program  was 
compiled  with  the  Q/C  compiler  and 
assembled  with  the  CWA  assembler 
distributed  by  The  Code  Works.  The 
structure  of  the  test  overlay  system  is 
shown  in  Figure  2  (page  1  16)  and  the 
command  file  used  to  create  the  over¬ 
lay  system  in  Figure  3  (page  116). 
The  code  for  the  root  segment  and 
each  overlay  is  presented  in  Listing 
Two  (page  !  19).  Listing  Three  (page 
125)  shows  the  output  that  ZLKO 
sent  to  the  console  during  the  test. 
The  linking  process  took  126  seconds 


and  the  resulting  overlay  system  exe¬ 
cutes  in  1 1  seconds. 

Using  ZLKO 

ZLKO  is  relatively  easy  to  use  once 
you  understand  how  to  pass  to  ovrlay 
a  pointer  to  the  overlay  to  be  loaded 
by  the  root  segment.  There  are,  how¬ 
ever,  a  couple  of  things  you  should 
keep  in  mind.  First  of  all,  because  the 
input  information  required  by  ZLKO 
is  extensive,  the  potential  for  key¬ 
board  errors  is  quite  high.  This  prob¬ 
lem  can  be  solved  bv  using  a  com¬ 
mand  file  instead  of  direct  input.  For 
example,  entering  the  command: 

ZLKO  CMND(CTEST); 

causes  ZLKO  to  take  its  input  from 
the  command  file  CTEST.ZLK.  Sec¬ 
ond,  you  may  have  trouble  getting  ac¬ 
customed  to  the  fact  that  all  com¬ 
mands  require  a  combination  of 
semicolon  and  carriage  return  as  a 
terminator.  Finally,  although  the 
manual  adequately  describes  the 
proper  use  of  ZLKO,  it  will  not  mat*- 
you  an  expert  at  using  overlays.  All  of 
the  commands  accepted  by  ZLKO  are 
shown  in  Figure  4  (page  116)  in  the 
form  of  a  printed  copy  of  the  output  of 
the  ZLKO  Help  command.  ZLKO  is 
quite  verbose  and  lets  you  know  what 
is  going  on  at  all  times.  Part  of  the 
verbosity  is  because  of  the  fact  that 
ZLKO  displays  each  component  of  a 
command  string  as  it  is  being  parsed. 

Conclusion 

ZLKO  is  an  easy-to-use,  relatively  ef¬ 
ficient  overlaying  linking  loader  that 
is  particularly  useful  to  anyone  using 
a  Small-C  compiler.  ZLKO  will  allow 
the  user  to  run  very  large  programs 
under  CP/M-80  and  to  construct  fan¬ 
cy  software  in  which  a  menu-selec¬ 
tion  operation  determines  which 
overlay  (program)  is  executed  next. 
There  are  at  least  two  other  overlay¬ 
ing  linkers  available  for  use  with  CP/ 
M-80,  but  they  are  much  more  ex¬ 
pensive  than  ZLKO  and  only  provide 
a  few  additional  capabilities.  ZLKO 
only  supports  a  multiple-file  overlay 
file  system,  whereas  some  others  also 
support  a  single-file  overlay  file  sys¬ 
tem  that  uses  the  random-access  ca¬ 
pabilities  of  CP/M-80.  A  single-file 


Dr.  Dobb's  Journal.  December  1985 


117 

933 


overlay  system  will  execute  slightly 
faster  but  is  more  difficult  to  con¬ 
struct  and  debug. 

Do  not  discard  your  older  8-bit  sys¬ 
tem  if  you  can  perform  the  tasks  you 
need  to  accomplish  by  simply  pur¬ 
chasing  an  overlaying  linking  loader 
such  as  ZLKO.  ZLKO  is  available 
from  Elliam  Associates,  2400  Besse¬ 
mer  St.,  Woodland  Hills,  CA  9 1 367. 

Notes 

'  A  good  discussion  of  overlays  is  pro¬ 
vided  by  S.  H.  Kaisler,  The  Design  of 
Operating  Systems  for  Small  Com¬ 
puter  Systems,  John  Wiley,  1983. 


DDJ 


CP/M  Exchange  (Text  begins  on  page  114) 

Listing  One 


?f  flfst  £ree  roemory  location  from  ROOT  header 
and  put  it  in  location  $MEMRY.  This  version  is  for  use  with  the 
Q/C  compiler.  Change  $MEMRY  to  _memptr  for  use  with  small-c  */ 


ovinit ( ) 

{ 

#  asm 


#  endasm 

} 


EXT 

$MEMRY 

LD 

HL, (0108H) 

?Get 

LD 

( 9MEMRY) ,HL 

;Put 

Listing  Two 


End  Listing  One 


/*  Root  program  L0.C  for  testing  ZLKO  with  Q/C  */ 

♦include  <gstdio.h> 

main ( ) 

{ 

char  *ovla,  *ovlb,  *ovlc; 
ovla  =  "LlA  " ; 

ovlb  =  "LlB 
ovlc  =  "LlC  "; 

/*  initialize  Root  Segment  For  Overlays  V 
ovinit ( ) ; 

printf ( "ZLKO  Overlaying  Linker  Test  with  Q/C\n\n")- 
printf ("Starting  at  level  zero\n"); 
printf ("Call  Overlay  LlA\n" ) ; 


/*  Load  Overlay  LlA  */ 

ovrlay (Rovla) ;  /*  Pass  pointer  to  ovlname  */ 

printf ( "Returned  to  Level  zero  from  Level  one  pgm  LlA\n" ) ; 
printf ("Next  Call  Overlay  LlB\n" ) ; 

/*  Load  Overlay  LlB  */ 

ovrlay (Sovlb) ;  /*  Pass  pointer  to  ovlname  V 

printf ( "Returned  to  Level  zero  from  Level  one  pgm  LlB\n" ) ; 
printf ("Next  Call  Overlay  LlC\n"); 

/*  Load  Overlay  LlC  */ 

ovrlay (&ovlc) ;  /*  Pass  pointer  to  ovlname  */ 

printf ( "Returned  to  level  zero,  Overlay  Test  Done\n"); 


/*  overlay  LlA  */ 
levella ( ) 

{ 

printf ("At  Level  one.  Overlay  LlA\n"); 

} 


/*  overlay  LIB  */ 
levellb ( ) 

{ 

printf  ("At  Level  one.  Overlay  LlB\n" ) ; 

) 


/*  overlay  LlC  */ 
levellc ( ) 

f 

char  *ovld; 

int  valuel,  value2,  sum; 
ovld  =  "L2A  "; 

printf ("At  Level  one.  Overlay  LlC\n"); 
printf ("Call  Level  two.  Overlay  L2A  5  times\n"); 
/*  Load  Overlay  2A  */ 
valuel  =  0,  value2  =9? 
while  (++valuel  <  6) 

{  ++value2; 

printf ("At  Level  one  Calling  L2A,  valuel  =  %d,  value2  = 


120 

934 


Dr.  Dobb’s  Journal,  December  1985 


CP/M  Exchange  (Listing  continued,  text  begins  on  page  1 14) 

Listing  Two 

%d\n", valuel, value2)  ; 

/*  The  pointer  to  the  overlay  name  must  be  the  last  parameter  */ 

/*  The  ZLKO  "ovrlay"  routine  uses  the  value  in  the  HL  register-  */ 

/*  -to  find  the  overlay  name  but  does  not  alter  the  stack  */ 

/*  The  calleB  routine  must  have  a  dummy  last  parameter  */ 
ovrlay (valuel, value2, Sovld) ;  ) 

/*  Pass  values  and  pointer  to  ovlname  */ 

} 

int  sum; 
retval (sum) 

( 

printf ("Back  at  Level  one.  Returned  sum  valuel  +  value2  =%d\n\n" , sum) ; 


/*  overlay  L2A  */ 
level 2a (argl,arg2,arg3) 

/*  argl  is  valuel,  arg2  is  value2,  and  arg3  is  ovlname  pointer  */ 

/*  dummy  arg3  is  required  to  match  ovrlay  function  call  in  LlC  */ 
int  argl,  arg2,  arg3; 

{ 

printf ("At  Level  two.  Overlay  L2A\n") ; 

printf ( "Received  parameter  valuel  =%d,  value2  =%d\n" ,argl,arg2) ; 

/*  Pass  sum  argl  +  arg2  back  to  level  one*/  End  Listing  Two 

retval (argl  +  arg2); 

} 


Listing  Three 


OUTPUT  FROM  ZLKO  TO  CONSOLE  DURING  TEST  OVERLAY  CONSTRUCTION 


->CMND (CTEST) ; 

CMND  ( 

CTEST) < — Command  File  Input 
ROOT  ( 

MAIN) 

OVINIT,< — Implied  Include  List 
OVRLAY, 

L0; 

END; < — Searching  For  Required  Modules 
—  Building  File  on  Disk  — 


Loading  (0:OVINIT  .REL) 

Loading  (0  .-OVRLAY  .REL) 

Loading  (0:L0  .REL) 

Loading  f rom ( 0 :CRUNLIB  .REL) 


—  Filename  — 

0 :MAIN  .COM  P.Base:  0100 

D.Base:  0FBB 
C.Basa :  1182 


File  Information  — 

P. Size :  0EA7  Seg.End+1:  1182 
D.  Size :  01C7 
C.Size:  0000 


SYMFIL ( 

CTEST) < — Creating  Symbol  File 

OVL( 

1, 

LlA) 

INCL  ( 

LIA) 

OLV  ( 

1, 

LIB) 

; 

INCL  ( 

LlB ) 

; 

END;< — Searching  For  Required  Modules 
7 

—  Building  File  on  Disk  — 

Loading  (0:L1B  .REL) 

—  Filename  —  —  File  Information  — 

0 : LIB  .OVL  P.Base:  1182  P.Size:  000E  Seg.End+1:  11B7 

D.Base:  119C  D.Size:  001B 

C.Base :  11B7  C.Size:  0000 


Dr.  Dobb's  Journal,  December  1985 


125 

935 


CP/M  Exchange 

Listing  Three 


(Listing  continued,  text  begins  on  page  114) 


OVL  ( 

1, 

L1C) 

INCL  f 
L1C) 

I 

END;< — Searching  For  Required  Modules 


—  Building  File  on  Disk  — 


Loading  (0:LlC  .REL) 

—  Filename  —  —  File  Information  — 

0:L1C  '  .OVL  P. Base :  1182  P.Size:  00B2  Seg.End+1:  12F5 

D. Base :  1240  D.Size:  00B5 

C.Base:  12F5  C.Size:  0000 

OVL  ( 

2, 

L2A) 

I 

INCL  ( 

L2A) 

/ 

END; < — Searching  For  Required  Modules 


--  Building  File  on  Disk  — 


Loading  (0;L2A  .REL) 


—  Filename  — 
0 :L2A  .OVL 


P.Base ;  12F5 
D.Base:  1346 
C. Bases  138C 


File  Information  — 

P.Size:  0045  Seg.End+1:  138c 
D.Size:  0046 
C.Size:  0000 


DOVL; 

-  Overlay  Structure  - 


Name 

Number 

P.Base 

End 

LlA 

0000 

1182 

11B6 

LIB 

0001 

1182 

11B6 

L1C 

0002 

1182 

1 2F4 

L2A 

0003 

12F5 

138B 

DONE; 

No  Segment  Under  Construction 


End  Listings 


126 


not 


Dr.  Dobb's  Journal,  December  1985 


Index 


16-Bit  Software  Toolbox,  75-84,  234-240,  323-327,  413^18,  481-486, 
560-571, 648-651, 723-729,  849-854,  925-928 

2651  USART,  458-462 

68000 

binary  search  routine,  849-854 
pseudo-random-number  generator,  849-854 
square  root  routine,  413-418,  849-854 
Tiny  BASIC  interpreter,  117-120 

8086 

binary-to-ASCII  conversion,  234-240 
unsigned  divide  subroutines,  234—240 

Ada,  An  Advanced  Introduction...,  164-165 
Adding  a  COPY  Command  to  ProDOS,  904-910 
Aho-Corasick  algorithm,  680-696 
Albrecht,  Bob,  106-109,  258-259,  559 
Algorithms 

Aho-Corasick,  680-696 
Bose-Nelson,  698-703 
Knuth’s  hyphenation,  742-758 
pattern  matching,  680-696 
search  678-679,  680-696,  697 
sort  678-679,  697,  698-703 
Allison,  Dennis,  conversation  with,  155 
AMPRO  Bookshelf  Computer,  405^07 
AMPRO  Little  Board.  405-407,  721-722 
real-time  clock  for,  794-799 
Anderson,  Brian  R.,  822-826 
ANSI  X3.28, 629-632 
APL*PLUS/PC,  Version  3.1,  59-69 
Apple  II 

adding  COPY  command  to  ProDOS,  904-910 
RAM-disk  driver  for  ProDOS,  918-924 
APL*PLUS/PC,  Version  3.1, 59-69 
Archiving  Fites  with  CP I M -80  and  CP/M-86,  24—4 1 
ASCOM,  629-632 

Ashdown,  Ian,  24—41,  73-74,  680-696 
Assembler.  Small-Mac,  623-628 
Asynchronous  Protocols,  629-632 
Aztec  C,  594-608 

root  module  for,  174-186 

BANKSWAP:  A  Banked  Memory  Debugging  Tool  for  CP/M  Plus, 
893-903 

BASIC,  407-4 12 

logarithms,  808-8 1 5 

Bass.  John  L..  71 1-720,  791-793,  929-930 
Batch  procedures,  CP/M,  328-332 
Batch  processing,  CP/M-68K,  91 1-917 
Benchmarks  for  C  compilers,  594-608 
BetterB ASIC,  Version  1.1,407-412 

Binary-to-ASCII  conversion,  8086  assembly  language,  234-240 
Bit  Manipulation  in  Modula-2,  822-826 
Bitmap  manipulation,  C,  436-445 

Blum,  Robert,  156-163,  241-246,  328-332,  489-494,  652-653, 

721-722,  794-799,  855-867 
Bose-Nelson  Sort,  698-703 
Bowhill,  Sidney  A.,  765-784 
Brandly,  Gordon,  1 17-120 


BRIEF,  833-848 
Brodie,  Leo,  759-764 
BU 

backup  utility  for  CP/M-80,  CP/M-86,  24-41 
patch  to  CP/M  2.2  BDOS,  426-427 
Bubble  sort,  678-679,  697 
Build  a  Custom  PC  or  Clone,  524-526 
Burton,  C.  E.,  121-139 
Buvel,  Raymond  L.,  94 

C 

bit-map  manipulation,  436-445 

book  review,  73-74 

Christensen  protocols,  463-478 

command-line  processing,  353-359 

compilers,  594-608,  664-670 

croot.c,  174—186 

cursor  manipulation,  879-888 

decision  variables  in  graphics  primitives,  360-366 

EFGETS,  879-888 

fgrep,  680-696 

getargs(),  353-359 

grep,  436-445 

Knuth's  hyphenation  algorithm,  742-758 
Is,  510-523,  742-758 
make,  633-647 

object-allocation  module,  428-435 
pattern  matching,  680-696 
pipes,  174-186 

preprocessor  use  and  abuse,  446-45 1 

printf(),  103-105 

queue  management,  436-445 

Quicksort,  316-322 

quoted  arguments,  174-186 

Shell  sort,  316-322 

shell  support,  879-888 

style,  103-105 

wildcard  expansion,  174-186 

C  Chest,  174-186,  316-322,  353-359, 436-445,  510-523,  633-647, 
664-670,  742-758,  879-888 
C  Compilers  for  MS-DOS,  594-608 
C  Programming  Tutor,  The,  73-74 
C-systems  8088  Software  Development  Package,  594-608 
C-Terp,  594-608 
C  UART  Controller,  458-462 
C/Unix  Programmer's  Notebook,  103-105 
Carroll,  David  W.,  479^80,  629-632,  816-821, 

Celko,  Joe,  698-703 
Children  and  computers,  106-109 
Christensen  Protocols  in  C.  463-478 
CICFIG  recompiler,  765-784 
Cipher  system,  121-139 
Clark,  David  D„  6-7,  70-72,  273-292 
Clock,  real-time  for  S-100  bus,  534—608 
Clute,  Alan,  787-790 

Coer  d'Alene,  printing  characters  on  MX- 100,  293-304 
Command-line  processing,  C,  353-359 
Commodore  64 

MBOOT  and  MODEM7  for  CP/M,  42-56 
solid-shape  graphics  package,  367-388 
Communications 

asynchronous  protocols,  629-632 


938 


Christensen  protocols  in  C,  463-478 
C  UART  controller,  458-462 
Envoy  review,  479—480 
freedom  and  security,  452-455 
increasing  modem  speed,  456-457 
MBOOT,  MODEM7  for  C-64,  42-56 
specialized  protocol  software,  629-632 
Compilers 

C  reviews,  594-608 
design  of,  660-663 
Forth  recompiler,  765-784 
how  they  work,  664-670 
peephole  optimizer  for,  609-622 
Prolog,  389-398 
recursive  descent,  664-670 
Small-C  update,  623-628 
Compiler  Written  in  Prolog,  A,  389-398 
CompuServe  A,  B,  629-632 
Computer  Calisthenics,  572-577 
Computer  Innovations  CI-86,  594-608 
Conn,  Richard,  405-407 
Control  C  CC-86,  594-608 
Controller,  C  UART,  458^*62 

Cortesi,  D.  E.,  8-10,  98-102,  196-209,  260-263,  345-349,  350-352, 
428-435,  507-509, 590-593, 660-663,  738-742,  808-815, 877-878 
CP/M 

batch  procedures,  328-332 
buffered  disk  I/O,  156-163 
debugging  console,  489-494 
File-backup  utility,  24-41 
MBOOT,  MODEM7  for  C-64,  42-56 
ZLKO  overlaying  linking  loader,  931-936 
CP/M  2.2,  BU,  patch  to  BDOS,  426-427 
CP/M  2.2  Goes  PUBlic 
errors,  257 
Heath  users,  341-343 
updates,  341-343 

CP/M-68K  Conditional  Batch  Processing,  911-917 
CP/M-80,  CP/M-86,  file-backup  utility  for,  24-41 
CP/M  Plus 

BANKSWAP,  a  banked-memory  debugging  tool,  893-903 

PIP  patch,  241-246 

RSX  for  problem  with,  241-246 

CP/M  Exchange,  156-163,241-246,328-332,489-494,  652-653, 
721-722,  794-799,  855-867,  931-936 
CR-LF,  629-632 

Croot.c,  wildcard  expansion  and  pipes  for  Aztec  C,  174-186 
Crosstalk,  629-632 

Datalight  C,  594-608 
Day,  Shawn,  904-910 

DBASE  II,  changing  default  File  type,  140-142 
Debugging  console  for  SID,  ZSID,  and  DDT,  489-494 
Debugging  tools 

CP/M  Plus,  893-903 
Turbo  Pascal,  95-97 

Decision  variables  in  graphics  primitives,  360-366 
Design  of  a  Forth  Target  Compiler,  765-784 
Designing  a  Real-Time  Clock  for  the  S-J00  Bus,  534-558 
DeSmet  C,  594-608 

Device  driver,  template  for  MS-DOS,  560-571 

DDJ  editor,  experience  as,  (Suzanne  Rodriguez),  106-109 

Diana,  An  Intermediate  Language  for  Ada,  164-165 

Disk  Maker  I,  400-405 

Donais,  Roger  E.,  91 1—917 

Dr.  Dohh  is  a  Subversive,  350-352 

Dr.  Dobbs  Clinic,  8-10,  98-102,  260-263,  345-349,  428^435,  507-509, 
590-593,  660-663,  738-742, 808-815, 877-878 
DRI  C,  594-608 
DUMP  Filter,  849-854 

Duncan,  Ray,  75-84,  234-240,  323-327,  413-418,  481^486,  560-571, 
648-651,  723-729,  849-854,  925-928 


EC,  833-848 
EcoSoft  C,  594-608 
Edgar,  G.  A.,  389-398 
Editors 

character,  for  FX-80,  273-292 
reviews,  833-848 
EDIX,  833-848 
Edwards,  Mark  U.,  833-848 
EFGETS,  879-888 
Emacs,  833-848 

Encryption/decryption,  121-139 
Enhanced  ADFGVX  Cipher  System,  An,  121-139 
Envoy,  Version  1.1, 479-480 
Epsilon,  833-848 

Epson  FX-80,  character  editor  for,  273-292 
Epson  MX- 100,  creating  fonts  on,  293-304 
Evslin,  Tom,  interview  with,  85-86 

Fancy  Font  System,  The,  Version  2.0,  70-72 

Fatten  Your  Mac,  2,  11-16,  170 

Festschrift  for  Doctor  Dobb,  1 06- 1 09 

Fgrep,  file-search  utility,  680-696 

File  transfer,  packet  protocol  program,  463-478 

Filters 

DUMP,  849-854 
TEE,  323-327,  648-651 
TRIM,  481-486 

Financial  &  Statistical  Library,  Version  1.0,  59-69 
Fire  in  the  Valley,  110-116 
FixpathO  function,  738-742 
Fonts,  creating  on  MX- 100,  293-304 
Forth 

MacFORTH  review,  787-790 
NC4000  processor,  759-764 
Neon  review,  785-787 
recompiler,  765-784 
unstructures  programming  in,  57-58 
Forth  compiler  for  MC68000,  errors,  94 
Fox,  David  L.,  609-622 
Freiberger,  Paul,  110-116 
Functions 

EFGETS,  editing  input,  879-888 
FixpathO,  processing  DOS  path  names,  738-742 
Furuta,  Richard,  704-710 

Fx80char:  A  Character  Editor  for  Epson  FX-80  Printers,  273-292 

Gay,  Don,  458-462 

GEM,  889-892 

Gengle,  Dean,  452-455 

Getargs(),  353-359 

GNU  Manifesto,  The,  187-190 

Grammars,  664-670 

Graphics  package,  C-64,  367-388 

Graphics  primitives,  using  decision  variables  in,  360-366 

Graphics  routines,  IBM  PC,  75—84 

Greek  and  math  symbols,  producing  on  Prowriter,  269-272 
Grep.c 

adding  bit  maps,  436-445 
compiling  with  C/80,  257,  263 
error,  5 

GW  BASIC,  comparison  with  BetterBASIC,  407-412 

Hackers,  106-109 

Hash-table  search,  833-848 

Head,  Gene,  140-142 

Heckel,  Paul,  827-832 

Hendrix,  J.  E.,  623-628,  875 

HEX  File-conversion  protocol,  629-632 

Hogan,  Tom,  360-366 

Holub,  Allen,  174-186,  316-322,  353-359,  436-445,  510-523,  633-647, 
664-670,  742-758,  879-888 


939 


Homebrew  Computer  Club,  110-116 
Horn,  Bruce,  785-787 
HP  LaserJet,  printing  utility  for,  723-729 
Human  factors 

designing  software,  314-315 
engineering,  264-268 

Hyphenation,  using  Knuth's  algorithm,  742-758 
IBM  PC 

editors,  833-848 
graphics  routines,  75-84 
sketching  program  for,  305-309 
Image  Writer,  producing  Lisa  QuickDraw  images  on,  17-23 
Income  tax,  analyzing,  210-233 
Information  Age  Issues,  452-455 
Instant  C,  594-608 
Interpreters 
C, 594-608 

Tiny  BASIC  for  68000,  117-120 
Introduction  to  Using  Your  MacSC SI  Host  Adapter,  929-930 

Jennings,  Earle,  95-97 
Johnson,  John  R.,  164-165 

Kachigan,  Karl  R.,  310-312 
KERMIT,  629-632 
King,  Steve,  247-249 
Krantz,  Donald,  463-478 
Kronman,  Jim,  400-405,  524-526 

Lafleur,  Tom,  11-16 
Lattice  C,  594-608 

Control-Break  handler  for,  723-729 
Let's  Mouse  Around,  305-309 

Lisa,  printing  QuickDraw  images  on  ImageWriter,  17-23 
Logarithms  in  BASIC,  Pascal,  808-815 
Logic  programming,  applications  and  features,  190-195 
Ls  directory  utility,  510-523,  742-758 

Mac  FORTH,  787-790 
Macintosh 

Open  and  Save  dialogs  for,  234-240 
SCSI  hard-disk  interface,  71 1-720 
upgrading  to512K,  11-16 
using  MacSCSl  host  adapter,  929-930 
MacKay,  Pierre,  704-710 
MacSCSl,  71 1-720 

driver  routine,  791-793 
installation  and  uses,  929-930 
Mac  Toolbox,  71 1-720,  791-793,  929-930 
Magic  Mushroom  for  the  Epson  Alice,  A,  293-304 
Make  utility,  633-647 
Malpas,  John,  190-195 
Mayer,  Thom,  17-23 

MBOOT  and  MODEM7  for  the  C-64's  CP/M,  42-56 

MBOOT  for  C-64,  42-56 

McKay,  Derek,  257,  341-343 

MDM730  for  C-64, 42-56 

Micro-PROLOG,  196-209 

compiler  written  in,  389-398 
Microsoft  C,  594-608 
Microsoft  Windows,  889-892 
MicroTEX,  Version  1 .4 A 1 ,  704-7 1 0 
Mitchell,  Bridger,  257,  341-343 
MITE,  629-632 
MIX,  833-848 
Mk  utility,  633-647 

MNP  (Microcom  Networking  Protocol),  629-632 
MODEM4  for  C-64,  42-56 
MODEM7,  629-632 

for  C-64's  CP/M,  42-56 
program  compatible  with,  463-478 


Modems 

increasing  speed  of,  456-457 
packet  protocol  file-transfer  program,  463-478 
S-100  review,  855-867 
Modems:  2400  Bit/Sec  and  Beyond,  456-457 
Modula-2 

bit  manipulation  in,  822-826 
compared  with  Pascal,  816-821 

Modula-2  versus  Pascal  for  Microcomputers:  An  Update,  8 1 6-82 1 

Modula-2/86,  Version  1.04,  143-154 

MONTY,  264-268 

MONTY  Plays  Scrabble,  264-268 

More  dBASE  Tips  and  Techniques,  140-142 

Morse.  Philip  D.,  II,  765-784 

Mouse,  using  with  Turbo  Pascal  sketching  program,  305-309 

Move-It,  629-632 

MS-DOS 

chart  of  DOS  services,  345-349 

cursor-manipulation,  character-output  routines,  879-888 
device  drivers,  program  to  display  chain,  4 1 3-4 1 8 
DUMP  filter  for,  849-854 
editing  input  function,  EFGETS,  879-888 
ls  directory  utility,  510-523 
make  utilities,  633-647 
processing  path  names,  738-742 
requesting  disk  input,  660-663 
shell  support  routines,  879-888 
TEE  filter  for,  323-327 
template  for  installable  device  driver,  560-571 
TRIM  filter  for,  481-486 
windowing  programs  for,  889-892 
MWC  C  Programming  System,  594—608 

NC4000,  759-764 

Nelson,  Amer  W.,  269-272 

Neon,  Version  1.0,  785-787 

New  Shell  for  MS-DOS,  A, Part  I,  879-888 

Object  allocation,  C,  428-435 
Object-oriented  programming,  history  of,  785-787 
Optimizer  for  assembly-language  source  code,  609-622 
Osborne  1 ,  characters  not  on  keyboard,  269-272 

Palmer,  Gary  B.,  293-304 

Parallel  Pattern  Matching  and  Fgrep,  680-6% 

Parsers,  recursive  descent,  664-670 
Parsons,  Ronald  G.,  69-70,  305-309 
Parsons,  Vincent,  305-309 
Pascal, 

QuickDraw  routines  in,  17-23 
sketching  program  in,  305-309 
Turbo  vs.  standard,  507-509 
using  mouse  in  programs,  305-309 
PC-DOS,  Version  3.0,  75-84 
PCTEX,  Version  1.0,  704-710 
PC/XT,  building  a  work-alike,  524-526 

Peephole  Optimizer  for  Assembly  Language  Source  Code,  A,  609-622 

Phonetic  characters,  printing  on  Epson  MX- 100,  293-304 

Piotrowski,  Walt,  42-56 

PIP,  patch  for  CP/M  Plus,  241-246 

Pipes,  assembly-language  support  routines  for,  174-186 

Pittman,  Tom,  106-109 

Pmate,  833-848 

Polar,  6-7 

Polymake  utility,  633-647 
Printers 

building  a  spooler,  527-533 
character  editor  for  FX-80,  273-292 
phonetic  characters  on  MX- 100,  293-304 
ProDOS 

adding  a  copy  command,  904-910 
RAM-disk  driver  for  918-924 


ProDOS  and  the  RAM  Disk,  918-924 
Programming 

C  preprocessor,  use  and  abuse,  446-451 
grammars,  664-670 
object-oriented,  785-787 
parsing,  664-670 
recursive  descent,  664-670 
sorting,  678-679,  697 
unstructured  Forth,  57-58 
using  NC4000,  759-764 
Programming  in  Logic,  190-195 
Programs 

adding  copy  command  to  ProDOS,  904-910 

BANKSWAP  debugging  tool,  893-903 

Bose-Nelson  sort,  698-703 

character  editor  for  FX-80,  273-292 

CICFIG  Forth  recompiler,  765-784 

conditional  batch  processing  with  CP/M-68K,  91 1-917 

CP/M  benchmarks  for  buffered  disk  I/O,  156-163 

debugging  console  for  SID,  489-494 

Greek  and  math  symbols  on  Prowriter,  269-272 

implementing  S-100  modem  features,  855-867 

Knuth's  hyphenation  algorithm,  742-758 

modifying  USART  parameters,  458-462 

MS-DOS  device  drivers,  4 1 3-4 1 8 

peephole  optimizer  for  assembly-language  source  code,  609-622 
phonetic  characters  on  MX- 100,  293-304 
Prolog  compiler,  389-398 
Quicksort,  316-322 

RAM-disk  driver  for  ProDOS,  918-924 
Shell  sort,  316-322 
sketching,  using  mouse,  305-309 
solid  shape  drawing,  C-64,  367-388 
TA,  Tax  Advisor,  210-233 
template  for  MS-DOS  device  driver,  560-571 
XFR,  file  transfer,  463-478 
Prolog 

compiler  in,  389-398 
features,  190-195 

income-tax  analysis  program  in,  210-233 
introduction  to,  196-209 
Micro-PROLOG,  196-209 
syntax,  196-209 

PROM-based  machines.  Forth  recompiler  for,  765-784 
Protocols 

ANSI  X3.28,  629-632 
ASCOM.629-632 
asynchronous,  629-632 
Christensen,  in  C,  463-478 
CompuServe  A,  B,  629-632 
CR-LF,  629-632 
Crosstalk,  629-632 
HEX  file  conversion,  629-632 
KERMIT,  629-632 
MITE,  629-632 

MNP  (Microcom  Networking  Protocol),  629-632 

MODEM7,  629-632 

Move-It,  629-632 

Smartcom,  629-632 

specialized  software,  629-632 

Telink,  629-632 

XMODEM,  629-632 

XON-XOFF,  629-632 

Prowriter,  producing  Greek  and  math  symbols  with,  269-272 
Pseudo-random-number  generator,  98-102 
Ps-make  utility,  633-647 
PUBLIC. ASM  utility 
error,  257 

Heath  users,  341-343 

Quasi-random-number  generator,  590-593 
Queue-management  routines,  436-445 


QuickDraw  Meets  Imagewriter,  17-23 

QuickDraw,  routine  to  produce  images  on  ImageWriter,  17-23 
Quicksort,  316-322,  678-679,  697 
Quoted  arguments,  174-186 

Raab,  Susan,  11-16 

RAM-disk  driver  for  ProDOS,  918-924 

Realizable  Fantasies,  106-109,  187-190,  258-259,  350-352,  559 

Relph,  Richard,  594-608 

Reviews 

Ada,  An  Advanced  Introduction  ....  164-165 
AMPRO  Bookshelf  Computer,  405-407 
AMPRO  Little  Board,  405-407,  721-722 
APL*PLUS/PC,  Version  3.1,  59-69 
Aztec  C68,  594-608 
BetterB  ASIC,  Version  1.1, 407—412 
BRIEF,  833-848 

Computer  Innovations  CI-86,  594-608 

Control  C  CC-86,  594-608 

C  Programming  Tutor,  The,  73-74 

c-systems  8088  Software  Development  Package,  594-608 

C-Terp,  594-608 

Datalight  C,  594-608 

DeSmet  C,  594-608 

Diana,  An  Intermediate  Language  for  Ada,  164-165 

Disk  Maker  I,  40(M05 

DRI  C,  594-608 

EC,  833-848 

EcoSoft  C,  594-608 

EDIX,  833-848 

Emacs,  833-848 

Envoy,  Version  1.1, 479-480 

Epsilon,  833-848 

Fancy  Font  System,  The,  Version  2.0,  70-72 

Financial  &  Statistical  Library,  Version  1.0,  59-69 

GEM,  889-892 

GW  BASIC,  407-412 

Instant  C,  594-608 

Lattice  C,  594-608 

Mac  FORTH  Levels  1,  2,  and  3,  787-790 
Microsoft  C,  594-608 
Microsoft  Windows,  889-892 
MicroTEX,  Version  1.4A1,  704-710 
MIX, 833-848 

Modula-2/86,  Version  1.04,  143-154 
MONTY,  264-268 

MWC  C  Programming  System,  594-608 

NC4000  processor,  759-764 

Neon,  Version  1.0,  785-787 

PC-DOS,  Version  3.0,  75-84 

PCTEX,  Version  1.0,  704-710 

Pmate,  833-848 

Run/C,  594-608 

SAVVY  PC,  247-249 

Software  Toolworks  C,  594-608 

SPSS/PC,  312-313,  319 

TOOLS,  Volume  1,  59-69 

Toolworks  C/80,  Version  3.1,  Mathpack,  399-400 

TopView,  889-892 

Turbo  Toolbox,  Version  1.0,  310-312 

U.S.  Robotics  Modem,  855-867 

VEDIT  PLUS,  833-848 

VSI — Virtual  Screen  Interface,  Version  2.09,  69-70 
Wizard  C,  594-608 
XTC,  833-848 
XyWrite  II  Plus,  833-848 
ZLKO,  931-936 
Rindsberg,  Don,  527-533 
Robinson,  Howard  H.,  765-784 
Rodriguez,  Suzanne,  106-109 
Routines 

2k  in  BASIC,  Pascal,  808-815 


941 


68000  square  root,  4 1 3-4 1 X 
binary  search,  849-854 
bit-map  manipulation,  436-445 
character  output.  879-888 
CP/M  file  I/O,  156-163 
cursor  manipulation,  879-888 
graphics,  for  IBM  PC,  75-84 
integer  square  root,  849-854 
MacSCSI  driver.  791-793 
object  allocation,  428-435 
pipe  support.  174-186 

printing  QuickDraw  images  on  ImageWriter.  17-23 
pseudo-random-number  generator,  98-102.  849-854 
quasi-random-number  generator  (8086,  Z80),  590-593 
queue  management.  436-445 
real-time  clock.  AMPRO  Little  Board.  794-799 
shell  support.  879-888 
Rylander.  Richard  L.,  367-388 
Run/C.  594-608 

S-100  bus.  real-time  clock  for,  534-558 

S- 1 00  modem,  855-867 

SAVVY  PC.  Version  4.0.  247-249 

Schlobohm.  Dean,  210-233 

Schmidt.  Michael,  143-154 

Schreiner.  Axel,  446-45 1 . 673-677 

SCISTAR  for  Prowriter  users,  269-272 

Scrabble,  a  computer  variation  of.  264-268 

SCSI  Your  Afar,  71 1-720 

Shell  sort,  316-322.  678-679,  697 

Shoemaker.  D.  C.,  399-400 

Shortcut  to  SCISTAR:  For  Prow  riter  Users.  269-272 

SID2TTY  debugging  console.  489-494 

Signals  (Unix),  673-677 

Skjellum,  A.,  103-105 

Small-C 

update,  623-628 
bug.  503 
fix,  875 

Small-Mac,  623-628 
Smartcom.  629-632 

Software  Designer.  The.  85-86.  155.  314-315.  678-679,  827-832 

Software  Toolworks  C.  598-608 

Sola,  Steven  Anthony,  3 1 2-3 1 3,  3 1 9 

Solid  Shape  Drawing  on  the  Commodore  64 .  367-388 

Spooler  for  parallel  printer,  527-533 

SPSS/PC.  312-313.319 

Square  root  routine.  68000.  413-418 

Stallman.  Richard.  1X7-190 

Steele.  Alfred.  918-924 

Swaine.  Michael.  85-86.  1 1 0-1 16,  155,  258-259,  314-315.  559, 
678-679,  889-892 
System  throughput.  98-102 

TA.  Tax  Advisor  Program.  210-233 

Tax  Advisor:  A  Prolog  Program  Analyzing  Income  Tax  Issues .  210-233 
TEE  filter,  323-327 
bug.  648-65 1 
Telink,  629-632 

Threaded  code.  Forth,  759-764.  765-784 

Threaded  Code  Microprocessor  Bursts  Forth.  A.  759-764 

Tiny  BASIC  for  the  68000,  117-1 20 

TOOLS.  Volume  I.  59-69 

Toolworks  C/80 

Mathpack.  399-400 
Version  3. 1 . 399-400 
TopView,  889-892 
Tour  of  Prolog.  A .  1 96-209 
Trask.  Matthew.  407-412 
TRIM  filter.  4X1^486 
Turbo  Pascal 

compared  with  Modula-2.  SI 6-82 J 


compared  with  standard.  507-509 
debugging  tool  in.  95-97 
sketching  program  in  305-309 
Turbo  Toolbox.  Version  1 .0.  310-3 1 2 
Two  TEX  Implementations  for  the  IBM  PC.  704-710 
Typesetting  technical  text,  704-710 

UART.  458-462 

Ultimate  Parallel  Printer.  Spooler  The.  527-533 

Unger.  Stefan  H..  59-69 

Unix 

compatible  software  system,  187-190 
signals.  673-677 

Unix  Exchange.  446-45 1 . 673-677 

Unsigned  divide  subroutines.  8086  assembly-language,  234-240 

Unstructured  Forth  Programming.  57-58 

USART 

C  UART  controller.  458-462 
in  S-100  modem.  855-867 

Using  Decision  Variables  in  Graphics  Primitives.  360-366 

U.S.  Robotics  Modem  Review.  855-867 

Utilities 

Control-Break  handler  for  Lattice  C.  723-729 

fgrep.  file  search.  680-696 

file  backup.  CP/M-80,  CP/M-86.  24—41 

landscape  printing  with  HP  LaserJet.  723-729 

Is,  510-523 

make.  633-647 

mk,  633-647 

Polymake,  633-647 

Ps-make.  633-647 

PUBLIC. ASM,  257.  .341-343 

V ALGOL,  compiler  for.  389-398 
VEDIT  PLUS,  833-848 

VSI — Virtual  Screen  Interface,  Version  2.09,  69-70 

Walsh.  Dale.  456-457 
Ward.  Terry  A.,  264-268 
Wiesenberg,  Michael,  572-577 
Wilcox.  Alan  D..  534-558 
Wildcard  expansion.  174-186 
Wilson.  W.  E.,  931-936 
Wilton,  Richard.  57-58 

Window  ing  Operating  environments:  Top\  iew  .  GF.M.  and  Windows. 
889-892 

Windowing  programs,  889-892 
Wizard  C.  594-608 
Woodhull.  Albert  S..  893-903 

WordStar  files,  incorporating  Greek  and  math  symbols  in.  269-272 

XFR.  file-transfer  program.  46.3-478 
XMODEM.  463-478.  629-632 
XON-XOFF.  629-632 
XTC.  833-848 
Xy Write  II  Plus,  833-848 

Z80,  ZLKO  overlaying  linking  loader.  931-936 

ZLKO  overlaying  linking  loader.  931-936 

Zoomracks:  Designing  a  New  Software  Metaphor.  827-832 


0/10 


Other  Software  Tools 

from  Dr.  Dobb's  Journal  and  M&T  Publishing 


Dr.  Dobb's  Toolbook  for  C  Item  #005  $29.95 

From  Dr.  Dobh's  and  Brady  Communications,  this  book  includes  a  comprehensive  library  of  valuable  C 
code.  Dr.  Dobb's  most  popular  articles  on  C  are  updated  and  reprinted  here,  along  with  new  C  programming 
tools.  This  book  includes  a  complete  C  compiler,  an  assembler,  text  processing  programs,  and  more! 


The  Small-C  Handbook  by  James  Hendrix  Item  #006  $17.95 

The  Small-C  Handbook  with  MS/PC-DOS  Addendum  Item  #006A  $22.95 

Also  from  Dr.  Dobb's  and  Brady  Communications,  the  handbook  is  a  valuable  companion  to  the  Small-C 
compiler,  described  below.  The  book  explains  the  language  and  the  compiler  and  contains  entire  source 
listings  of  the  compiler  and  its  library  of  arithmetic  and  logical  routines. 


Small-C  Compiler  Item  #007  $19.95 

Like  a  home  study  course  in  compiler  design,  the  Small-C  Compiler  and  The  Small-C  Handbook  (above) 
provide  all  you  need  to  learn  how  compilers  are  contructed,  as  well  as  teaching  the  C  language  at  its  most 
fundamental  level.  Full  source  code  is  included  on  disk  in  both  CP/M  and  MS/PC-DOS  versions. 


Small  Tools:  Programs  or  Text  Processing  Item  #010A  $29.95 

This  package  of  text  processing  programs  written  in  Small-C  is  designed  to  perform  specific,  modular 
functions  on  text  files.  Source  code  only  is  included.  Small  Tools  is  available  in  both  CP/M  and  MS/PC- 
DOS  versions  and  includes  complete  documentation. 


Small-Mac:  An  Assembler  for  Small-C  Item  #012A  $29.95 

Small-Mac  is  a  macro  assembler  designed  to  stress  simplicity,  portability,  adaptability,  and  educational 
value.  Small-Mac  is  available  for  CP/M  systems  only  and  includes  source  code  on  disk  with  complete 
documentation. 


SH :  A  Unix-like  Utility  Package  for  MS-DOS  Item  #160  $29.95 

This  MS-DOS  shell  written  in  C  replaces  COMMAND.COM  and  implements  the  most-used  parts  of  the 
Unix  C  shell.  The  package  includes  a  disk  with  full  C  source  code  and  documentation  in  a  Unix-style 
manual. 


/util:  A  U nix-like  Utility  package  for  MS-DOS  Item  #161  $29.95 

This  collection  of  utilities  is  intended  to  be  accessed  through  SH  but  can  be  used  separately.  It  contains 
programs  and  subroutines  that,  when  coupled  with  SH.  create  a  fully  functional  Unix-like  environment.  The 
package  includes  a  disk  with  full  C  source  code  and  documentation  in  a  Unix-style  manual. 


Dr.  Dobb's  Toolbook  for  Z80  by  David  Cortesi  Item  #  022  $25 

Z80  Toolbook  with  software  on  disk  Item  #  022 A  $40 

This  book  contains  everything  users  need  to  write  their  own  Z80  assembly  language  programs,  including  a 
method  of  designing  programs  and  coding  them  in  assembly  language  and  a  complete,  integrated  toolkit  of 
subroutines.  All  the  software  in  the  book  is  available  on  disk. 


943 


Dr.  Dobb's  Toolbook  of  Forth  Item  #  030  $22.95 

Toolbook  of  Forth  with  disk  Item  #031  $39.95 

This  comprehensive  collection  of  useful  Forth  programs  and  tutorials  contains  expanded  versions  of  DDJ' s 
best  Forth  articles  and  other  material,  including  practical  code  and  in-depth  discussions  of  advanced  Forth 
topics.  The  screens  in  the  book  are  also  available  on  disk  as  ASCII  files  in  the  following  formats:  MS/PC- 
DOS,  Apple  II,  Macintosh,  CP/M. 


Dr.  Dobb's  Toolbook  of  68000  Programming  Item  #  040  $29.95 

From  Dr.  Dobb's  and  Brady  Communications,  this  collection  of  practical  programming  tips  and  tools  for 
the  68000  family  contains  the  best  68000  articles  reprinted  from  Dr.  Dobb's  along  with  much  new  material. 
The  book  contains  many  useful  applications  and  examples.  The  software  in  the  book  is  also  available  on 
disk  in  the  following  formats:  MS/PC-DOS,  Macintosh,  CP/M  8",  Osborne,  Amiga,  Atari  520ST. 


Dr.  Dobb’s  Listing  Disk  #1/86  Item  #170  $14.95 

As  a  useful  adjunct  to  the  magazine.  Dr.  Dobb's  offers  the  convenience  of  selected  listings  on  disk.  Disk 
#1/86  contains  a  collection  of  listings  from  Dr.  Dobb's  January  through  April  1986  issues.  Formats:  MS- 
DOS,  Macintosh,  CP/M. 


Dr.  Dobb's  Listing  Disk  #2/86  Item  #171  $14.95 

Disk  #2/86  contains  selected  listings  from  Dr.  Dobb's  May  through  August  1986  issues.  Formats:  MS- 
DOS,  Macintosh,  CP/M. 


Dr.  Dobb’s  Journal  of  Software  Tools,  1 -Year  Subscription  Item  #001  $25 


To  order  any  of  these  products,  send  your  payment  along  with  $2.25  per  item  for  shipping 
to  M&T  Publishing  Inc.,  501  Galveston  Dr.,  Redwood  City,  CA  94063.  When  ordering 
disks,  please  indicate  format. 


QAA 


The  Dr  Dobb's  Series 


Volume  1 

Chronicles  the  advent  of  the  microcomputer  era  in  1976.  Always  pertinent  for  bit  crunch- 
ing  and  byte  saving,  home-brew  computer  projects,  and  the  technical  history  of  home 
computing.  Topics  include:  Tiny  BASIC,  the  first  words  on  CP/M,  Speech  Synthesis,  Float¬ 
ing  Point  Routines,  6502  Disassembler  for  the  Apple,  and  much  more. 

Volume  2 

The  small  computer  emerges  as  a  powerful  tool  for  the  modern  age  in  these  issues  from 
1977.  Topics  include:  Lawrence  Livermore  Lab’s  BASIC,  Dr.  Starkweather’s  PILOT,  Using 
a  Modem,  String  Handling  Techniques,  Ciphers,  Turtle  Graphics,  micro  utilities. 

Volume  3 

Explores  the  foundations  of  the  mass  market  computer  industry  in  1978.  Topics  include: 
Programming  in  BASIC,  PILOT,  and  Pascal,  RAM  Memory  Testers,  Apple  utility  pro¬ 
grams,  S-100  Bus  Standard,  STRUBAL:  A  Structured  BASIC  Compiler. 

Volume  4 

Heralds  the  widespread  acceptance  of  the  microcomputer  in  America.  Innovative  ideas  and 
articles  guide  the  reader  through  the  age  of  discovery  in  1979.  Topics  include:  Selecting 
Business  Software,  Microcomputer  Speech  and  Music,  Information  Networks,  interfacing 
techniques.  < 

Volumes 

Focuses  on  the  technological  promise  of  the  modern  microcomputer  and  the  creative  chal¬ 
lenge  facing  programmers  in  1980.  Topics  include:  The  revolutionary  impact  of  CP/M,  C 
programming  and  the  UNIX  operating  systems,  survey  of  computer  networks,  software 
portability,  introduction  to  FORTH,  compiler  writing.  , 

Volume  6 

The  microcomputer  enters  the  mainstream — these  issues  assess  the  revolutionary  impact  of 
microcomputers  on  the  individual  and  on  our  society  in  1981.  Topics  include:  Computer 
Conferencing,  The  Power  of  FORTH,  8-  and  16-Bit  Technology,  Rubik’s  Cube  Simulator, 
An  Adventure  Game  Development  System. 

Volume  7 

Examines  the  potential  of  powerful  16-bit  micros  including  the  birth  of  the  IBM  PC  ih 
1982.  Topics  include:  In-depth  coverage  of  FORTH,  68000,  8088  programming,  C  software 
tools,  utilities  for  CP/M  including  a  spelling  checker,  using  bulletin  boards,  and  more. 

Volume  8 

DDJ  turns  pro.  Some  of  the  most  powerful,  professional  programmer’s  tools  ever  published 
in  a  magazine  are  in  this  volume,  including  Small-C,  the  RED  editor,  and  an  Ada  subset. 

Volume  9 

Shaping  things  to  come.  In  1984  DDJ  examined  new  programming  environments:  Prolog, 
expert  systems,  Modula-2,  and  Pascal.  Other  topics  include  GREP,  Unix  internals,  and  two 
encryption  systems. 

Volume  10 

The  year  of  living  dangerously.  In  1985  DDJ  added  more  memory,  a  SCSI  port,  and  a  hard 
disk  to  the  Macintosh;  challenged  the  Unix  establishment  with  plans  for  a  free  Unix;  and 
asked  hard  questions  about  privacy  and  control  in  the  Information  Age.  Includes  software 
tools  in  C,  Modula-2,  Forth,  Pascal,  assembly  language,  and  Prolog. 


