{-----------------------------------------------------------------------------
The contents of this file are subject to the Mozilla Public License
Version 1.1 (the "License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.mozilla.org/MPL/MPL-1.1.html

Software distributed under the License is distributed on an "AS IS" basis,
WITHOUT WARRANTY OF ANY KIND, either expressed or implied. See the License for
the specific language governing rights and limitations under the License.

The Original Code is: JvForth.PAS, released on 2002-06-15.

The Initial Developer of the Original Code is Jan Verhoeven [jan1 dott verhoeven att wxs dott nl]
Portions created by Jan Verhoeven are Copyright (C) 2002 Jan Verhoeven.
All Rights Reserved.

Contributor(s): Robert Love [rlove att slcdug dott org].

You may retrieve the latest version of this file at the Project JEDI's JVCL home page,
located at http://jvcl.sourceforge.net

Known Issues:
-----------------------------------------------------------------------------}
// $Id: JvForth.pas 11157 2007-01-18 14:06:18Z marquardt $

unit JvForth;

{$I jvcl.inc}
{$I crossplatform.inc}

interface

uses
  {$IFDEF UNITVERSIONING}
  JclUnitVersioning,
  {$ENDIF UNITVERSIONING}
  SysUtils, Classes,
  {$IFDEF MSWINDOWS}
  ShellAPI,
  {$ENDIF MSWINDOWS}
  Windows, Messages, Forms, Dialogs, FileCtrl,
  {$IFDEF HAS_UNIT_VARIANTS}
  Variants,
  {$ENDIF HAS_UNIT_VARIANTS}
  JvXMLTree, JvComponentBase, JvStrings, JvTypes;

const
  StackMax = 1000;

type
  EJvJanScriptError = class(EJVCLException);

  TToken = (dfoError, dfoNop,
    // flow actions
    dfoIf, dfoElse, dfoEndIf, dfoRepeat, dfoUntil,
    // sub routines
    dfoSub, dfoEndSub, dfoCall,
    // stack operations
    dfoDup, dfoDrop, dfoSwap,
    // conversion
    dfoCstr,
    // data source object, symbols starting with _
    dfoDSO, dfoSelDir, dfoDSOBase,
    // xmldso starts with ?
    dfoXML,
    // system io
    dfoSystem,
    // internal variables
    dfoIntVar,
    // external variables
    dfoExtVar,
    // direct action
    dfoInteger, dfoFloat, dfoSet, dfoString, dfoBoolean,
    dfoDate,
    // end direct action
    dfoEq, dfoNe, dfoGt, dfoLt, dfoGe, dfoLe, dfoLike, dfoUnlike,
    dfoNot, dfoAnd, dfoXor, dfoOr,
    dfoIn,
    dfoAdd, dfoSubtract, dfoMultiply, dfoDivide, dfoPower,
    dfoAbs,
    // some usefull constants
    dfoCrLf,
    // some gonio functions
    dfoSin, dfoCos, dfoPi, dfoTan,
    dfoArcSin, dfoArcCos, dfoArcTan, dfoArcTan2,

    dfoNegate, dfoSqr, dfoSqrt,
    dfoLeft, dfoRight,
    // windows api
    dfoShellExecute,
    // date and time
    dfoNow, dfoTime, dfoDateStr, dfoTimeStr
   );

  TProcVar = procedure of object;

  TOnGetVariable = procedure(Sender: TObject; Symbol: string; var Value: Variant;
    var Handled: Boolean; var ErrorStr: string) of object;
  TOnSetVariable = procedure(Sender: TObject; Symbol: string; Value: Variant;
    var Handled: Boolean; var ErrorStr: string) of object;
  TOnGetSystem = procedure(Sender: TObject; Symbol, Prompt: string; var Value: Variant;
    var Handled: Boolean; var ErrorStr: string) of object;
  TOnSetSystem = procedure(Sender: TObject; Symbol: string; Value: Variant;
    var Handled: Boolean; var ErrorStr: string) of object;
  TOnInclude = procedure(Sender: TObject; IncludeFile: string; var Value: string;
    var Handled: Boolean; var ErrorStr: string) of object;

  TJvJanDSO = class(TStringList)
  private
    function InternalGetValue(Index: Integer; const AField: string): string;
    procedure InternalSetValue(Index: Integer; const AField, AValue: string);
  public
    // when a key is not found it will be added
    procedure SetValue(AKey: Variant; const AField, AValue: string);
    function GetValue(AKey: Variant; const AField: string): string;
  end;

  TJvJanDSOList = class(TStringList)
  public
    destructor Destroy; override;
    procedure ClearTables;
    function Table(const AName: string): TJvJanDSO;
  end;

  TJvJanXMLList = class(TStringList)
  public
    destructor Destroy; override;
    procedure ClearXMLS;
    function Xml(const AName: string): TJvXMLTree;
  end;

  TVariantObject = class(TObject)
  private
    FValue: Variant;
    procedure SetValue(const Value: Variant);
  public
    property Value: Variant read FValue write SetValue;
  end;

  TVariantList = class(TStringList)
  public
    destructor Destroy; override;
    procedure ClearObjects;
    procedure SetVariable(const Symbol: string; AValue: Variant);
    function GetVariable(const Symbol: string): Variant;
    function GetObject(const Symbol: string): TVariantObject; reintroduce;
  end;

  TAtom = class(TObject)
  private
    FToken: TToken;
    FSymbol: string;
    FValue: Variant;
    FProc: TProcVar;
    FIsOperand: Boolean;
    procedure SetToken(const Value: TToken);
    procedure SetSymbol(const Value: string);
    procedure SetValue(const Value: Variant);
    procedure SetProc(const Value: TProcVar);
    procedure SetIsOperand(const Value: Boolean);
  public
    property Token: TToken read FToken write SetToken;
    property Proc: TProcVar read FProc write SetProc;
    property Symbol: string read FSymbol write SetSymbol;
    property Value: Variant read FValue write SetValue;
    property IsOperand: Boolean read FIsOperand write SetIsOperand;
  end;

  TAtomList = class(TList)
  public
    destructor Destroy; override;
    procedure ClearObjects;
  end;

  TJvForthScript = class(TJvComponent)
  private
    FScript: string;
    FIncludes: TStringList;
    FInDevice: string;
    FOutDevice: string;
    FSubsList: TStringList;
    FVarsList: TVariantList;
    FDSOList: TJvJanDSOList;
    FXMLList: TJvJanXMLList;
    FXMLSelect: TList;
    FXMLSelectRecord: Integer;
    FDSOBase: string; // root directory for DSO tables
    FAtoms: TAtomList;
    // FRStack if the return stack for loop, sub etc.
    FRStack: array [0..StackMax] of Integer;
    FRSP: Integer;
    FVStack: array [0..StackMax] of Variant;
    FVSP: Integer;
    // ostack: array[0..StackMax] of TToken;
    // osp: Integer;
    FPStack: array [0..StackMax] of TToken;
    FPSP: Integer;
    FPC: Integer;
    FCurrentSymbol: string;
    FCurrentValue: Variant;
    FOnGetVariable: TOnGetVariable;
    FOnSetVariable: TOnSetVariable;
    FScriptTimeOut: Integer;
    FOnGetSystem: TOnGetSystem;
    FOnSetSystem: TOnSetSystem;
    FOnInclude: TOnInclude;
    // procedure ClearAtoms;
    procedure SetScript(const Value: string);
    procedure SetOnGetVariable(const Value: TOnGetVariable);
    procedure SetOnSetVariable(const Value: TOnSetVariable);
    // expresssion procedures

    // constants
    procedure ProcCrLf;
    // date and time
    procedure ProcNow;
    procedure ProcDateStr;
    procedure ProcTimeStr;
    // shell
    procedure ProcShellExecute;
    // xml variables
    procedure ProcXML;
    // data source variables
    procedure ProcDSO;
    procedure ProcSelDir;
    procedure ProcDSOBase;
    // external variables
    procedure ProcExtVar; // general dispatcher
    procedure ProcAssign;
    procedure ProcVariable;

    // internal variables
    procedure ProcIntVar; // general dispatcher
    procedure ProcVarGet;
    procedure ProcVarSet;
    procedure ProcVarInc;
    procedure ProcVarIncIndex;
    procedure ProcVarDec;
    procedure ProcVarDecTestZero;
    procedure ProcVarAdd;
    procedure ProcVarSub;
    procedure ProcVarMul;
    procedure ProcVarDiv;
    procedure ProcVarNeg;
    procedure ProcVarLoad;
    procedure ProcVarSave;
    // system io
    procedure ProcSystem; // general dispatcher
    procedure ProcSysGet;
    procedure ProcSysSet;
    // flow expressions
    procedure ProcIf;
    procedure ProcElse;
    procedure ProcEndif;
    procedure ProcUntil;
    procedure ProcRepeat;
    // end flow expressions

    // sub expressions
    procedure ProcSub;
    procedure ProcEndsub;
    procedure ProcCall;
    // conversion expressions
    procedure ProcCStr;
    procedure ProcNop;
    procedure ProcDup;
    procedure ProcDrop;
    procedure ProcSwap;
    procedure ProcInteger;
    procedure ProcFloat;
    procedure ProcSet;
    procedure ProcString;
    procedure ProcBoolean;
    procedure ProcDate;
    procedure ProcEq;
    procedure ProcNe;
    procedure ProcGt;
    procedure ProcLt;
    procedure ProcGe;
    procedure ProcLe;
    procedure ProcLike;
    procedure ProcUnlike;
    procedure ProcNot;
    procedure ProcAnd;
    procedure ProcXor;
    procedure ProcOr;
    procedure ProcIn;
    procedure ProcAdd;
    procedure ProcSubtract;
    procedure ProcMultiply;
    procedure ProcDivide;
    procedure ProcPower;
    procedure ProcAbs;
    // some gonio functions
    procedure Procpi;
    procedure ProcSin;
    procedure ProcCos;
    procedure ProcTan;
    procedure ProcArcSin;
    procedure ProcArcCos;
    procedure ProcArcTan;
    procedure ProcArcTan2;

    procedure ProcNegate;
    procedure ProcSqr;
    procedure ProcSqrt;
    procedure ProcLeft;
    procedure ProcRight;
    function VPop: Variant;
    procedure VPush(AValue: Variant);
//    function opop: TToken;
//    procedure opush(AValue: TToken);
//    function ppop: TToken;
//    procedure ppush(AValue: TToken);
    function RPop: Integer;
    procedure RPush(AValue: Integer);
    procedure DoProc;
    procedure DoToken(AToken: TToken);
    procedure SetScriptTimeOut(const Value: Integer);
    procedure ParseScript;
    procedure SetOnGetSystem(const Value: TOnGetSystem);
    procedure SetOnSetSystem(const Value: TOnSetSystem);
    procedure SetOnInclude(const Value: TOnInclude);
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    function Execute: Variant;
    function PopValue: Variant;
    function CanPopValue: Boolean;
    procedure PushValue(AValue: Variant);
    function CanPushValue: Boolean;
  published
    property Script: string read FScript write SetScript;
    property ScriptTimeOut: Integer read FScriptTimeOut write SetScriptTimeOut;
    property OnGetVariable: TOnGetVariable read FOnGetVariable write SetOnGetVariable;
    property OnSetVariable: TOnSetVariable read FOnSetVariable write SetOnSetVariable;
    property OnSetSystem: TOnSetSystem read FOnSetSystem write SetOnSetSystem;
    property OnGetSystem: TOnGetSystem read FOnGetSystem write SetOnGetSystem;
    property OnInclude: TOnInclude read FOnInclude write SetOnInclude;
  end;

// runs an external file or progam
procedure Launch(const AFile: string);

{$IFDEF UNITVERSIONING}
const
  UnitVersioning: TUnitVersionInfo = (
    RCSfile: '$URL: https://jvcl.svn.sourceforge.net/svnroot/jvcl/tags/JVCL3_32/run/JvForth.pas $';
    Revision: '$Revision: 11157 $';
    Date: '$Date: 2007-01-18 15:06:18 +0100 (jeu., 18 janv. 2007) $';
    LogPath: 'JVCL\run'
  );
{$ENDIF UNITVERSIONING}

implementation

uses
  Math,
  JvConsts, JvResources;

{ some utility functions }

procedure Launch(const AFile: string);
var
  Command, Params, WorkDir: string;
begin
  Command := AFile;
  Params := #0;
  WorkDir := #0;
  {$IFDEF VCL}
  ShellExecute(Application.Handle, 'open', PChar(Command),
    PChar(Params), PChar(WorkDir), SW_SHOWNORMAL);
  {$ENDIF VCL}
  {$IFDEF VisualCLX}
  ShellExecute(0, 'open', PChar(Command),
    PChar(Params), PChar(WorkDir), SW_SHOWNORMAL);
  {$ENDIF VisualCLX}
end;

procedure GlobalSetValue(var AText: string; const AName, AValue: string);
var
  P, P2, L: Integer;
begin
  L := Length(AName) + 2;
  if AText = '' then
  begin
    AText := AName + '="' + AValue + '"';
  end
  else
  begin
    P := PosText(AName + '="', AText);
    if P = 0 then
      AText := AText + ' ' + AName + '="' + AValue + '"'
    else
    begin
      P2 := PosStr('"', AText, P + L);
      if P2 = 0 then
        Exit;
      Delete(AText, P + L, P2 - (P + L));
      Insert(AValue, AText, P + L);
    end;
  end;
end;

function GlobalGetValue(const AText, AName: string): string;
var
  P, P2, L: Integer;
begin
  Result := '';
  L := Length(AName) + 2;
  P := PosText(AName + '="', AText);
  if P = 0 then
    Exit;
  P2 := PosStr('"', AText, P + L);
  if P2 = 0 then
    Exit;
  Result := Copy(AText, P + L, P2 - (P + L));
  Result := StringReplace(Result, '~~', sLineBreak, [rfReplaceAll]);
end;

// some special expression functions

// returns the Index of Integer v in aList

function IndexOfInteger(AList: TStringList; Value: Variant): Integer;
var
  C, I, Index, P: Integer;
  S, S1, S2: string;
begin
  Result := -1;
  I := Value;
  C := AList.Count;
  if C = 0 then
    Exit;
  for Index := 0 to C - 1 do
  begin
    try
      S := AList[Index];
      P := Pos('..', S);
      if P = 0 then
      begin
        if StrToInt(AList[Index]) = I then
        begin
          Result := Index;
          Exit;
        end;
      end
      else
      begin // have range
        S1 := Trim(Copy(S, 1, P - 1));
        S2 := Trim(Copy(S, P + 2, Length(S)));
        if (I >= StrToInt(S1)) and (I <= StrToInt(S2)) then
        begin
          Result := Index;
          Exit;
        end;
      end;
    except
      Exit;
    end;
  end;
end;

// returns the Index of float Value (single or double) in AList

function IndexOfFloat(AList: TStringList; Value: Variant): Integer;
var
  C, Index, P: Integer;
  F: Extended;
  S, S1, S2: string;
begin
  Result := -1;
  F := Value;
  C := AList.Count;
  if C = 0 then
    Exit;
  for Index := 0 to C - 1 do
  begin
    try
      S := AList[Index];
      P := Pos('..', S);
      if P = 0 then
      begin
        if StrToFloat(S) = F then
        begin
          Result := Index;
          Exit;
        end;
      end
      else
      begin // have range
        S1 := Trim(Copy(S, 1, P - 1));
        S2 := Trim(Copy(S, P + 2, Length(S)));
        if (F >= StrToFloat(S1)) and (F <= StrToFloat(S2)) then
        begin
          Result := Index;
          Exit;
        end;
      end;
    except
      raise EJvJanScriptError.CreateResFmt(@RsEInvalidNumbers, [S]);
    end;
  end;
end;

// returns the Index of date Value in AList

function IndexOfDate(AList: TStringList; Value: Variant): Integer;
var
  C, Index, P: Integer;
  D: TDateTime;
  S, S1, S2: string;
begin
  Result := -1;
  D := Value;
  C := AList.Count;
  if C = 0 then
    Exit;
  for Index := 0 to C - 1 do
  begin
    try
      S := AList[Index];
      P := Pos('..', S);
      if P = 0 then
      begin
        if StrToDate(AList[Index]) = D then
        begin
          Result := Index;
          Exit;
        end;
      end
      else
      begin
        S1 := Trim(Copy(S, 1, P - 1));
        S2 := Trim(Copy(S, P + 2, Length(S)));
        if (D >= StrToDate(S1)) and (D <= StrToDate(S2)) then
        begin
          Result := Index;
          Exit;
        end;
      end;
    except
      Exit;
    end;
  end;
end;

// returns the Index of string Value in AList

function IndexOfString(AList: TStringList; Value: Variant): Integer;
var
  C, Index, P: Integer;
  SV: string;
  S, S1, S2: string;
begin
  Result := -1;
  SV := Value;
  C := AList.Count;
  if C = 0 then
    Exit;
  for Index := 0 to C - 1 do
  begin
    try
      S := AList[Index];
      P := Pos('..', S);
      if P = 0 then
      begin
        if AList[Index] = SV then
        begin
          Result := Index;
          Exit;
        end;
      end
      else
      begin
        S1 := Trim(Copy(S, 1, P - 1));
        S2 := Trim(Copy(S, P + 2, Length(S)));
        if (SV >= S1) and (SV <= S2) then
        begin
          Result := Index;
          Exit;
        end;
      end;
    except
      Exit;
    end;
  end;
end;

// used by dfoIN
// tests if AValue is in ASet

function FuncIn(AValue: Variant; ASet: Variant): Boolean;
var
  List: TStringList;
  S: string;
  P: Integer;
  Token: string;

  function GetToken: Boolean;
  begin
    Result := False;
    S := TrimLeft(S);
    if S = '' then
      Exit;
    P := 1;
    if S[1] = '"' then
    begin // get string
      P := PosStr('"', S, 2);
      if P = 0 then
        raise EJvJanScriptError.CreateResFmt(@RsEUnterminatedStringNears, [S]);
      Token := Copy(S, 2, P - 2);
      Delete(S, 1, P);
      Result := True;
    end
    else
    begin
      P := Pos(' ', S);
      if P = 0 then
      begin
        Token := S;
        Result := True;
        S := '';
      end
      else
      begin
        Token := Copy(S, 1, P - 1);
        Delete(S, 1, P);
        Result := True;
      end;
    end
  end;

begin
  Result := False;
  S := ASet;
  if S = '' then
    Exit;
  List := TStringList.Create;
  try
    while GetToken do
      List.Append(Token);
    //    c:=List.Count;
    case VarType(AValue) of
      varString:
        Result := IndexOfString(List, AValue) > -1;
      varInteger, varByte:
        Result := IndexOfInteger(List, AValue) > -1;
      varSingle, varDouble:
        Result := IndexOfFloat(List, AValue) > -1;
      varDate:
        Result := IndexOfDate(List, AValue) > -1;
    else
      raise EJvJanScriptError.CreateRes(@RsEUnrecognizedDataTypeInSetOperation);
    end;
  finally
    List.Free;
  end;
end;

//=== { TJvForthScript } =====================================================

constructor TJvForthScript.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FAtoms := TAtomList.Create;
  FIncludes := TStringList.Create;
  FSubsList := TStringList.Create;
  FVarsList := TVariantList.Create;
  FDSOList := TJvJanDSOList.Create;
  FXMLList := TJvJanXMLList.Create;
  FXMLSelect := TList.Create;
  FDSOBase := ExtractFilePath(ParamStr(0));
  if FDSOBase[Length(FDSOBase)] = PathDelim then
    Delete(FDSOBase, Length(FDSOBase), 1);
  FVSP := 0;
  // osp := 0;
  FRSP := 0;
  FInDevice := 'dialog';
  FOutDevice := 'dialog';
  FScriptTimeOut := 30; // seconds
end;

destructor TJvForthScript.Destroy;
begin
  FAtoms.Free;
  FIncludes.Free;
  FSubsList.Free;
  FVarsList.Free;
  FDSOList.Free;
  FXMLList.Free;
  FXMLSelect.Free;
  inherited Destroy;
end;

procedure TJvForthScript.VPush(AValue: Variant);
begin
  //FVStack.push(AValue);
  FVStack[FVSP] := AValue;
  if FVSP < StackMax then
    Inc(FVSP)
  else
    raise EJvJanScriptError.CreateRes(@RsEStackOverflow);
end;

(*
procedure TJvForthScript.opush(AValue: TToken);
begin
  ostack[osp] := AValue;
  if osp < StackMax then
    Inc(osp);
end;
*)
(*
function TJvForthScript.opop: TToken;
begin
  showmessage('opop');
  if osp <= 0 then
    Result := dfonop
  else
  begin
    Dec(osp);
    Result := ostack[osp];
  end;
end;
*)
(*
procedure TJvForthScript.ppush(AValue: TToken);
begin
  FPStack[FPSP] := AValue;
  if FPSP < StackMax then
    Inc(FPSP);
end;
*)
(*
function TJvForthScript.ppop: TToken;
begin
  if FPSP = 0 then
    Result := dfoError
  else
  begin
    Dec(FPSP);
    Result := FPStack[FPSP];
  end;
end;
*)
function TJvForthScript.VPop: Variant;
begin
  if FVSP = 0 then
    raise EJvJanScriptError.CreateRes(@RsEStackUnderflow)
  else
  begin
    Dec(FVSP);
    Result := FVStack[FVSP];
  end;
end;

procedure TJvForthScript.SetScript(const Value: string);
begin
  if Value <> FScript then
  begin
    FScript := Value;
    ParseScript;
  end;
end;

procedure TJvForthScript.ParseScript;
var
  S: string;
  I, P, P2: Integer;
  Atom: TAtom;
  //  atomoperation: TToken;
  AtomSymbol: string;
  AtomValue: Variant;
  //  atomproc: TProcVar;
  Token: string;
  VInteger: Integer;
  VFloat: Double;
  VDate: TDateTime;
  // handling of includes:
  IncFile: string;
  Handled: Boolean;
  IncScript: string;
  ErrStr: string;
  TimeOutTicks: Cardinal;
  DeltaTicks: Cardinal;

  function PushAtom(AToken: TToken): Integer;
    //    var cc: Integer;
  begin
    Atom := TAtom.Create;
    Atom.Token := AToken;
    Atom.Symbol := AtomSymbol;
    Atom.Value := AtomValue;
    Result := FAtoms.Add(Atom);
  end;

  procedure OPush(AToken: TToken);
    //    var cc: Integer;
  begin
    Atom := TAtom.Create;
    Atom.Token := AToken;
    Atom.Symbol := Token;
    Atom.Value := AtomValue;
    FAtoms.Add(Atom);
  end;

  procedure BrcPush(Proc: TProcVar);
    //    var cc: Integer;
  begin
    Atom := TAtom.Create;
    Atom.Proc := Proc;
    Atom.Symbol := AtomSymbol;
    Atom.Value := AtomValue;
    Atom.IsOperand := False;
    FAtoms.Add(Atom);
  end;

  function GetToken: Boolean;
  begin
    Result := False;
    S :=TrimLeft(S);
    if S = '' then
      Exit;
    P := 1;
    if S[1] = '"' then
    begin // get string
      P := PosStr('"', S, 2);
      if P = 0 then
        raise EJvJanScriptError.CreateResFmt(@RsEUnterminatedStringNears, [S]);
      Token := Copy(S, 1, P);
      Delete(S, 1, P);
      Result := True;
    end
    else
    if S[1] = '[' then
    begin // get block
      P := PosStr(']', S, 2);
      if P = 0 then
        raise EJvJanScriptError.CreateResFmt(@RsEUnterminatedBlockNear, [S]);
      Token := Copy(S, 1, P);
      Delete(S, 1, P);
      Result := True;
    end
    else
    begin
      P := Pos(' ', S);
      if P = 0 then
      begin
        Token := S;
        Result := True;
        S := '';
      end
      else
      begin
        Token := Copy(S, 1, P - 1);
        Delete(S, 1, P);
        Result := True;
      end;
    end;
  end;

begin
  FAtoms.ClearObjects;
  FSubsList.Clear;
  // reset return stack; needed in resolving flow statements
  FRSP := 0;
  S := FScript;
  // include any include files, include files start with $$ and end with ;
  // when the parser detects and include file it will raise the oninclude event
  // include files can also include files (nested includes)
  DeltaTicks := FScriptTimeOut * 1000;
  TimeOutTicks := GetTickCount + DeltaTicks;
  FIncludes.Clear; // Clear the includes List
  repeat
    if GetTickCount > TimeOutTicks then
      raise EJvJanScriptError.CreateResFmt(@RsEParserTimedOutAfterdSecondsYouMayHa, [FScriptTimeout]);
    P := PosStr('$$', S);
    if P > 0 then
    begin
      P2 := PosStr(';', S, P);
      if P2 = 0 then
        raise EJvJanScriptError.CreateResFmt(@RsEUnterminatedIncludeNears, [Copy(S, P, Length(S))]);
      IncFile := Copy(S, P + 2, P2 - P - 2) + '.jan';
      if PosStr(' ', IncFile, 1) > 0 then
        raise EJvJanScriptError.CreateResFmt(@RsEIllegalSpaceCharacterInTheIncludeFi, [IncFile]);
      I := FIncludes.IndexOf(IncFile);
      if I <> -1 then
        Delete(S, P, P2 - P + 1)
      else
      begin
        ErrStr := Format(RsECanNotFindIncludeFiles, [IncFile]);
        Handled := False;
        IncScript := '';
        if not Assigned(OnInclude) then
          raise EJvJanScriptError.CreateResFmt(@RsEOnIncludeHandlerNotAssignedCanNotHa, [Copy(S, P, Length(S))]);
        OnInclude(Self, IncFile, IncScript, Handled, ErrStr);
        if not Handled then
          raise EJvJanScriptError.Create(ErrStr);
        Delete(S, P, P2 - P + 1);
        Insert(IncScript, S, P);
        FIncludes.Append(IncFile);
      end;
    end;
  until P = 0;
  S := Trim(StringReplace(S, sLineBreak, ' ', [rfReplaceAll]));
  // remove comments
  repeat
    P := Pos('{', S);
    if P > 0 then
    begin
      P2 := PosStr('}', S, P);
      if P2 = 0 then
        raise EJvJanScriptError.CreateResFmt(@RsEMissingCommentTerminatorNears, [S]);
      Delete(S, P, P2 - P + 1);
    end;
  until P = 0;
  if S = '' then
    Exit;
  while GetToken do
  begin
    if Token = 'cstr' then
      OPush(dfoCstr)
    else
    if Token = 'seldir' then
      OPush(dfoSelDir)
    else
    if Token = 'dsobase' then
      OPush(dfoDSOBase)
    else
    if Token = 'dup' then
      OPush(dfoDup)
    else
    if Token = 'drop' then
      OPush(dfoDrop)
    else
    if Token = 'swap' then
      OPush(dfoSwap)
    else
    if Token = 'if' then
    begin
      P := PushAtom(dfoIf);
      RPush(P);
    end
    else
    if Token = 'endif' then
    begin
      P := PushAtom(dfoEndIf);
      P2 := RPop;
      Atom := TAtom(FAtoms[P2]);
      Atom.Value := P + 1;
    end
    else
    if Token = 'else' then
    begin
      P := PushAtom(dfoElse);
      P2 := RPop;
      RPush(P);
      Atom := TAtom(FAtoms[P2]);
      Atom.Value := P + 1;
    end
    else
    if Token = 'repeat' then
    begin
      P := PushAtom(dfoRepeat);
      RPush(P);
    end
    else
    if Token = 'until' then
    begin
      AtomValue := RPop;
      PushAtom(dfoUntil);
    end
    else
    if Token = 'now' then
      OPush(dfoNow)
    else
    if Token = 'datestr' then
      OPush(dfoDateStr)
    else
    if Token = 'timestr' then
      OPush(dfoTimeStr)
    else
    if Token = 'shellexecute' then
      OPush(dfoShellExecute)
    else
    if Token = ';' then
      OPush(dfoEndSub)
    else
    if Token = 'crlf' then
      OPush(dfoCrLf)
    else
    if Token = '--' then
      OPush(dfoNegate)
    else
    if Token = '-' then
      OPush(dfoSubtract)
    else
    if Token = '+' then
      OPush(dfoAdd)
    else
    if Token = '*' then
      OPush(dfoMultiply)
    else
    if Token = '/' then
      OPush(dfoDivide)
    else
    if Token = '^' then
      OPush(dfoPower)
    else
    if Token = 'abs' then
      OPush(dfoAbs)
    else
    if Token = 'left' then
      OPush(dfoLeft)
    else
    if Token = 'right' then
      OPush(dfoRight)
    else
    if Token = 'sqr' then
      OPush(dfoSqr)
    else
    if Token = 'sqrt' then
      OPush(dfoSqrt)
    else
    if Token = 'sin' then
      OPush(dfoSin)
    else
    if Token = 'cos' then
      OPush(dfoCos)
    else
    if Token = 'tan' then
      OPush(dfoTan)
    else
    if Token = 'arcsin' then
      OPush(dfoArcSin)
    else
    if Token = 'arccos' then
      OPush(dfoArcCos)
    else
    if Token = 'arctan' then
      OPush(dfoArcTan)
    else
    if Token = 'arctan2' then
      OPush(dfoArcTan2)
    else
    if Token = 'pi' then
      OPush(dfoPi)
    else
    if Token = '<>' then
      OPush(dfoNe)
    else
    if Token = '>=' then
      OPush(dfoGe)
    else
    if Token = '>' then
      OPush(dfoGt)
    else
    if Token = '<=' then
      OPush(dfoLe)
    else
    if Token = '<' then
      OPush(dfoLt)
    else
    if Token = '=' then
      OPush(dfoEq)
    else
    if Token = 'or' then
      OPush(dfoOr)
    else
    if Token = 'and' then
      OPush(dfoAnd)
    else
    if Token = 'in' then
      OPush(dfoIn)
    else
    if Token = 'xor' then
      OPush(dfoXor)
    else
    if Token = 'not' then
      OPush(dfoNot)
    else
    if Token = 'like' then
      OPush(dfoLike)
    else
    if Token = 'unlike' then
      OPush(dfoUnlike)
    // check for block
    else
    if Token[1] = '[' then
    begin
      AtomSymbol := Token;
      AtomValue := Copy(Token, 2, Length(Token) - 2);
      PushAtom(dfoSet);
    end
    // check for sub
    else
    if Token[Length(Token)] = '=' then
    begin
      AtomSymbol := Copy(Token, 1, Length(Token) - 1);
      P := PushAtom(dfoSub);
      FSubsList.AddObject(AtomSymbol, TObject(P + 1));
    end
    // check for xml object
    else
    if (Token[1] = '?') and (Length(Token) > 1) then
    begin
      P := Pos('.', Token);
      if (P = 0) or (P < 3) or (P = Length(Token)) then
        raise EJvJanScriptError.CreateResFmt(@RsEMissingXmlMethodSpecifierNears, [S]);
      AtomSymbol := Copy(Token, 2, P - 2);
      AtomValue := Copy(Token, P + 1, Length(Token));
      PushAtom(dfoXML);
    end
    // check for data source object
    else
    if (Token[1] = '_') and (Length(Token) > 1) then
    begin
      P := Pos('.', Token);
      if (P = 0) or (P < 3) or (P = Length(Token)) then
        raise EJvJanScriptError.CreateResFmt(@RsEMissingDataSourceMethodSpecifierNea, [S]);
      AtomSymbol := Copy(Token, 2, P - 2);
      AtomValue := Copy(Token, P + 1, Length(Token));
      PushAtom(dfoDSO);
    end
    // system
    else
    if (Token[1] = ')') and (Length(Token) > 1) then
    begin
      P := Pos('.', Token);
      if (P = 0) or (P < 3) or (P = Length(Token)) then
        raise EJvJanScriptError.CreateResFmt(@RsEMissingSystemMethodSpecifierNears, [S]);
      AtomSymbol := Copy(Token, 2, P - 2);
      AtomValue := Copy(Token, P + 1, Length(Token));
      PushAtom(dfoSystem);
    end
    // external variable
    else
    if (Token[1] = '>') and (Length(Token) > 1) then
    begin
      P := Pos('.', Token);
      if (P = 0) or (P < 3) or (P = Length(Token)) then
        raise EJvJanScriptError.CreateResFmt(@RsEMissingExternalVariableMethodSpecif, [S]);
      AtomSymbol := Copy(Token, 2, P - 2);
      AtomValue := Copy(Token, P + 1, Length(Token));
      PushAtom(dfoExtVar);
    end
    // check for internal variable
    else
    if (Token[1] = ':') and (Length(Token) > 1) then
    begin
      P := Pos('.', Token);
      if (P = 0) or (P < 3) or (P = Length(Token)) then
        raise EJvJanScriptError.CreateResFmt(@RsEMissingInternalVariableMethodSpecif, [S]);
      AtomSymbol := Copy(Token, 2, P - 2);
      AtomValue := Copy(Token, P + 1, Length(Token));
      PushAtom(dfoIntVar);
    end
    // check for string
    else
    if Token[1] = '"' then
    begin
      AtomSymbol := Token;
      AtomValue := Copy(Token, 2, Length(Token) - 2);
      PushAtom(dfoString);
    end
    // check Integer, float or date
    else
    begin
      try // Integer
        VInteger := StrToInt(Token);
        AtomSymbol := Token;
        AtomValue := VInteger;
        PushAtom(dfoInteger);
      except
        try // float
          VFloat := StrToFloat(Token);
          AtomSymbol := Token;
          AtomValue := VFloat;
          PushAtom(dfoFloat);
        except
          try // date
            VDate := StrToDate(Token);
            AtomSymbol := Token;
            AtomValue := VDate;
            PushAtom(dfoDate);
          except // must be call to sub
            AtomSymbol := Token;
            P := FSubsList.IndexOf(AtomSymbol);
            if P = -1 then
              raise EJvJanScriptError.CreateResFmt(@RsEUndefinedWordsNears, [AtomSymbol, S]);
            P := Integer(FsubsList.Objects[P]);
            AtomValue := P;
            PushAtom(dfoCall);
          end;
        end;
      end;
    end;
  end;
end;

procedure TJvForthScript.DoToken(AToken: TToken);
begin
  case AToken of
    dfoNow:
      ProcNow;
    dfoDateStr:
      ProcDateStr;
    dfoTimeStr:
      ProcTimeStr;
    dfoShellExecute:
      ProcShellExecute;
    dfoCrLf:
      ProcCrLf;
    dfoCStr:
      ProcCStr;
    dfoXML:
      ProcXML;
    dfoDSO:
      ProcDSO;
    dfoSeldir:
      ProcSelDir;
    dfoDSOBase:
      ProcDSOBase;
    dfoIntVar:
      ProcIntVar;
    dfoExtVar:
      ProcExtVar;
    dfoSystem:
      ProcSystem;
    //    dfoVarGet: ProcVarGet;
    //    dfoVarset: ProcVarSet;
    //    dfoSysGet: ProcSysGet;
    //    dfoSysSet: ProcSysSet;
    dfoSub:
      ProcSub;
    dfoEndSub:
      ProcEndSub;
    dfoCall:
      ProcCall;
    dfoDrop:
      ProcDrop;
    dfoDup:
      ProcDup;
    dfoSwap:
      ProcSwap;
    dfoIf:
      ProcIf;
    dfoElse:
      ProcElse;
    dfoEndIf:
      ProcEndIf;
    dfoRepeat:
      ProcRepeat;
    dfoUntil:
      ProcUntil;
    dfoNop:
      ProcNop;
    //    dfoAssign: ProcAssign;
    //    dfoVariable: ProcVariable;
    dfoInteger:
      ProcInteger;
    dfoFloat:
      ProcFloat;
    dfoSet:
      ProcSet;
    dfoString:
      ProcString;
    dfoBoolean:
      ProcBoolean;
    dfoDate:
      ProcDate;
    dfoEq:
      ProcEq;
    dfoNe:
      ProcNe;
    dfoGt:
      ProcGt;
    dfoLt:
      ProcLt;
    dfoGe:
      ProcGe;
    dfoLe:
      ProcLe;
    dfoLike:
      ProcLike;
    dfoUnlike:
      ProcUnlike;
    dfoNot:
      ProcNot;
    dfoAnd:
      ProcAnd;
    dfoXor:
      ProcXor;
    dfoOr:
      ProcOr;
    dfoIn:
      ProcIn;
    dfoAdd:
      ProcAdd;
    dfoSubtract:
      ProcSubtract;
    dfoMultiply:
      ProcMultiply;
    dfoDivide:
      ProcDivide;
    dfoPower:
      ProcPower;
    dfoAbs:
      ProcAbs;
    dfoPi:
      ProcPi;
    dfoSin:
      ProcSin;
    dfoCos:
      ProcCos;
    dfoTan:
      ProcTan;
    dfoArcSin:
      ProcArcSin;
    dfoArcCos:
      ProcArcCos;
    dfoArcTan:
      ProcArcTan;
    dfoArcTan2:
      ProcArcTan2;
    dfoNegate:
      ProcNegate;
    dfoSqr:
      ProcSqr;
    dfoSqrt:
      ProcSqrt;
    dfoLeft:
      ProcLeft;
    dfoRight:
      ProcRight;
  end;
end;

function TJvForthScript.Execute: Variant;
var
  C: Integer;
  Atom: TAtom;
  Token: TToken;
  TimeOutTicks: Cardinal;
  DeltaTicks: Cardinal;
begin
  Result := Null;
  // osp := 0;
  FVSP := 0;
  FPSP := 0;
  FRSP := 0;
  C := FAtoms.Count;
  FVarsList.ClearObjects;
  FDSOList.ClearTables;
  FXMLList.ClearXMLS;
  FXMLSelect.Clear;
  FXMLSelectRecord := -1;
  if C = 0 then
    Exit;
  FPC := 0;
  DeltaTicks := FScriptTimeOut * 1000;
  TimeOutticks := GetTickCount + DeltaTicks;
  // evaluate all FAtoms
  while FPC < C do
  begin
    if GetTickCount > TimeOutTicks then
      raise EJvJanScriptError.CreateResFmt(@RsEScriptTimedOutAfterdSeconds, [FScriptTimeout]);
    Atom := TAtom(FAtoms[FPC]);
    Inc(FPC);
    FCurrentValue := Atom.Value;
    FCurrentSymbol := Atom.Symbol;
    Token := Atom.Token;
    case Token of
      dfoInteger..dfoDate:
        VPush(FCurrentValue);
    else
      DoToken(Token);
    end;
  end;
  if FVSP <= 0 then
    Result := Null
  else
    Result := VPop;
end;

procedure TJvForthScript.SetOnGetVariable(const Value: TOnGetVariable);
begin
  FOnGetVariable := Value;
end;

(*)
procedure TJvForthScript.ClearAtoms;
var
  i, c: Integer;
begin
  c := FAtoms.Count;
  if c = 0 then
    Exit;
  for i := 0 to c - 1 do
    Tobject(FAtoms[i]).Free;
  FAtoms.Clear;
end;
(*)

procedure TJvForthScript.SetOnSetVariable(const Value: TOnSetVariable);
begin
  FOnSetVariable := Value;
end;

procedure TJvForthScript.ProcAdd;
var
  Value: Variant;
begin
  Value := VPop;
  Value := VPop + Value;
  VPush(Value);
end;

procedure TJvForthScript.ProcAnd;
var
  Value: Variant;
begin
  Value := VPop;
  VPush(VPop and Value);
end;

procedure TJvForthScript.ProcAssign;
var
  Value: Variant;
  Handled: Boolean;
  Err: string;
begin
  Value := VPop;
  VPush(Value);
  Handled := False;
  Err := Format(RsECanNotAssignVariables, [FCurrentSymbol]);
  if Assigned(OnSetVariable) then
  begin
    OnSetVariable(Self, FCurrentSymbol, Value, Handled, Err);
    if not Handled then
      raise EJvJanScriptError.Create(Err);
  end;
end;

procedure TJvForthScript.ProcBoolean;
begin
  VPush(FCurrentValue);
  DoProc;
end;

procedure TJvForthScript.DoProc;
var
  Token: TToken;
begin
  if FPSP <= 0 then
    Exit;
  Dec(FPSP);
  Token := FPStack[FPSP];
  DoToken(Token);
end;

procedure TJvForthScript.ProcCos;
var
  Value: Variant;
begin
  Value := VPop;
  VPush(Cos(Value));
end;

procedure TJvForthScript.ProcDate;
begin
  VPush(FCurrentValue);
  DoProc;
end;

procedure TJvForthScript.ProcDivide;
var
  Value: Variant;
begin
  Value := VPop;
  VPush(VPop / Value);
end;

procedure TJvForthScript.ProcEq;
var
  Value: Variant;
begin
  Value := VPop;
  VPush(VPop = Value);
end;

procedure TJvForthScript.ProcFloat;
begin
  VPush(FCurrentValue);
  DoProc;
end;

procedure TJvForthScript.ProcGe;
var
  Value: Variant;
begin
  Value := VPop;
  VPush(VPop >= Value);
end;

procedure TJvForthScript.ProcGt;
var
  Value: Variant;
begin
  Value := VPop;
  VPush(VPop > Value);
end;

procedure TJvForthScript.ProcIn;
var
  Value: Variant;
begin
  Value := VPop;
  VPush(FuncIn(VPop, Value));
end;

procedure TJvForthScript.ProcInteger;
begin
  VPush(FCurrentValue);
  DoProc;
end;

procedure TJvForthScript.ProcLe;
var
  Value: Variant;
begin
  Value := VPop;
  VPush(VPop <= Value);
end;

procedure TJvForthScript.ProcLeft;
var
  Value, V2: Variant;
  Vali: Integer;
  Vals: string;
begin
  Value := VPop;
  V2 := VPop;
  Vali := Value;
  Vals := V2;
  Value := Copy(Vals, 1, Vali);
  VPush(Value);
end;

procedure TJvForthScript.ProcLike;
var
  Value: Variant;
begin
  Value := VarToStr(VPop);
  VPush(Pos(LowerCase(Value), LowerCase(VarToStr(VPop))) > 0);
end;

procedure TJvForthScript.ProcLt;
var
  Value: Variant;
begin
  Value := VPop;
  VPush(VPop < Value);
end;

procedure TJvForthScript.ProcMultiply;
var
  Value: Variant;
begin
  Value := VPop;
  Value := VPop * Value;
  VPush(Value);
end;

procedure TJvForthScript.ProcNe;
var
  Value: Variant;
begin
  Value := VPop;
  VPush(VPop <> Value);
end;

procedure TJvForthScript.ProcNegate;
var
  Value: Variant;
begin
  Value := VPop;
  VPush(0 - Value);
end;

procedure TJvForthScript.ProcNop;
begin
  //  just do nothing
end;

procedure TJvForthScript.ProcNot;
var
  Value: Variant;
begin
  Value := VPop;
  VPush(not Value);
end;

procedure TJvForthScript.ProcOr;
var
  Value: Variant;
begin
  Value := VPop;
  VPush(VPop or Value);
end;

procedure TJvForthScript.ProcRight;
var
  Value, V2: Variant;
  Vali: Integer;
  Vals: string;
begin
  Value := VPop;
  V2 := VPop;
  Vali := Value;
  Vals := V2;
  if Vali <= Length(Vals) then
    Value := Copy(Vals, Length(Vals) - Vali + 1, Vali)
  else
    Value := Vals;
  VPush(Value);
end;

procedure TJvForthScript.ProcSet;
begin
  VPush(FCurrentValue);
  DoProc;
end;

procedure TJvForthScript.ProcSin;
var
  Value: Variant;
begin
  Value := VPop;
  VPush(Sin(Value));
end;

procedure TJvForthScript.ProcSqr;
var
  Value: Variant;
begin
  Value := VPop;
  VPush(Sqr(Value));
end;

procedure TJvForthScript.ProcSqrt;
var
  Value: Variant;
begin
  Value := VPop;
  VPush(Sqrt(Value));
end;

procedure TJvForthScript.ProcString;
begin
  VPush(FCurrentValue);
  DoProc;
end;

procedure TJvForthScript.ProcSubtract;
var
  Value: Variant;
begin
  Value := VPop;
  VPush(VPop - Value);
end;

procedure TJvForthScript.ProcUnlike;
var
  Value: Variant;
begin
  Value := VarToStr(VPop);
  VPush(Pos(LowerCase(Value), LowerCase(VarToStr(VPop))) = 0);
end;

procedure TJvForthScript.ProcVariable;
var
  Value: Variant;
  Handled: Boolean;
  Err: string;
begin
  Handled := False;
  Err := Format(RsEVariablesNotDefined, [FCurrentSymbol]);
  if Assigned(FOnGetVariable) then
    FOnGetVariable(Self, FCurrentSymbol, Value, Handled, Err);
  if not Handled then
    raise EJvJanScriptError.Create(Err)
  else
    VPush(Value);
end;

procedure TJvForthScript.ProcXor;
var
  Value: Variant;
begin
  Value := VPop;
  VPush(VPop xor Value);
end;

procedure TJvForthScript.ProcIf;
var
  V: Variant;
begin
  V := VPop;
  if V then
    Exit
  else
    FPC := FCurrentValue;
end;

procedure TJvForthScript.ProcElse;
begin
  FPC := FCurrentValue;
end;

procedure TJvForthScript.ProcDrop;
begin
  VPop;
end;

procedure TJvForthScript.ProcDup;
var
  V: Variant;
begin
  V := VPop;
  VPush(V);
  VPush(V);
end;

procedure TJvForthScript.ProcSwap;
var
  V1, V2: Variant;
begin
  V1 := VPop;
  V2 := VPop;
  VPush(V1);
  VPush(V2);
end;

// just a marker

procedure TJvForthScript.ProcEndif;
begin
  // do nothing
end;

// keep looping until vpop=True

procedure TJvForthScript.ProcUntil;
begin
  if not VPop then
    FPC := FCurrentValue;
end;

procedure TJvForthScript.ProcRepeat;
begin
  // do nothing
end;

function TJvForthScript.RPop: Integer;
begin
  if FRSP <= 0 then
    raise EJvJanScriptError.CreateRes(@RsEReturnStackUnderflow)
  else
  begin
    Dec(FRSP);
    Result := FRStack[FRSP];
  end;
end;

procedure TJvForthScript.RPush(AValue: Integer);
begin
  FRStack[FRSP] := AValue;
  if FRSP < StackMax then
    Inc(FRSP)
  else
    raise EJvJanScriptError.CreateRes(@RsEReturnStackOverflow);
end;

procedure TJvForthScript.SetScriptTimeOut(const Value: Integer);
begin
  FScriptTimeOut := Value;
end;

procedure TJvForthScript.ProcEndsub;
begin
  FPC := RPop;
end;

// just skip till endSub

procedure TJvForthScript.ProcSub;
var
  C: Integer;
  Token: TToken;
begin
  { TODO -oJVCL -cPOSSIBLEBUG : (p3) What should "c" really be here? }
  C := FAtoms.Count; //??
  while FPC < C do
  begin
    Token := TAtom(FAtoms[FPC]).Token;
    if Token = dfoEndSub then
    begin
      Inc(FPC);
      Exit;
    end;
    Inc(FPC);
  end;
end;

// call to a user sub, just look it up

procedure TJvForthScript.ProcCall;
var
  Index: Integer;
begin
  //  Index:=FSubsList.IndexOf(FCurrentSymbol);
  Index := FCurrentValue;
  if Index <> -1 then
  begin
    RPush(FPC);
    //    FPC:=Integer(FsubsList.Objects[Index]);
    FPC := Index;
    Exit;
  end
  else
    raise EJvJanScriptError.CreateResFmt(@RsEProceduresNotDefined, [FCurrentSymbol]);
end;

procedure TJvForthScript.ProcVarGet;
var
  V: Variant;
begin
  V := FVarsList.GetVariable(FCurrentSymbol);
  if V <> null then
    VPush(V)
  else
    raise EJvJanScriptError.CreateResFmt(@RsEVariablesNotDefined_, [FCurrentSymbol]);
end;

procedure TJvForthScript.ProcVarSet;
var
  V: Variant;
begin
  V := VPop;
  FVarsList.SetVariable(FCurrentSymbol, V);
end;

procedure TJvForthScript.ProcCStr;
var
  S: string;
begin
  S := VPop;
  VPush(S);
end;

procedure TJvForthScript.ProcSysGet;
var
  Value: Variant;
  Handled: Boolean;
  Err, Prompt: string;
begin
  Prompt := VPop;
  Handled := False;
  Err := Format(RsESystemsNotDefined, [FCurrentSymbol]);
  if Assigned(OnGetSystem) then
    OnGetSystem(Self, FCurrentSymbol, Prompt, Value, Handled, Err);
  if not Handled then
    raise EJvJanScriptError.Create(Err)
  else
    VPush(Value);
end;

procedure TJvForthScript.ProcSysSet;
var
  Value: Variant;
  Handled: Boolean;
  Err: string;
begin
  Value := VPop;
  VPush(Value);
  Handled := False;
  Err := Format(RsECanNotAssignSystems, [FCurrentSymbol]);
  if Assigned(FOnSetSystem) then
  begin
    FOnSetSystem(Self, FCurrentSymbol, Value, Handled, Err);
    if not Handled then
      raise EJvJanScriptError.Create(Err);
  end;
end;

procedure TJvForthScript.SetOnGetSystem(const Value: TOnGetSystem);
begin
  FOnGetSystem := Value;
end;

procedure TJvForthScript.SetOnSetSystem(const Value: TOnSetSystem);
begin
  FOnSetSystem := Value;
end;

function TJvForthScript.PopValue: Variant;
begin
  Result := VPop;
end;

procedure TJvForthScript.PushValue(AValue: Variant);
begin
  VPush(AValue);
end;

function TJvForthScript.CanPopValue: Boolean;
begin
  Result := FVSP > 0;
end;

function TJvForthScript.CanPushValue: Boolean;
begin
  Result := FVSP < StackMax;
end;

procedure TJvForthScript.ProcPi;
begin
  VPush(Pi);
end;

procedure TJvForthScript.ProcDSO;
var
  AName, AMethod: string;
  Table: TJvJanDSO;
  AField, AValue: string;
  AKey: Variant;
  C: Integer;
begin
  AName := FCurrentSymbol;
  AMethod := FCurrentValue;
  Table := FDSOList.Table(AName);
  if AMethod = 'set' then
  begin
    AKey := VPop;
    AField := VPop;
    AValue := VPop;
    Table.SetValue(AKey, AField, AValue);
  end
  else
  if AMethod = 'get' then
  begin
    AKey := VPop;
    AField := VPop;
    AValue := Table.GetValue(AKey, AField);
    VPush(AValue);
  end
  else
  if AMethod = 'load' then
    Table.LoadFromFile(FDSOBase + PathDelim + AName + '.txt')
  else
  if AMethod = 'save' then
    Table.SaveToFile(FDSOBase + PathDelim + AName + '.txt')
  else
  if AMethod = 'Clear' then
    Table.Clear
  else
  if AMethod = 'Count' then
  begin
    C := Table.Count;
    VPush(C);
  end;
end;

procedure TJvForthScript.ProcDSOBase;
var
  S: string;
begin
  S := VPop;
  FDSOBase := S;
end;

procedure TJvForthScript.ProcSelDir;
{$IFDEF VCL}
var
  Dir: string;
begin
  Dir := FDSOBase;
  if SelectDirectory(Dir, [sdAllowCreate, sdPerformCreate, sdPrompt], 0) then
    FDSOBase := Dir;
end;
{$ENDIF VCL}
{$IFDEF VisualCLX}
var
  Dir: WideString;
begin
  Dir := FDSOBase;
  if SelectDirectory('Select Directory', PathDelim, Dir {$IFDEF UNIX}, True {$ENDIF}) then
    FDSOBase := Dir;
end;
{$ENDIF VisualCLX}

procedure TJvForthScript.ProcExtVar;
var
  AName, AMethod: string;
begin
  AName := FCurrentSymbol;
  AMethod := FCurrentValue;
  if AMethod = 'set' then
    ProcAssign
  else
  if AMethod = 'get' then
    ProcVariable
  else
    raise EJvJanScriptError.CreateResFmt(@RsEUnrecognizedExternalVariableMethodss, [AName, AMethod]);
end;

procedure TJvForthScript.ProcIntVar;
var
  AName, AMethod: string;
begin
  AName := FCurrentSymbol;
  AMethod := FCurrentValue;
  if AMethod = 'set' then
    ProcVarSet
  else
  if AMethod = 'get' then
    ProcVarGet
  else
  if AMethod = '1+' then
    ProcVarInc
  else
  if AMethod = '[1+]' then
    ProcVarIncIndex
  else
  if AMethod = '1-' then
    ProcVarDec
  else
  if AMethod = '1-?0' then
    ProcVarDecTestZero
  else
  if AMethod = '+' then
    ProcVarAdd
  else
  if AMethod = '-' then
    ProcVarSub
  else
  if AMethod = '*' then
    ProcVarMul
  else
  if AMethod = '/' then
    ProcVarDiv
  else
  if AMethod = '--' then
    ProcVarNeg
  else
  if AMethod = 'load' then
    ProcVarLoad
  else
  if AMethod = 'save' then
    ProcVarSave
  else
    raise EJvJanScriptError.CreateResFmt(@RsEUnrecognizedInternalVariableMethodss, [AName, AMethod]);
end;

procedure TJvForthScript.ProcSystem;
var
  AName, AMethod: string;
begin
  AName := FCurrentSymbol;
  AMethod := FCurrentValue;
  if AMethod = 'set' then
    ProcSysSet
  else
  if AMethod = 'get' then
    ProcSysGet
  else
    raise EJvJanScriptError.CreateResFmt(@RsEUnrecognizedSystemMethodss, [AName, AMethod]);
end;

procedure TJvForthScript.ProcVarDec;
var
  VO: TVariantObject;
begin
  VO := FVarsList.GetObject(FCurrentSymbol);
  if VO <> nil then
    VO.Value := VO.Value - 1
  else
    raise EJvJanScriptError.CreateResFmt(@RsEVariablesNotDefined_, [FCurrentSymbol]);
end;

procedure TJvForthScript.ProcVarInc;
var
  VO: TVariantObject;
begin
  VO := FVarsList.GetObject(FCurrentSymbol);
  if VO <> nil then
    VO.Value := VO.Value + 1
  else
    raise EJvJanScriptError.CreateResFmt(@RsEVariablesNotDefined_, [FCurrentSymbol]);
end;

procedure TJvForthScript.ProcVarAdd;
var
  VO: TVariantObject;
begin
  VO := FVarsList.GetObject(FCurrentSymbol);
  if VO <> nil then
    VO.Value := VO.Value + VPop
  else
    raise EJvJanScriptError.CreateResFmt(@RsEVariablesNotDefined_, [FCurrentSymbol]);
end;

procedure TJvForthScript.ProcVarDiv;
var
  VO: TVariantObject;
begin
  VO := FVarsList.GetObject(FCurrentSymbol);
  if VO <> nil then
    VO.Value := VO.Value / VPop
  else
    raise EJvJanScriptError.CreateResFmt(@RsEVariablesNotDefined_, [FCurrentSymbol]);
end;

procedure TJvForthScript.ProcVarMul;
var
  VO: TVariantObject;
begin
  VO := FVarsList.GetObject(FCurrentSymbol);
  if VO <> nil then
    VO.Value := VO.Value * VPop
  else
    raise EJvJanScriptError.CreateResFmt(@RsEVariablesNotDefined_, [FCurrentSymbol]);
end;

procedure TJvForthScript.ProcVarSub;
var
  VO: TVariantObject;
begin
  VO := FVarsList.GetObject(FCurrentSymbol);
  if VO <> nil then
    VO.Value := VO.Value - VPop
  else
    raise EJvJanScriptError.CreateResFmt(@RsEVariablesNotDefined_, [FCurrentSymbol]);
end;

procedure TJvForthScript.ProcVarNeg;
var
  VO: TVariantObject;
begin
  VO := FVarsList.GetObject(FCurrentSymbol);
  if VO <> nil then
    VO.Value := 0 - VO.Value
  else
    raise EJvJanScriptError.CreateResFmt(@RsEVariablesNotDefined_, [FCurrentSymbol]);
end;

procedure TJvForthScript.ProcPower;
var
  Value: Variant;
begin
  Value := VPop;
  VPush(Power(VPop, Value));
end;

procedure TJvForthScript.ProcAbs;
var
  Value: Variant;
begin
  Value := VPop;
  VPush(Abs(Value));
end;

procedure TJvForthScript.SetOnInclude(const Value: TOnInclude);
begin
  FOnInclude := Value;
end;

procedure TJvForthScript.ProcTan;
var
  Value: Variant;
begin
  Value := VPop;
  VPush(Tan(Value));
end;

procedure TJvForthScript.ProcArcCos;
var
  Value: Variant;
begin
  Value := VPop;
  VPush(ArcCos(Value));
end;

procedure TJvForthScript.ProcArcSin;
var
  Value: Variant;
begin
  Value := VPop;
  VPush(ArcSin(Value));
end;

procedure TJvForthScript.ProcArcTan;
var
  Value: Variant;
begin
  Value := VPop;
  VPush(ArcTan(Value));
end;

procedure TJvForthScript.ProcArcTan2;
var
  Value: Variant;
begin
  Value := VPop;
  VPush(ArcTan2(VPop, Value));
end;

procedure TJvForthScript.ProcVarLoad;
var
  VO: TVariantObject;
  AP, FN, S: string;
begin
  FN := VPop;
  AP := ExtractFilePath(ParamStr(0));
  FN := StringReplace(FN, '%', AP, []);
  VO := FVarsList.GetObject(FCurrentSymbol);
  if VO <> nil then
  begin
    S := LoadString(FN);
    VO.Value := S;
  end
  else
    raise EJvJanScriptError.CreateResFmt(@RsEVariablesNotDefined_, [FCurrentSymbol]);
end;

procedure TJvForthScript.ProcVarSave;
var
  VO: TVariantObject;
  AP, FN, S: string;
begin
  FN := VPop;
  AP := ExtractFilePath(ParamStr(0));
  FN := StringReplace(FN, '%', AP, []);
  VO := FVarsList.GetObject(FCurrentSymbol);
  if VO <> nil then
  begin
    S := VO.Value;
    SaveString(FN, S);
  end
  else
    raise EJvJanScriptError.CreateResFmt(@RsEVariablesNotDefined_, [FCurrentSymbol]);
end;

procedure TJvForthScript.ProcXML;
var
  AName, AMethod: string;
  XmlDSO: TJvXMLTree;
  N: TJvXMLNode;
  A: TJvXMLAttribute;
  APath, AtName: string;
  AValue: Variant;
  C, I, C2: Integer;
  ApplDir: string;
  B: Boolean;
begin
  N := nil;
  ApplDir := ExtractFilePath(ParamStr(0));
  AName := FCurrentSymbol;
  AMethod := FCurrentValue;
  XmlDSO := FXMLList.Xml(AName);
  if AMethod = 'set' then
  begin
    APath := VPop;
    AValue := VPop;
    N := XmlDSO.ForceNamePathNode(APath);
    N.Value := AValue;
  end
  else
  if AMethod = '@set' then
  begin
    APath := VPop;
    AtName := VPop;
    AValue := VPop;
    XmlDSO.ForceNamePathNodeAttribute(APath, AtName, AValue);
  end
  else
  if AMethod = 'get' then
  begin
    APath := VPop;
    N := XmlDSO.GetNamePathNode(APath);
    if N = nil then
      AValue := ''
    else
      AValue := N.Value;
    VPush(AValue);
  end
  else
  if AMethod = 'Count' then
  begin
    APath := VPop;
    N := XmlDSO.GetNamePathNode(APath);
    AValue := 0;
    C2 := 0;
    if N <> nil then
    begin
      // now Count named node
      C := N.Nodes.Count;
      APath := VPop;
      if C > 0 then
      begin
        for I := 0 to C - 1 do
          if TJvXMLNode(N.Nodes[I]).Name = APath then
            Inc(C2);
      end;
      AValue := C2;
    end;
    VPush(AValue);
  end
  else
  if AMethod = '@get' then
  begin
    APath := VPop;
    AtName := VPop;
    A := XmlDSO.GetNamePathNodeAttribute(APath, AtName);
    if N = nil then
      AValue := ''
    else
      AValue := A.Value;
    VPush(AValue);
  end
  else
  if AMethod = 'load' then
  begin
    APath := VPop;
    APath := StringReplace(APath, '%', ApplDir, []);
    if not FileExists(APath) then
      raise EJvJanScriptError.CreateResFmt(@RsEFilesDoesNotExist, [APath]);
    XmlDSO.LoadFromFile(APath);
  end
  else
  if AMethod = 'save' then
  begin
    APath := VPop;
    APath := StringReplace(APath, '%', ApplDir, []);
    try
      XmlDSO.SaveToFile(APath);
    except
      raise EJvJanScriptError.CreateResFmt(@RsECanNotSaveToFiles, [APath]);
    end
  end
  else
  if AMethod = 'astext' then
  begin
    AValue := XmlDSO.AsText;
    VPush(AValue);
  end
  else
  if AMethod = 'Delete' then
  begin
    APath := VPop;
    XmlDSO.deleteNamePathNode(APath);
  end
  else
  if AMethod = '@Delete' then
  begin
    APath := VPop;
    AtName := VPop;
    XmlDSO.DeleteNamePathNodeAttribute(APath, AtName);
  end
  else
  if AMethod = 'select' then
  begin
    APath := VPop;
    APath := StringReplace(APath, '''', '"', [rfReplaceAll]);
    FXMLSelect.Clear;
    FXMLSelectRecord := -1;
    XmlDSO.SelectNodes(APath, FXMLSelect);
    VPush(FXMLSelect.Count > 0);
  end
  else
  if AMethod = 'selectfirst' then
  begin
    B := FXMLSelect.Count <> 0;
    if B then
      FXMLSelectRecord := 0
    else
      FXMLSelectRecord := -1;
    AValue := B;
    VPush(AValue);
  end
  else
  if AMethod = 'selectnext' then
  begin
    B := FXMLSelect.Count <> 0;
    if B then
      Inc(FXMLSelectRecord)
    else
      FXMLSelectRecord := -1;
    if FXMLSelectRecord >= FXMLSelect.Count then
    begin
      B := False;
      FXMLSelectRecord := -1;
    end;
    AValue := B;
    VPush(AValue);
  end
  else
  if AMethod = 'selectget' then
  begin
    if FXMLSelect.Count = 0 then
      raise EJvJanScriptError.CreateRes(@RsEXMLSelectionIsEmpty);
    if FXMLSelectRecord = -1 then
      raise EJvJanScriptError.CreateRes(@RsENoXMLSelectionSelected);
    if FXMLSelectRecord >= FXMLSelect.Count then
      raise EJvJanScriptError.CreateRes(@RsEXMLSelectionOutOfRange);
    N := TJvXMLNode(FXMLSelect[FXMLSelectRecord]);
    AValue := N.Value;
    VPush(AValue);
  end
  else
  if AMethod = '@selectget' then
  begin
    if FXMLSelect.Count = 0 then
      raise EJvJanScriptError.CreateRes(@RsEXMLSelectionIsEmpty);
    if FXMLSelectRecord = -1 then
      raise EJvJanScriptError.CreateRes(@RsENoXMLSelectionSelected);
    if FXMLSelectRecord >= FXMLSelect.Count then
      raise EJvJanScriptError.CreateRes(@RsEXMLSelectionOutOfRange);
    N := TJvXMLNode(FXMLSelect[FXMLSelectRecord]);
    AtName := VPop;
    AValue := N.GetAttributeValue(AtName);
    VPush(AValue);
  end
  else
    raise EJvJanScriptError.CreateResFmt(@RsEInvalidXmlMethodSpecifiers, [AMethod]);
end;

procedure TJvForthScript.ProcVarDecTestZero;
var
  V: Variant;
  VO: TVariantObject;
begin
  VO := FVarsList.GetObject(FCurrentSymbol);
  if VO <> nil then
  begin
    V := VO.Value - 1;
    VO.Value := V;
    VPush(V = 0);
  end
  else
    raise EJvJanScriptError.CreateResFmt(@RsEVariablesNotDefined_, [FCurrentSymbol]);
end;

procedure TJvForthScript.ProcVarIncIndex;
var
  VO: TVariantObject;
  S, SIdx: string;
  PB, PE: Integer;
  Index: Integer;
begin
  VO := FVarsList.GetObject(FCurrentSymbol);
  if VO <> nil then
  begin
    S := VO.Value;
    PB := LastPosChar('[', S);
    if PB = 0 then
      raise EJvJanScriptError.CreateResFmt(@RsEIncrementIndexExpectedIns, [S]);
    PE := LastPosChar(']', S);
    if PE = 0 then
      raise EJvJanScriptError.CreateResFmt(@RsEIncrementIndexExpectedIns_, [S]);
    SIdx := Copy(S, PB + 1, PE - PB - 1);
    try
      Index := StrToInt(SIdx);
      Inc(Index);
      S := Copy(S, 1, PB - 1) + '[' + IntToStr(Index) + ']';
      VO.Value := S;
      VPush(S);
    except
      raise EJvJanScriptError.CreateResFmt(@RsEIncrementIndexExpectedIntegerBetwee, [S]);
    end;
  end
  else
    raise EJvJanScriptError.CreateResFmt(@RsEVariablesNotDefined_, [FCurrentSymbol]);
end;

procedure TJvForthScript.ProcCrLf;
begin
  VPush(sLineBreak);
end;

procedure TJvForthScript.ProcShellExecute;
var
  AFile: string;
  ApplDir: string;
begin
  ApplDir := ExtractFilePath(ParamStr(0));
  AFile := VPop;
  AFile := StringReplace(AFile, '%', ApplDir, []);
  Launch(AFile);
end;

procedure TJvForthScript.ProcDateStr;
var
  S: string;
begin
  S := FormatDateTime('dd-mmm-yyyy', Now);
  VPush(S);
end;

procedure TJvForthScript.ProcTimeStr;
var
  S: string;
begin
  S := FormatDateTime('hh:nn:ss', Now);
  VPush(S);
end;

procedure TJvForthScript.ProcNow;
begin
  VPush(Now);
end;

//=== { TAtom } ==============================================================

procedure TAtom.SetIsOperand(const Value: Boolean);
begin
  FIsOperand := Value;
end;

procedure TAtom.SetToken(const Value: TToken);
begin
  FToken := Value;
end;

procedure TAtom.SetProc(const Value: TProcVar);
begin
  FProc := Value;
end;

procedure TAtom.SetSymbol(const Value: string);
begin
  FSymbol := Value;
end;

procedure TAtom.SetValue(const Value: Variant);
begin
  FValue := Value;
end;

//=== { TAtomList } ==========================================================

procedure TAtomList.ClearObjects;
var
  I, C: Integer;
begin
  C := Count;
  if C = 0 then
    Exit;
  for I := 0 to C - 1 do
    TAtom(Items[I]).Free;
  Clear;
end;

destructor TAtomList.Destroy;
begin
  ClearObjects;
  inherited Destroy;
end;

//=== { TVariantObject } =====================================================

procedure TVariantObject.SetValue(const Value: Variant);
begin
  FValue := Value;
end;

//=== { TVariantList } =======================================================

procedure TVariantList.ClearObjects;
var
  I, C: Integer;
begin
  C := Count;
  if C = 0 then
    Exit;
  for I := 0 to C - 1 do
    TVariantObject(Objects[I]).Free;
  Clear;
end;

destructor TVariantList.Destroy;
begin
  ClearObjects;
  inherited Destroy;
end;

function TVariantList.GetObject(const Symbol: string): TVariantObject;
var
  Index: Integer;
begin
  Result := nil;
  if Count = 0 then
    Exit;
  Index := IndexOf(Symbol);
  if Index = -1 then
    Exit;
  Result := TVariantObject(Objects[Index]);
end;

function TVariantList.GetVariable(const Symbol: string): Variant;
var
  Index: Integer;
begin
  Result := null;
  if Count = 0 then
    Exit;
  Index := IndexOf(Symbol);
  if Index = -1 then
    Exit;
  Result := TVariantObject(Objects[Index]).Value;
end;

procedure TVariantList.SetVariable(const Symbol: string; AValue: Variant);
var
  Index: Integer;
  Obj: TVariantObject;
begin
  Index := IndexOf(Symbol);
  if Index = -1 then
  begin
    Obj := TVariantObject.Create;
    Obj.Value := AValue;
    AddObject(Symbol, Obj);
  end
  else
  begin
    TVariantObject(Objects[Index]).Value := AValue;
  end;
end;

//=== { TJvJanDSOList } ======================================================

procedure TJvJanDSOList.ClearTables;
var
  I, C: Integer;
begin
  C := Count;
  if C <> 0 then
    for I := 0 to C - 1 do
      TJvJanDSO(Objects[I]).Free;
  Clear;
end;

destructor TJvJanDSOList.Destroy;
begin
  ClearTables;
  inherited Destroy;
end;

function TJvJanDSOList.Table(const AName: string): TJvJanDSO;
var
  Index: Integer;
  DSO: TJvJanDSO;
begin
  Index := IndexOf(AName);
  if Index = -1 then
  begin
    DSO := TJvJanDSO.Create;
    AddObject(AName, DSO);
    Result := DSO;
  end
  else
    Result := TJvJanDSO(Objects[Index]);
end;

//=== { TJvJanDSO } ==========================================================

function TJvJanDSO.GetValue(AKey: Variant; const AField: string): string;
var
  Index: Integer;
  Key: string;
  StrKey: Boolean;
begin
  Key := AKey;
  StrKey := False;
  Index := 0;
  try
    Index := StrToInt(Key)
  except
    StrKey := True;
  end;
  if not StrKey then
  begin
    if Index >= Count then
      raise EJvJanScriptError.CreateResFmt(@RsEDSOIndexOutOfRanged, [Index])
    else
      Result := InternalGetValue(Index, AField);
  end
  else
  begin
    Index := IndexOfName(Key);
    if Index = -1 then
      raise EJvJanScriptError.CreateResFmt(@RsEDSOUnknownKeys, [Key]);
    Result := InternalGetValue(Index, AField);
  end
end;

function TJvJanDSO.InternalGetValue(Index: Integer; const AField: string): string;
var
  Key, S: string;
  P: Integer;
begin
  S := Strings[Index];
  P := Pos('=', S);
  Key := Copy(S, 1, P - 1);
  S := Copy(S, P + 1, Length(S));
  Result := GlobalGetValue(S, AField);
end;

procedure TJvJanDSO.InternalSetValue(Index: Integer; const AField, AValue: string);
var
  Key, S: string;
  P: Integer;
begin
  S := Strings[Index];
  P := Pos('=', S);
  Key := Copy(S, 1, P - 1);
  S := Copy(S, P + 1, Length(S));
  GlobalSetValue(S, AField, AValue);
  Strings[Index] := Key + '=' + S;
end;

procedure TJvJanDSO.SetValue(AKey: Variant; const AField, AValue: string);
var
  Index: Integer;
  Key: string;
  StrKey: Boolean;
begin
  Key := AKey;
  StrKey := False;
  Index := 0;
  try
    Index := StrToInt(Key)
  except
    StrKey := True;
  end;
  if not StrKey then
  begin
    if Index >= Count then
      raise EJvJanScriptError.CreateResFmt(@RsEDSOIndexOutOfRanged, [Index])
    else
      InternalSetValue(Index, AField, AValue);
  end
  else
  begin
    Index := IndexOfName(Key);
    if Index = -1 then
      Index := Add(Key + '=');
    InternalSetValue(Index, AField, AValue);
  end
end;

//=== { TJvJanXMLList } ======================================================

procedure TJvJanXMLList.ClearXMLS;
var
  I, C: Integer;
begin
  C := Count;
  if C <> 0 then
    for I := 0 to C - 1 do
      TJvXMLTree(Objects[I]).Free;
  Clear;
end;

destructor TJvJanXMLList.Destroy;
begin
  ClearXMLS;
  inherited Destroy;
end;

function TJvJanXMLList.Xml(const AName: string): TJvXMLTree;
var
  Index: Integer;
  XmlDSO: TJvXMLTree;
begin
  Index := IndexOf(AName);
  if Index = -1 then
  begin
    XmlDSO := TJvXMLTree.Create(AName, '', nil);
    AddObject(AName, XmlDSO);
    Result := XmlDSO;
  end
  else
    Result := TJvXMLTree(Objects[Index]);
end;

{$IFDEF UNITVERSIONING}
initialization
  RegisterUnitVersion(HInstance, UnitVersioning);

finalization
  UnregisterUnitVersion(HInstance);
{$ENDIF UNITVERSIONING}

end.
