feat: added support typescript

This commit is contained in:
2026-03-23 16:04:31 +03:00
parent 40b81de08d
commit d41cef5576
7 changed files with 1469 additions and 35 deletions
+635
View File
@@ -0,0 +1,635 @@
package generator
import (
"fmt"
"strings"
"github.com/edmand46/arpack/parser"
)
// GenerateTypeScript generates TypeScript code for the given messages
func GenerateTypeScript(messages []parser.Message, namespace string) ([]byte, error) {
return GenerateTypeScriptSchema(parser.Schema{Messages: messages}, namespace)
}
// GenerateTypeScriptSchema generates TypeScript code from a schema
func GenerateTypeScriptSchema(schema parser.Schema, namespace string) ([]byte, error) {
messages := schema.Messages
var b strings.Builder
b.WriteString("// <auto-generated> arpack </auto-generated>\n")
b.WriteString("// Code generated by arpack. DO NOT EDIT.\n\n")
enumNames := make(map[string]struct{}, len(schema.Enums))
for _, enum := range schema.Enums {
enumNames[enum.Name] = struct{}{}
}
// Generate enums
for _, enum := range schema.Enums {
writeTSEnum(&b, enum)
b.WriteString("\n")
}
// Generate messages
for i, msg := range messages {
if err := writeTSMessage(&b, msg, enumNames); err != nil {
return nil, fmt.Errorf("message %s: %w", msg.Name, err)
}
if i < len(messages)-1 {
b.WriteString("\n")
}
}
return []byte(b.String()), nil
}
func writeTSEnum(b *strings.Builder, enum parser.Enum) {
fmt.Fprintf(b, "export enum %s {\n", enum.Name)
for i, value := range enum.Values {
fmt.Fprintf(b, " %s = %s", value.Name, value.Value)
if i < len(enum.Values)-1 {
b.WriteString(",")
}
b.WriteString("\n")
}
b.WriteString("}\n")
}
func writeTSMessage(b *strings.Builder, msg parser.Message, enumNames map[string]struct{}) error {
segs := segmentFields(msg.Fields)
fmt.Fprintf(b, "export class %s {\n", msg.Name)
// Field declarations with defaults
for _, f := range msg.Fields {
defaultValue := tsDefaultValue(f)
fmt.Fprintf(b, " %s: %s = %s;\n", toCamelCase(f.Name), tsTypeName(f, enumNames), defaultValue)
}
b.WriteString("\n")
// Serialize method
b.WriteString(" serialize(view: DataView, offset: number): number {\n")
b.WriteString(" let pos = offset;\n")
for i, seg := range segs {
if seg.single != nil {
if err := writeTSSerializeField(b, "this", *seg.single, " ", enumNames); err != nil {
return err
}
} else {
writeTSBoolGroupSerialize(b, "this", seg.bools, i, " ")
}
}
b.WriteString(" return pos - offset;\n")
b.WriteString(" }\n\n")
// Deserialize method
fmt.Fprintf(b, " static deserialize(view: DataView, offset: number): [%s, number] {\n", msg.Name)
b.WriteString(" let pos = offset;\n")
b.WriteString(fmt.Sprintf(" const msg = new %s();\n", msg.Name))
for i, seg := range segs {
if seg.single != nil {
if err := writeTSDeserializeField(b, "msg", *seg.single, " ", enumNames); err != nil {
return err
}
} else {
writeTSBoolGroupDeserialize(b, "msg", seg.bools, i, " ")
}
}
b.WriteString(" return [msg, pos - offset];\n")
b.WriteString(" }\n")
b.WriteString("}\n")
return nil
}
func writeTSBoolGroupSerialize(b *strings.Builder, recv string, bools []parser.Field, groupIdx int, indent string) {
varName := fmt.Sprintf("_boolByte%d", groupIdx)
fmt.Fprintf(b, "%slet %s = 0;\n", indent, varName)
for bit, f := range bools {
fmt.Fprintf(b, "%sif (%s.%s) %s |= 1 << %d;\n", indent, recv, toCamelCase(f.Name), varName, bit)
}
fmt.Fprintf(b, "%sview.setUint8(pos, %s);\n", indent, varName)
b.WriteString(fmt.Sprintf("%spos += 1;\n", indent))
}
func writeTSBoolGroupDeserialize(b *strings.Builder, recv string, bools []parser.Field, groupIdx int, indent string) {
varName := fmt.Sprintf("_boolByte%d", groupIdx)
fmt.Fprintf(b, "%sconst %s = view.getUint8(pos);\n", indent, varName)
b.WriteString(fmt.Sprintf("%spos += 1;\n", indent))
for bit, f := range bools {
fmt.Fprintf(b, "%s%s.%s = (%s & (1 << %d)) !== 0;\n", indent, recv, toCamelCase(f.Name), varName, bit)
}
}
func writeTSSerializeField(b *strings.Builder, recv string, f parser.Field, indent string, enumNames map[string]struct{}) error {
access := recv + "." + toCamelCase(f.Name)
switch f.Kind {
case parser.KindPrimitive:
return writeTSSerializePrimitive(b, access, f, indent, enumNames)
case parser.KindNested:
fmt.Fprintf(b, "%spos += %s.serialize(view, pos);\n", indent, access)
case parser.KindFixedArray:
iVar := "_i" + f.Name
fmt.Fprintf(b, "%sfor (let %s = 0; %s < %d; %s++) {\n",
indent, iVar, iVar, f.FixedLen, iVar)
elemField := parser.Field{
Name: f.Name + "[" + iVar + "]",
Kind: f.Elem.Kind,
Primitive: f.Elem.Primitive,
NamedType: f.Elem.NamedType,
Quant: f.Elem.Quant,
TypeName: f.Elem.TypeName,
Elem: f.Elem.Elem,
FixedLen: f.Elem.FixedLen,
}
if err := writeTSSerializeField(b, recv, elemField, indent+" ", enumNames); err != nil {
return err
}
fmt.Fprintf(b, "%s}\n", indent)
case parser.KindSlice:
fmt.Fprintf(b, "%sview.setUint16(pos, %s.length, true);\n", indent, access)
b.WriteString(fmt.Sprintf("%spos += 2;\n", indent))
iVar := "_i" + f.Name
fmt.Fprintf(b, "%sfor (const %s of %s) {\n", indent, iVar, access)
elemField := parser.Field{
Name: iVar,
Kind: f.Elem.Kind,
Primitive: f.Elem.Primitive,
NamedType: f.Elem.NamedType,
Quant: f.Elem.Quant,
TypeName: f.Elem.TypeName,
Elem: f.Elem.Elem,
FixedLen: f.Elem.FixedLen,
}
if err := writeTSSerializeFieldElement(b, iVar, elemField, indent+" ", enumNames); err != nil {
return err
}
fmt.Fprintf(b, "%s}\n", indent)
}
return nil
}
func writeTSSerializeFieldElement(b *strings.Builder, access string, f parser.Field, indent string, enumNames map[string]struct{}) error {
switch f.Kind {
case parser.KindPrimitive:
return writeTSSerializePrimitiveElement(b, access, f, indent, enumNames)
case parser.KindNested:
fmt.Fprintf(b, "%spos += %s.serialize(view, pos);\n", indent, access)
}
return nil
}
func writeTSSerializePrimitiveElement(b *strings.Builder, access string, f parser.Field, indent string, enumNames map[string]struct{}) error {
if f.Quant != nil {
return writeTSSerializeQuantElement(b, access, f, indent)
}
valueExpr := tsSerializeValueExpr(access, f, enumNames)
switch f.Primitive {
case parser.KindFloat32:
fmt.Fprintf(b, "%sview.setFloat32(pos, %s, true);\n", indent, valueExpr)
b.WriteString(fmt.Sprintf("%spos += 4;\n", indent))
case parser.KindFloat64:
fmt.Fprintf(b, "%sview.setFloat64(pos, %s, true);\n", indent, valueExpr)
b.WriteString(fmt.Sprintf("%spos += 8;\n", indent))
case parser.KindInt8:
fmt.Fprintf(b, "%sview.setInt8(pos, %s);\n", indent, valueExpr)
b.WriteString(fmt.Sprintf("%spos += 1;\n", indent))
case parser.KindUint8:
fmt.Fprintf(b, "%sview.setUint8(pos, %s);\n", indent, valueExpr)
b.WriteString(fmt.Sprintf("%spos += 1;\n", indent))
case parser.KindBool:
fmt.Fprintf(b, "%sview.setUint8(pos, %s ? 1 : 0);\n", indent, valueExpr)
b.WriteString(fmt.Sprintf("%spos += 1;\n", indent))
case parser.KindInt16:
fmt.Fprintf(b, "%sview.setInt16(pos, %s, true);\n", indent, valueExpr)
b.WriteString(fmt.Sprintf("%spos += 2;\n", indent))
case parser.KindUint16:
fmt.Fprintf(b, "%sview.setUint16(pos, %s, true);\n", indent, valueExpr)
b.WriteString(fmt.Sprintf("%spos += 2;\n", indent))
case parser.KindInt32:
fmt.Fprintf(b, "%sview.setInt32(pos, %s, true);\n", indent, valueExpr)
b.WriteString(fmt.Sprintf("%spos += 4;\n", indent))
case parser.KindUint32:
fmt.Fprintf(b, "%sview.setUint32(pos, %s, true);\n", indent, valueExpr)
b.WriteString(fmt.Sprintf("%spos += 4;\n", indent))
case parser.KindInt64:
fmt.Fprintf(b, "%sview.setBigInt64(pos, %s, true);\n", indent, valueExpr)
b.WriteString(fmt.Sprintf("%spos += 8;\n", indent))
case parser.KindUint64:
fmt.Fprintf(b, "%sview.setBigUint64(pos, %s, true);\n", indent, valueExpr)
b.WriteString(fmt.Sprintf("%spos += 8;\n", indent))
case parser.KindString:
lenVar := "_slen" + sanitizeVarName(access)
fmt.Fprintf(b, "%sconst %s = new TextEncoder().encode(%s);\n", indent, lenVar, valueExpr)
fmt.Fprintf(b, "%sview.setUint16(pos, %s.length, true);\n", indent, lenVar)
b.WriteString(fmt.Sprintf("%spos += 2;\n", indent))
fmt.Fprintf(b, "%snew Uint8Array(view.buffer, pos, %s.length).set(%s);\n", indent, lenVar, lenVar)
fmt.Fprintf(b, "%spos += %s.length;\n", indent, lenVar)
}
return nil
}
func writeTSSerializePrimitive(b *strings.Builder, access string, f parser.Field, indent string, enumNames map[string]struct{}) error {
if f.Quant != nil {
return writeTSSerializeQuant(b, access, f, indent)
}
valueExpr := tsSerializeValueExpr(access, f, enumNames)
switch f.Primitive {
case parser.KindFloat32:
fmt.Fprintf(b, "%sview.setFloat32(pos, %s, true);\n", indent, valueExpr)
b.WriteString(fmt.Sprintf("%spos += 4;\n", indent))
case parser.KindFloat64:
fmt.Fprintf(b, "%sview.setFloat64(pos, %s, true);\n", indent, valueExpr)
b.WriteString(fmt.Sprintf("%spos += 8;\n", indent))
case parser.KindInt8:
fmt.Fprintf(b, "%sview.setInt8(pos, %s);\n", indent, valueExpr)
b.WriteString(fmt.Sprintf("%spos += 1;\n", indent))
case parser.KindUint8:
fmt.Fprintf(b, "%sview.setUint8(pos, %s);\n", indent, valueExpr)
b.WriteString(fmt.Sprintf("%spos += 1;\n", indent))
case parser.KindBool:
fmt.Fprintf(b, "%sview.setUint8(pos, %s ? 1 : 0);\n", indent, valueExpr)
b.WriteString(fmt.Sprintf("%spos += 1;\n", indent))
case parser.KindInt16:
fmt.Fprintf(b, "%sview.setInt16(pos, %s, true);\n", indent, valueExpr)
b.WriteString(fmt.Sprintf("%spos += 2;\n", indent))
case parser.KindUint16:
fmt.Fprintf(b, "%sview.setUint16(pos, %s, true);\n", indent, valueExpr)
b.WriteString(fmt.Sprintf("%spos += 2;\n", indent))
case parser.KindInt32:
fmt.Fprintf(b, "%sview.setInt32(pos, %s, true);\n", indent, valueExpr)
b.WriteString(fmt.Sprintf("%spos += 4;\n", indent))
case parser.KindUint32:
fmt.Fprintf(b, "%sview.setUint32(pos, %s, true);\n", indent, valueExpr)
b.WriteString(fmt.Sprintf("%spos += 4;\n", indent))
case parser.KindInt64:
fmt.Fprintf(b, "%sview.setBigInt64(pos, %s, true);\n", indent, valueExpr)
b.WriteString(fmt.Sprintf("%spos += 8;\n", indent))
case parser.KindUint64:
fmt.Fprintf(b, "%sview.setBigUint64(pos, %s, true);\n", indent, valueExpr)
b.WriteString(fmt.Sprintf("%spos += 8;\n", indent))
case parser.KindString:
lenVar := "_slen" + sanitizeVarName(access)
fmt.Fprintf(b, "%sconst %s = new TextEncoder().encode(%s);\n", indent, lenVar, valueExpr)
fmt.Fprintf(b, "%sview.setUint16(pos, %s.length, true);\n", indent, lenVar)
b.WriteString(fmt.Sprintf("%spos += 2;\n", indent))
fmt.Fprintf(b, "%snew Uint8Array(view.buffer, pos, %s.length).set(%s);\n", indent, lenVar, lenVar)
fmt.Fprintf(b, "%spos += %s.length;\n", indent, lenVar)
}
return nil
}
func writeTSSerializeQuant(b *strings.Builder, access string, f parser.Field, indent string) error {
q := f.Quant
maxUint := q.MaxUint()
varName := "_q" + sanitizeVarName(access)
if q.Bits == 8 {
fmt.Fprintf(b, "%sconst %s = Math.round((%s - (%g)) / (%g - (%g)) * %g);\n",
indent, varName, access, q.Min, q.Max, q.Min, maxUint)
fmt.Fprintf(b, "%sview.setUint8(pos, %s);\n", indent, varName)
b.WriteString(fmt.Sprintf("%spos += 1;\n", indent))
} else {
fmt.Fprintf(b, "%sconst %s = Math.round((%s - (%g)) / (%g - (%g)) * %g);\n",
indent, varName, access, q.Min, q.Max, q.Min, maxUint)
fmt.Fprintf(b, "%sview.setUint16(pos, %s, true);\n", indent, varName)
b.WriteString(fmt.Sprintf("%spos += 2;\n", indent))
}
return nil
}
func writeTSSerializeQuantElement(b *strings.Builder, access string, f parser.Field, indent string) error {
q := f.Quant
maxUint := q.MaxUint()
varName := "_q" + sanitizeVarName(access)
if q.Bits == 8 {
fmt.Fprintf(b, "%sconst %s = Math.round((%s - (%g)) / (%g - (%g)) * %g);\n",
indent, varName, access, q.Min, q.Max, q.Min, maxUint)
fmt.Fprintf(b, "%sview.setUint8(pos, %s);\n", indent, varName)
b.WriteString(fmt.Sprintf("%spos += 1;\n", indent))
} else {
fmt.Fprintf(b, "%sconst %s = Math.round((%s - (%g)) / (%g - (%g)) * %g);\n",
indent, varName, access, q.Min, q.Max, q.Min, maxUint)
fmt.Fprintf(b, "%sview.setUint16(pos, %s, true);\n", indent, varName)
b.WriteString(fmt.Sprintf("%spos += 2;\n", indent))
}
return nil
}
func writeTSDeserializeField(b *strings.Builder, recv string, f parser.Field, indent string, enumNames map[string]struct{}) error {
access := recv + "." + toCamelCase(f.Name)
switch f.Kind {
case parser.KindPrimitive:
return writeTSDeserializePrimitive(b, access, f, indent, enumNames)
case parser.KindNested:
fmt.Fprintf(b, "%sconst [_%s, _n%s] = %s.deserialize(view, pos);\n", indent, f.Name, f.Name, f.TypeName)
fmt.Fprintf(b, "%s%s = _%s;\n", indent, access, f.Name)
fmt.Fprintf(b, "%spos += _n%s;\n", indent, f.Name)
case parser.KindFixedArray:
iVar := "_i" + f.Name
fmt.Fprintf(b, "%s%s = new Array(%d);\n", indent, access, f.FixedLen)
fmt.Fprintf(b, "%sfor (let %s = 0; %s < %d; %s++) {\n",
indent, iVar, iVar, f.FixedLen, iVar)
elemField := parser.Field{
Name: f.Name + "[" + iVar + "]",
Kind: f.Elem.Kind,
Primitive: f.Elem.Primitive,
NamedType: f.Elem.NamedType,
Quant: f.Elem.Quant,
TypeName: f.Elem.TypeName,
Elem: f.Elem.Elem,
FixedLen: f.Elem.FixedLen,
}
if err := writeTSDeserializeField(b, recv, elemField, indent+" ", enumNames); err != nil {
return err
}
fmt.Fprintf(b, "%s}\n", indent)
case parser.KindSlice:
lenVar := "_len" + f.Name
fmt.Fprintf(b, "%sconst %s = view.getUint16(pos, true);\n", indent, lenVar)
b.WriteString(fmt.Sprintf("%spos += 2;\n", indent))
fmt.Fprintf(b, "%s%s = new Array(%s);\n", indent, access, lenVar)
iVar := "_i" + f.Name
fmt.Fprintf(b, "%sfor (let %s = 0; %s < %s; %s++) {\n", indent, iVar, iVar, lenVar, iVar)
elemField := parser.Field{
Name: f.Name + "[" + iVar + "]",
Kind: f.Elem.Kind,
Primitive: f.Elem.Primitive,
NamedType: f.Elem.NamedType,
Quant: f.Elem.Quant,
TypeName: f.Elem.TypeName,
Elem: f.Elem.Elem,
FixedLen: f.Elem.FixedLen,
}
if err := writeTSDeserializeFieldElement(b, recv, elemField, indent+" ", enumNames); err != nil {
return err
}
fmt.Fprintf(b, "%s}\n", indent)
}
return nil
}
func writeTSDeserializeFieldElement(b *strings.Builder, recv string, f parser.Field, indent string, enumNames map[string]struct{}) error {
access := recv + "." + toCamelCase(f.Name)
switch f.Kind {
case parser.KindPrimitive:
return writeTSDeserializePrimitiveElement(b, access, f, indent, enumNames)
case parser.KindNested:
fmt.Fprintf(b, "%sconst [_elem, _nElem] = %s.deserialize(view, pos);\n", indent, f.TypeName)
fmt.Fprintf(b, "%s%s = _elem;\n", indent, access)
fmt.Fprintf(b, "%spos += _nElem;\n", indent)
}
return nil
}
func writeTSDeserializePrimitiveElement(b *strings.Builder, access string, f parser.Field, indent string, enumNames map[string]struct{}) error {
if f.Quant != nil {
return writeTSDeserializeQuantElement(b, access, f, indent)
}
switch f.Primitive {
case parser.KindFloat32:
expr := fmt.Sprintf("view.getFloat32(pos, true)")
fmt.Fprintf(b, "%s%s = %s;\n", indent, access, tsDeserializeValueExpr(expr, f, enumNames))
b.WriteString(fmt.Sprintf("%spos += 4;\n", indent))
case parser.KindFloat64:
expr := fmt.Sprintf("view.getFloat64(pos, true)")
fmt.Fprintf(b, "%s%s = %s;\n", indent, access, tsDeserializeValueExpr(expr, f, enumNames))
b.WriteString(fmt.Sprintf("%spos += 8;\n", indent))
case parser.KindInt8:
expr := fmt.Sprintf("view.getInt8(pos)")
fmt.Fprintf(b, "%s%s = %s;\n", indent, access, tsDeserializeValueExpr(expr, f, enumNames))
b.WriteString(fmt.Sprintf("%spos += 1;\n", indent))
case parser.KindUint8:
expr := fmt.Sprintf("view.getUint8(pos)")
fmt.Fprintf(b, "%s%s = %s;\n", indent, access, tsDeserializeValueExpr(expr, f, enumNames))
b.WriteString(fmt.Sprintf("%spos += 1;\n", indent))
case parser.KindBool:
expr := fmt.Sprintf("view.getUint8(pos) !== 0")
fmt.Fprintf(b, "%s%s = %s;\n", indent, access, tsDeserializeValueExpr(expr, f, enumNames))
b.WriteString(fmt.Sprintf("%spos += 1;\n", indent))
case parser.KindInt16:
expr := fmt.Sprintf("view.getInt16(pos, true)")
fmt.Fprintf(b, "%s%s = %s;\n", indent, access, tsDeserializeValueExpr(expr, f, enumNames))
b.WriteString(fmt.Sprintf("%spos += 2;\n", indent))
case parser.KindUint16:
expr := fmt.Sprintf("view.getUint16(pos, true)")
fmt.Fprintf(b, "%s%s = %s;\n", indent, access, tsDeserializeValueExpr(expr, f, enumNames))
b.WriteString(fmt.Sprintf("%spos += 2;\n", indent))
case parser.KindInt32:
expr := fmt.Sprintf("view.getInt32(pos, true)")
fmt.Fprintf(b, "%s%s = %s;\n", indent, access, tsDeserializeValueExpr(expr, f, enumNames))
b.WriteString(fmt.Sprintf("%spos += 4;\n", indent))
case parser.KindUint32:
expr := fmt.Sprintf("view.getUint32(pos, true)")
fmt.Fprintf(b, "%s%s = %s;\n", indent, access, tsDeserializeValueExpr(expr, f, enumNames))
b.WriteString(fmt.Sprintf("%spos += 4;\n", indent))
case parser.KindInt64:
expr := fmt.Sprintf("view.getBigInt64(pos, true)")
fmt.Fprintf(b, "%s%s = %s;\n", indent, access, tsDeserializeValueExpr(expr, f, enumNames))
b.WriteString(fmt.Sprintf("%spos += 8;\n", indent))
case parser.KindUint64:
expr := fmt.Sprintf("view.getBigUint64(pos, true)")
fmt.Fprintf(b, "%s%s = %s;\n", indent, access, tsDeserializeValueExpr(expr, f, enumNames))
b.WriteString(fmt.Sprintf("%spos += 8;\n", indent))
case parser.KindString:
lenVar := "_slen" + sanitizeVarName(access)
fmt.Fprintf(b, "%sconst %s = view.getUint16(pos, true);\n", indent, lenVar)
b.WriteString(fmt.Sprintf("%spos += 2;\n", indent))
expr := fmt.Sprintf("new TextDecoder().decode(new Uint8Array(view.buffer, pos, %s))", lenVar)
fmt.Fprintf(b, "%s%s = %s;\n", indent, access, tsDeserializeValueExpr(expr, f, enumNames))
fmt.Fprintf(b, "%spos += %s;\n", indent, lenVar)
}
return nil
}
func writeTSDeserializePrimitive(b *strings.Builder, access string, f parser.Field, indent string, enumNames map[string]struct{}) error {
if f.Quant != nil {
return writeTSDeserializeQuant(b, access, f, indent)
}
switch f.Primitive {
case parser.KindFloat32:
expr := fmt.Sprintf("view.getFloat32(pos, true)")
fmt.Fprintf(b, "%s%s = %s;\n", indent, access, tsDeserializeValueExpr(expr, f, enumNames))
b.WriteString(fmt.Sprintf("%spos += 4;\n", indent))
case parser.KindFloat64:
expr := fmt.Sprintf("view.getFloat64(pos, true)")
fmt.Fprintf(b, "%s%s = %s;\n", indent, access, tsDeserializeValueExpr(expr, f, enumNames))
b.WriteString(fmt.Sprintf("%spos += 8;\n", indent))
case parser.KindInt8:
expr := fmt.Sprintf("view.getInt8(pos)")
fmt.Fprintf(b, "%s%s = %s;\n", indent, access, tsDeserializeValueExpr(expr, f, enumNames))
b.WriteString(fmt.Sprintf("%spos += 1;\n", indent))
case parser.KindUint8:
expr := fmt.Sprintf("view.getUint8(pos)")
fmt.Fprintf(b, "%s%s = %s;\n", indent, access, tsDeserializeValueExpr(expr, f, enumNames))
b.WriteString(fmt.Sprintf("%spos += 1;\n", indent))
case parser.KindBool:
expr := fmt.Sprintf("view.getUint8(pos) !== 0")
fmt.Fprintf(b, "%s%s = %s;\n", indent, access, tsDeserializeValueExpr(expr, f, enumNames))
b.WriteString(fmt.Sprintf("%spos += 1;\n", indent))
case parser.KindInt16:
expr := fmt.Sprintf("view.getInt16(pos, true)")
fmt.Fprintf(b, "%s%s = %s;\n", indent, access, tsDeserializeValueExpr(expr, f, enumNames))
b.WriteString(fmt.Sprintf("%spos += 2;\n", indent))
case parser.KindUint16:
expr := fmt.Sprintf("view.getUint16(pos, true)")
fmt.Fprintf(b, "%s%s = %s;\n", indent, access, tsDeserializeValueExpr(expr, f, enumNames))
b.WriteString(fmt.Sprintf("%spos += 2;\n", indent))
case parser.KindInt32:
expr := fmt.Sprintf("view.getInt32(pos, true)")
fmt.Fprintf(b, "%s%s = %s;\n", indent, access, tsDeserializeValueExpr(expr, f, enumNames))
b.WriteString(fmt.Sprintf("%spos += 4;\n", indent))
case parser.KindUint32:
expr := fmt.Sprintf("view.getUint32(pos, true)")
fmt.Fprintf(b, "%s%s = %s;\n", indent, access, tsDeserializeValueExpr(expr, f, enumNames))
b.WriteString(fmt.Sprintf("%spos += 4;\n", indent))
case parser.KindInt64:
expr := fmt.Sprintf("view.getBigInt64(pos, true)")
fmt.Fprintf(b, "%s%s = %s;\n", indent, access, tsDeserializeValueExpr(expr, f, enumNames))
b.WriteString(fmt.Sprintf("%spos += 8;\n", indent))
case parser.KindUint64:
expr := fmt.Sprintf("view.getBigUint64(pos, true)")
fmt.Fprintf(b, "%s%s = %s;\n", indent, access, tsDeserializeValueExpr(expr, f, enumNames))
b.WriteString(fmt.Sprintf("%spos += 8;\n", indent))
case parser.KindString:
lenVar := "_slen" + sanitizeVarName(access)
fmt.Fprintf(b, "%sconst %s = view.getUint16(pos, true);\n", indent, lenVar)
b.WriteString(fmt.Sprintf("%spos += 2;\n", indent))
expr := fmt.Sprintf("new TextDecoder().decode(new Uint8Array(view.buffer, pos, %s))", lenVar)
fmt.Fprintf(b, "%s%s = %s;\n", indent, access, tsDeserializeValueExpr(expr, f, enumNames))
fmt.Fprintf(b, "%spos += %s;\n", indent, lenVar)
}
return nil
}
func writeTSDeserializeQuant(b *strings.Builder, access string, f parser.Field, indent string) error {
q := f.Quant
maxUint := q.MaxUint()
varName := "_q" + sanitizeVarName(access)
if q.Bits == 8 {
fmt.Fprintf(b, "%sconst %s = view.getUint8(pos);\n", indent, varName)
b.WriteString(fmt.Sprintf("%spos += 1;\n", indent))
expr := fmt.Sprintf("%s / %g * (%g - (%g)) + (%g)", varName, maxUint, q.Max, q.Min, q.Min)
fmt.Fprintf(b, "%s%s = %s;\n", indent, access, tsDeserializeValueExpr(expr, f, nil))
} else {
fmt.Fprintf(b, "%sconst %s = view.getUint16(pos, true);\n", indent, varName)
b.WriteString(fmt.Sprintf("%spos += 2;\n", indent))
expr := fmt.Sprintf("%s / %g * (%g - (%g)) + (%g)", varName, maxUint, q.Max, q.Min, q.Min)
fmt.Fprintf(b, "%s%s = %s;\n", indent, access, tsDeserializeValueExpr(expr, f, nil))
}
return nil
}
func writeTSDeserializeQuantElement(b *strings.Builder, access string, f parser.Field, indent string) error {
q := f.Quant
maxUint := q.MaxUint()
varName := "_q" + sanitizeVarName(access)
if q.Bits == 8 {
fmt.Fprintf(b, "%sconst %s = view.getUint8(pos);\n", indent, varName)
b.WriteString(fmt.Sprintf("%spos += 1;\n", indent))
expr := fmt.Sprintf("%s / %g * (%g - (%g)) + (%g)", varName, maxUint, q.Max, q.Min, q.Min)
fmt.Fprintf(b, "%s%s = %s;\n", indent, access, tsDeserializeValueExpr(expr, f, nil))
} else {
fmt.Fprintf(b, "%sconst %s = view.getUint16(pos, true);\n", indent, varName)
b.WriteString(fmt.Sprintf("%spos += 2;\n", indent))
expr := fmt.Sprintf("%s / %g * (%g - (%g)) + (%g)", varName, maxUint, q.Max, q.Min, q.Min)
fmt.Fprintf(b, "%s%s = %s;\n", indent, access, tsDeserializeValueExpr(expr, f, nil))
}
return nil
}
func tsTypeName(f parser.Field, enumNames map[string]struct{}) string {
switch f.Kind {
case parser.KindPrimitive:
if tsIsEnumType(f, enumNames) {
return f.NamedType
}
return tsPrimitiveTypeName(f.Primitive)
case parser.KindNested:
return f.TypeName
case parser.KindFixedArray, parser.KindSlice:
return tsTypeName(*f.Elem, enumNames) + "[]"
}
return "unknown"
}
func tsPrimitiveTypeName(k parser.PrimitiveKind) string {
switch k {
case parser.KindFloat32, parser.KindFloat64:
return "number"
case parser.KindInt8, parser.KindInt16, parser.KindInt32, parser.KindUint8, parser.KindUint16, parser.KindUint32:
return "number"
case parser.KindInt64, parser.KindUint64:
return "bigint"
case parser.KindBool:
return "boolean"
case parser.KindString:
return "string"
}
return "unknown"
}
func tsDefaultValue(f parser.Field) string {
switch f.Kind {
case parser.KindPrimitive:
if tsIsEnumType(f, nil) {
return "0"
}
switch f.Primitive {
case parser.KindFloat32, parser.KindFloat64, parser.KindInt8, parser.KindInt16, parser.KindInt32,
parser.KindUint8, parser.KindUint16, parser.KindUint32:
return "0"
case parser.KindInt64, parser.KindUint64:
return "0n"
case parser.KindBool:
return "false"
case parser.KindString:
return `""`
}
case parser.KindNested:
return fmt.Sprintf("new %s()", f.TypeName)
case parser.KindFixedArray:
elemDefault := tsDefaultValue(*f.Elem)
elemType := tsTypeName(*f.Elem, nil)
return fmt.Sprintf("new Array<%s>(%d).fill(%s)", elemType, f.FixedLen, elemDefault)
case parser.KindSlice:
return "[]"
}
return "undefined"
}
func tsSerializeValueExpr(access string, f parser.Field, enumNames map[string]struct{}) string {
if !tsIsEnumType(f, enumNames) {
return access
}
return access + " as " + tsPrimitiveTypeName(f.Primitive)
}
func tsDeserializeValueExpr(expr string, f parser.Field, enumNames map[string]struct{}) string {
if !tsIsEnumType(f, enumNames) {
return expr
}
return "(" + expr + " as " + f.NamedType + ")"
}
func tsIsEnumType(f parser.Field, enumNames map[string]struct{}) bool {
if f.NamedType == "" || enumNames == nil {
return false
}
_, ok := enumNames[f.NamedType]
return ok
}
// toCamelCase converts PascalCase to camelCase (e.g., EntityID -> entityID)
func toCamelCase(s string) string {
if s == "" {
return ""
}
// First character to lowercase
result := strings.ToLower(s[:1]) + s[1:]
return result
}
+401
View File
@@ -0,0 +1,401 @@
package generator
import (
"strings"
"testing"
"github.com/edmand46/arpack/parser"
)
func TestGenerateTypeScript_Primitives(t *testing.T) {
schema := parser.Schema{
Messages: []parser.Message{
{
PackageName: "test",
Name: "PrimitiveMessage",
Fields: []parser.Field{
{Name: "F32", Kind: parser.KindPrimitive, Primitive: parser.KindFloat32},
{Name: "F64", Kind: parser.KindPrimitive, Primitive: parser.KindFloat64},
{Name: "I8", Kind: parser.KindPrimitive, Primitive: parser.KindInt8},
{Name: "I16", Kind: parser.KindPrimitive, Primitive: parser.KindInt16},
{Name: "I32", Kind: parser.KindPrimitive, Primitive: parser.KindInt32},
{Name: "I64", Kind: parser.KindPrimitive, Primitive: parser.KindInt64},
{Name: "U8", Kind: parser.KindPrimitive, Primitive: parser.KindUint8},
{Name: "U16", Kind: parser.KindPrimitive, Primitive: parser.KindUint16},
{Name: "U32", Kind: parser.KindPrimitive, Primitive: parser.KindUint32},
{Name: "U64", Kind: parser.KindPrimitive, Primitive: parser.KindUint64},
{Name: "B", Kind: parser.KindPrimitive, Primitive: parser.KindBool},
{Name: "S", Kind: parser.KindPrimitive, Primitive: parser.KindString},
},
},
},
}
src, err := GenerateTypeScriptSchema(schema, "Test")
if err != nil {
t.Fatalf("GenerateTypeScriptSchema: %v", err)
}
code := string(src)
// Check field declarations (now using camelCase)
if !strings.Contains(code, "f32: number = 0;") {
t.Error("Missing f32 field")
}
if !strings.Contains(code, "i64: bigint = 0n;") {
t.Error("Missing i64 field with bigint type")
}
if !strings.Contains(code, "u64: bigint = 0n;") {
t.Error("Missing u64 field with bigint type")
}
if !strings.Contains(code, "b: boolean = false;") {
t.Error("Missing b field")
}
if !strings.Contains(code, "s: string = \"\";") {
t.Error("Missing s field")
}
// Check serialize method exists
if !strings.Contains(code, "serialize(view: DataView, offset: number): number") {
t.Error("Missing serialize method")
}
// Check deserialize method exists
if !strings.Contains(code, "static deserialize(view: DataView, offset: number): [PrimitiveMessage, number]") {
t.Error("Missing deserialize method")
}
}
func TestGenerateTypeScript_QuantizedFloats(t *testing.T) {
schema := parser.Schema{
Messages: []parser.Message{
{
PackageName: "test",
Name: "QuantMessage",
Fields: []parser.Field{
{
Name: "Q8",
Kind: parser.KindPrimitive,
Primitive: parser.KindFloat32,
Quant: &parser.QuantInfo{Min: 0, Max: 100, Bits: 8},
},
{
Name: "Q16",
Kind: parser.KindPrimitive,
Primitive: parser.KindFloat32,
Quant: &parser.QuantInfo{Min: -500, Max: 500, Bits: 16},
},
},
},
},
}
src, err := GenerateTypeScriptSchema(schema, "Test")
if err != nil {
t.Fatalf("GenerateTypeScriptSchema: %v", err)
}
code := string(src)
// Check 8-bit quantization (using camelCase field names)
if !strings.Contains(code, "Math.round((this.q8 - (0)) / (100 - (0)) * 255)") {
t.Error("Missing 8-bit quantization code")
}
// Check 16-bit quantization (using camelCase field names)
if !strings.Contains(code, "Math.round((this.q16 - (-500)) / (500 - (-500)) * 65535)") {
t.Error("Missing 16-bit quantization code")
}
// Check deserialization with dequantization
if !strings.Contains(code, "/ 255 * (100 - (0)) + (0)") {
t.Error("Missing 8-bit dequantization")
}
if !strings.Contains(code, "/ 65535 * (500 - (-500)) + (-500)") {
t.Error("Missing 16-bit dequantization")
}
}
func TestGenerateTypeScript_BoolPacking(t *testing.T) {
schema := parser.Schema{
Messages: []parser.Message{
{
PackageName: "test",
Name: "BoolMessage",
Fields: []parser.Field{
{Name: "A", Kind: parser.KindPrimitive, Primitive: parser.KindBool},
{Name: "B", Kind: parser.KindPrimitive, Primitive: parser.KindBool},
{Name: "C", Kind: parser.KindPrimitive, Primitive: parser.KindBool},
{Name: "X", Kind: parser.KindPrimitive, Primitive: parser.KindUint32},
{Name: "D", Kind: parser.KindPrimitive, Primitive: parser.KindBool},
{Name: "E", Kind: parser.KindPrimitive, Primitive: parser.KindBool},
},
},
},
}
src, err := GenerateTypeScriptSchema(schema, "Test")
if err != nil {
t.Fatalf("GenerateTypeScriptSchema: %v", err)
}
code := string(src)
// Check that consecutive bools are packed (using camelCase field names)
if !strings.Contains(code, "let _boolByte0 = 0;") {
t.Error("Missing first bool group packing")
}
if !strings.Contains(code, "if (this.a) _boolByte0 |= 1 << 0;") {
t.Error("Missing a bool packing")
}
if !strings.Contains(code, "if (this.b) _boolByte0 |= 1 << 1;") {
t.Error("Missing b bool packing")
}
if !strings.Contains(code, "if (this.c) _boolByte0 |= 1 << 2;") {
t.Error("Missing c bool packing")
}
// Check second bool group after uint32 (index is 2, not 4, based on segment index)
if !strings.Contains(code, "let _boolByte2 = 0;") {
t.Error("Missing second bool group packing")
}
// Check deserialization (using camelCase field names)
if !strings.Contains(code, "msg.a = (_boolByte0 & (1 << 0)) !== 0;") {
t.Error("Missing a bool unpacking")
}
}
func TestGenerateTypeScript_NestedTypes(t *testing.T) {
schema := parser.Schema{
Messages: []parser.Message{
{
PackageName: "test",
Name: "Inner",
Fields: []parser.Field{
{Name: "Value", Kind: parser.KindPrimitive, Primitive: parser.KindInt32},
},
},
{
PackageName: "test",
Name: "Outer",
Fields: []parser.Field{
{Name: "Inner", Kind: parser.KindNested, TypeName: "Inner"},
},
},
},
}
src, err := GenerateTypeScriptSchema(schema, "Test")
if err != nil {
t.Fatalf("GenerateTypeScriptSchema: %v", err)
}
code := string(src)
// Check nested type default value (using camelCase field name)
if !strings.Contains(code, "inner: Inner = new Inner();") {
t.Error("Missing nested type field with default")
}
// Check serialize calls nested serialize (using camelCase field name)
if !strings.Contains(code, "pos += this.inner.serialize(view, pos);") {
t.Error("Missing nested serialize call")
}
// Check deserialize calls nested deserialize
if !strings.Contains(code, "const [_Inner, _nInner] = Inner.deserialize(view, pos);") {
t.Error("Missing nested deserialize call")
}
}
func TestGenerateTypeScript_FixedArrays(t *testing.T) {
schema := parser.Schema{
Messages: []parser.Message{
{
PackageName: "test",
Name: "ArrayMessage",
Fields: []parser.Field{
{
Name: "Values",
Kind: parser.KindFixedArray,
FixedLen: 3,
Elem: &parser.Field{
Kind: parser.KindPrimitive,
Primitive: parser.KindFloat32,
},
},
},
},
},
}
src, err := GenerateTypeScriptSchema(schema, "Test")
if err != nil {
t.Fatalf("GenerateTypeScriptSchema: %v", err)
}
code := string(src)
// Check default value (using camelCase field name)
if !strings.Contains(code, "values: number[] = new Array<number>(3).fill(0);") {
t.Error("Missing fixed array field with default")
}
// Check serialization loop (using camelCase field name)
if !strings.Contains(code, "for (let _iValues = 0; _iValues < 3; _iValues++)") {
t.Error("Missing fixed array serialization loop")
}
// Check deserialization loop (using camelCase field name)
if !strings.Contains(code, "msg.values = new Array(3);") {
t.Error("Missing fixed array allocation in deserialize")
}
}
func TestGenerateTypeScript_Slices(t *testing.T) {
schema := parser.Schema{
Messages: []parser.Message{
{
PackageName: "test",
Name: "SliceMessage",
Fields: []parser.Field{
{
Name: "Items",
Kind: parser.KindSlice,
Elem: &parser.Field{
Kind: parser.KindPrimitive,
Primitive: parser.KindInt32,
},
},
},
},
},
}
src, err := GenerateTypeScriptSchema(schema, "Test")
if err != nil {
t.Fatalf("GenerateTypeScriptSchema: %v", err)
}
code := string(src)
// Check default value (using camelCase field name)
if !strings.Contains(code, "items: number[] = [];") {
t.Error("Missing slice field with default")
}
// Check length prefix in serialize (using camelCase field name)
if !strings.Contains(code, "view.setUint16(pos, this.items.length, true);") {
t.Error("Missing slice length prefix in serialize")
}
// Check length reading in deserialize
if !strings.Contains(code, "const _lenItems = view.getUint16(pos, true);") {
t.Error("Missing slice length reading in deserialize")
}
// Check array allocation in deserialize (using camelCase field name)
if !strings.Contains(code, "msg.items = new Array(_lenItems);") {
t.Error("Missing slice allocation in deserialize")
}
}
func TestGenerateTypeScript_Enums(t *testing.T) {
schema := parser.Schema{
Enums: []parser.Enum{
{
Name: "Status",
Primitive: parser.KindUint16,
Values: []parser.EnumValue{
{Name: "Pending", Value: "0"},
{Name: "Active", Value: "1"},
{Name: "Done", Value: "2"},
},
},
},
Messages: []parser.Message{
{
PackageName: "test",
Name: "EnumMessage",
Fields: []parser.Field{
{
Name: "Status",
Kind: parser.KindPrimitive,
Primitive: parser.KindUint16,
NamedType: "Status",
},
},
},
},
}
src, err := GenerateTypeScriptSchema(schema, "Test")
if err != nil {
t.Fatalf("GenerateTypeScriptSchema: %v", err)
}
code := string(src)
// Check enum definition
if !strings.Contains(code, "export enum Status {") {
t.Error("Missing enum definition")
}
if !strings.Contains(code, "Pending = 0,") {
t.Error("Missing Pending enum value")
}
if !strings.Contains(code, "Active = 1,") {
t.Error("Missing Active enum value")
}
// Check enum field type (using camelCase field name)
if !strings.Contains(code, "status: Status = 0;") {
t.Error("Missing enum field with correct type")
}
// Check enum serialization (cast to number, using camelCase field name)
if !strings.Contains(code, "view.setUint16(pos, this.status as number, true);") {
t.Error("Missing enum cast in serialize")
}
// Check enum deserialization (cast from number, using camelCase field name)
if !strings.Contains(code, "msg.status = (view.getUint16(pos, true) as Status);") {
t.Error("Missing enum cast in deserialize")
}
}
func TestGenerateTypeScript_Strings(t *testing.T) {
schema := parser.Schema{
Messages: []parser.Message{
{
PackageName: "test",
Name: "StringMessage",
Fields: []parser.Field{
{Name: "Name", Kind: parser.KindPrimitive, Primitive: parser.KindString},
},
},
},
}
src, err := GenerateTypeScriptSchema(schema, "Test")
if err != nil {
t.Fatalf("GenerateTypeScriptSchema: %v", err)
}
code := string(src)
// Check TextEncoder usage
if !strings.Contains(code, "new TextEncoder().encode(") {
t.Error("Missing TextEncoder in serialize")
}
// Check length prefix
if !strings.Contains(code, "view.setUint16(pos, _slen") {
t.Error("Missing string length prefix in serialize")
}
// Check TextDecoder usage
if !strings.Contains(code, "new TextDecoder().decode(") {
t.Error("Missing TextDecoder in deserialize")
}
}