summaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
Diffstat (limited to 'internal')
-rw-r--r--internal/allwriter.go174
-rw-r--r--internal/base64.go9
-rw-r--r--internal/parse.go124
3 files changed, 303 insertions, 4 deletions
diff --git a/internal/allwriter.go b/internal/allwriter.go
new file mode 100644
index 0000000..187aa8e
--- /dev/null
+++ b/internal/allwriter.go
@@ -0,0 +1,174 @@
+// Copyright (C) 2023 Luke Shumaker <lukeshu@lukeshu.com>
+//
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package internal
+
+import (
+ "io"
+ "unicode/utf8"
+)
+
+// interfaces /////////////////////////////////////////////////////////////////
+
+type RuneWriter interface {
+ WriteRune(rune) (int, error)
+}
+
+// An AllWriter is the union of several common writer interfaces.
+type AllWriter interface {
+ io.Writer
+ io.ByteWriter
+ RuneWriter
+ io.StringWriter
+}
+
+// implementations ////////////////////////////////////////////////////////////
+
+func WriteByte(w io.Writer, b byte) error {
+ var buf [1]byte
+ buf[0] = b
+ _, err := w.Write(buf[:])
+ return err
+}
+
+func WriteRune(w io.Writer, r rune) (int, error) {
+ var buf [utf8.UTFMax]byte
+ n := utf8.EncodeRune(buf[:], r)
+ return w.Write(buf[:n])
+}
+
+func WriteString(w io.Writer, s string) (int, error) {
+ return w.Write([]byte(s))
+}
+
+// wrappers ///////////////////////////////////////////////////////////////////
+
+// NNN
+
+type (
+ writerNNN interface{ io.Writer }
+ writerNNNWrapper struct{ writerNNN }
+)
+
+func (w writerNNNWrapper) WriteByte(b byte) error { return WriteByte(w, b) }
+func (w writerNNNWrapper) WriteRune(r rune) (int, error) { return WriteRune(w, r) }
+func (w writerNNNWrapper) WriteString(s string) (int, error) { return WriteString(w, s) }
+
+// NNY
+
+type (
+ writerNNY interface {
+ io.Writer
+ io.StringWriter
+ }
+ writerNNYWrapper struct{ writerNNY }
+)
+
+func (w writerNNYWrapper) WriteByte(b byte) error { return WriteByte(w, b) }
+func (w writerNNYWrapper) WriteRune(r rune) (int, error) { return WriteRune(w, r) }
+
+// NYN
+
+type (
+ writerNYN interface {
+ io.Writer
+ RuneWriter
+ }
+ writerNYNWrapper struct{ writerNYN }
+)
+
+func (w writerNYNWrapper) WriteByte(b byte) error { return WriteByte(w, b) }
+func (w writerNYNWrapper) WriteString(s string) (int, error) { return WriteString(w, s) }
+
+// NYY
+
+type (
+ writerNYY interface {
+ io.Writer
+ RuneWriter
+ io.StringWriter
+ }
+ writerNYYWrapper struct{ writerNYY }
+)
+
+func (w writerNYYWrapper) WriteByte(b byte) error { return WriteByte(w, b) }
+
+// YNN
+
+type (
+ writerYNN interface {
+ io.Writer
+ io.ByteWriter
+ }
+ writerYNNWrapper struct{ writerYNN }
+)
+
+func (w writerYNNWrapper) WriteRune(r rune) (int, error) { return WriteRune(w, r) }
+func (w writerYNNWrapper) WriteString(s string) (int, error) { return WriteString(w, s) }
+
+// YNY
+
+type (
+ writerYNY interface {
+ io.Writer
+ io.ByteWriter
+ io.StringWriter
+ }
+ writerYNYWrapper struct{ writerYNY }
+)
+
+func (w writerYNYWrapper) WriteRune(r rune) (int, error) { return WriteRune(w, r) }
+
+// YYN
+
+type (
+ writerYYN interface {
+ io.Writer
+ io.ByteWriter
+ RuneWriter
+ }
+ writerYYNWrapper struct{ writerYYN }
+)
+
+func (w writerYYNWrapper) WriteString(s string) (int, error) { return WriteString(w, s) }
+
+// NewAllWriter wraps an io.Writer turning it in to an AllWriter. If
+// the io.Writer already has any of the other write methods, then its
+// native version of those methods are used.
+func NewAllWriter(inner io.Writer) AllWriter {
+ switch inner := inner.(type) {
+ // 3 Y bits
+ case AllWriter: // YYY:
+ return inner
+ // 2 Y bits
+ case writerNYY:
+ return writerNYYWrapper{writerNYY: inner}
+ case writerYNY:
+ return writerYNYWrapper{writerYNY: inner}
+ case writerYYN:
+ return writerYYNWrapper{writerYYN: inner}
+ // 1 Y bit
+ case writerNNY:
+ return writerNNYWrapper{writerNNY: inner}
+ case writerNYN:
+ return writerNYNWrapper{writerNYN: inner}
+ case writerYNN:
+ return writerYNNWrapper{writerYNN: inner}
+ // 0 Y bits
+ default: // NNN:
+ return writerNNNWrapper{writerNNN: inner}
+ }
+}
+
+// discard /////////////////////////////////////////////////////////////////////
+
+// Discard is like io.Discard, but implements AllWriter.
+var Discard = discard{}
+
+type discard struct{}
+
+func (discard) Write(p []byte) (int, error) { return len(p), nil }
+func (discard) WriteByte(b byte) error { return nil }
+func (discard) WriteRune(r rune) (int, error) { return 0, nil }
+func (discard) WriteString(s string) (int, error) { return len(s), nil }
diff --git a/internal/base64.go b/internal/base64.go
index 15adbf4..291a229 100644
--- a/internal/base64.go
+++ b/internal/base64.go
@@ -19,7 +19,10 @@ type base64Decoder struct {
bufLen int
}
-func NewBase64Decoder(w io.Writer) io.WriteCloser {
+func NewBase64Decoder(w io.Writer) interface {
+ io.WriteCloser
+ RuneWriter
+} {
return &base64Decoder{
dst: w,
}
@@ -112,6 +115,10 @@ func (dec *base64Decoder) Write(dat []byte) (int, error) {
return len(dat), nil
}
+func (dec *base64Decoder) WriteRune(r rune) (int, error) {
+ return WriteRune(dec, r)
+}
+
func (dec *base64Decoder) Close() error {
if dec.bufLen == 0 {
return nil
diff --git a/internal/parse.go b/internal/parse.go
index b11aae6..36db4a9 100644
--- a/internal/parse.go
+++ b/internal/parse.go
@@ -313,6 +313,13 @@ type Parser struct {
// a ["x","y"
// ["x","y"]
stack []RuneType
+
+ barriers []barrier
+}
+
+type barrier struct {
+ closed bool
+ stack []RuneType
}
func (par *Parser) init() {
@@ -345,8 +352,26 @@ func (par *Parser) stackString() string {
return buf.String()
}
+func (par *Parser) depth() int {
+ n := len(par.stack)
+ for _, barrier := range par.barriers {
+ n += len(barrier.stack)
+ }
+ return n
+}
+
func (par *Parser) StackIsEmpty() bool {
- return len(par.stack) == 0 || (len(par.stack) == 1 && par.stack[0] == runeTypeAny)
+ if len(par.barriers) > 0 {
+ return false
+ }
+ if len(par.stack) == 0 {
+ return true
+ }
+ return len(par.stack) == 1 && par.stack[0] == runeTypeAny
+}
+
+func (par *Parser) StackSize() int {
+ return len(par.stack)
}
// Reset all Parser state.
@@ -356,6 +381,99 @@ func (par *Parser) Reset() {
}
}
+// PushReadBarrier causes the parser to expect EOF once the end of the
+// element that is started by the current top-of-stack is reached,
+// until this is un-done with PopBarrier. It essentially turns the
+// parser in to a sub-parser.
+//
+// PushReadBarrier may only be called at the beginning of an element,
+// whether that be
+//
+// - runeTypeAny
+// - RuneTypeObjectBeg
+// - RuneTypeArrayBeg
+// - RuneTypeStringBeg
+// - RuneTypeNumberIntNeg, RuneTypeNumberIntZero, RuneTypeNumberIntDig
+// - RuneTypeTrueT
+// - RuneTypeFalseF
+// - RuneTypeNullN
+func (par *Parser) PushReadBarrier() {
+ // Sanity checking.
+ par.init()
+ if len(par.stack) == 0 {
+ panic(errors.New("illegal PushReadBarrier call: empty stack"))
+ }
+ curState := par.stack[len(par.stack)-1]
+ switch curState {
+ case runeTypeAny,
+ RuneTypeObjectBeg,
+ RuneTypeArrayBeg,
+ RuneTypeStringBeg,
+ RuneTypeNumberIntNeg, RuneTypeNumberIntZero, RuneTypeNumberIntDig,
+ RuneTypeTrueT,
+ RuneTypeFalseF,
+ RuneTypeNullN:
+ // OK
+ default:
+ panic(fmt.Errorf("illegal PushReadBarrier call: %q", curState))
+ }
+ // Actually push.
+ par.barriers = append(par.barriers, barrier{
+ closed: par.closed,
+ stack: par.stack[:len(par.stack)-1],
+ })
+ par.stack = []RuneType{curState}
+}
+
+// PushWriteBarrier causes the parser to expect EOF once the end of
+// the about-to-start element is reached, until this is un-done with
+// PopBarrier. It essentially turns the parser in to a sub-parser.
+//
+// PushWriteBarrier may only be called at the places where an element
+// of any type may start:
+//
+// - runeTypeAny for top-level and object-value elements
+// - RuneTypeArrayBeg for array-item elements
+//
+// PushWriteBarrier signals intent to write an element; if it is
+// called in a place where an element is optional (at the beginning of
+// an array), it becomes a syntax error to not write the element.
+func (par *Parser) PushWriteBarrier() {
+ par.init()
+ if len(par.stack) == 0 {
+ panic(errors.New("illegal PushWriteBarrier call: empty stack"))
+ }
+ switch par.stack[len(par.stack)-1] {
+ case runeTypeAny:
+ par.popState()
+ par.barriers = append(par.barriers, barrier{
+ closed: par.closed,
+ stack: par.stack,
+ })
+ par.stack = []RuneType{runeTypeAny}
+ case RuneTypeArrayBeg:
+ par.replaceState(RuneTypeArrayComma)
+ par.barriers = append(par.barriers, barrier{
+ closed: par.closed,
+ stack: par.stack,
+ })
+ par.stack = []RuneType{runeTypeAny}
+ default:
+ panic(fmt.Errorf("illegal PushWriteBarrier call: %q", par.stack[len(par.stack)-1]))
+ }
+}
+
+// PopBarrier reverses a call to PushReadBarrier or PushWriteBarrier.
+func (par *Parser) PopBarrier() {
+ if len(par.barriers) == 0 {
+ panic(errors.New("illegal PopBarrier call: empty barrier stack"))
+ }
+ barrier := par.barriers[len(par.barriers)-1]
+ par.barriers = par.barriers[:len(par.barriers)-1]
+ par.closed = barrier.closed
+ par.stack = append(barrier.stack, par.stack...)
+}
+
// HandleEOF feeds EOF to the Parser. The returned RuneType is either
// RuneTypeEOF or RuneTypeError.
//
@@ -435,12 +553,12 @@ func (par *Parser) HandleRune(c rune) (RuneType, error) {
case 0x0020, 0x000A, 0x000D, 0x0009:
return RuneTypeSpace, nil
case '{':
- if par.MaxDepth > 0 && len(par.stack) > par.MaxDepth {
+ if par.MaxDepth > 0 && par.depth() > par.MaxDepth {
return RuneTypeError, ErrParserExceededMaxDepth
}
return par.replaceState(RuneTypeObjectBeg), nil
case '[':
- if par.MaxDepth > 0 && len(par.stack) > par.MaxDepth {
+ if par.MaxDepth > 0 && par.depth() > par.MaxDepth {
return RuneTypeError, ErrParserExceededMaxDepth
}
return par.replaceState(RuneTypeArrayBeg), nil