package mkmorph
import (
"bytes"
"encoding/binary"
"fmt"
"os"
"chirami/internal/morph"
"github.com/ikawaha/kagome-dict/ipa"
)
// Build extracts ipa.Dict() and writes a serialized morph.bin file to out.
func Build(out string) error {
d := ipa.Dict()
if d == nil {
return fmt.Errorf("mkmorph: nil ipa dict")
}
strBuf := &bytes.Buffer{}
strBuf.WriteByte(0) // offset 0 is ""
strMap := make(map[string]uint32)
strMap[""] = 0
strMap["*"] = 0
addStr := func(s string) uint32 {
if s == "" || s == "*" {
return 0
}
if off, ok := strMap[s]; ok {
return off
}
off := uint32(strBuf.Len())
strBuf.WriteString(s)
strBuf.WriteByte(0)
strMap[s] = off
return off
}
for _, s := range d.POSTable.NameList {
addStr(s)
}
for _, c := range d.Contents {
for _, s := range c {
addStr(s)
}
}
for _, c := range d.UnkDict.Contents {
for _, s := range c {
addStr(s)
}
}
for _, s := range d.CharClass {
addStr(s)
}
// POS table (unique [4]string combinations)
type posTuple [4]string
posMap := make(map[posTuple]uint8)
var posTuples []posTuple
for _, p := range d.POSTable.POSs {
var t posTuple
for i := 0; i < 4; i++ {
t[i] = d.POSTable.NameList[p[i]]
}
if _, ok := posMap[t]; !ok {
if len(posTuples) >= 256 {
return fmt.Errorf("mkmorph: POS table overflow (%d > 255)", len(posTuples))
}
posMap[t] = uint8(len(posTuples))
posTuples = append(posTuples, t)
}
}
// InflTypes table
inflTypeMap := make(map[string]uint8)
var inflTypes []string
for _, c := range d.Contents {
s := c[0]
if _, ok := inflTypeMap[s]; !ok {
if len(inflTypes) >= 256 {
return fmt.Errorf("mkmorph: InflType table overflow (%d > 255)", len(inflTypes))
}
inflTypeMap[s] = uint8(len(inflTypes))
inflTypes = append(inflTypes, s)
}
}
// InflForms table
inflFormMap := make(map[string]uint8)
var inflForms []string
for _, c := range d.Contents {
s := c[1]
if _, ok := inflFormMap[s]; !ok {
if len(inflForms) >= 256 {
return fmt.Errorf("mkmorph: InflForm table overflow (%d > 255)", len(inflForms))
}
inflFormMap[s] = uint8(len(inflForms))
inflForms = append(inflForms, s)
}
}
tmpOut := out + ".tmp"
f, err := os.Create(tmpOut)
if err != nil {
return fmt.Errorf("mkmorph: create %s: %w", tmpOut, err)
}
defer func() {
f.Close()
_ = os.Remove(tmpOut)
}()
header := make([]byte, morph.HeaderSize)
copy(header[0:8], []byte(morph.Magic))
binary.LittleEndian.PutUint32(header[8:12], morph.CurrentVersion)
offset := uint32(morph.HeaderSize)
daOffset := offset
daCount := uint32(len(d.Index.Da))
offset += daCount * 8
dupOffset := offset
dupCount := uint32(len(d.Morphs))
offset += dupCount * 1
morphOffset := offset
morphCount := uint32(len(d.Morphs))
offset += morphCount * 6
posTableOffset := offset
posTableCount := uint32(len(posTuples))
offset += posTableCount * 16
inflTypeOffset := offset
inflTypeCount := uint32(len(inflTypes))
offset += inflTypeCount * 4
inflFormOffset := offset
inflFormCount := uint32(len(inflForms))
offset += inflFormCount * 4
featureOffset := offset
featureCount := uint32(len(d.Contents))
offset += featureCount * 16
stringOffset := offset
stringSize := uint32(strBuf.Len())
offset += stringSize
connOffset := offset
connRows := uint32(d.Connection.Row)
connCols := uint32(d.Connection.Col)
offset += connRows * connCols * 2
charCatOffset := offset
charCatCount := uint32(len(d.CharCategory))
offset += charCatCount
charClassOffset := offset
charClassCount := uint32(len(d.CharClass))
offset += charClassCount * 16
unkMorphOffset := offset
unkMorphCount := uint32(len(d.UnkDict.Morphs))
offset += unkMorphCount * 6
unkFeatureOffset := offset
unkFeatureCount := uint32(len(d.UnkDict.Contents))
offset += unkFeatureCount * 16
// Fill header table of contents
binary.LittleEndian.PutUint32(header[16:20], daOffset)
binary.LittleEndian.PutUint32(header[20:24], daCount)
binary.LittleEndian.PutUint32(header[24:28], dupOffset)
binary.LittleEndian.PutUint32(header[28:32], dupCount)
binary.LittleEndian.PutUint32(header[32:36], morphOffset)
binary.LittleEndian.PutUint32(header[36:40], morphCount)
binary.LittleEndian.PutUint32(header[40:44], posTableOffset)
binary.LittleEndian.PutUint32(header[44:48], posTableCount)
binary.LittleEndian.PutUint32(header[48:52], inflTypeOffset)
binary.LittleEndian.PutUint32(header[52:56], inflTypeCount)
binary.LittleEndian.PutUint32(header[56:60], inflFormOffset)
binary.LittleEndian.PutUint32(header[60:64], inflFormCount)
binary.LittleEndian.PutUint32(header[64:68], featureOffset)
binary.LittleEndian.PutUint32(header[68:72], featureCount)
binary.LittleEndian.PutUint32(header[72:76], stringOffset)
binary.LittleEndian.PutUint32(header[76:80], stringSize)
binary.LittleEndian.PutUint32(header[80:84], connOffset)
binary.LittleEndian.PutUint32(header[84:88], connRows)
binary.LittleEndian.PutUint32(header[88:92], connCols)
binary.LittleEndian.PutUint32(header[92:96], charCatOffset)
binary.LittleEndian.PutUint32(header[96:100], charCatCount)
binary.LittleEndian.PutUint32(header[100:104], charClassOffset)
binary.LittleEndian.PutUint32(header[104:108], charClassCount)
binary.LittleEndian.PutUint32(header[108:112], unkMorphOffset)
binary.LittleEndian.PutUint32(header[112:116], unkMorphCount)
binary.LittleEndian.PutUint32(header[116:120], unkFeatureOffset)
binary.LittleEndian.PutUint32(header[120:124], unkFeatureCount)
if _, err := f.Write(header); err != nil {
return err
}
// 1. DA
daBuf := make([]byte, daCount*8)
for i := range d.Index.Da {
binary.LittleEndian.PutUint32(daBuf[i*8:i*8+4], uint32(d.Index.Da[i].Base))
binary.LittleEndian.PutUint32(daBuf[i*8+4:i*8+8], uint32(d.Index.Da[i].Check))
}
if _, err := f.Write(daBuf); err != nil {
return err
}
// 2. Dup
dupBuf := make([]byte, dupCount)
for i := range dupCount {
dupBuf[i] = uint8(d.Index.Dup[int32(i)])
}
if _, err := f.Write(dupBuf); err != nil {
return err
}
// 3. Morphs
morphBuf := make([]byte, morphCount*6)
for i, m := range d.Morphs {
binary.LittleEndian.PutUint16(morphBuf[i*6:i*6+2], uint16(m.LeftID))
binary.LittleEndian.PutUint16(morphBuf[i*6+2:i*6+4], uint16(m.RightID))
binary.LittleEndian.PutUint16(morphBuf[i*6+4:i*6+6], uint16(m.Weight))
}
if _, err := f.Write(morphBuf); err != nil {
return err
}
// 4. POS Tuples
posBuf := make([]byte, posTableCount*16)
for i, pt := range posTuples {
for j := 0; j < 4; j++ {
binary.LittleEndian.PutUint32(posBuf[i*16+j*4:i*16+j*4+4], addStr(pt[j]))
}
}
if _, err := f.Write(posBuf); err != nil {
return err
}
// 5. InflTypes
itBuf := make([]byte, inflTypeCount*4)
for i, it := range inflTypes {
binary.LittleEndian.PutUint32(itBuf[i*4:i*4+4], addStr(it))
}
if _, err := f.Write(itBuf); err != nil {
return err
}
// 6. InflForms
ifBuf := make([]byte, inflFormCount*4)
for i, ifm := range inflForms {
binary.LittleEndian.PutUint32(ifBuf[i*4:i*4+4], addStr(ifm))
}
if _, err := f.Write(ifBuf); err != nil {
return err
}
// 7. Features
featBuf := make([]byte, featureCount*16)
for i, c := range d.Contents {
p := d.POSTable.POSs[i]
var t posTuple
for j := 0; j < 4; j++ {
t[j] = d.POSTable.NameList[p[j]]
}
featBuf[i*16] = posMap[t]
featBuf[i*16+1] = inflTypeMap[c[0]]
featBuf[i*16+2] = inflFormMap[c[1]]
featBuf[i*16+3] = 0 // reserved
binary.LittleEndian.PutUint32(featBuf[i*16+4:i*16+8], addStr(c[2]))
binary.LittleEndian.PutUint32(featBuf[i*16+8:i*16+12], addStr(c[3]))
binary.LittleEndian.PutUint32(featBuf[i*16+12:i*16+16], addStr(c[4]))
}
if _, err := f.Write(featBuf); err != nil {
return err
}
// 8. Strings
if _, err := f.Write(strBuf.Bytes()); err != nil {
return err
}
// 9. Connection
connBuf := make([]byte, len(d.Connection.Vec)*2)
for i, v := range d.Connection.Vec {
binary.LittleEndian.PutUint16(connBuf[i*2:i*2+2], uint16(v))
}
if _, err := f.Write(connBuf); err != nil {
return err
}
// 10. CharCat
if _, err := f.Write(d.CharCategory); err != nil {
return err
}
// 11. CharClass
ccBuf := make([]byte, charClassCount*16)
for i := range charClassCount {
if d.InvokeList[i] {
ccBuf[i*16] = 1
}
if d.GroupList[i] {
ccBuf[i*16+1] = 1
}
binary.LittleEndian.PutUint32(ccBuf[i*16+4:i*16+8], uint32(d.UnkDict.Index[int32(i)]))
binary.LittleEndian.PutUint32(ccBuf[i*16+8:i*16+12], uint32(d.UnkDict.IndexDup[int32(i)]))
binary.LittleEndian.PutUint32(ccBuf[i*16+12:i*16+16], addStr(d.CharClass[i]))
}
if _, err := f.Write(ccBuf); err != nil {
return err
}
// 12. UnkMorph
umBuf := make([]byte, unkMorphCount*6)
for i, m := range d.UnkDict.Morphs {
binary.LittleEndian.PutUint16(umBuf[i*6:i*6+2], uint16(m.LeftID))
binary.LittleEndian.PutUint16(umBuf[i*6+2:i*6+4], uint16(m.RightID))
binary.LittleEndian.PutUint16(umBuf[i*6+4:i*6+6], uint16(m.Weight))
}
if _, err := f.Write(umBuf); err != nil {
return err
}
// 13. UnkFeature
ufBuf := make([]byte, unkFeatureCount*16)
for i, c := range d.UnkDict.Contents {
for j := 0; j < 4; j++ {
binary.LittleEndian.PutUint32(ufBuf[i*16+j*4:i*16+j*4+4], addStr(c[j]))
}
}
if _, err := f.Write(ufBuf); err != nil {
return err
}
if err := f.Sync(); err != nil {
return err
}
if err := f.Close(); err != nil {
return err
}
if err := os.Rename(tmpOut, out); err != nil {
return fmt.Errorf("mkmorph: rename %s -> %s: %w", tmpOut, out, err)
}
return nil
}