Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 104 additions & 0 deletions extensions/omniv21/fileformat/edi/reader.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package edi

import (
"bufio"
"fmt"

"github.com/jf-tech/omniparser/idr"
)

type rawSegElem struct {
elemIndex int // for this piece of data, the index of which element it belongs to. 1-based.
compIndex int // for this piece of data, the index of which component it belongs to. 1-based.
data []byte
}

type rawSeg struct {
name string // name of the segment, e.g. 'ISA', 'GS', etc.
raw []byte // the raw data of the entire segment, including segment delimiter.
elems []rawSegElem // all the broken down pieces of elements of the segment.
}

const (
defaultElemsPerSeg = 20
)

func newRawSeg() rawSeg {
return rawSeg{
elems: make([]rawSegElem, 0, defaultElemsPerSeg),
}
}

type stackEntry struct {
segDecl *segDecl // the current stack entry's segment decl
segNode *idr.Node // the current stack entry segment's IDR node
curChild int // which child segment is the current segment is processing.
occurred int // how many times the current segment is fully processed.
}

const (
defaultStackDepth = 50
)

func newStack() []stackEntry {
return make([]stackEntry, 0, defaultStackDepth)
}

type ediReader struct {
filename string
scanner *bufio.Scanner
segDelim string
elemDelim string
compDelim *string
releaseChar *rune
stack []stackEntry
target *idr.Node
runeCount int
unprocessedSegData rawSeg
}

func inRange(i, lowerBoundInclusive, upperBoundInclusive int) bool {
return i >= lowerBoundInclusive && i <= upperBoundInclusive
}

func (r *ediReader) resetRawSeg() {
r.unprocessedSegData.name = ""
r.unprocessedSegData.raw = nil
r.unprocessedSegData.elems = r.unprocessedSegData.elems[:0]
}

// stackTop returns the pointer to the 'frame'-th stack entry from the top.
// 'frame' is optional, if not specified, default 0 (aka the very top of
// the stack) is assumed. Note caller NEVER owns the memory of the returned
// entry, thus caller can use the pointer and its data values inside locally
// but should never cache/save it somewhere for later usage.
func (r *ediReader) stackTop(frame ...int) *stackEntry {
nth := 0
if len(frame) == 1 {
nth = frame[0]
}
if !inRange(nth, 0, len(r.stack)-1) {
panic(fmt.Sprintf("frame requested: %d, but stack length: %d", nth, len(r.stack)))
}
return &r.stack[len(r.stack)-nth-1]
}

// shrinkStack removes the top frame of the stack and returns the pointer to the NEW TOP
// FRAME to caller. Note caller NEVER owns the memory of the returned entry, thus caller can
// use the pointer and its data values inside locally but should never cache/save it somewhere
// for later usage.
func (r *ediReader) shrinkStack() *stackEntry {
if len(r.stack) < 1 {
panic("stack length is empty")
}
r.stack = r.stack[:len(r.stack)-1]
if len(r.stack) < 1 {
return nil
}
return &r.stack[len(r.stack)-1]
}

// growStack adds a new stack entry to the top of the stack.
func (r *ediReader) growStack(e stackEntry) {
r.stack = append(r.stack, e)
}
108 changes: 108 additions & 0 deletions extensions/omniv21/fileformat/edi/reader_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package edi

import (
"testing"

"github.com/stretchr/testify/assert"

"github.com/jf-tech/omniparser/idr"
)

func TestRawSeg(t *testing.T) {
rawSegName := "test"
rawSegData := []byte("test data")
r := ediReader{
unprocessedSegData: newRawSeg(),
}
assert.Equal(t, "", r.unprocessedSegData.name)
assert.Nil(t, r.unprocessedSegData.raw)
assert.Equal(t, 0, len(r.unprocessedSegData.elems))
assert.Equal(t, defaultElemsPerSeg, cap(r.unprocessedSegData.elems))
r.unprocessedSegData.name = rawSegName
r.unprocessedSegData.raw = rawSegData
r.unprocessedSegData.elems = append(
r.unprocessedSegData.elems, rawSegElem{1, 1, rawSegData[0:4]}, rawSegElem{2, 1, rawSegData[5:]})
r.resetRawSeg()
assert.Equal(t, "", r.unprocessedSegData.name)
assert.Nil(t, r.unprocessedSegData.raw)
assert.Equal(t, 0, len(r.unprocessedSegData.elems))
assert.Equal(t, defaultElemsPerSeg, cap(r.unprocessedSegData.elems))
}

// Adding a benchmark for rawSeg operation to ensure there is no alloc:
// BenchmarkRawSeg-8 81410766 13.9 ns/op 0 B/op 0 allocs/op
func BenchmarkRawSeg(b *testing.B) {
rawSegName := "test"
rawSegData := []byte("test data")
r := ediReader{
unprocessedSegData: newRawSeg(),
}
for i := 0; i < b.N; i++ {
r.resetRawSeg()
r.unprocessedSegData.name = rawSegName
r.unprocessedSegData.raw = rawSegData
r.unprocessedSegData.elems = append(
r.unprocessedSegData.elems, rawSegElem{1, 1, rawSegData[0:4]}, rawSegElem{2, 1, rawSegData[5:]})
}
}

func TestStack(t *testing.T) {
r := ediReader{
stack: newStack(),
}
assert.Equal(t, 0, len(r.stack))
assert.Equal(t, defaultStackDepth, cap(r.stack))
// try to access top of stack while there is nothing in it => panic.
assert.PanicsWithValue(t,
"frame requested: 0, but stack length: 0",
func() {
r.stackTop()
})
// try to shrink empty stack => panic.
assert.PanicsWithValue(t,
"stack length is empty",
func() {
r.shrinkStack()
})
newEntry1 := stackEntry{
segDecl: &segDecl{},
segNode: idr.CreateNode(idr.TextNode, "test"),
curChild: 5,
occurred: 10,
}
r.growStack(newEntry1)
assert.Equal(t, 1, len(r.stack))
assert.Equal(t, newEntry1, *r.stackTop())
newEntry2 := stackEntry{
segDecl: &segDecl{},
segNode: idr.CreateNode(idr.TextNode, "test 2"),
curChild: 10,
occurred: 20,
}
r.growStack(newEntry2)
assert.Equal(t, 2, len(r.stack))
assert.Equal(t, newEntry2, *r.stackTop())
// try to access a frame that doesn't exist => panic.
assert.PanicsWithValue(t,
"frame requested: 2, but stack length: 2",
func() {
r.stackTop(2)
})
assert.Equal(t, newEntry1, *r.shrinkStack())
assert.Nil(t, r.shrinkStack())
}

// Adding a benchmark for stack operation to ensure there is no alloc:
// BenchmarkStack-8 12901227 89.0 ns/op 0 B/op 0 allocs/op
func BenchmarkStack(b *testing.B) {
r := ediReader{
stack: newStack(),
}
for i := 0; i < b.N; i++ {
for j := 0; j < 20; j++ {
r.growStack(stackEntry{})
}
for r.shrinkStack() != nil {
}
}
}
17 changes: 16 additions & 1 deletion extensions/omniv21/fileformat/edi/seg.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,21 @@ import (
"github.com/jf-tech/go-corelib/maths"
)

// variable/func naming guide:
//
// full name | short name
// -----------------------------------
// segment | seg
// component | comp
// element | elem
// node | n
// reader | r
// delimiter | delim
// current | cur
// number | num
// declaration | decl
// character | char

const (
segTypeSeg = "segment"
segTypeGroup = "segment_group"
Expand All @@ -17,7 +32,7 @@ const (
type elem struct {
Name string `json:"name,omitempty"`
Index int `json:"index,omitempty"`
ComponentIndex *int `json:"component_index,omitempty"`
CompIndex *int `json:"component_index,omitempty"`
EmptyIfMissing bool `json:"empty_if_missing,omitempty"`
}

Expand Down