Skip to content

Commit b30e146

Browse files
committed
decoder: add shared registry and constructor infrastructure
1 parent 3dda3aa commit b30e146

File tree

5 files changed

+235
-12
lines changed

5 files changed

+235
-12
lines changed

docs/AddingANewDecoder.md

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# Adding A New Decoder
2+
3+
This note describes how to add another decoder implementation to Trice.
4+
5+
## Goal
6+
7+
A decoder package should:
8+
9+
- Implement the common `decoder.Decoder` interface.
10+
- Reuse shared initialization from `internal/decoder`.
11+
- Register itself so translator selection works automatically.
12+
13+
## Steps
14+
15+
1. Create a new package (for example `internal/myDecoder`).
16+
17+
2. Implement a constructor with the standard signature:
18+
19+
```go
20+
func New(
21+
w io.Writer,
22+
lut id.TriceIDLookUp,
23+
m *sync.RWMutex,
24+
li id.TriceIDLookUpLI,
25+
in io.Reader,
26+
endian bool,
27+
) decoder.Decoder
28+
```
29+
30+
3. Initialize common fields with:
31+
32+
```go
33+
decoder.NewDecoderData(decoder.Config{
34+
Out: w,
35+
LUT: lut,
36+
LUTMutex: m,
37+
LI: li,
38+
In: in,
39+
Endian: endian,
40+
NeedBuffers: false, // true for framed/packet decoders
41+
})
42+
```
43+
44+
4. Register the decoder in `init()`:
45+
46+
```go
47+
func init() {
48+
decoder.Register("MY_ENCODING", New)
49+
}
50+
```
51+
52+
5. Ensure package registration runs by importing it for side effects in translator:
53+
54+
```go
55+
import _ "github.com/rokath/trice/internal/myDecoder"
56+
```
57+
58+
Then `decoder.NewForEncoding("MY_ENCODING", ...)` can create it.
59+
60+
## Notes
61+
62+
- Encoding lookup is case-insensitive.
63+
- `Config.Out == nil` is normalized to `io.Discard`.
64+
- `Config.LUTMutex == nil` gets a default mutex.
65+
- Add package-specific tests for constructor behavior and `Read` edge cases.

internal/decoder/decoder.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,17 @@ func init() {
143143
// New abstracts the function type for a new decoder.
144144
type New func(out io.Writer, lut id.TriceIDLookUp, m *sync.RWMutex, li id.TriceIDLookUpLI, in io.Reader, endian bool) Decoder
145145

146+
// Config contains common constructor parameters for all decoders.
147+
type Config struct {
148+
Out io.Writer
149+
LUT id.TriceIDLookUp
150+
LUTMutex *sync.RWMutex
151+
LI id.TriceIDLookUpLI
152+
In io.Reader
153+
Endian bool
154+
NeedBuffers bool // NeedBuffers allocates B, B0 and InnerBuffer for framed decoders.
155+
}
156+
146157
// Decoder is providing a byte reader returning decoded trice's.
147158
// SetInput allows switching the input stream to a different source.
148159
type Decoder interface {
@@ -168,6 +179,33 @@ type DecoderData struct {
168179
Trice id.TriceFmt // id.TriceFmt // received trice
169180
}
170181

182+
// NewDecoderData initializes the common base fields for a decoder.
183+
//
184+
// Callers can request pre-allocated internal working buffers with NeedBuffers.
185+
func NewDecoderData(c Config) DecoderData {
186+
if c.Out == nil {
187+
c.Out = io.Discard
188+
}
189+
if c.LUTMutex == nil {
190+
c.LUTMutex = new(sync.RWMutex)
191+
}
192+
d := DecoderData{
193+
W: c.Out,
194+
In: c.In,
195+
IBuf: make([]byte, 0, DefaultSize),
196+
Endian: c.Endian,
197+
Lut: c.LUT,
198+
LutMutex: c.LUTMutex,
199+
Li: c.LI,
200+
}
201+
if c.NeedBuffers {
202+
d.B = make([]byte, 0, DefaultSize)
203+
d.B0 = make([]byte, DefaultSize)
204+
d.InnerBuffer = make([]byte, DefaultSize)
205+
}
206+
return d
207+
}
208+
171209
// SetInput allows switching the input stream to a different source.
172210
//
173211
// This function is for easier testing with cycle counters.

internal/decoder/registry.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package decoder
2+
3+
import (
4+
"fmt"
5+
"io"
6+
"strings"
7+
"sync"
8+
9+
"github.com/rokath/trice/internal/id"
10+
)
11+
12+
var (
13+
registryMu sync.RWMutex
14+
registry = map[string]New{}
15+
)
16+
17+
func normalizeEncoding(name string) string {
18+
return strings.ToUpper(strings.TrimSpace(name))
19+
}
20+
21+
// Register associates an encoding name with a decoder constructor.
22+
//
23+
// Names are matched case-insensitively.
24+
func Register(name string, ctor New) {
25+
key := normalizeEncoding(name)
26+
if key == "" {
27+
panic("decoder: empty encoding name")
28+
}
29+
if ctor == nil {
30+
panic("decoder: nil constructor")
31+
}
32+
registryMu.Lock()
33+
registry[key] = ctor
34+
registryMu.Unlock()
35+
}
36+
37+
// NewForEncoding creates a decoder instance for the requested encoding.
38+
func NewForEncoding(encoding string, out io.Writer, lut id.TriceIDLookUp, m *sync.RWMutex, li id.TriceIDLookUpLI, in io.Reader, endian bool) (Decoder, error) {
39+
key := normalizeEncoding(encoding)
40+
registryMu.RLock()
41+
ctor, ok := registry[key]
42+
registryMu.RUnlock()
43+
if !ok {
44+
return nil, fmt.Errorf("unknown encoding %q", encoding)
45+
}
46+
return ctor(out, lut, m, li, in, endian), nil
47+
}

internal/decoder/registry_test.go

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package decoder
2+
3+
import (
4+
"bytes"
5+
"io"
6+
"strings"
7+
"sync"
8+
"testing"
9+
10+
"github.com/rokath/trice/internal/id"
11+
)
12+
13+
type fakeDecoder struct {
14+
DecoderData
15+
}
16+
17+
func (f *fakeDecoder) Read(p []byte) (int, error) {
18+
if f.In == nil {
19+
return 0, io.EOF
20+
}
21+
return f.In.Read(p)
22+
}
23+
24+
func TestNewDecoderDataDefaults(t *testing.T) {
25+
d := NewDecoderData(Config{
26+
Out: nil,
27+
LUTMutex: nil,
28+
NeedBuffers: true,
29+
})
30+
if d.W == nil {
31+
t.Fatal("expected non-nil writer")
32+
}
33+
if d.LutMutex == nil {
34+
t.Fatal("expected non-nil LUT mutex")
35+
}
36+
if cap(d.IBuf) != DefaultSize {
37+
t.Fatalf("unexpected IBuf cap: %d", cap(d.IBuf))
38+
}
39+
if cap(d.B) != DefaultSize || len(d.B0) != DefaultSize || len(d.InnerBuffer) != DefaultSize {
40+
t.Fatal("expected packet buffers to be allocated")
41+
}
42+
}
43+
44+
func TestNewForEncodingUsesRegistry(t *testing.T) {
45+
const enc = "UNIT_TEST_DECODER"
46+
Register(enc, func(out io.Writer, lut id.TriceIDLookUp, m *sync.RWMutex, li id.TriceIDLookUpLI, in io.Reader, endian bool) Decoder {
47+
return &fakeDecoder{
48+
DecoderData: NewDecoderData(Config{
49+
Out: out,
50+
LUT: lut,
51+
LUTMutex: m,
52+
LI: li,
53+
In: in,
54+
Endian: endian,
55+
}),
56+
}
57+
})
58+
59+
in := strings.NewReader("ok")
60+
var out bytes.Buffer
61+
dec, err := NewForEncoding(strings.ToLower(enc), &out, nil, nil, nil, in, LittleEndian)
62+
if err != nil {
63+
t.Fatalf("unexpected error: %v", err)
64+
}
65+
buf := make([]byte, 8)
66+
n, rerr := dec.Read(buf)
67+
if rerr != nil && rerr != io.EOF {
68+
t.Fatalf("unexpected read error: %v", rerr)
69+
}
70+
if string(buf[:n]) != "ok" {
71+
t.Fatalf("unexpected decoder output: %q", string(buf[:n]))
72+
}
73+
}
74+
75+
func TestNewForEncodingUnknown(t *testing.T) {
76+
if _, err := NewForEncoding("does-not-exist", io.Discard, nil, nil, nil, nil, LittleEndian); err == nil {
77+
t.Fatal("expected error for unknown encoding")
78+
}
79+
}

internal/translator/translator.go

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,14 @@ import (
1212
"syscall"
1313
"time"
1414

15-
"github.com/rokath/trice/internal/charDecoder"
15+
_ "github.com/rokath/trice/internal/charDecoder"
1616
"github.com/rokath/trice/internal/decoder"
17-
"github.com/rokath/trice/internal/dumpDecoder"
17+
_ "github.com/rokath/trice/internal/dumpDecoder"
1818
"github.com/rokath/trice/internal/emitter"
1919
"github.com/rokath/trice/internal/id"
2020
"github.com/rokath/trice/internal/keybcmd"
2121
"github.com/rokath/trice/internal/receiver"
22-
"github.com/rokath/trice/internal/trexDecoder"
22+
_ "github.com/rokath/trice/internal/trexDecoder"
2323
"github.com/rokath/trice/pkg/msg"
2424
)
2525

@@ -54,15 +54,9 @@ func Translate(w io.Writer, sw *emitter.TriceLineComposer, lut id.TriceIDLookUp,
5454
default:
5555
log.Fatal(fmt.Sprintln("unknown endianness", TriceEndianness, "- accepting litteEndian or bigEndian."))
5656
}
57-
switch strings.ToUpper(Encoding) {
58-
case "TREX":
59-
dec = trexDecoder.New(w, lut, m, li, rwc, endian)
60-
case "CHAR":
61-
dec = charDecoder.New(w, lut, m, li, rwc, endian)
62-
case "DUMP":
63-
dec = dumpDecoder.New(w, lut, m, li, rwc, endian)
64-
default:
65-
log.Fatal(fmt.Sprintln("unknown encoding ", Encoding))
57+
dec, err := decoder.NewForEncoding(Encoding, w, lut, m, li, rwc, endian)
58+
if err != nil {
59+
log.Fatal(err)
6660
}
6761
if emitter.DisplayRemote {
6862
keybcmd.ReadInput(rwc)

0 commit comments

Comments
 (0)