Skip to content

user-defined aggregate types #609

Open
@YashasSamaga

Description

@YashasSamaga

Issue description:

Most developers (ab)use enums to create aggregate user-defined types. There are few disadvantages to this method:

  • it's a hack! enum structs are handled in an unconventional way and has absurd rules and special cases to make it work
  • identifiers are spilled into global namespace

We can implement a new system to support user-defined aggregate types once in for all. To proceed we need to draft a spec for the syntax and semantics and also have consensus for the feature.

Here is my proposal (ad-hoc and not-well thought) to kick off discussions:

Tags are currently used for user-defined types. We can extend it to allow user-defined aggregate types.

/* static */ struct Player {
    id,
    playerid,
    Float:health,
    bool:spawned,
    
    bool:isValid() { return id != 0; }
    spawn(Float:pX, Float:pY, Float:pY) { /* do something */ }

    Player:setHealth(Float:hp) { SetPlayerHealth(playerid, hp); return this; }
    Player:setArmour(Float:ar) { SetPlayerArmor(playerid, ar); return this; }

    static Player:create() { /* no 'this' here */ } // just syntactic sugar: Player.create()? do we even need this in the first draft?
    static const Float:MAX_HEALTH = 100.0; // Player.MAX_HEALTH?
};

bool:operator==(Player:a, Player:b) { return a.id == b.id; } // operator overloads as member functions isn't required

sizeof(Player); // returns the size of the struct (number of cells)
tagof(Player:); // returns the tag id

new Player:x; // allocates a block of memory of sizeof(Player) -- practically equivalent to x[sizeof(Player)]
new Player:pInfo[MAX_PLAYERS]; // equivalent to x[MAX_PLAYERS][sizeof(Player)];

x.setHealth(100.0).setArmour(100.0);

doPlayerStuff(&Player:p) { } // passes the address
doPlayerStuff(Player:p) { } // passes a copy of the object in stack

We can have a new class for tags: aggregate-type tag and primitive-type tag. We can use the last but one bit to classify these (the MSB is currently used to mark STRONG and WEAK tags). Tag overrides b/w aggregate types might trigger a warning.

Questions:

  1. what should be printed by the following code:
    new Player:x;
    main () { printf("%d", _:x); }
    
  2. Does pawn allow references to be returned?
  3. Does pawn allow operator overloads to accept references?
  4. What is this? What should be the output of printf("%d", _:this);?
  5. Is call-by-value as default newbie friendly?
  6. What does new Player:a = Player:0; mean?
  7. How to embed debug information?
  8. What happens to an empty struct?

We could in principle implement the whole thing as an enum based struct internally and add special handling to prevent identifiers from being spilled out. This would mean passing an object by default would pass by reference.


Bonus: simulate namespaces with static members

struct Vector3f {
    Float:x,
    Float:y,
    Float:z

    Float:norm() { return VectorSize(x, y, z); }
};

struct Vehicle {
    static const INVALID_ID = 65535;

    static Create(modelid, Vector3f:pos) { return CreateVehicle(modelid, pos.x, pos.y, pos.z); }
    static Create(modelid, Float:x, Float:y, Float:z) { /* ... */ } // in case we support function overloading for non-publics some day
    static Destroy(id) { return DestroyVehicle(id); }

    static bool:IsValid(id) { }
};

new Vector3f:position;
position.x = 123.0;
position.y = 555.0;
position.z = 1234.0;

new id = Vehicle.Create(420, position);

While this does simulate a namespace, it uses a . instead of the _ convention for naming modules. I don't like this syntax but this is a possibility.


Bonus: unions with some tag overrides

struct TypeA {
    Float:x
};

struct TypeB {
    x
};

new TypeA:something;

new x = (TypeB:something).x;

We can similarly make variants by having a type member variable.


Bonus: abuse to support polymorphism (needs more work but it seems to be possible)

struct BaseClass {
    type,
    something() {
        switch(type) {
            case 0: { /* something for base class -- maybe crash if u want base to be abstract? very bad but ... */ }
            case 1: (DerivedClass:this).something();
        }
     }
};

struct DerivedClass {
    type,
    something() { }
}


Related issues:

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions