200sc / bebop

bebop wire format in Go
Apache License 2.0
72 stars 5 forks source link

Bebop v3 #40

Open andrewmd5 opened 8 months ago

andrewmd5 commented 8 months ago

Hey! So I just wanted to drop a note that Bebop v3 is officially out and it has a number of breaking changes to the schema languages.

With that being said, it also has a new compiler extension layer which would allow you to write a Go generator in Go, and I wanted to see if you'd be interested in collaborating to bring this project into the ecosystem in a more standard way, so folks can use bebopc to generate code, and continue relying on your runtime.

You can find an example of a Go extension here.

200sc commented 8 months ago

Just so I understand, is there an existing extension format that requires the use of e.g. gjson and tiny go or bebopc just supports any wasm based extension?

In the latter case, it's not unreasonable to offer a prebuilt wasm target using this codebase, however there's a number of questions, e.g. does the extension parse the AST or is it provided the AST to render; how are the incompatibilities around recursive type definitions, etc resolved; does bebopc support an output mode compatible with distinct modules or would the extension have to output assuming all dependencies are adjacent in the output file, and so on.

I'll open issues for the new functionality (mut, env vars, custom decorators, tempo)

andrewmd5 commented 8 months ago

Just so I understand, is there an existing extension format that requires the use of e.g. gjson and tiny go or bebopc just supports any wasm based extension?

I need to improve the documentation, but there is a new extension format called chords (you can find the chord compiler on the release page). Chords are ultimately just WASM/WASI binaries, and bebopc has glue code to interact with them. Right now, there is built in support for chords produced from TinyGo, Javascript, and AssemblyScript.

At a high-level, bebopc feeds a JSON string (the AST) to the extension which contains all the definitions in a schema:

{
  "definitions": {
    "Instrument": {
      "kind": "enum",
      "documentation": "A a collection of would play see  for more information",
      "decorators": {
        "flags": {}
      },
      "isBitFlags": true,
      "minimalEncodedSize": 4,
      "baseType": "uint32",
      "members": {
        "Default": {
          "value": "0"
        },
        "Sax": {
          "documentation": "What",
          "value": "1"
        },
        "Trumpet": {
          "decorators": {
            "deprecated": {
              "arguments": {
                "reason": {
                  "type": "string",
                  "value": "Use of Trumpet is not recommended"
                }
              }
            }
          },
          "value": "2"
        },
        "Clarinet": {
          "value": "3"
        }
      }
    },
    "Musician": {
      "kind": "struct",
      "documentation": "A musician in the band",
      "minimalEncodedSize": 32,
      "mutable": false,
      "isFixedSize": false,
      "fields": {
        "name": {
          "documentation": "The name of the musician",
          "type": "string"
        },
        "plays": {
          "documentation": "The instrument the musician plays",
          "type": "Instrument"
        },
        "birthDate": {
          "type": "date"
        },
        "id": {
          "type": "guid"
        }
      }
    },
    "Something": {
      "kind": "struct",
      "minimalEncodedSize": 28,
      "mutable": false,
      "isFixedSize": false,
      "fields": {
        "name": {
          "type": "string"
        },
        "age": {
          "type": "uint32"
        },
        "arrayOfMaps": {
          "type": "array",
          "array": {
            "depth": 0,
            "memberType": "map",
            "map": {
              "keyType": "string",
              "valueType": "string"
            }
          }
        },
        "complexIntMapArray": {
          "type": "array",
          "array": {
            "depth": 0,
            "memberType": "map",
            "map": {
              "keyType": "string",
              "valueType": "array",
              "array": {
                "depth": 0,
                "memberType": "int32"
              }
            }
          }
        },
        "testMap": {
          "type": "map",
          "map": {
            "keyType": "string",
            "valueType": "string"
          }
        },
        "complexMap": {
          "type": "map",
          "map": {
            "keyType": "string",
            "valueType": "array",
            "array": {
              "depth": 0,
              "memberType": "string"
            }
          }
        },
        "nestedMap": {
          "type": "map",
          "map": {
            "keyType": "string",
            "valueType": "map",
            "map": {
              "keyType": "string",
              "valueType": "string"
            }
          }
        }
      }
    },
    "Song": {
      "kind": "message",
      "documentation": "A song",
      "minimalEncodedSize": 5,
      "fields": {
        "title": {
          "documentation": "The title of the song",
          "type": "string",
          "index": 1
        },
        "year": {
          "documentation": "The year the song was released",
          "type": "uint16",
          "index": 2
        },
        "performers": {
          "documentation": "The musicians who performed the song",
          "decorators": {
            "deprecated": {
              "arguments": {
                "reason": {
                  "type": "string",
                  "value": "Use of performers is not recommended"
                }
              }
            }
          },
          "type": "array",
          "array": {
            "depth": 0,
            "memberType": "Musician"
          },
          "index": 3
        }
      }
    },
    "MySong": {
      "kind": "struct",
      "minimalEncodedSize": 4,
      "discriminatorInParent": 1,
      "mutable": true,
      "isFixedSize": false,
      "fields": {
        "name": {
          "type": "string"
        }
      },
      "parent": "SongOrMusician"
    },
    "Test": {
      "kind": "message",
      "minimalEncodedSize": 5,
      "discriminatorInParent": 2,
      "fields": {
        "name": {
          "type": "string",
          "index": 1
        }
      },
      "parent": "SongOrMusician"
    },
    "SongOrMusician": {
      "kind": "union",
      "minimalEncodedSize": 9,
      "branches": {
        "MySong": 1,
        "Test": 2
      }
    }
  },
  "services": {
    "Music": {
      "kind": "service",
      "documentation": "A service for getting songs",
      "decorators": {
        "debug": {
          "arguments": {
            "astring": {
              "type": "string",
              "value": "dadad"
            },
            "anumber": {
              "type": "uint32",
              "value": "2"
            },
            "aboolean": {
              "type": "bool",
              "value": "false"
            },
            "anumber2": {
              "type": "uint32",
              "value": "0"
            }
          }
        }
      },
      "methods": {
        "getSongsByMusicianUnary": {
          "decorators": {
            "deprecated": {
              "arguments": {
                "reason": {
                  "type": "string",
                  "value": "Use of getSongsByMusician is not recommended"
                }
              }
            }
          },
          "documentation": "Gets a stream of songs by the specified musician",
          "type": "Unary",
          "requestType": "Musician",
          "responseType": "Song",
          "id": 4079743983
        },
        "getSongsByMusicianDuplex": {
          "type": "DuplexStream",
          "requestType": "Musician",
          "responseType": "Song",
          "id": 3611889031
        },
        "getSongsByMusicianClientStream": {
          "type": "ClientStream",
          "requestType": "Musician",
          "responseType": "Song",
          "id": 444332555
        },
        "getSongsByMusicianServerStream": {
          "type": "ServerStream",
          "requestType": "Musician",
          "responseType": "Song",
          "id": 3879401943
        }
      }
    }
  },
  "constants": {
    "exampleConstGuid": {
      "kind": "const",
      "documentation": "An ID for my favorite song",
      "type": "guid",
      "value": "e215a946b26f4567a27613136f0a1708"
    }
  },
  "config": {
    "alias": "acme",
    "outFile": "yo.json",
    "namespace": "",
    "emitNotice": true,
    "emitBinarySchema": true,
    "services": "both",
    "options": {}
  }
}

The extension can then render a string and return it to the compiler. In an extensions chord.json file it contributes a "generator" and that generate is surfaced through bebopc just like all the existing built-ins. I will document everything in detail over the next few days and get feedback from you on that.

The reason I used TinyGo is simply because exporting with Go's WASI target didn't seem to work, and that meant I had to use gjson to parse the string sent by the compiler. The example I provided probably isn't the greatest since Go is not a language I'm strong in.

Right now, the extension would have to commit its result assuming all dependencies are adjacent in the output file. I'm open to feedback here.