[READ-ONLY] Mirror of https://github.com/bombshell-dev/tty. Platform independent 2D layout engine for terminal applications based on Clay
3.0 kB
150 lines
1export type Attrs<T> = {
2 [K in keyof T]: TypeDef<T[K]>;
3};
4
5export type Alignment = 1 | 2 | 4 | 8;
6
7export type LayoutElement<T> = {
8 type: "padding";
9 byteLength: number;
10} | {
11 type: "field";
12 name: string;
13 offset: number;
14 typedef: Attrs<T>[keyof Attrs<T>];
15};
16
17export type Struct<T> = {
18 type: "struct";
19 byteLength: number;
20 byteAlign: Alignment;
21 layout: LayoutElement<T>[];
22};
23
24export type Num<T> = {
25 type:
26 | "i32"
27 | "f32"
28 | "f64"
29 | "uint8"
30 | "uint16"
31 | "uint32"
32 | "int16"
33 | "int32";
34 byteAlign: Alignment;
35 byteLength: number;
36 T?: T;
37};
38
39export type Arr<T> = {
40 type: "array";
41 element: TypeDef<T>;
42 length: number;
43 byteLength: number;
44 byteAlign: Alignment;
45};
46
47export type TypeDef<T> = Num<T> | Struct<T> | Arr<T>;
48
49export function array<T>(element: TypeDef<T>, length: number): Arr<T[]> {
50 return {
51 type: "array",
52 element: element as TypeDef<T[]>,
53 length,
54 byteLength: element.byteLength * length,
55 byteAlign: element.byteAlign,
56 };
57}
58
59export const f32 = (): TypeDef<number> => ({
60 type: "f32",
61 byteLength: 4,
62 byteAlign: 4,
63});
64export const int32 = (): TypeDef<number> => ({
65 type: "int32",
66 byteLength: 4,
67 byteAlign: 4,
68});
69export const uint8 = (): TypeDef<number> => ({
70 type: "uint8",
71 byteLength: 1,
72 byteAlign: 1,
73});
74export const uint16 = (): TypeDef<number> => ({
75 type: "uint16",
76 byteLength: 2,
77 byteAlign: 2,
78});
79export const uint32 = (): TypeDef<number> => ({
80 type: "uint32",
81 byteAlign: 4,
82 byteLength: 4,
83});
84
85export function struct<T extends object>(
86 attrs: Attrs<T>,
87): Struct<T> {
88 let entries = Object.entries(attrs) as [
89 keyof Attrs<T>,
90 Attrs<T>[keyof Attrs<T>],
91 ][];
92
93 let acc = {
94 layout: [] as Struct<T>["layout"],
95 offset: 0,
96 };
97
98 let byteAlign = Math.max(
99 ...entries.map(([, typedef]) => typedef.byteAlign),
100 ) as Alignment;
101
102 for (let [name, typedef] of entries) {
103 let padding = pad(acc.offset, typedef.byteAlign);
104 if (padding > 0) {
105 acc.layout.push({ type: "padding", byteLength: padding });
106 acc.offset += padding;
107 }
108
109 acc.layout.push({
110 type: "field",
111 name: name as string,
112 offset: acc.offset,
113 typedef,
114 });
115 acc.offset += typedef.byteLength;
116 }
117
118 let padding = pad(acc.offset, byteAlign);
119 if (padding > 0) {
120 acc.layout.push({ type: "padding", byteLength: padding });
121 acc.offset += padding;
122 }
123
124 return {
125 type: "struct",
126 layout: acc.layout,
127 byteLength: acc.offset,
128 byteAlign,
129 };
130}
131
132export function offsets<T extends object>(
133 def: Struct<T>,
134): { [K in keyof T]: number } {
135 let result = {} as { [K in keyof T]: number };
136 for (let element of def.layout) {
137 if (element.type === "field") {
138 result[element.name as keyof T] = element.offset;
139 }
140 }
141 return result;
142}
143
144export function pad(offset: number, alignment: number): number {
145 if ((offset % alignment) !== 0) {
146 return alignment - (offset % alignment);
147 } else {
148 return 0;
149 }
150}