Blitzmax - Class methods outside Type block?

Started by PixelOutlaw, March 02, 2025, 04:24:12

Previous topic - Next topic

PixelOutlaw

Hello All,

One of the things I'm working on is a Lisp implementation in BlitzMax.

It's going quite well but one thing that I'm stuck on is how to make a Type more modular.

I need many methods for the Lisp state machine but unfortunately I'd like to group them across multiple files for organization (the way one might in other OOP languages such as C++).
Is there a possible way to define methods outside the class definition block?
For example, I'd like the testing methods defined in one file, and the memory methods defined in another etc.

Here is a sample of the REPL. This stuff is all very early.

One DEFUN to rule them all, One DEFUN to find them, One DEFUN to RETURN them all, and in the darkness MULTIPLE-VALUE-BIND them.

Derron

you could use "include" --- as this does insert the include file during compile only.

In your video I only see "functions", not methods.. so no "type" in use at all.

You can also do things like this:


'file1.bmx
Type TLispFunctions_Image
  Function bla()
  End Function
End Type

'file2.bmx
Type TLispFunctions
  Global Image:TLispFunctions
End Type

'main.bmx:
TLispFunctions.Image.bla()


Instead of globals you can also use "field" and instances... eg if each "function block" has some local data ("currentGraphicsContext" etc).

If all functions can be wrapped into a function class (so some "indirection") you could use a hash map (TPointerMap or TMap or ...) in which you store your function ID / name / hash as key and the function class instance as value.


bye
Ron

PixelOutlaw

#2
I've been meaning to respond but I've been away most of today.

Apologies for the confusion, the video was just to show what I was implementing (the basic functionality of the REPL)

I don't have my local code up on github as I've been working on an experimental branch.

Essentially the "machine" owns an array of NaN boxed values that implements the type system for Lisp.
I've rolled that into a field in the BLisp type object.

Now, nearly every one of those functions in the video (there are probably 30 or 40) need to become methods of the BLisp machine Type.

There are different categories of method that'd I'd like in different files.
Type BLisp
    Field N:UInt = 1024 * 1024 * 5 ' number of Lisp objects (doubles) to store
    Field hp:ULong = 0 ' heap pointer
    Field sp:ULong = N ' stack pointer
    Field cell:Double[n]

    ' about 40 or so like below in theroy ...
    Method resetCells()
      For Local i:Int = 0 Until n
        cell[i] = 0
      Next
    End Method
End type

' I just have a single example method there but I was hoping I could split them out of that body
' Something like (pseudo code here)

Method BLisp::resetCells()
 ...
End Method

Here is what might be done in C++...
#include <iostream>
#include <array>

class foo
{
private:
    std::array<double, 10> my_array = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};

public:
    double bar(double val);
    double baz(double val);
    void qux();
    void print();
};


// pretend these are in a different file

double foo::bar(double val)
{
    return val * 10;
}

double foo::baz(double val)
{
    return val - 5;
}

void foo::qux()
{
    my_array[3] = baz(bar(baz(baz(bar(42)))));
}

void foo::print()
{
    for (int i : my_array)
        std::cout << i << " ";
    std::cout << std::endl;
}

int main()
{
    foo my_foo; // Correct object instantiation
    std::cout << "Printing defaults" << std::endl;
    my_foo.print();
    std::cout << "Printing mutated state (4th index)" << std::endl;
    my_foo.qux();
    my_foo.print();
    return 0;
}


I suppose I could use the hashmap and function class as a last resort.
Also I'm not quite sure what you mean on your include idea.
One DEFUN to rule them all, One DEFUN to find them, One DEFUN to RETURN them all, and in the darkness MULTIPLE-VALUE-BIND them.

Derron

I am nowhere using include, and am not able to check it now ... but I meant something like this:
Type TMyType
include "mytype_fields.bmx"
include "mytype_methods1.bmx"
include "mytype_methods2.bmx"
End Type

Of course I discourage from using such a ... construct.


So as said ... simply use a components/handler approach - or extend types (but this is also .. not the best here).

Code (Blitzmax) Select
Type TLispData
Field N:UInt = 1024 * 1024 * 5 ' number of Lisp objects (doubles) to store
Field hp:ULong = 0 ' heap pointer 
Field sp:ULong = N ' stack pointer 
Field cell:Double[n]
End Type


Type TLispHandler 'this could also be an "interface" !
Method Run(data:TLispData)
'eg reset cells..
End Method
End Type


Type TLisp
Field data:TLispData
Field handlers:TStringMap = New TStringMap()

Method AddHandler(key:String, handler:TLispHandleR)
handlers.insert(key, handler)
End Method

Method Run(key:String)
Local handler:TLispHandler = handlers.ValueForKey(key)
If handler Then handler.Run(data)
End Method
End Type


'implementation:
Type TLispHandler_ResetCells extends TLispHandler
Method Run(data:TLispData)
'eg reset cells..
End Method
End Type


Local l:TLisp = New TLisp
l.addHandler("ResetCells", New TLispHandler_ResetCells)
l.Run("ResetCells")


While above allows to have individual data assigned to each "function" (handler) the following is the function-pointer based code proposal:
Type TLispData
Field N:UInt = 1024 * 1024 * 5 ' number of Lisp objects (doubles) to store
Field hp:ULong = 0 ' heap pointer 
Field sp:ULong = N ' stack pointer 
Field cell:Double[n]
End Type


Type TLisp
Field data:TLispData
Field handlers:Int(data:TLispData)[]

Global fn_ResetCells:Int = 0
Global fn_InsertCells:Int = 1


Method RegisterHandler(id:Int, handler:Int(data:TLispData))
If id < 0 Then Return
If handlers.length <= id Then handlers = handlers[.. id+1]
handlers[id] = handler
End Method

Method Run(id:Int)
If id < 0 or id >= handlers.length Then Return
Local handler:TLispHandler = handlers[id]
If handler Then handler(data)
End Method
End Type


'implementation:
Function Lisp_ResetCells(data:TLispData)
'eg reset cells..
End Function

Local l:TLisp = New TLisp
l.RegisterHandler(TLisp.fn_ResetCells, Lisp_ResetCells)
l.Run(TLisp.fn_ResetCells)


As you need to connect "functions" with "the one to use" you can also simply define your functions in some file ... and only have the "interface" in your TLisp type
Type TLisp
Field N:UInt = 1024 * 1024 * 5 ' number of Lisp objects (doubles) to store
Field hp:ULong = 0 ' heap pointer 
Field sp:ULong = N ' stack pointer 
Field cell:Double[n]


Method ResetCells()
Lisp_ResetCells(N, hp, sp, cell)
End Method
End Type


'somewhere in other files:
Function Lisp_ResetCells(N:UInt, hp:ULong, sp:ULong, cell)
'eg reset cells..
End Function

As you always pass around the used variables, and they are primitives, the function"files" do not need to know about things.


bye
Ron

PixelOutlaw

#4
Thanks Derron, this gives me a good starting point!

Looks like the first option was a non-starter. :(


One DEFUN to rule them all, One DEFUN to find them, One DEFUN to RETURN them all, and in the darkness MULTIPLE-VALUE-BIND them.