package code import ( "encoding/binary" "jinx/pkg/libs/source" "math" ) type Builder struct { code []byte debugInfo DebugInfo markers map[Marker]int markerRefs map[int]Marker currentLine int lineStart int } func NewBuilder() Builder { return Builder{ code: make([]byte, 0, 64), debugInfo: NewDebugInfo("unknown file"), markers: make(map[Marker]int, 0), markerRefs: make(map[int]Marker, 0), lineStart: -1, currentLine: -1, } } func (b *Builder) AppendOp(op Op) { b.code = append(b.code, byte(op)) } func (b *Builder) AppendInt(x int64) { b.code = append(b.code, make([]byte, 8)...) binary.LittleEndian.PutUint64(b.code[len(b.code)-8:], uint64(x)) } func (b *Builder) AppendFloat(x float64) { b.AppendInt(int64(math.Float64bits(x))) } func (b *Builder) AppendString(s string) { b.code = append(b.code, []byte(s)...) b.code = append(b.code, 0) } const UnfilledMarkerReference = 0x11_5AB07A6ED_600D5 func (b *Builder) AppendMarkerReference(marker Marker) { b.markerRefs[b.Len()] = marker b.AppendInt(UnfilledMarkerReference) } func (b *Builder) AppendRaw(raw Raw) { b.code = append(b.code, raw...) } func (b *Builder) AppendBuilder(other Builder) error { for marker, pc := range other.markers { if parentPc, ok := b.markers[marker]; ok { return ErrBuilderAppendMarkerDefinitionClash{ Marker: marker, ParentPc: parentPc, ChildPc: pc, } } b.SetMarker(marker, b.Len()+pc) } for pc, marker := range other.markerRefs { b.markerRefs[b.Len()+pc] = marker } b.AppendRaw(other.code) b.debugInfo.AppendOther(other.debugInfo) return nil } func (b *Builder) PutMarker(marker Marker) { b.SetMarker(marker, b.Len()) } func (b *Builder) SetMarker(marker Marker, pc int) { b.markers[marker] = pc } func (b *Builder) StartLine(line int) { if b.lineStart != -1 { panic("line already started") } b.currentLine = line b.lineStart = b.Len() } func (b *Builder) EndLine() { defer func() { b.currentLine = -1 b.lineStart = -1 }() if b.lineStart == -1 { panic("line not started") } if b.lineStart == b.Len() { return } b.debugInfo.AppendLine(b.Len()-1, b.currentLine) } func (b *Builder) AddDebugInfo(fromPc, toPc int, loc source.Loc) { b.debugInfo.FillInfo(fromPc, toPc, loc) } func (b *Builder) SetInt(at int, x int64) { binary.LittleEndian.PutUint64(b.code[at:], uint64(x)) } func (b *Builder) Code() Raw { return Raw(b.code) } func (b *Builder) Len() int { return len(b.code) } func (b *Builder) Build() (Code, error) { for pc, marker := range b.markerRefs { markerPc, ok := b.markers[marker] if !ok { return Code{}, ErrBuilderUnfulfilledMarker{ Marker: marker, Pc: pc, } } valueAtRefPc := binary.LittleEndian.Uint64(b.code[pc : pc+8]) if valueAtRefPc != UnfilledMarkerReference { return Code{}, ErrBuilderOverwrittenMarkerReference{ Marker: marker, Pc: pc, UnexpectedValue: valueAtRefPc, } } b.SetInt(pc, int64(markerPc)) } return New(b.code, b.debugInfo), nil }