home *** CD-ROM | disk | FTP | other *** search
/ QBasic & Borland Pascal & C / Delphi5.iso / Pascal / TP.7_1 / TP / EXAMPLES / TVDEMO / TVHC.PAS < prev    next >
Encoding:
Pascal/Delphi Source File  |  1992-10-05  |  31.5 KB  |  1,116 lines

  1. {************************************************}
  2. {                                                }
  3. {   Turbo Vision Demo                            }
  4. {   Copyright (c) 1992 by Borland International  }
  5. {                                                }
  6. {************************************************}
  7.  
  8. {===== TVHC version 1.1 ================================================}
  9. {  Turbo Vision help file compiler documentation.                       }
  10. {=======================================================================}
  11. {                                                                       }
  12. {    Refer to DEMOHELP.TXT for an example of a help source file.        }
  13. {                                                                       }
  14. {    This program takes a help script and produces a help file (.HLP)   }
  15. {    and a help context file (.PAS).  The format for the help file is   }
  16. {    very simple.  Each context is given a symbolic name (i.e FileOpen) }
  17. {    which is then put in the context file (i.e. hcFileOpen).  The text }
  18. {    following the topic line is put into the help file.  Since the     }
  19. {    help file can be resized, some of the text will need to be wrapped }
  20. {    to fit into the window.  If a line of text is flush left with      }
  21. {    no preceeding white space, the line will be wrapped.  All adjacent }
  22. {    wrappable lines are wrapped as a paragraph.  If a line begins with }
  23. {    a space it will not be wrapped. For example, the following is a    }
  24. {    help topic for a File|Open menu item.                              }
  25. {                                                                       }
  26. {       |.topic FileOpen                                                }
  27. {       |  File|Open                                                    }
  28. {       |  ---------                                                    }
  29. {       |This menu item will bring up a dialog...                       }
  30. {                                                                       }
  31. {    The "File|Open" will not be wrapped with the "----" line since     }
  32. {    they both begin with a space, but the "This menu..." line will     }
  33. {    be wrapped.                                                        }
  34. {      The syntax for a ".topic" line is:                               }
  35. {                                                                       }
  36. {        .topic symbol[=number][, symbol[=number][...]]                 }
  37. {                                                                       }
  38. {    Note a topic can have multiple symbols that define it so that one  }
  39. {    topic can be used by multiple contexts.  The number is optional    }
  40. {    and will be the value of the hcXXX context in the context file     }
  41. {    Once a number is assigned all following topic symbols will be      }
  42. {    assigned numbers in sequence.  For example,                        }
  43. {                                                                       }
  44. {       .topic FileOpen=3, OpenFile, FFileOpen                          }
  45. {                                                                       }
  46. {    will produce the follwing help context number definitions,         }
  47. {                                                                       }
  48. {        hcFileOpen  = 3;                                               }
  49. {        hcOpenFile  = 4;                                               }
  50. {        hcFFileOpen = 5;                                               }
  51. {                                                                       }
  52. {    Cross references can be imbedded in the text of a help topic which }
  53. {    allows the user to quickly access related topics.  The format for  }
  54. {    a cross reference is as follows,                                   }
  55. {                                                                       }
  56. (*        {text[:alias]}                                               *)
  57. {                                                                       }
  58. {    The text in the brackets is highlighted by the help viewer.  This  }
  59. {    text can be selected by the user and will take the user to the     }
  60. {    topic by the name of the text.  Sometimes the text will not be     }
  61. {    the same as a topic symbol.  In this case you can use the optional }
  62. {    alias syntax.  The symbol you wish to use is placed after the text }
  63. {    after a ':'. The following is a paragraph of text using cross      }
  64. {    references,                                                        }
  65. {                                                                       }
  66. (*      |The {file open dialog:FileOpen} allows you specify which      *)
  67. {       |file you wish to view.  If it also allow you to navigate       }
  68. {       |directories.  To change to a given directory use the           }
  69. (*      |{change directory dialog:ChDir}.                              *)
  70. {                                                                       }
  71. {    The user can tab or use the mouse to select more information about }
  72. {    the "file open dialog" or the "change directory dialog". The help  }
  73. {    compiler handles forward references so a topic need not be defined }
  74. {    before it is referenced.  If a topic is referenced but not         }
  75. {    defined, the compiler will give a warning but will still create a  }
  76. {    useable help file.  If the undefined reference is used, a message  }
  77. {    ("No help available...") will appear in the help window.           }
  78. {=======================================================================}
  79.  
  80. program TVHC;
  81.  
  82. {$S-}
  83.  
  84. {$M 8192,8192,655360}
  85.  
  86. uses Drivers, Objects, Dos, Strings, HelpFile;
  87.  
  88. {======================= File Management ===============================}
  89.  
  90. procedure Error(Text: String); forward;
  91.  
  92. type
  93.   PProtectedStream = ^TProtectedStream;
  94.   TProtectedStream = object(TBufStream)
  95.     FileName: FNameStr;
  96.     Mode: Word;
  97.     constructor Init(AFileName: FNameStr; AMode, Size: Word);
  98.     destructor Done; virtual;
  99.     procedure Error(Code, Info: Integer); virtual;
  100.   end;
  101.  
  102. var
  103.   TextStrm,
  104.   SymbStrm: TProtectedStream;
  105.  
  106. const
  107.   HelpStrm: PProtectedStream = nil;
  108.  
  109. constructor TProtectedStream.Init(AFileName: FNameStr; AMode, Size: Word);
  110. begin
  111.   inherited Init(AFileName, AMode, Size);
  112.   FileName := AFileName;
  113.   Mode := AMode;
  114. end;
  115.  
  116. destructor TProtectedStream.Done;
  117. var
  118.   F: File;
  119. begin
  120.   inherited Done;
  121.   if (Mode = stCreate) and ((Status <> stOk) or (ExitCode <> 0)) then
  122.   begin
  123.     Assign(F, FileName);
  124.     Erase(F);
  125.   end;
  126. end;
  127.  
  128. procedure TProtectedStream.Error(Code, Info: Integer);
  129. begin
  130.   case Code of
  131.     stError:
  132.       TVHC.Error('Error encountered in file ' + FileName);
  133.     stInitError:
  134.       if Mode = stCreate then
  135.         TVHC.Error('Could not create ' + FileName)
  136.       else
  137.         TVHC.Error('Could not find ' + FileName);
  138.     stReadError: Status := Code; {EOF is "ok"}
  139.     stWriteError:
  140.       TVHC.Error('Disk full encountered writting file '+ FileName);
  141.   else
  142.       TVHC.Error('Internal error.');
  143.   end;
  144. end;
  145.  
  146. {----- UpStr(Str) ------------------------------------------------------}
  147. {  Returns a string with Str uppercased.                }
  148. {-----------------------------------------------------------------------}
  149.  
  150. function UpStr(Str: String): String;
  151. var
  152.   I: Integer;
  153. begin
  154.   for I := 1 to Length(Str) do
  155.     Str[I] := UpCase(Str[I]);
  156.   UpStr := Str;
  157. end;
  158.  
  159. {----- ReplaceExt(FileName, NExt, Force) -------------------------------}
  160. {  Replace the extension of the given file with the given extension.    }
  161. {  If the an extension already exists Force indicates if it should be   }
  162. {  replaced anyway.                                                     }
  163. {-----------------------------------------------------------------------}
  164.  
  165. function ReplaceExt(FileName: PathStr; NExt: ExtStr; Force: Boolean):
  166.   PathStr;
  167. var
  168.   Dir: DirStr;
  169.   Name: NameStr;
  170.   Ext: ExtStr;
  171. begin
  172.   FileName := UpStr(FileName);
  173.   FSplit(FileName, Dir, Name, Ext);
  174.   if Force or (Ext = '') then
  175.     ReplaceExt := Dir + Name + NExt else
  176.     ReplaceExt := FileName;
  177. end;
  178.  
  179. {----- FExist(FileName) ------------------------------------------------}
  180. {  Returns true if the file exists false otherwise.                     }
  181. {-----------------------------------------------------------------------}
  182.  
  183. function FExists(FileName: PathStr): Boolean;
  184. var
  185.   F: file;
  186.   Attr: Word;
  187. begin
  188.   Assign(F, FileName);
  189.   GetFAttr(F, Attr);
  190.   FExists := DosError = 0;
  191. end;
  192.  
  193.  
  194. {======================== Line Management ==============================}
  195.  
  196. {----- GetLine(S) ------------------------------------------------------}
  197. {  Return the next line out of the stream.                              }
  198. {-----------------------------------------------------------------------}
  199.  
  200. const
  201.   Line: String = '';
  202.   LineInBuffer: Boolean = False;
  203.   Count: Integer = 0;
  204.  
  205. function GetLine(var S: TStream): String;
  206. var
  207.   C, I: Byte;
  208. begin
  209.   if S.Status <> stOk then
  210.   begin
  211.     GetLine := #26;
  212.     Exit;
  213.   end;
  214.   if not LineInBuffer then
  215.   begin
  216.     Line := '';
  217.     C := 0;
  218.     I := 0;
  219.     while (Line[I] <> #13) and (I < 254) and (S.Status = stOk) do
  220.     begin
  221.       Inc(I);
  222.       S.Read(Line[I], 1);
  223.     end;
  224.     Dec(I);
  225.     S.Read(C, 1); { Skip #10 }
  226.     Line[0] := Char(I);
  227.   end;
  228.   Inc(Count);
  229.  
  230.   { Return a blank line if the line is a comment }
  231.   if Line[1] = ';' then Line[0] := #0;
  232.  
  233.   GetLine := Line;
  234.   LineInBuffer := False;
  235. end;
  236.  
  237. {----- UnGetLine(S) ----------------------------------------------------}
  238. {  Return given line into the stream.                                   }
  239. {-----------------------------------------------------------------------}
  240.  
  241. procedure UnGetLine(S: String);
  242. begin
  243.   Line := S;
  244.   LineInBuffer := True;
  245.   Dec(Count);
  246. end;
  247.  
  248. {========================= Error routines ==============================}
  249.  
  250. {----- PrntMsg(Text) ---------------------------------------------------}
  251. {  Used by Error and Warning to print the message.                      }
  252. {-----------------------------------------------------------------------}
  253.  
  254. procedure PrntMsg(Pref: String; var Text: String);
  255. const
  256.   Blank: String[1] = '';
  257. var
  258.   S: String;
  259.   L: array[0..3] of LongInt;
  260. begin
  261.   L[0] := LongInt(@Pref);
  262.   if HelpStrm <> nil then
  263.     L[1] := LongInt(@HelpStrm^.FileName)
  264.   else
  265.     L[1] := LongInt(@Blank);
  266.   L[2] := Count;
  267.   L[3] := LongInt(@Text);
  268.   if Count > 0 then FormatStr(S, '%s: %s(%d): %s'#13#10, L)
  269.   else FormatStr(S, '%s: %s %3#%s', L);
  270.   PrintStr(S);
  271. end;
  272.  
  273. {----- Error(Text) -----------------------------------------------------}
  274. {  Used to indicate an error.  Terminates the program                   }
  275. {-----------------------------------------------------------------------}
  276.  
  277. procedure Error(Text: String);
  278. begin
  279.   PrntMsg('Error', Text);
  280.   Halt(1);
  281. end;
  282.  
  283. {----- Warning(Text) ---------------------------------------------------}
  284. {  Used to indicate an warning.                                         }
  285. {-----------------------------------------------------------------------}
  286.  
  287. procedure Warning(Text: String);
  288. begin
  289.   PrntMsg('Warning', Text);
  290. end;
  291.  
  292. {================ Built-in help context number managment ===============}
  293.  
  294. type
  295.   TBuiltInContext = record
  296.     Text: PChar;
  297.     Number: Word;
  298.   end;
  299.  
  300. { A list of all the help contexts defined in APP }
  301. const
  302.   BuiltInContextTable: array[0..21] of TBuiltInContext = (
  303.     (Text: 'Cascade';   Number: $FF21),
  304.     (Text: 'ChangeDir'; Number: $FF06),
  305.     (Text: 'Clear';     Number: $FF14),
  306.     (Text: 'Close';     Number: $FF27),
  307.     (Text: 'CloseAll';  Number: $FF22),
  308.     (Text: 'Copy';      Number: $FF12),
  309.     (Text: 'Cut';       Number: $FF11),
  310.     (Text: 'DosShell';  Number: $FF07),
  311.     (Text: 'Dragging';  Number: 1),
  312.     (Text: 'Exit';      Number: $FF08),
  313.     (Text: 'New';       Number: $FF01),
  314.     (Text: 'Next';      Number: $FF25),
  315.     (Text: 'Open';      Number: $FF02),
  316.     (Text: 'Paste';     Number: $FF13),
  317.     (Text: 'Prev';      Number: $FF26),
  318.     (Text: 'Resize';    Number: $FF23),
  319.     (Text: 'Save';      Number: $FF03),
  320.     (Text: 'SaveAll';   Number: $FF05),
  321.     (Text: 'SaveAs';    Number: $FF04),
  322.     (Text: 'Tile';      Number: $FF20),
  323.     (Text: 'Undo';      Number: $FF10),
  324.     (Text: 'Zoom';      Number: $FF24)
  325.     );
  326.  
  327. function IsBuiltInContext(Text: String; var Number: Word): Boolean;
  328. var
  329.   Hi, Lo, Mid, Cmp: Integer;
  330. begin
  331.   { Convert Text into a #0 terminted PChar }
  332.   Inc(Text[0]);
  333.   Text[Length(Text)] := #0;
  334.  
  335.   Hi := High(BuiltInContextTable);
  336.   Lo := Low(BuiltInContextTable);
  337.   while Lo <= Hi do
  338.   begin
  339.     Mid := (Hi + Lo) div 2;
  340.     Cmp := StrComp(@Text[1], BuiltInContextTable[Mid].Text);
  341.     if Cmp > 0 then
  342.       Lo := Mid + 1
  343.     else if Cmp < 0 then
  344.       Hi := Mid - 1
  345.     else
  346.     begin
  347.       Number := BuiltInContextTable[Mid].Number;
  348.       IsBuiltInContext := True;
  349.       Exit;
  350.     end;
  351.   end;
  352.   IsBuiltInContext := False;
  353. end;
  354.  
  355. {====================== Topic Reference Management =====================}
  356.  
  357. type
  358.   PFixUp = ^TFixUp;
  359.   TFixUp = record
  360.     Pos: LongInt;
  361.     Next: PFixUp;
  362.   end;
  363.  
  364.   PReference = ^TReference;
  365.   TReference = record
  366.     Topic: PString;
  367.     case Resolved: Boolean of
  368.       True:  (Value: Word);
  369.       False: (FixUpList: PFixUp);
  370.   end;
  371.  
  372.   PRefTable = ^TRefTable;
  373.   TRefTable = object(TSortedCollection)
  374.     function Compare(Key1, Key2: Pointer): Integer; virtual;
  375.     procedure FreeItem(Item: Pointer); virtual;
  376.     function GetReference(var Topic: String): PReference;
  377.     function KeyOf(Item: Pointer): Pointer; virtual;
  378.   end;
  379.  
  380. const
  381.   RefTable: PRefTable = nil;
  382.  
  383. procedure DisposeFixUps(P: PFixUp);
  384. var
  385.   Q: PFixUp;
  386. begin
  387.   while P <> nil do
  388.   begin
  389.     Q := P^.Next;
  390.     Dispose(P);
  391.     P := Q;
  392.   end;
  393. end;
  394.  
  395. {----- TRefTable -------------------------------------------------------}
  396. {  TRefTable is a collection of PReference's used as a symbol table.    }
  397. {  If the topic has not been seen, a forward reference is inserted and  }
  398. {  a fix-up list is started.  When the topic is seen all forward        }
  399. {  references are resolved.  If the topic has been seen already the     }
  400. {  value it has is used.                                                }
  401. {-----------------------------------------------------------------------}
  402.  
  403. function TRefTable.Compare(Key1, Key2: Pointer): Integer;
  404. var
  405.   K1,K2: String;
  406. begin
  407.   K1 := UpStr(PString(Key1)^);
  408.   K2 := UpStr(PString(Key2)^);
  409.   if K1 > K2 then Compare := 1
  410.   else if K1 < K2 then Compare := -1
  411.   else Compare := 0;
  412. end;
  413.  
  414. procedure TRefTable.FreeItem(Item: Pointer);
  415. var
  416.   Ref: PReference absolute Item;
  417.   P, Q: PFixUp;
  418. begin
  419.   if not Ref^.Resolved then DisposeFixUps(Ref^.FixUpList);
  420.   DisposeStr(Ref^.Topic);
  421.   Dispose(Ref);
  422. end;
  423.  
  424. function TRefTable.GetReference(var Topic: String): PReference;
  425. var
  426.   Ref: PReference;
  427.   I: Integer;
  428. begin
  429.   if Search(@Topic, I) then
  430.     Ref := At(I)
  431.   else
  432.   begin
  433.     New(Ref);
  434.     Ref^.Topic := NewStr(Topic);
  435.     Ref^.Resolved := False;
  436.     Ref^.FixUpList := nil;
  437.     Insert(Ref);
  438.   end;
  439.   GetReference := Ref;
  440. end;
  441.  
  442. function TRefTable.KeyOf(Item: Pointer): Pointer;
  443. begin
  444.   KeyOf := PReference(Item)^.Topic;
  445. end;
  446.  
  447. {----- InitRefTable ----------------------------------------------------}
  448. {  Make sure the reference table is initialized.                        }
  449. {-----------------------------------------------------------------------}
  450.  
  451. procedure InitRefTable;
  452. begin
  453.   if RefTable = nil then
  454.     RefTable := New(PRefTable, Init(5,5));
  455. end;
  456.  
  457. {----- RecordReference -------------------------------------------------}
  458. {  Record a reference to a topic to the given stream.  This routine     }
  459. {  handles forward references.                                          }
  460. {-----------------------------------------------------------------------}
  461.  
  462. procedure RecordReference(var Topic: String; var S: TStream);
  463. var
  464.   I: Integer;
  465.   Ref: PReference;
  466.   FixUp: PFixUp;
  467. begin
  468.   InitRefTable;
  469.   Ref := RefTable^.GetReference(Topic);
  470.   if Ref^.Resolved then
  471.     S.Write(Ref^.Value, SizeOf(Ref^.Value))
  472.   else
  473.   begin
  474.     New(FixUp);
  475.     FixUp^.Pos := S.GetPos;
  476.     I := -1;
  477.     S.Write(I, SizeOf(I));
  478.     FixUp^.Next := Ref^.FixUpList;
  479.     Ref^.FixUpList := FixUp;
  480.   end;
  481. end;
  482.  
  483. {----- ResolveReference ------------------------------------------------}
  484. {  Resolve a reference to a topic to the given stream.  This routine    }
  485. {  handles forward references.                                          }
  486. {-----------------------------------------------------------------------}
  487.  
  488. procedure ResolveReference(var Topic: String; Value: Word; var S: TStream);
  489. var
  490.   I: Integer;
  491.   Ref: PReference;
  492.  
  493. procedure DoFixUps(P: PFixUp);
  494. var
  495.   Pos: LongInt;
  496. begin
  497.   Pos := S.GetPos;
  498.   while P <> nil do
  499.   begin
  500.     S.Seek(P^.Pos);
  501.     S.Write(Value, SizeOf(Value));
  502.     P := P^.Next;
  503.   end;
  504.   S.Seek(Pos);
  505. end;
  506.  
  507. begin
  508.   InitRefTable;
  509.   Ref := RefTable^.GetReference(Topic);
  510.   if Ref^.Resolved then
  511.     Error('Redefinition of ' + Ref^.Topic^)
  512.   else
  513.   begin
  514.     DoFixUps(Ref^.FixUpList);
  515.     DisposeFixUps(Ref^.FixUpList);
  516.     Ref^.Resolved := True;
  517.     Ref^.Value := Value;
  518.   end;
  519. end;
  520.  
  521. {======================== Help file parser =============================}
  522.  
  523. {----- GetWord ---------------------------------------------------------}
  524. {   Extract the next word from the given line at offset I.              }
  525. {-----------------------------------------------------------------------}
  526.  
  527. function GetWord(var Line: String; var I: Integer): String;
  528. var
  529.   J: Integer;
  530. const
  531.   WordChars = ['A'..'Z','a'..'z','0'..'9','_'];
  532.  
  533. procedure SkipWhite;
  534. begin
  535.   while (I <= Length(Line)) and (Line[I] = ' ') or (Line[I] = #8) do
  536.     Inc(I);
  537. end;
  538.  
  539. procedure SkipToNonWord;
  540. begin
  541.   while (I <= Length(Line)) and (Line[I] in WordChars) do Inc(I);
  542. end;
  543.  
  544. begin
  545.   SkipWhite;
  546.   J := I;
  547.   if J > Length(Line) then GetWord := ''
  548.   else
  549.   begin
  550.     Inc(I);
  551.     if Line[J] in WordChars then SkipToNonWord;
  552.     GetWord := Copy(Line, J, I - J);
  553.   end;
  554. end;
  555.  
  556. {----- TopicDefinition -------------------------------------------------}
  557. {  Extracts the next topic definition from the given line at I.         }
  558. {-----------------------------------------------------------------------}
  559.  
  560. type
  561.   PTopicDefinition = ^TTopicDefinition;
  562.   TTopicDefinition = object(TObject)
  563.     Topic: PString;
  564.     Value: Word;
  565.     Next: PTopicDefinition;
  566.     constructor Init(var ATopic: String; AValue: Word);
  567.     destructor Done; virtual;
  568.   end;
  569.  
  570. constructor TTopicDefinition.Init(var ATopic: String; AValue: Word);
  571. begin
  572.   Topic := NewStr(ATopic);
  573.   Value := AValue;
  574.   Next := nil;
  575. end;
  576.  
  577. destructor TTopicDefinition.Done;
  578. begin
  579.   DisposeStr(Topic);
  580.   if Next <> nil then Dispose(Next, Done);
  581. end;
  582.  
  583. function TopicDefinition(var Line: String; var I: Integer): PTopicDefinition;
  584. var
  585.   J,K: Integer;
  586.   TopicDef: PTopicDefinition;
  587.   Value: Word;
  588.   Topic, W: String;
  589.   HelpNumber: Word;
  590. const
  591.   HelpCounter: Word = 2; {1 is hcDragging}
  592. begin
  593.   Topic := GetWord(Line, I);
  594.   if Topic = '' then
  595.   begin
  596.     Error('Expected topic definition');
  597.     TopicDefinition := nil;
  598.   end
  599.   else
  600.   begin
  601.     J := I;
  602.     W := GetWord(Line, J);
  603.     if W = '=' then
  604.     begin
  605.       I := J;
  606.       W := GetWord(Line, I);
  607.       Val(W, J, K);
  608.       if K <> 0 then Error('Expected numeric')
  609.       else
  610.       begin
  611.         HelpCounter := J;
  612.         HelpNumber := J;
  613.       end
  614.     end
  615.     else
  616.       if not IsBuiltInContext(Topic, HelpNumber) then
  617.       begin
  618.         Inc(HelpCounter);
  619.         HelpNumber := HelpCounter;
  620.       end;
  621.     TopicDefinition := New(PTopicDefinition, Init(Topic, HelpNumber));
  622.   end;
  623. end;
  624.  
  625. {----- TopicDefinitionList----------------------------------------------}
  626. {  Extracts a list of topic definitions from the given line at I.       }
  627. {-----------------------------------------------------------------------}
  628.  
  629. function TopicDefinitionList(var Line: String; var I: Integer):
  630.   PTopicDefinition;
  631. var
  632.   J: Integer;
  633.   W: String;
  634.   TopicList, P: PTopicDefinition;
  635. begin
  636.   J := I;
  637.   TopicList := nil;
  638.   repeat
  639.     I := J;
  640.     P := TopicDefinition(Line, I);
  641.     if P = nil then
  642.     begin
  643.       if TopicList <> nil then Dispose(TopicList, Done);
  644.       TopicDefinitionList := nil;
  645.       Exit;
  646.     end;
  647.     P^.Next := TopicList;
  648.     TopicList := P;
  649.     J := I;
  650.     W := GetWord(Line, J);
  651.   until W <> ',';
  652.   TopicDefinitionList := TopicList;
  653. end;
  654.  
  655. {----- TopicHeader -----------------------------------------------------}
  656. {  Parse a the Topic header                                             }
  657. {-----------------------------------------------------------------------}
  658.  
  659. const
  660.   CommandChar = '.';
  661.  
  662. function TopicHeader(var Line: String): PTopicDefinition;
  663. var
  664.   I,J: Integer;
  665.   W: String;
  666.   TopicDef: PTopicDefinition;
  667.  
  668. begin
  669.   I := 1;
  670.   W := GetWord(Line, I);
  671.   if W <> CommandChar then
  672.   begin
  673.     TopicHeader := nil;
  674.     Exit;
  675.   end;
  676.   W := UpStr(GetWord(Line, I));
  677.   if W = 'TOPIC' then
  678.     TopicHeader := TopicDefinitionList(Line, I)
  679.   else
  680.   begin
  681.     Error('TOPIC expected');
  682.     TopicHeader := nil;
  683.   end;
  684. end;
  685.  
  686. {----- ReadParagraph ---------------------------------------------------}
  687. { Read a paragraph of the screen.  Returns the paragraph or nil if the  }
  688. { paragraph was not found in the given stream.  Searches for cross      }
  689. { references and updates the XRefs variable.                            }
  690. {-----------------------------------------------------------------------}
  691. type
  692.   PCrossRefNode = ^TCrossRefNode;
  693.   TCrossRefNode = record
  694.     Topic: PString;
  695.     Offset: Integer;
  696.     Length: Byte;
  697.     Next: PCrossRefNode;
  698.   end;
  699. const
  700.   BufferSize = 4096;
  701. var
  702.   Buffer: array[0..BufferSize-1] of Byte;
  703.   Ofs: Integer;
  704.  
  705. function ReadParagraph(var TextFile: TStream; var XRefs: PCrossRefNode;
  706.  var Offset: Integer): PParagraph;
  707. var
  708.   Line: String;
  709.   State: (Undefined, Wrapping, NotWrapping);
  710.   P: PParagraph;
  711.  
  712. procedure CopyToBuffer(var Line: String; Wrapping: Boolean); assembler;
  713. asm
  714.         PUSH    DS
  715.         CLD
  716.         PUSH    DS
  717.         POP     ES
  718.         MOV     DI,OFFSET Buffer
  719.         ADD     DI,Ofs
  720.         LDS     SI,Line
  721.         LODSB
  722.         XOR     AH,AH
  723.         ADD     ES:Ofs,AX
  724.         XCHG    AX,CX
  725.         REP     MOVSB
  726.         XOR     AL,AL
  727.         TEST    Wrapping,1      { Only add a #13, line terminator, if not }
  728.         JE      @@1             { currently wrapping the text. Otherwise  }
  729.         MOV     AL,' '-13       { add a ' '.                              }
  730. @@1:    ADD     AL,13
  731. @@2:    STOSB
  732.         POP     DS
  733.         INC     Ofs
  734. end;
  735.  
  736. procedure AddToBuffer(var Line: String; Wrapping: Boolean);
  737. begin
  738.   if Length(Line) + Ofs > BufferSize - 1 then
  739.     Error('Topic too large.')
  740.   else
  741.     CopyToBuffer(Line, Wrapping);
  742. end;
  743.  
  744. procedure ScanForCrossRefs(var Line: String);
  745. var
  746.   I, BegPos, EndPos, Alias: Integer;
  747. const
  748.   BegXRef = '{';
  749.   EndXRef = '}';
  750.   AliasCh = ':';
  751.  
  752. procedure AddXRef(XRef: String; Offset: Integer; Length: Byte);
  753. var
  754.   P: PCrossRefNode;
  755.   PP: ^PCrossRefNode;
  756. begin
  757.   New(P);
  758.   P^.Topic := NewStr(XRef);
  759.   P^.Offset := Offset;
  760.   P^.Length := Length;
  761.   P^.Next := nil;
  762.   PP := @XRefs;
  763.   while PP^ <> nil do
  764.     PP := @PP^^.Next;
  765.   PP^ := P;
  766. end;
  767.  
  768. procedure ReplaceSpacesWithFF(var Line: String; Start: Integer;
  769.   Length: Byte);
  770. var
  771.   I: Integer;
  772. begin
  773.   for I := Start to Start + Length do
  774.     if Line[I] = ' ' then Line[I] := #$FF;
  775. end;
  776.  
  777. begin
  778.   I := 1;
  779.   repeat
  780.     BegPos := Pos(BegXRef, Copy(Line, I, 255));
  781.     if BegPos = 0 then I := 0
  782.     else
  783.     begin
  784.       Inc(I, BegPos);
  785.       if Line[I] = BegXRef then
  786.       begin
  787.         Delete(Line, I, 1);
  788.         Inc(I);
  789.       end
  790.       else
  791.       begin
  792.         EndPos := Pos(EndXRef, Copy(Line, I, 255));
  793.         if EndPos = 0 then
  794.         begin
  795.           Error('Unterminated topic reference.');
  796.           Inc(I);
  797.         end
  798.         else
  799.         begin
  800.           Alias := Pos(AliasCh, Copy(Line, I, 255));
  801.           if (Alias = 0) or (Alias > EndPos) then
  802.             AddXRef(Copy(Line, I, EndPos - 1), Offset + Ofs + I - 1, EndPos - 1)
  803.           else
  804.           begin
  805.             AddXRef(Copy(Line, I + Alias, EndPos - Alias - 1),
  806.               Offset + Ofs + I - 1, Alias - 1);
  807.             Delete(Line, I + Alias - 1, EndPos - Alias);
  808.             EndPos := Alias;
  809.           end;
  810.           ReplaceSpacesWithFF(Line, I, EndPos-1);
  811.           Delete(Line, I + EndPos - 1, 1);
  812.           Delete(Line, I - 1, 1);
  813.           Inc(I, EndPos - 2);
  814.         end;
  815.       end;
  816.     end;
  817.   until I = 0;
  818. end;
  819.  
  820. function IsEndParagraph: Boolean;
  821. begin
  822.   IsEndParagraph :=
  823.      (Line = '') or
  824.      (Line[1] = CommandChar) or
  825.      (Line = #26) or
  826.      ((Line[1] =  ' ') and (State = Wrapping)) or
  827.      ((Line[1] <> ' ') and (State = NotWrapping));
  828. end;
  829.  
  830. begin
  831.   Ofs := 0;
  832.   ReadParagraph := nil;
  833.   State := Undefined;
  834.   Line := GetLine(TextFile);
  835.   while Line = '' do
  836.   begin
  837.     AddToBuffer(Line, State = Wrapping);
  838.     Line := GetLine(TextFile);
  839.   end;
  840.  
  841.   if IsEndParagraph then
  842.   begin
  843.     ReadParagraph := nil;
  844.     UnGetLine(Line);
  845.     Exit;
  846.   end;
  847.   while not IsEndParagraph do
  848.   begin
  849.     if State = Undefined then
  850.       if Line[1] = ' ' then State := NotWrapping
  851.       else State := Wrapping;
  852.     ScanForCrossRefs(Line);
  853.     AddToBuffer(Line, State = Wrapping);
  854.     Line := GetLine(TextFile);
  855.   end;
  856.   UnGetLine(Line);
  857.   GetMem(P, SizeOf(P^) + Ofs);
  858.   P^.Size := Ofs;
  859.   P^.Wrap := State = Wrapping;
  860.   Move(Buffer, P^.Text, Ofs);
  861.   Inc(Offset, Ofs);
  862.   ReadParagraph := P;
  863. end;
  864.  
  865. {----- ReadTopic -------------------------------------------------------}
  866. { Read a topic from the source file and write it to the help file       }
  867. {-----------------------------------------------------------------------}
  868. var
  869.   XRefs: PCrossRefNode;
  870.  
  871. procedure HandleCrossRefs(var S: TStream; XRefValue: Integer); far;
  872. var
  873.   P: PCrossRefNode;
  874. begin
  875.   P := XRefs;
  876.   while XRefValue > 1 do
  877.   begin
  878.     if P <> nil then P := P^.Next;
  879.     Dec(XRefValue);
  880.   end;
  881.   if P <> nil then RecordReference(P^.Topic^, S);
  882. end;
  883.  
  884. procedure ReadTopic(var TextFile: TStream; var HelpFile: THelpFile);
  885. var
  886.   Line: String;
  887.   P: PParagraph;
  888.   Topic: PHelpTopic;
  889.   TopicDef: PTopicDefinition;
  890.   I, J, Offset: Integer;
  891.   Ref: TCrossRef;
  892.   RefNode: PCrossRefNode;
  893.  
  894. procedure SkipBlankLines(var S: TStream);
  895. var
  896.   Line: String;
  897. begin
  898.   Line := '';
  899.   while Line = '' do
  900.     Line := GetLine(S);
  901.   UnGetLine(Line);
  902. end;
  903.  
  904. function XRefCount: Integer;
  905. var
  906.   I: Integer;
  907.   P: PCrossRefNode;
  908. begin
  909.   I := 0;
  910.   P := XRefs;
  911.   while P <> nil do
  912.   begin
  913.     Inc(I);
  914.     P := P^.Next;
  915.   end;
  916.   XRefCount := I;
  917. end;
  918.  
  919. procedure DisposeXRefs(P: PCrossRefNode);
  920. var
  921.   Q: PCrossRefNode;
  922. begin
  923.   while P <> nil do
  924.   begin
  925.     Q := P;
  926.     P := P^.Next;
  927.     if Q^.Topic <> nil then DisposeStr(Q^.Topic);
  928.     Dispose(Q);
  929.   end;
  930. end;
  931.  
  932. procedure RecordTopicDefinitions(P: PTopicDefinition);
  933. begin
  934.   while P <> nil do
  935.   begin
  936.     ResolveReference(P^.Topic^, P^.Value, HelpFile.Stream^);
  937.     HelpFile.RecordPositionInIndex(P^.Value);
  938.     P := P^.Next;
  939.   end;
  940. end;
  941.  
  942. begin
  943.   { Get Screen command }
  944.   SkipBlankLines(TextFile);
  945.   Line := GetLine(TextFile);
  946.  
  947.   TopicDef := TopicHeader(Line);
  948.  
  949.   Topic := New(PHelpTopic, Init);
  950.  
  951.   { Read paragraphs }
  952.   XRefs := nil;
  953.   Offset := 0;
  954.   P := ReadParagraph(TextFile, XRefs, Offset);
  955.   while P <> nil do
  956.   begin
  957.     Topic^.AddParagraph(P);
  958.     P := ReadParagraph(TextFile, XRefs, Offset);
  959.   end;
  960.  
  961.   I := XRefCount;
  962.   Topic^.SetNumCrossRefs(I);
  963.   RefNode := XRefs;
  964.   for J := 1 to I do
  965.   begin
  966.     Ref.Offset := RefNode^.Offset;
  967.     Ref.Length := RefNode^.Length;
  968.     Ref.Ref := J;
  969.     Topic^.SetCrossRef(J, Ref);
  970.     RefNode := RefNode^.Next;
  971.   end;
  972.  
  973.   RecordTopicDefinitions(TopicDef);
  974.  
  975.   CrossRefHandler := HandleCrossRefs;
  976.   HelpFile.PutTopic(Topic);
  977.  
  978.   if Topic <> nil then Dispose(Topic, Done);
  979.   if TopicDef <> nil then Dispose(TopicDef, Done);
  980.   DisposeXRefs(XRefs);
  981.  
  982.   SkipBlankLines(TextFile);
  983. end;
  984.  
  985. {----- WriteSymbFile ---------------------------------------------------}
  986. { Write the .PAS file containing all screen titles as constants.        }
  987. {-----------------------------------------------------------------------}
  988.  
  989. procedure WriteSymbFile(var SymbFile: TProtectedStream);
  990. const
  991.   HeaderText1 =
  992.     'unit ';
  993.   HeaderText2 =
  994.     ';'#13#10 +
  995.     #13#10 +
  996.     'interface'#13#10 +
  997.     #13#10 +
  998.     'const'#13#10 +
  999.     #13#10;
  1000.   FooterText =
  1001.     #13#10 +
  1002.     'implementation'#13#10 +
  1003.     #13#10 +
  1004.     'end.'#13#10;
  1005.   Header1: array[1..Length(HeaderText1)] of Char = HeaderText1;
  1006.   Header2: array[1..Length(HeaderText2)] of Char = HeaderText2;
  1007.   Footer: array[1..Length(FooterText)] of Char = FooterText;
  1008. var
  1009.   I, Count: Integer;
  1010.   Dir: DirStr;
  1011.   Name: NameStr;
  1012.   Ext: ExtStr;
  1013.  
  1014. procedure DoWriteSymbol(P: PReference); far;
  1015. var
  1016.   L: array[0..1] of LongInt;
  1017.   Line: String;
  1018.   I: Word;
  1019. begin
  1020.   if (P^.Resolved) then
  1021.   begin
  1022.     if not IsBuiltInContext(P^.Topic^, I) then
  1023.     begin
  1024.       L[0] := LongInt(P^.Topic);
  1025.       L[1] := P^.Value;
  1026.       FormatStr(Line, '  hc%-20s = %d;'#13#10, L);
  1027.       SymbFile.Write(Line[1], Length(Line));
  1028.     end
  1029.   end
  1030.   else Warning('Unresolved forward reference "' + P^.Topic^ + '"');
  1031. end;
  1032.  
  1033. begin
  1034.   SymbFile.Write(Header1, SizeOf(Header1));
  1035.   FSplit(SymbFile.FileName, Dir, Name, Ext);
  1036.   SymbFile.Write(Name[1], Length(Name));
  1037.   SymbFile.Write(Header2, SizeOf(Header2));
  1038.  
  1039.   RefTable^.ForEach(@DoWriteSymbol);
  1040.  
  1041.   SymbFile.Write(Footer, SizeOf(Footer));
  1042. end;
  1043.  
  1044. {----- ProcessText -----------------------------------------------------}
  1045. { Compile the given stream, and output a help file.                     }
  1046. {-----------------------------------------------------------------------}
  1047.  
  1048. procedure ProcessText(var TextFile, HelpFile, SymbFile: TProtectedStream);
  1049. var
  1050.   HelpRez: THelpFile;
  1051. begin
  1052.   HelpRez.Init(@HelpFile);
  1053.   while TextFile.Status = stOk do
  1054.     ReadTopic(TextFile, HelpRez);
  1055.   WriteSymbFile(SymbFile);
  1056.   HelpRez.Done;
  1057. end;
  1058.  
  1059. {========================== Program Block ==========================}
  1060.  
  1061. var
  1062.   TextName,
  1063.   HelpName,
  1064.   SymbName: PathStr;
  1065.  
  1066. procedure ExitClean; far;
  1067. begin
  1068.   { Print a message if an out of memory error encountered }
  1069.   if ExitCode = 201 then
  1070.   begin
  1071.     Writeln('Error: Out of memory.');
  1072.     ErrorAddr := nil;
  1073.     ExitCode := 1;
  1074.   end;
  1075.  
  1076.   { Clean up files }
  1077.   TextStrm.Done;
  1078.   SymbStrm.Done;
  1079. end;
  1080.  
  1081. begin
  1082.   { Banner messages }
  1083.   PrintStr('Help Compiler  Version 1.1  Copyright (c) 1992 Borland International.'#13#10);
  1084.   if ParamCount < 1 then
  1085.   begin
  1086.     PrintStr(
  1087.       #13#10 +
  1088.       '  Syntax:  TVHC <Help text>[.TXT] [<Help file>[.HLP] [<Symbol file>[.PAS]]'#13#10 +
  1089.       #13#10+
  1090.       '     Help text   = Help file source'#13#10 +
  1091.       '     Help file   = Compiled help file'#13#10 +
  1092.       '     Symbol file = A Pascal file containing all the screen names as CONST''s'#13#10);
  1093.     Halt(0);
  1094.   end;
  1095.  
  1096.   { Calculate file names }
  1097.   TextName := ReplaceExt(ParamStr(1), '.TXT', False);
  1098.   if not FExists(TextName) then
  1099.     Error('File "' + TextName + '" not found.');
  1100.   if ParamCount >= 2 then
  1101.     HelpName := ReplaceExt(ParamStr(2), '.HLP', False) else
  1102.     HelpName := ReplaceExt(TextName, '.HLP',  True);
  1103.   if ParamCount >= 3 then
  1104.     SymbName := ReplaceExt(ParamStr(3), '.PAS', False) else
  1105.     SymbName := ReplaceExt(HelpName, '.PAS', True);
  1106.  
  1107.   ExitProc := @ExitClean;
  1108.  
  1109.   RegisterHelpFile;
  1110.  
  1111.   TextStrm.Init(TextName, stOpenRead, 1024);
  1112.   SymbStrm.Init(SymbName, stCreate,   1024);
  1113.   HelpStrm := New(PProtectedStream, Init(HelpName, stCreate, 1024));
  1114.   ProcessText(TextStrm, HelpStrm^, SymbStrm);
  1115. end.
  1116.