ProtoDef-io/protodefc

Proposal: Enums

CertainLach opened this issue · 14 comments

Simple 1<=>1 enum mapping

Makes output code much easier to use

enum("a", type: ::u8)=>{ # Incorrect code, idents can't appear in Item
     value("A") => "3",
     value("B") => "4"
}

Needs to be done:

  • allowing idents to appear in items
  • enum item

@rom1504 It is, but if i want to have a enum declaration in other line of file?
Or same union keys (But not values) is used in different points of file?

Yeah, it is something like repeating yourself, but... Output code will be more usable if there is a actually enum in needed point

I think this is covered by map

Well, there is map available in protodefc? :D

So in the old JSON protodef, it was called "mapper" (https://github.com/PrismarineJS/minecraft-data/blob/master/data/pc/1.12.2/protocol.json#L3846). Can't find it in protodefc. But this is basically what you want right ?

Yeah, exactly

As far as I can see, there is nothing this can accomplish that can't be done with union. I also don't really see how this would make the output code any easier to read. A couple more examples would be appreciated.

Afaik mapper (and switch) was replaced by union in protodefc. (@roblabla)

It would be useful to use same mapper in multiple unions, to have only one enum emitted in target code

I still don't understand what this is supposed to accomplish, could you please post some more concrete examples?

As far as I understand it this does exactly what you are trying to do.

virtual_field("tag", value: "enum/@tag") => ::u8;
field("enum") => union("name_of_enum", tag: "../tag") {
    variant("a", match: "3") => container {};
    variant("b", match: "4") => container {};
};
enum("packet_type", type: ::u8)=>{
     variant("entity_look", match: "0")
     variant("position", match: "1")
}
namespace("play") {
    namespace("server") {
        def("to_client") => container {
            virtual_field("tag", value: "enum/@tag") => ::u8;
            field("data ") => switch(::packet_type, tag: "../tag") { # Incorrect code (ident in item)
                variant("entity_look") => ::play::server::s_entity_look;
                variant("position") => ::play::server::s_position;
            };
        }
    }
    namespace("client") {
        def("to_server") => container {
            field("client_version") => ::u8;
            virtual_field("tag", value: "enum/@tag") => ::u8;
            field("data") => switch(::packet_type, tag: "../tag") { # Incorrect code (ident in item)
                variant("entity_look") => ::play::server::c_entity_look;
                variant("position") => ::play::server::c_position;
            };
        }
    }       
}

My syntax is bad, needs to be improved
=> (Compiled code)

class Protocol {
    public static enum PacketType {
        ENTITY_LOOK(0),
        POSITION(1);
        byte match; // byte - because of u8
        PacketType(byte match){
            this.match = match;
        }
        static PacketType byMatch(byte){
             //...
        }
    }
    // Wrapping class
    public static class Play {
        public static class Server {
            @Data
            public static class ToClient {
                 Object data; // Can be Protocol.Play.Server.SEntityLook or Protocol.Play.Server.SPosition
                 public deserialize(byte[] data, offset){
                     Protocol.PacketType packetType = Protocol.PacketType.byMatch(readU8(data,offset)); 
                     
                     switch(packetType) {
                         case ENTITY_LOOK:
                             // ...
                         // ...
                     }
                 }
            }
        }
    }
}

In addition to what Rom said, the example you posted only makes sense for languages that don't support union types. It would make it harder to generate idiomatic code for languages like TypeScript, Rust or Haskell.

On the other hand, there is a feature that I have been planning that can accomplish some of the same things I imagine you intend with this, namely supporting native container types. This would enable you to write something like an Option and write idiomatic implementations in most languages.