OxygenBasic 60

Started by James C. Fuller, May 16, 2023, 07:05:26 PM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

James C. Fuller

Charles,
  Congrats on OxygenBasic 60
Tested a few apps and all is well so far.

James
  •  

Charles Pegge

Thanks James,

There are some small but important additions to 050, to improve encapsulation options. It is now possible to nest functions, inside functions. We also have subroutines for use with gosub, as an alternative to using raw labels. A subroutine can be safely placed anywhere inside a parent function, and have its own local variables.

'05/05/2023
'test to demo inner functions
'defpro
function f()
  function g()
    function h()
      function i()
        print "i"
      end function
      i
    end function
    h
  end function
  g
end function
f

more to follow..

Charles Pegge

#2

subroutine sb
...
if a>b
  exit subroutine
end if
...
end subroutine


function main(int a=1,b=2,c=3) as int
  a*=2
  b*=3
  gosub showAB
  a*=4
  b*=5
  gosub showAB

  subroutine showAB
    ...
    if a>b
      exit subroutine
    end if
    print "A=" a ",  B=" b
  end subroutine
end function
main

Charles Pegge

Subroutines can be nested, and also have their own local variables:

function main() as int
  int i=1
  subroutine sa
    int i=2
    subroutine sb
      int i=3
      subroutine sc
        int i=4
      end subroutine
      gosub sc
      print i '3
    end subroutine
    gosub sb
    print i '2
  end subroutine
  gosub sa
  print i '1
end function
main

Charles Pegge


Subs and functions are treated as equivalent by the o2 compiler. Both may or may not return a value. It is the programmer's choice of semantics. I've also introduced the equivalent term procedure. In the o2 compiler source code I use it to replace sub. It reads better and the 'sub' is only used in assembler.

Charles Pegge

#5
Type and Class now follow the same route through the compiler. So if you dislike classes use type instead :)

Note that internally defined classes do not carry a VFTP (Virtual Function Table Pointer). The structure is exactly as defined.

So you can simply add functions to well known types

type RECT
  int left
  int top
  int right
  int bottom
  '
  function area() as int
    'avoid confusion with rigft() and left()
    int x=this.right-this.left
    int y=bottom-top
    return x*y
  end function
end type
'
'#recordof rect
RECT r
r={1,1,3,5} '2*4
print r.area '8

Pierre Bellisle

#6
A function that call two functions inside a structure !!!
It make life real easy.

Charles, I now have the proof that you have found an extra-temporal door to visit the fifth dimension.
No way a normal human can come up with those ideas.
Thanks a lot
I like !

type RECT

  int left
  int top
  int right
  int bottom

  function x() as int
    // avoid confusion with string right() / left()
    int x = this.right - this.left
    return x
  end function

  function y() as int
    int y = this.bottom - this.top
    return y
  end function

  function area() as int
    return x() * y()
  end function

end type

RECT r = {-80,-50,-40,0}
print "Size x " r.x    '40
print "Size y " r.y    '50
print "Area "   r.area '2000
  •  

Theo Gottwald

#7
We have a Type structure with a function instead of a type?
Sounds interesting. I generally like such new Options.

This way we could make an manual "ON Initializer" and "ON Destroy" Option for Types.
However an automatic way would be nice.
I pledge for "On First Run" and "On Destroy" as an Optional Feature for each Object, like Types, Functions etc.
The Point is that OFTEN when using such Components, you will additionally need to initialize something
GLOBAL, or in MAIN(). And this currently can not yet be encapsulated easily.

Thinking about your TYPES. Seems like you have re-invented Objects in a preferable way (no COM Overhead).
Now also add the Option to let them "Live alone" (start them as own thread).
If we can design programs as in interaction of "Living types" (each an own thread comparable to a worker in a company)  it would be a new sort of programming paradigma.

About Subroutines ... the advantage of a normal GOSUB in Powerbasic was that it will just "compile to a JSR.-Mnemonic". No Overhead.
Now if it becomes local variables, it will also get a stackframe which is Overhead.
Do Subroutines always have that stackframe or ONLY IF they have local variables?

Charles Pegge

#8
Thanks Pierre,

I have some more updates in the pipeline for further nesting options - not quite working yet.

Hi Theo,

We have quite a wide range of options for classes/types. If constructor() and destructor() functions are included in the class, they are automatically invoked with new and del respectively, as in C++.

new RECT r
del RECT r


Regarding subroutines, they share the static and local space with their parent function, and are called directly without setting a new stack frame. Any variables defined inside a subroutine also belong to the parent function but are in a nested scope, so they are not visible outside the subroutine. The efficiency of subroutines is uncompromised.

Theo Gottwald

@Charles Pegge thats the best way to implement it.

Finally. Assume that you have a Subprogram in a Library that will need to Declare and define a
GLOBAL Variable.

How would you do that inside the Library definition currently?

Charles Pegge

Not sure what you mean by subprogram, Theo.

You can define anything in a library header file. Do you mean getting access to a DLL's Global variable space? That is technically possible.


Nicola

Hi Charles.
You amaze us every time you do an update.
Thank you for your great work.

I also have work for Help.

Cheers

Charles Pegge

Thanks Nicola,

I've added a few more things to RECT. It is starting to look rather bulky :)

'18/05/2023

macro RECTcalc()
  'avoid confusion with rigft() and left()
  float x=this.right-this.left
  float y=bottom-top
end macro
'
type RECT
  '
  int left
  int top
  int right
  int bottom
  '
  function constructor(int x1,y1,x2,y2)
    with this
    .left=x1
    .top=y1
    .right=x2
    .bottom=y2
    end with
    print "RECT created"
  end function
  '
  function destructor()
    print "RECT destroyed"
  end function
  '
  function area() as float
    RECTcalc
    return x*y
  end function
  '
  function diagonal() as float
    RECTcalc
    return hypot(x,y)
  end function
  '
  function aspect() as float
    RECTcalc
    return x/y
  end function
  '
end type
'
'#recordof rect

new RECT r(1,1,3,5)
print r.area '8
print r.diagonal '4.47
print r.aspect
del r


Theo Gottwald

@Charles Pegge
 
Here is a Code-Example. It shows the Part of a Library-Subprogram that needs to be put into the GLOBAL Scope of the Program. Currently i must call "#include Globalincludes.inc" and inside you will find stuff like this:
The Variable "%X_PROCESS_Globalincludes" is defined when that Library is used.

#IF %DEF(%X_PROCESS_Globalincludes)
 #IF NOT %DEF(%X_PROCESS_Globalincludes_done)
  %X_PROCESS_Globalincludes_done=1

%TH32CS_SNAPPROCESS = &H2& ' dwFlags for
%TH32CS_SNAPMODULE = &H8& ' CreateToolhelp32Snapshot
  %MAX_MODULE_NAME32 = 255

TYPE WR_CR_PROCESSENTRY32
dwSize AS DWORD
cntUsage AS DWORD
th32ProcessID AS DWORD ' This process
th32DefaultHeapID AS LONG PTR
th32ModuleID AS DWORD ' Associated exe
cntThreads AS DWORD
th32ParentProcessID AS DWORD ' This process's parent process
pcPriClassBase AS LONG ' Base priority of process threads
dwFlags AS DWORD
szExeFile AS ASCIIZ * %MAX_PATH ' Path
END TYPE

TYPE WR_CR_MODULEENTRY32
dwSize AS DWORD
th32ModuleID AS DWORD ' This module
th32ProcessID AS DWORD ' Owning process
GlblcntUsage AS DWORD ' Global usage count on the module
ProccntUsage AS DWORD ' Module usage count in th32ProcessID's context
modBaseAddr AS BYTE PTR ' Base address of module in th32ProcessID's context
modBaseSize AS DWORD ' Size in bytes of module starting at modBaseAddr
hModule AS DWORD ' The hModule of this module in th32ProcessID's context
szModule AS ASCIIZ * (%MAX_MODULE_NAME32 + 1)
szExePath AS ASCIIZ * %MAX_PATH
END TYPE

DECLARE FUNCTION CreateToolhelp32Snapshot (BYVAL dwFlags AS DWORD, BYVAL th32ProcessID AS DWORD) AS LONG
DECLARE FUNCTION Process32First (BYVAL hSnapshot AS DWORD, pe AS WR_CR_PROCESSENTRY32) AS LONG
DECLARE FUNCTION Process32Next (BYVAL hSnapshot AS DWORD, pe AS WR_CR_PROCESSENTRY32) AS LONG
DECLARE FUNCTION Module32First (BYVAL hSnapshot AS DWORD, ME AS WR_CR_MODULEENTRY32) AS LONG
DECLARE FUNCTION Module32Next (BYVAL hSnapshot AS DWORD, ME AS WR_CR_MODULEENTRY32) AS LONG
DECLARE FUNCTION EnumProcesses (idProcess AS DWORD, BYVAL cb AS DWORD, cbNeeded AS DWORD) AS LONG
DECLARE FUNCTION GetModuleFileNameEx (BYVAL hProcess AS DWORD, BYVAL hModule AS DWORD, ModuleName AS ASCIIZ, BYVAL nSize AS DWORD) AS DWORD
DECLARE FUNCTION EnumProcessModules (BYVAL hProcess AS DWORD, hModule AS DWORD, BYVAL cb AS DWORD, cbNeeded AS DWORD) AS LONG
DECLARE FUNCTION GetVersionStringInfo (BYVAL Fname AS STRING) AS STRING
 #ENDIF
#ENDIF
'------------------------------------------------------------------------------------------------
#IF %DEF(%WR_CS_Globalincludes)
 #IF NOT %DEF(%WR_CS_Globalincludes_done)
  %WR_CS_Globalincludes_done=1
  GLOBAL WR_CS_A AS STRING, WR_CS_B AS STRING, WR_CS_C AS LONG
 #ENDIF
#ENDIF



In the same way i currently must use a "#Include "Maincludes.inc"§ that contains the Code-Portion of a Library-Code that needs to be placed into the "MAIN()".

Here is an example. There may be several Subprogram which may later use these Initialized Datatypes,
that need to be initializes in MAIN(). This is done using the definition of "%D_SList_01_INC".
I would like to place such stuff inside the Library instead of in an external Include like i must do now.

#IF %DEF(%D_SList_01_INC)
   InitializeCriticalSection D_List_01A
   DIM D_ListBAT_01(%D_ListPANZ01,%D_ListVd01)
   D_Init_List_01()
   D_ListC01.A=0 ' reale Elemente
   D_ListC01.B=%D_ListVd01 ' Aktuelle Dimensionierung
   D_ListC01.Z=%D_ListPANZ01 ' erste Dimension
   D_ListC01.C=%D_ListVu01 ' Stepfaktor
   D_ListC01.F=0 ' erste Freie Zelle
 #ENDIF

Charles Pegge

#14
This is very interesting. It is not new but O2 has a facility for sharing global space with DLLs, as well as exported procedures, of course. You will need an inc file for each DLL with which you want to share variables. The variables are declared in a bind block. I have and example in inc\winutil.inc for sharing GUI state variables:

  '--------------------------------------
  'INCLUDE ON BOTH SERVER AND CLIENT SIDE
  '--------------------------------------
  sys b
  b = guistate()
  bind b {
    sys    hWndMain,hInst,inst,hDC,hRC
    int    pixelform
    int    mposx,mposy,sposx,sposy,eposx,eposy,iposx,iposy
    int    mmove,bleft,bmid,bright,bwheel
    int    pause
    int    bkey,keyd,lastkey,lastchar
    int    running
    int    key[256]
  }
  '--------------------------------------

On the DLL (server) side, the shared space is set up as follows:


  sys bu[0x400] 'STATIC BUFFER TO HOLD SHARED STATE VARIABLES
 
  function guistate() as sys export
    return @bu
  end function

  sys b = guistate()
  bind b {
    sys    hWndMain,hInst,inst,hDC,hRC
    int    pixelform
    int    mposx,mposy,sposx,sposy,eposx,eposy,iposx,iposy
    ...
  }

on the client side
  declare guistate lib "MyGuiLib.dll" () as sys

  sys b = guistate()
  bind b {
    sys    hWndMain,hInst,inst,hDC,hRC
    int    pixelform
    int    mposx,mposy,sposx,sposy,eposx,eposy,iposx,iposy
    ...
  }