Decimal BASIC Extension Guide

Inside structure of Decimal BASIC

 Decimal BASIC is a compiler that generates object Pascal objects, allowing the objects to operate independently.
 It defines an object type (Class) called TStatement that corresponds to BASIC statements, and achieves the desired processing by overriding the virtual method execute in its subtypes.
 Using BASIC Generic 0.7.5.9 as an example, the internal structure is described in the form of the procedure for adding new commands.
 On the Decimal BASIC homepage, go to the BASIC Generic page, download BASIC0759En_Source.zip (or a later version), and unzip it.
Download and install Lazarus (Lazarus Download)
Start Lazarus, select "Open Project" from the Project menu, and select basic.lpi from the BASIC source folder.
 From the Lazarus File menu, load extensio.pas into the source editor.
 Make sure  the file ends with:

begin
  tableInitProcs.accept(statementTableinit);
  tableInitProcs.accept(FunctionTableInit);
end. 


The Pascal begin-end statements are executed when the program starts.
tableInitProcs.accept is used to register statements and built-in functions.
statementTableinit is used to register statements, and FunctionTableInit is used to register built-in functions.

Adding Imperative Statements

The procedure

    StatementTableInitImperative('BEEP',BEEPst);

within begin ~ end of statementTableinit specifies that when 'BEEP' is read, translation will be performed by BEEPst.
 The definition of BEEPst is as follows:

function BEEPst(prev,eld:TStatement):TStatement;
begin
   BEEPst:=TBEEP.create(prev,eld)
end;  

This is a statement that creates a TBEEP type object. The arguments prev and eld of create are used to create a link between statements, but for now, it is enough to understand that this is the way it should be written.
The definition of the object type TBEEP is as follows:

type
  TBEEP=class(TStatement)
     exp1,exp2:TPrincipal;
      constructor create(prev,eld:TStatement);
      destructor destroy;override;
      procedure exec;override;
   end;  

TBEEP is a subclass that inherits from the TStatement class.
BEEP statement in Decimal BASIC does not have arguments by default, but in Windows there is also a form that takes two numeric expressions as arguments. The TPrincipal type variables exp1 and exp2 are used to hold the numeric expressions.
TBEEP.create is defined as follows:

constructor TBeep.create;
begin
  inherited create(prev,eld);
  if (tokenspec<>tail) and (token<>'ELSE') then
  begin
    exp1:=NExpression;
    check(',',IDH_FILE_ENLARGE);
    exp2:=NExpression;
  end;
end;  

In constructor, the constructor from which the object inherits is called first. prev and eld are used there. When constructor is executed, variables in the object are initialized to nil.
There are two types of BEEP statements: one with no arguments and one with two arguments, so processing in constructor is a little complicated. The end of a statement excluding end-of-line comments is determined by tokenspec=tail, but since the BEEP statement is a imperative statement, it can also be written in the part of IF ... THEN ~ ELSE ~. Therefore, to determine whether a statement has not ended, write an if statement like the one shown above (ELSE is a reserved word in Full BASIC). token is a global variable that holds the current token during translation. NExpression is a global function that translates a numeric expression. The results are stored in exp1 and exp2.
A comma is written between the two numeric expressions. check(',',IDH_FILE_ENLARGE) checks whether the comma is written correctly, and if it is correct, it skips the comma, and if not, generates an error according to the information in IDH_FILE_ENLARGE. If the position where IDH_FILE_ENLARGE is written is set to 0, it will not affect normal operation.
The destructor is called when the object is destroyed. Be sure to declare it with override.
The definition of the destructor is as follows:

destructor TBeep.destroy;
begin
   exp1.free;
   exp2.free;
   inherited destroy
end;  

To delete the two variables written in the TBEEP declaration section, exp1.free; and exp2.free; are executed, and then the inherited destructor is executed.
TBEEP.exec is executed when it is this object's turn to execute. Regardless of what the statement is, TStatement.exec is called. In order for TBEEP.exec to be executed correctly at that time, exec is declared in the TBEEP declaration section with override added.
The contents are as follows:

procedure TBEEP.exec;
var
   freq,duration:integer;
begin
   if exp1=nil then
      SysUtils.beep
   else
      begin
         freq:=exp1.evalInteger;
         duration:=exp2.evalInteger;
         {$IFDEF Windows}
         Windows.Beep(freq,duration);
         {$ELSE}
         SysUtils.beep;
         {$ENDIF}
      end;
end;  

 If there are no arguments, SysUtils.beep is simply executed. To determine if there are no arguments, a check is made to see if exp1 is nil. If arguments are written, the values ​​of the two numeric expressions are obtained and Windows.Beep is called. If arguments are written, the values ​​of the two numeric expressions are obtained and Windows.Beep is called. In the Decimal BASIC processing system, the method for evaluating a TPrincipal type variable to obtain an integer value is evalInteger. However, Windows.beep cannot be used in environments other than Windows. In that case, SysUtils.beep is executed, ignoring the values ​​of the two variables.
{$IFDEF ○○○}~{$ELSE}~{$ENDIF} are translation-time switches. If ○○○ is defined during translation, the first ~ will be translated. When running Lazarus in a Windows environment, Windows is defined.

Adding functions

 Let's look at the GETKEYSTATE function. The GETKEYSTATE function is a numeric function that takes a single numeric expression as an argument.

    {$IFNDEF Darwin}
     SuppliedFunctionTableInit('GETKEYSTATE' , GetKeyStatefnc);
    {$ENDIF}

{$IFNDEF ○○}~{$ENDIF} instructs that if ○○ is not defined, ~ is translated. Darwin is MAC OS. GETKEYSTATE is invalid in MAC OS, so it is restricted so that it cannot be translated.
 This statement instructs that if the token is 'GETKEYSTATE', GetKeyStatefnc is executed.
 The definition part of GetKeyStatefnc is as follows:

function  GetKeyStatefnc:TPrincipal;
begin
      GetKeyStatefnc:=NOperation(TGetKeyState.create)
end;

The function to be registered in SuppliedFunctionTableInit takes a TPrincipal value.
 The definition part of GetKeyStatefnc is as follows:

    TGetKeyState=class(TMiscInt)
       exp:TPrincipal;
      constructor create;
      function evalLongint:longint;override;
      destructor destroy;override;
    end; 

It has a TPrincipal type variable exp to hold the numeric expression argument. GETKEYSTATE acquires its function behavior by defining a function evalLongint that gives a LongInt value. Functions that determine function values ​​by setting a value with EvalLongint are defined by inheriting the TMiscInt class.
The constructor for TGetKeyState is as follows. checkToken is almost the same as check, but differs in that it semi-forcibly corrects the program if it is incorrect.

constructor TGetKeyState.create;
begin
     inherited create;
     checkToken('(',IDH_EXTENSION) ;
     exp:=NExpression;
     checkToken(')',IDH_EXTENSION);
end;

 The destructor for TGetKeyState looks like this:

destructor TGetKeyState.destroy;
begin
    exp.free;
    inherited destroy
end;   

 The GetKeyState function is defined as a descendant of TMiscInt. Its actual behavior is determined by defining a virtual method evalLongint that takes a value of type Longint.

function TGetKeyState.evalLongint:longint;
begin
   result:=GetKeyState(exp.evalinteger);
end;

The GetKeyState used here is defined in LCLIntf in the Lazarus LCL library. You can use the functions defined in LCLIntf by adding LCLIntf to the uses section of extensio.pas.

Adding real-valued functions

 Functions that take real values ​​are implemented by declaring them as a subtype of TMiscReal and defining a virtual method evalX that takes an Extended type value.
 It is important to note that when using Windows 64-bit or when the CPU is ARM, Extended = double. It may be necessary to use {$IF FPC_HAS_TYPE_EXTENDED} ~ {$ELSE} ~ {$ENDIF} to distinguish between the two cases.

Adding string functions

 A function that takes a string value can be implemented as a subclass of the TStrExpression class and by overriding the evalS method that takes a string.

New unit template

Save the following content as newcmd.pas, then in the Lazarus Project menu, select "Add" in the "Project Inspector", select "Add" again, and select newcmd.pas.
You can use a different spelling for the newcmd part, but make sure the unit name and file name match.
 After this, make the appropriate edits in the Lazarus IDE and compile. 
—――――――――――――――――――(From here)――――――――――――――――――――

unit newcmd;

  {$MODE DELPHI}{$H+}

interface

implementation
uses  Controls, Dialogs, Forms, SysUtils, lclintf, FileUtil, math,
    base, arithmet, texthand, variabl, struct, express, compiler, control,
 float,  helpctex, textfrm, MainFrm, sconsts, supplied;

{***********************************}
{ Imperative statement, one number and one string parameter }
{***********************************}

type
   TNewCommand=class(TStatement)

      exp1,exp2:TPrincipal;
      constructor create(prev,eld:TStatement);
      destructor destroy;override;
      procedure exec;override;
    end;

constructor TNewCommand.create(prev,eld:TStatement);
begin
   inherited create(prev,eld);
   exp1:=NExpression;
   check(',',0);
   exp2:=SExpression;
end;

destructor TNewCommand.destroy;
begin
   exp2.free;
   exp1.free;
   inherited destroy
end;

procedure TNewCommand.exec;
var
   x:double;
  s:string;
begin
   x:=exp1.EvalF;
   s:=exp2.EvalS;
  //Write the necessary processing hereend 
end;

function  NewCommandst(prev,eld:TStatement):TStatement;
begin
    NewCommandst:=TNewCommand.create(prev,eld);
end;

{********************}
{Numeric function, takes one numeric argument }
{*********************}
type
   TNewNFunction=Class(TMiscReal)
             exp:TPrincipal;
          constructor create;
          function evalX:extended;override;
          destructor destroy;override;
     end;

constructor TNewNFunction.create;
begin
    inherited create;
    check('(',0);
    exp:=NExpression;
    check(')',0);
end;

function TNewNFunction.evalX:extended;
var
   x, y: extended;
begin
   x:=exp.evalX;
  //here, find y from x 
   evalX:=y;
end;

destructor TNewNFunction.destroy;
begin
   exp.free;
   inherited destroy;
end;

function NewNFunctionfnc:TPrincipal;
begin
   NewNFunctionfnc:=NOperation(TNewNFunction.create);
end;

{*********************************}
{string-valued function, arguments are one string and one number} 
{**********************************}

type
   TNewSFunction=class(TStrExpression)
             exp1,exp2:TPrincipal;
          constructor create;
          function evalS:string;override;
          destructor destroy;override;
     end;

constructor TNewSFunction.create;
begin
   inherited create;
   check('(',0);
   exp1:=SExpression;
   check(',',0);
   exp2:=NExpression;
   check(')',0);
end;

destructor TNewSFunction.destroy;
begin
   exp2.free;
   exp1.free;
   inherited destroy;
end;

function TNewSFunction.evalS:ansistring;
var
   n:longint;
   s,t:string;
begin
   s:=exp1.evalS;
   n:=exp2.evalInteger;
  //Find t from s and n
   evalS:=s;
end;

function NewSFunctionfnc:TPrincipal;
begin
   NewSFunctionfnc:=TNewSFunction.create;
end;


{**********}
{initialize}
{**********}

procedure statementTableinit;
begin
       StatementTableInitImperative('NEWCOMMAND', NewCommandst);

end;


procedure  FunctionTableInit;
begin
     SuppliedFunctionTableInit('NEWFN' , NewNFunctionfnc);
     SuppliedFunctionTableInit('NEWF$' , NewSFunctionfnc);
end;


begin
  tableInitProcs.accept(statementTableinit);
  tableInitProcs.accept(FunctionTableInit);
end.

—―――――――――――――――――――(Fin)―—―――――――――――――――――

Please note that statement names and function names must be 10 characters or less, including the $ at the end of string function names, and alphabetic characters must be registered in uppercase.
When using functions defined in other units, including LCL, add the name of the unit to the uses clause.

Linking external DLLs

You can use external DLLs to create new commands. For example, to use Beep in win32 API, add a line

function Beep(dwFreq:DWORD; dwDuration:DWORD):WINBOOL; stdcall; external 'kernel32' name 'Beep';

Only this line defines a function BEEP. Assign the name of a DLL in external section, and the name in the DLL in the name section. The instructions for external and name (the part enclosed in ' ') distinguish between case and uppercase. The argument type and number, and result type are used to call the DLL function. The argument names have no meaning. If you call a function in a DLL made for C, substitute cdecl for stdcall.
TBEEP.exec, shown above, can be rewriten as folows.

{$IFDEF Windows}
function Beep(dwFreq:DWORD; dwDuration:DWORD):WINBOOL; stdcall; external 'kernel32' name 'Beep';
{$ENDIF}
procedure TBEEP.exec;
var
   freq,duration:integer;
begin
   if exp1=nil then
      SysUtils.beep
   else
      begin
         freq:=exp1.evalInteger;
         duration:=exp2.evalInteger;
         {$IFDEF Windows}
         Beep(freq,duration);
         {$ELSE}
         SysUtils.beep;
         {$ENDIF}
      end;
end;                               

Note that the program will not start in an environment where the specified DLL does not exist.
And note in Decimal BASIC Ver.8, Ver.0.9, BASIC Accelerator, and Paract BASIC, the GUI cannot be executed in the execution thread.

BASIC Accelerator,Paract BASIC

In BASIC Accelerator and Paract BASIC, Pascal statements are generated by overriding the virtual method "code", which has an ansistring type value, for both statements and functions. The code method of the components can be used.
 See statemen.pas and supplied.pas for examples.
You can also use procedures and functions in your own libraries in the Pascal code you generate. In that case, you must add them (the units used at run time) to the UsesBlock in TProcTbl.Code in struct.pas. In the test phase, you can modify and run BASICunit.pas, which can be obtained by selecting Code from the Run menu. Note that in the units used at run time, procedures and functions referenced from outside must be declared in the interface section.

 
Return