Skip to content

Commit

Permalink
Add Value.All method to iterate over all values (#35)
Browse files Browse the repository at this point in the history
  • Loading branch information
dsnet authored Oct 10, 2024
1 parent 2048673 commit 29efb4a
Show file tree
Hide file tree
Showing 9 changed files with 140 additions and 61 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@ jobs:
- name: Install Go
uses: actions/setup-go@v2
with:
go-version: 1.18.x
go-version: 1.23.x
- name: Checkout code
uses: actions/checkout@v2
- name: Format
run: diff -u <(echo -n) <(gofmt -s -d .)
test-all:
strategy:
matrix:
go-version: [1.18.x]
go-version: [1.23.x]
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
Expand Down
40 changes: 15 additions & 25 deletions format.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,21 +88,6 @@ func (v *Value) Format() {
v.UpdateOffsets()
}

// Range iterates through a Value in depth-first order and
// calls f for each value (including the root value).
// It stops iteration when f returns false.
func (v *Value) Range(f func(v *Value) bool) bool {
if !f(v) {
return false
}
if comp, ok := v.Value.(composite); ok {
return comp.rangeValues(func(v2 *Value) bool {
return v2.Range(f)
})
}
return true
}

// normalize performs simple normalization changes. In particular, it:
// - normalizes strings,
// - normalizes empty objects and arrays as simply {} or [],
Expand All @@ -129,15 +114,16 @@ func (v *Value) normalize() bool {

// If there is only whitespace between the name and colon,
// or between the value and comma, then remove the whitespace.
v2.rangeValues(func(v *Value) bool {
if !v.AfterExtra.hasComment() {
v.AfterExtra = nil
for v3 := range v2.allValues() {
if !v3.AfterExtra.hasComment() {
v3.AfterExtra = nil
}
return true
})
}

// Normalize all sub-values.
v2.rangeValues((*Value).normalize)
for v3 := range v2.allValues() {
v3.normalize()
}
}
return true
}
Expand Down Expand Up @@ -583,7 +569,9 @@ func (v *Value) alignObjectValues() bool {

// Recursively align all sub-objects.
if comp, ok := v.Value.(composite); ok {
comp.rangeValues((*Value).alignObjectValues)
for v2 := range comp.allValues() {
v2.alignObjectValues()
}
}
return true
}
Expand All @@ -593,9 +581,11 @@ func (v Value) hasNewline(checkTopLevelExtra bool) bool {
return true
}
if comp, ok := v.Value.(composite); ok {
return !comp.rangeValues(func(v *Value) bool {
return !v.hasNewline(true)
})
for v := range comp.allValues() {
if v.hasNewline(true) {
return true
}
}
}
return false
}
Expand Down
4 changes: 2 additions & 2 deletions format_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,13 @@ var testdataFormat = []struct {
in: "[//\r\t\n]",
want: "[ //\n]",
}, {
in: `{"name" :"value" ,"name":"value"}`,
in: `{"name" :"value" ,"name":"value"}`,
want: `{"name": "value", "name": "value"}`,
}, {
in: `{"name"/**/:"value"/**/,"name":"value"}`,
want: `{"name" /**/ : "value" /**/ , "name": "value"}`,
}, {
in: `[null ,null]`,
in: `[null ,null]`,
want: `[null, null]`,
}, {
in: `[null/**/,null]`,
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module github.com/tailscale/hujson

go 1.18
go 1.23

require github.com/google/go-cmp v0.5.8
2 changes: 1 addition & 1 deletion go.work
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
go 1.18
go 1.23

use (
.
Expand Down
4 changes: 2 additions & 2 deletions patch.go
Original file line number Diff line number Diff line change
Expand Up @@ -417,8 +417,8 @@ func (b *Extra) extractTrailingcomments(readonly bool) (trailing Extra) {

// classifyComments classifies comments as belonging to the previous element
// or belonging to the current element such that:
// * b[:prevEnd] belongs to the previous element, and
// * b[currStart:] belongs to the current element.
// - b[:prevEnd] belongs to the previous element, and
// - b[currStart:] belongs to the current element.
//
// Invariant: prevEnd <= currStart
func (b Extra) classifyComments() (prevEnd, currStart int) {
Expand Down
20 changes: 12 additions & 8 deletions standard.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ func (v *Value) isStandard() bool {
return false
}
if comp, ok := v.Value.(composite); ok {
if !comp.rangeValues((*Value).isStandard) {
return false
for v2 := range comp.allValues() {
if !v2.isStandard() {
return false
}
}
if hasTrailingComma(comp) || !comp.afterExtra().IsStandard() {
return false
Expand All @@ -41,15 +43,16 @@ func (v *Value) Minimize() {
v.minimize()
v.UpdateOffsets()
}
func (v *Value) minimize() bool {
func (v *Value) minimize() {
v.BeforeExtra = nil
if v2, ok := v.Value.(composite); ok {
v2.rangeValues((*Value).minimize)
for v3 := range v2.allValues() {
v3.minimize()
}
setTrailingComma(v2, false)
*v2.afterExtra() = nil
}
v.AfterExtra = nil
return true
}

// Standardize strips any features specific to HuJSON from v,
Expand All @@ -60,18 +63,19 @@ func (v *Value) Standardize() {
v.standardize()
v.UpdateOffsets() // should be noop if offsets are already correct
}
func (v *Value) standardize() bool {
func (v *Value) standardize() {
v.BeforeExtra.standardize()
if comp, ok := v.Value.(composite); ok {
comp.rangeValues((*Value).standardize)
for v2 := range comp.allValues() {
v2.standardize()
}
if last := comp.lastValue(); last != nil && last.AfterExtra != nil {
*comp.afterExtra() = append(append(last.AfterExtra, ' '), *comp.afterExtra()...)
last.AfterExtra = nil
}
comp.afterExtra().standardize()
}
v.AfterExtra.standardize()
return true
}
func (b *Extra) standardize() {
for i, c := range *b {
Expand Down
88 changes: 68 additions & 20 deletions types.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@
//
// See https://nigeltao.github.io/blog/2021/json-with-commas-comments.html
//
//
// Functionality
// # Functionality
//
// The Parse function parses HuJSON input as a Value,
// which is a syntax tree exactly representing the input.
Expand All @@ -32,8 +31,7 @@
// but instead for the HuJSON and standard JSON format.
// The Patch method applies a JSON Patch (RFC 6902) to the receiving value.
//
//
// Grammar
// # Grammar
//
// The changes to the JSON grammar are:
//
Expand Down Expand Up @@ -72,8 +70,7 @@
// '000A' ws
// '000D' ws
//
//
// Use with the Standard Library
// # Use with the Standard Library
//
// This package operates with HuJSON as an AST. In order to parse HuJSON
// into arbitrary Go types, use this package to parse HuJSON input as an AST,
Expand All @@ -95,6 +92,7 @@ import (
"bytes"
"encoding/json"
"fmt"
"iter"
"math"
"strconv"
"unicode/utf8"
Expand Down Expand Up @@ -147,6 +145,39 @@ func (v Value) Clone() Value {
return v
}

// Range iterates through a Value in depth-first order and
// calls f for each value (including the root value).
// It stops iteration when f returns false.
//
// Deprecated: Use [All] instead.
func (v *Value) Range(f func(v *Value) bool) bool {
for v2 := range v.All() {
if !f(v2) {
return false
}
}
return true
}

// All returns an iterator over all values in depth-first order,
// starting with v itself.
func (v *Value) All() iter.Seq[*Value] {
return func(yield func(*Value) bool) {
if !yield(v) {
return
}
if comp, ok := v.Value.(composite); ok {
for v2 := range comp.allValues() {
for v3 := range v2.All() {
if !yield(v3) {
return
}
}
}
}
}
}

// ValueTrimmed is a JSON value without surrounding whitespace or comments.
// This is a sum type consisting of Literal, *Object, or *Array.
type ValueTrimmed interface {
Expand Down Expand Up @@ -335,42 +366,51 @@ type Object struct {
// after the preceding open brace or comma and before the closing brace.
AfterExtra Extra
}

type ObjectMember struct {
Name, Value Value
}

func (obj Object) length() int {
return len(obj.Members)
}

func (obj Object) firstValue() *Value {
if len(obj.Members) > 0 {
return &obj.Members[0].Name
}
return nil
}
func (obj Object) rangeValues(f func(*Value) bool) bool {
for i := range obj.Members {
if !f(&obj.Members[i].Name) {
return false
}
if !f(&obj.Members[i].Value) {
return false

// allValues iterates all members of the object,
// interleaved between the member name and the member value.
func (obj Object) allValues() iter.Seq[*Value] {
return func(yield func(*Value) bool) {
for i := range obj.Members {
if !yield(&obj.Members[i].Name) {
return
}
if !yield(&obj.Members[i].Value) {
return
}
}
}
return true
}

func (obj Object) lastValue() *Value {
if len(obj.Members) > 0 {
return &obj.Members[len(obj.Members)-1].Value
}
return nil
}

func (obj *Object) beforeExtraAt(i int) *Extra {
if i < len(obj.Members) {
return &obj.Members[i].Name.BeforeExtra
}
return &obj.AfterExtra
}

func (obj *Object) afterExtra() *Extra {
return &obj.AfterExtra
}
Expand Down Expand Up @@ -401,37 +441,45 @@ type Array struct {
// after the preceding open bracket or comma and before the closing bracket.
AfterExtra Extra
}

type ArrayElement = Value

func (arr Array) length() int {
return len(arr.Elements)
}

func (arr Array) firstValue() *Value {
if len(arr.Elements) > 0 {
return &arr.Elements[0]
}
return nil
}
func (arr Array) rangeValues(f func(*Value) bool) bool {
for i := range arr.Elements {
if !f(&arr.Elements[i]) {
return false

// allValues iterates all elements of the array.
func (arr Array) allValues() iter.Seq[*Value] {
return func(yield func(*Value) bool) {
for i := range arr.Elements {
if !yield(&arr.Elements[i]) {
return
}
}
}
return true
}

func (arr Array) lastValue() *Value {
if len(arr.Elements) > 0 {
return &arr.Elements[len(arr.Elements)-1]
}
return nil
}

func (arr *Array) beforeExtraAt(i int) *Extra {
if i < len(arr.Elements) {
return &arr.Elements[i].BeforeExtra
}
return &arr.AfterExtra
}

func (arr *Array) afterExtra() *Extra {
return &arr.AfterExtra
}
Expand All @@ -457,7 +505,7 @@ type composite interface {
length() int

firstValue() *Value
rangeValues(func(*Value) bool) bool
allValues() iter.Seq[*Value]
lastValue() *Value

getAt(int) ValueTrimmed
Expand Down
Loading

0 comments on commit 29efb4a

Please sign in to comment.