summaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
authorLuke Shumaker <lukeshu@lukeshu.com>2023-01-29 20:59:37 -0700
committerLuke Shumaker <lukeshu@lukeshu.com>2023-01-30 22:56:40 -0700
commitb3f4186f2b8e992f56f898784b1cd28bfd7550ca (patch)
tree9c9f0086c386a63a12b622945248916d51c480a5 /internal
parentbc1bacc410ddfa444c5bf0e56f33a7da440658ae (diff)
Invent "barriers" instead of nesting parsers
Diffstat (limited to 'internal')
-rw-r--r--internal/parse.go120
1 files changed, 117 insertions, 3 deletions
diff --git a/internal/parse.go b/internal/parse.go
index 9db57fb..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,22 @@ 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 {
@@ -360,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.
//
@@ -439,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