kaitai_struct_compiler icon indicating copy to clipboard operation
kaitai_struct_compiler copied to clipboard

WIP: Typescript compiler.

Open aquach opened this issue 4 years ago • 7 comments

Hey there, I have a Typescript compiler I whipped up yesterday that I'd like to share. Rather than generating Typescript code directly, it generates .d.ts files, which are declaration files like .h files for C, that add types to untyped JS code.

Here's an example of generated code: https://github.com/aquach/test-ksy-typescript/blob/main/src/generated/Sqlite3.d.ts which is generated from https://github.com/aquach/test-ksy-typescript/blob/main/sqlite3.ksy. The goal of the .d.ts is to match the shape of the runtime, which is in https://github.com/aquach/test-ksy-typescript/blob/main/src/generated/Sqlite3.js. The net result is that Typescript code can get autocomplete and type inference for Kaitai-generated structs, which you can see if you check out the project and look at the test example https://github.com/aquach/test-ksy-typescript/blob/main/src/test.ts. Rather than being unchecked code, Typescript is able to complain if you misspell a field, or access something without checking nullity/undefinedness.

The implementation is actually not too bad because most of the runtime stuff can be entirely ignored. You really just need the class shape and the fields. I'm relatively new to Kaitai so please let me know what I'm missing. I tried to exercise a few different .ksy files from the site which exercised a fair amount of the feature set, but there's a lot I could be missing.

The only runtime change I needed to make was to the JS compiler to add a new __type field. This is a common pattern for discriminating unions in Typescript, and makes the resulting code much easier and safer to use. For example, with this .ksy snippet:

seq:
  - id: key_name
    type: cstring
  - id: body
    type:
      switch-on: key_name.value
      cases:
        '"ssh-rsa"': key_rsa
        '"ssh-dss"': key_dsa

Without __type:

const s = /* struct */;
switch (s.key_name) {
  case 'ssh-rsa':
    const body = s.body as KeyRsa;
    // operate on body
    break;
  case 'ssh-dss':
    const body = s.body as KeyDsa;
    // operate on body
    break;
}

With __type:

const s = /* struct */;
const body = s.body
switch (body?.__type) {
  case 'KeyRsa':
    // operate on body, which is automatically narrowed to KeyRsa
    break;
  case 'ssh-dss':
    // operate on body, which is automatically narrowed to KeyDsa
    break;
  case undefined:
    // handle undefined case
    break;
}

I'd also like to know how to go about writing tests for this, since a lot of the testing seems to be in the translation layer and not the compilation layer.

aquach avatar Nov 06 '21 23:11 aquach

thank you so much for this! i'm using it successfully for a typescript-based project already :smile:

naclomi avatar Nov 06 '21 23:11 naclomi

Hey @aquach , thanks for this contribution!

Just for sake of visibility - there was a previous attempt to add TypeScript support - https://github.com/kaitai-io/kaitai_struct_compiler/pull/165 - which seems to have largely stalled and never finished.

I'll try to get to review this one soon.

GreyCat avatar Nov 08 '21 20:11 GreyCat

Thanks! I did see that one. It looks like it was trying to generate native Typescript code instead of just the definition file. I believe generating the definition file is significantly easier (as evidenced by having something usable with only a few hundred lines of added code) so I decided to go down that path. Thanks for the review!

aquach avatar Nov 08 '21 21:11 aquach

Looks there are some test failures, but at first glance they don't seem related to my change 🤔

aquach avatar Nov 08 '21 21:11 aquach

Bump It'd be really nice to have this upstreamed

naclomi avatar Dec 17 '21 22:12 naclomi

Any updates on the status of this?

naclomi avatar Oct 18 '22 03:10 naclomi