home *** CD-ROM | disk | FTP | other *** search
/ The World of Computer Software / World_Of_Computer_Software-02-385-Vol-1of3.iso / s / scrmgr.zip / SCRPRIM.PAS < prev   
Pascal/Delphi Source File  |  1992-11-04  |  11KB  |  419 lines

  1. {$A+,F+,R-,S-,V-,X+}
  2.  
  3. {***************************************************}
  4. {*                SCRPRIM.PAS 1.00                 *}
  5. {*        Primitives for Script management         *}
  6. {*      Steve Sneed, TurboPower Software 1992      *}
  7. {*          Released to the public domain          *}
  8. {*  Requires TP6 & Object Professional to compile  *}
  9. {***************************************************}
  10.  
  11. (*
  12.  
  13.   This unit implements a simple script language manager.  Based on the script
  14.   parser in OzCIS, it is built on the "one statement per line" principle,
  15.   similar to the DOS batch language.  It's overall design allows much more
  16.   complex script parsers to be developed, however.
  17.  
  18.   In a nutshell: the entire script file is loaded into a DoubleList object.
  19.   Script execution starts at the head of the list by retrieving the first
  20.   active line and parsing it, and repeating the process as needed.  Labels
  21.   are declared as in the DOS batch language, a word or phrase (no spaces) that
  22.   starts with a colon (:), on a line by itself.  The JUMP (GOTO) command scans
  23.   the list looking for the appropriate label, and when found, resets CurP to
  24.   that line and continues.
  25.  
  26.   GOSUB works differently: each node of the list contains a byte variable
  27.   (Level) that is initialized to 0.  The list object itself also has a byte
  28.   field (CurLevel) that is initialized to 0.  When a GOSUB command is
  29.   encountered, CurLevel is incremented and the GOSUB command line's Level
  30.   variable is set to CurLevel, and the list is searched for the GOSUB label
  31.   using the same logic as GOTO.  When a RETURN command is encountered, the
  32.   list is scanned for a node with a Level value = CurLevel; if found, CurP is
  33.   set to that node, the node's Level variable is reset to 0 and CurLevel is
  34.   decremented.  This scheme allows up to 255 nested levels of GOSUB and an
  35.   unlimited number of GOSUBs within one script.
  36.  
  37.   With this engine, one important virtual method must be overridden: the
  38.   ProcessLine function.  The script manager gets each line of the script in
  39.   turn and calls ProcessLine, passing the line in question.  ProcessLine's
  40.   job is to do whatever the line commands in your script language, returning
  41.   True if successful or False if the script should abort.  Under this system
  42.   your language can be a very simplistic, one-statement-per-line scheme or a
  43.   more complex token-based language; what and how the parser does it's actual
  44.   work is up to you.
  45.  
  46. *)
  47.  
  48. unit ScrPrim;  {primitives for script file management}
  49.  
  50. interface
  51.  
  52. uses
  53.   DOS,
  54.   OpInline,
  55.   OpDos,
  56.   OpRoot,
  57.   OpString;
  58.  
  59. const
  60.   {command constants}
  61.   scGoTo   = 997;
  62.   scGoSub  = 998;
  63.   scReturn = 999;
  64.  
  65. const
  66.   CommentChar = ';';
  67.  
  68. type
  69.   {one line of a script}
  70.   PStr = ^String;
  71.   PLine = ^SLine;
  72.   SLine =
  73.     object(DoubleListNode)
  74.       LP    : PStr;
  75.       Level : Byte;
  76.  
  77.       constructor Init(S : String);
  78.       destructor Done; virtual;
  79.     end;
  80.  
  81.   {our script manager}
  82.   ScriptPtr = ^ScriptMgr;
  83.   ScriptMgr =
  84.     object(DoubleList)
  85.       CurP       : PLine;
  86.       CurLevel   : Byte;
  87.       Running    : Boolean;
  88.  
  89.       {...initialization and virtual methods}
  90.       constructor Init;
  91.         {-instantiate our script manager object}
  92.       destructor Done; virtual;
  93.         {-dispose of object when done}
  94.       function ProcessLine(S : String) : Boolean; virtual;
  95.         {-process a line of the script. *MUST BE OVERRIDDEN*}
  96.       procedure Process; virtual;
  97.         {-run the script}
  98.       procedure PrepareLine(var S : String); virtual;
  99.         {-allows pre-processing a line before adding it to the list}
  100.  
  101.       {...other public methods}
  102.       function LoadScript(FN : PathStr) : Boolean;
  103.         {-load a script file into the manager}
  104.       function LoadSubScript(FN : PathStr) : Boolean;
  105.         {-load a secondary script and GOSUB to it}
  106.       function Jump(S : PathStr) : Boolean;
  107.         {-jump (GOTO) to label S}
  108.       function GoSub(S : PathStr) : Boolean;
  109.         {-jump to a label with provision for returning later}
  110.       function Return : Boolean;
  111.         {-return from a GOSUB}
  112.  
  113.       {...private methods}
  114.       procedure NextActive;
  115.       function FindLabel(S : PathStr) : PLine;
  116.     end;
  117.  
  118.  
  119. implementation
  120.  
  121.   procedure CleanCmts(var S : String);
  122.   {-removes trailing comments from a line}
  123.   var
  124.     B : Byte;
  125.   begin
  126.     B := Length(S);
  127.     while B > 0 do begin
  128.       {exit if we've received a quote char}
  129.       if (S[b] = '"') or (S[b] = #39) then
  130.         exit;
  131.       {if we've found the comment marker, remove the rest of the line and go}
  132.       if (S[b] = CommentChar) then begin
  133.         S[0] := Chr(B-1);
  134.         S := Trim(S);
  135.         exit;
  136.       end;
  137.       Dec(B);
  138.     end;
  139.   end;
  140.  
  141. {--- Script line node methods ---}
  142.  
  143.   constructor SLine.Init(S : String);
  144.   begin
  145.     if not DoubleListNode.Init then Fail;
  146.     LP := PStr(StringToHeap(S));
  147.     if LP = nil then Fail;
  148.   end;
  149.  
  150.   destructor SLine.Done;
  151.   begin
  152.     DisposeString(Pointer(LP));
  153.   end;
  154.  
  155. {--- ScriptMgr methods ---}
  156.  
  157.   constructor ScriptMgr.Init;
  158.   begin
  159.     if not DoubleList.Init then Fail;
  160.  
  161.     {init our internal vars}
  162.     CurLevel := 0;
  163.     CurP := nil;
  164.     Running := False;
  165.   end;
  166.  
  167.   destructor ScriptMgr.Done;
  168.   begin
  169.     DoubleList.Done;
  170.   end;
  171.  
  172.   function ScriptMgr.ProcessLine(S : String) : Boolean;
  173.   {-method to actually process the script line.  Returns false to abort script.}
  174.   { *MUST BE OVERRIDDEN*}
  175.   begin
  176.     RunError(211);
  177.   end;
  178.  
  179.   procedure ScriptMgr.PrepareLine(var S : String);
  180.   {-perform pre-processing on line before adding it to the list.  Allows}
  181.   { comments removal, etc.}
  182.   begin
  183.     S := Trim(S);
  184.     CleanCmts(S);
  185.   end;
  186.  
  187.   function ScriptMgr.LoadScript(FN : PathStr) : Boolean;
  188.   {-load a script into our manager.  Returns false on error}
  189.   label
  190.     Breakout;
  191.   var
  192.     F : Text;
  193.     S : String;
  194.     P : PLine;
  195.   begin
  196.     {assume failure}
  197.     LoadScript := False;
  198.  
  199.     Assign(F, FN);
  200.     Reset(F);
  201.     if IOResult <> 0 then exit;
  202.  
  203.     {create a dummy label to start the script}
  204.     New(P, Init(':HEAD_OF_SCRIPT'));
  205.     if P = nil then goto Breakout;
  206.     Append(P);
  207.  
  208.     {read in the file}
  209.     while not EOF(F) do begin
  210.       ReadLn(F, S);
  211.       if IOResult <> 0 then goto Breakout;
  212.  
  213.       {pre-process line}
  214.       PrepareLine(S);
  215.  
  216.       {make sure it's a valid line}
  217.       if (S <> '') then begin
  218.         {if this is a label line, upcase it for later}
  219.         if S[1] = ':' then
  220.           S := StUpcase(S);
  221.  
  222.         {add it to the list}
  223.         New(P, Init(S));
  224.         if P = nil then goto Breakout;
  225.         Append(P);
  226.       end;
  227.     end;
  228.  
  229.     {script loaded, initialize the current-line pointer}
  230.     CurP := Pline(Head);
  231.     LoadScript := True;
  232.  
  233. Breakout:
  234.     Close(F);
  235.     if IOResult = 0 then ;
  236.   end;
  237.  
  238.   function ScriptMgr.LoadSubScript(FN : PathStr) : Boolean;
  239.   {-Loads a secondary script onto the end of the first and GOSUBS to it}
  240.   label
  241.     Breakout;
  242.   var
  243.     F : Text;
  244.     S : String;
  245.     P, Q : PLine;
  246.   begin
  247.     LoadSubScript := False;
  248.     {if we're maxed on GOSUB levels, fail}
  249.     if CurLevel = 255 then exit;
  250.  
  251.     {generate a label using the file's name}
  252.     S := JustFileName(FN);
  253.     S := StUpCase(S);
  254.     S := ':' + S;
  255.  
  256.     {see if said label is already in file...}
  257.     P := FindLabel(S);
  258.     if P <> nil then begin
  259.       {this secondary script is already loaded, just jump to it}
  260.       Inc(CurLevel);
  261.       CurP^.Level := CurLevel;
  262.       CurP := P;
  263.       LoadSubScript := True;
  264.       exit;
  265.     end;
  266.  
  267.     Assign(F, FN);
  268.     Reset(F);
  269.     if IOResult <> 0 then exit;
  270.  
  271.     {add the new label to the list}
  272.     New(P, Init(S));
  273.     if P = nil then goto Breakout;
  274.     Append(P);
  275.  
  276.     {load the rest of the script}
  277.     while not EOF(F) do begin
  278.       ReadLn(F, S);
  279.       if IOResult <> 0 then goto Breakout;
  280.  
  281.       {pre-process line}
  282.       PrepareLine(S);
  283.  
  284.       if (S <> '') then begin
  285.         {if a label, upcase for later}
  286.         if S[1] = ':' then
  287.           S := StUpcase(S);
  288.  
  289.         {add to the list}
  290.         New(Q, Init(S));
  291.         if Q = nil then goto Breakout;
  292.         Append(Q);
  293.       end;
  294.     end;
  295.  
  296.     {load was successful, GOSUB to the new script}
  297.     Inc(CurLevel);
  298.     CurP^.Level := CurLevel;
  299.     CurP := P;
  300.     LoadSubScript := True;
  301.  
  302. Breakout:
  303.     Close(F);
  304.     if IOResult = 0 then ;
  305.   end;
  306.  
  307.   procedure ScriptMgr.NextActive;
  308.   {-sets CurP to the next non-label line in the script}
  309.   begin
  310.     CurP := PLine(Next(CurP));
  311.     {skip label lines}
  312.     while (CurP <> nil) and (CurP^.LP^[1] = ':') do
  313.       CurP := PLine(Next(CurP));
  314.   end;
  315.  
  316.   function ScriptMgr.FindLabel(S : PathStr) : PLine;
  317.   {-find the requested label; return line pointer or nil if not found}
  318.   var
  319.     P : PLine;
  320.   begin
  321.     FindLabel := nil;
  322.  
  323.     {make sure passed label is in proper format}
  324.     S := StUpcase(S);
  325.     if S[1] <> ':' then
  326.       S := ':' + S;
  327.  
  328.     {scan the list looking for our matching label}
  329.     P := PLine(Head);
  330.     while P <> nil do begin
  331.       if P^.LP^ = S then begin
  332.         {found it!}
  333.         FindLabel := P;
  334.         exit;
  335.       end;
  336.       P := PLine(Next(P));
  337.     end;
  338.   end;
  339.  
  340.  
  341.   function ScriptMgr.Jump(S : PathStr) : Boolean;
  342.   {-jump (GOTO) to label S}
  343.   var
  344.     P : PLine;
  345.   begin
  346.     Jump := False;
  347.  
  348.     P := FindLabel(S);
  349.     if P <> nil then begin
  350.       {found it, set our vars}
  351.       Jump := True;
  352.       CurP := P;
  353.     end;
  354.   end;
  355.  
  356.   function ScriptMgr.GoSub(S : PathStr) : Boolean;
  357.   var
  358.     P : PLine;
  359.   begin
  360.     GoSub := False;
  361.  
  362.     {make sure we have a return marker}
  363.     if CurLevel = 255 then exit;
  364.  
  365.     P := FindLabel(S);
  366.     if P <> nil then begin
  367.       {found it, set our vars}
  368.       Inc(CurLevel);
  369.       CurP^.Level := CurLevel;
  370.       CurP := P;
  371.       GoSub := True;
  372.     end;
  373.   end;
  374.  
  375.   function ScriptMgr.Return : Boolean;
  376.   {-return from a GOSUB}
  377.   var
  378.     P : PLine;
  379.   begin
  380.     Return := False;
  381.  
  382.     {make sure we've got somewhere to return to}
  383.     if CurLevel = 0 then exit;
  384.  
  385.     {look for our return point}
  386.     P := PLine(Head);
  387.     while P <> nil do begin
  388.       if P^.Level = CurLevel then begin
  389.         {found it; clean up nessessary vars and exit}
  390.         Dec(CurLevel);
  391.         P^.Level := 0;
  392.         CurP := P;
  393.         Return := True;
  394.         exit;
  395.       end;
  396.       P := PLine(Next(P));
  397.     end;
  398.   end;
  399.  
  400.   procedure ScriptMgr.Process;
  401.   {-run the script}
  402.   begin
  403.     {set flag noting script is now running}
  404.     Running := True;
  405.  
  406.     while CurP <> nil do begin
  407.       {get the next active line}
  408.       NextActive;
  409.       if CurP <> nil then
  410.         {process it}
  411.         if not ProcessLine(CurP^.LP^) then
  412.           CurP := nil;
  413.     end;
  414.  
  415.     Running := False;
  416.   end;
  417.  
  418. end.
  419.