kaitai_struct_compiler
kaitai_struct_compiler copied to clipboard
WIP: Typescript compiler.
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.
thank you so much for this! i'm using it successfully for a typescript-based project already :smile:
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.
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!
Looks there are some test failures, but at first glance they don't seem related to my change 🤔
Bump It'd be really nice to have this upstreamed
Any updates on the status of this?