vendor tsgo
This commit is contained in:
76
tools/tsgo/internal/collections/cow.go
Normal file
76
tools/tsgo/internal/collections/cow.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package collections
|
||||
|
||||
import "maps"
|
||||
|
||||
// CopyOnWriteMap is a map that defers cloning of an inherited backing map
|
||||
// until the first mutation, and supports nested scopes that share the parent's
|
||||
// map for reads but get their own clone on write.
|
||||
//
|
||||
// The zero value is an empty map ready to use.
|
||||
type CopyOnWriteMap[K comparable, V any] struct {
|
||||
m map[K]V
|
||||
owned bool
|
||||
}
|
||||
|
||||
// Get returns the value for k and whether it was present.
|
||||
func (c *CopyOnWriteMap[K, V]) Get(k K) (V, bool) {
|
||||
v, ok := c.m[k]
|
||||
return v, ok
|
||||
}
|
||||
|
||||
// Has reports whether k is in the map.
|
||||
func (c *CopyOnWriteMap[K, V]) Has(k K) bool {
|
||||
_, ok := c.m[k]
|
||||
return ok
|
||||
}
|
||||
|
||||
// Set assigns v to k, cloning the inherited backing map first if necessary.
|
||||
func (c *CopyOnWriteMap[K, V]) Set(k K, v V) {
|
||||
c.ensureOwned()
|
||||
c.m[k] = v
|
||||
}
|
||||
|
||||
func (c *CopyOnWriteMap[K, V]) ensureOwned() {
|
||||
if c.owned {
|
||||
return
|
||||
}
|
||||
if c.m == nil {
|
||||
c.m = make(map[K]V)
|
||||
} else {
|
||||
c.m = maps.Clone(c.m)
|
||||
}
|
||||
c.owned = true
|
||||
}
|
||||
|
||||
// EnterScope returns a function that restores this map to its current state.
|
||||
// While the scope is active, the map shares its current backing storage with
|
||||
// the parent scope: reads see the inherited entries, and the first mutation
|
||||
// transparently clones the storage so the parent's view is not modified.
|
||||
func (c *CopyOnWriteMap[K, V]) EnterScope() func() {
|
||||
saved := *c
|
||||
c.owned = false
|
||||
return func() { *c = saved }
|
||||
}
|
||||
|
||||
type CopyOnWriteSet[K comparable] struct {
|
||||
m CopyOnWriteMap[K, struct{}]
|
||||
}
|
||||
|
||||
// Has reports whether k is in the set.
|
||||
func (c *CopyOnWriteSet[K]) Has(k K) bool {
|
||||
_, ok := c.m.Get(k)
|
||||
return ok
|
||||
}
|
||||
|
||||
// Set adds k to the set, cloning the inherited backing map first if necessary.
|
||||
func (c *CopyOnWriteSet[K]) Add(k K) {
|
||||
c.m.Set(k, struct{}{})
|
||||
}
|
||||
|
||||
// EnterScope returns a function that restores this set to its current state.
|
||||
// While the scope is active, the set shares its current backing storage with
|
||||
// the parent scope: reads see the inherited entries, and the first mutation
|
||||
// transparently clones the storage so the parent's view is not modified.
|
||||
func (c *CopyOnWriteSet[K]) EnterScope() func() {
|
||||
return c.m.EnterScope()
|
||||
}
|
||||
75
tools/tsgo/internal/collections/multimap.go
Normal file
75
tools/tsgo/internal/collections/multimap.go
Normal file
@@ -0,0 +1,75 @@
|
||||
package collections
|
||||
|
||||
import (
|
||||
"iter"
|
||||
"maps"
|
||||
"slices"
|
||||
)
|
||||
|
||||
type MultiMap[K comparable, V comparable] struct {
|
||||
M map[K][]V
|
||||
}
|
||||
|
||||
func NewMultiMapWithSizeHint[K comparable, V comparable](hint int) *MultiMap[K, V] {
|
||||
return &MultiMap[K, V]{
|
||||
M: make(map[K][]V, hint),
|
||||
}
|
||||
}
|
||||
|
||||
func GroupBy[K comparable, V comparable](items []V, groupId func(V) K) *MultiMap[K, V] {
|
||||
m := &MultiMap[K, V]{}
|
||||
for _, item := range items {
|
||||
m.Add(groupId(item), item)
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func (s *MultiMap[K, V]) Has(key K) bool {
|
||||
_, ok := s.M[key]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (s *MultiMap[K, V]) Get(key K) []V {
|
||||
return s.M[key]
|
||||
}
|
||||
|
||||
func (s *MultiMap[K, V]) Add(key K, value V) {
|
||||
if s.M == nil {
|
||||
s.M = make(map[K][]V)
|
||||
}
|
||||
s.M[key] = append(s.M[key], value)
|
||||
}
|
||||
|
||||
func (s *MultiMap[K, V]) Remove(key K, value V) {
|
||||
if values, ok := s.M[key]; ok {
|
||||
i := slices.Index(values, value)
|
||||
if i >= 0 {
|
||||
if len(values) == 1 {
|
||||
delete(s.M, key)
|
||||
} else {
|
||||
values = append(values[:i], values[i+1:]...)
|
||||
s.M[key] = values
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *MultiMap[K, V]) RemoveAll(key K) {
|
||||
delete(s.M, key)
|
||||
}
|
||||
|
||||
func (s *MultiMap[K, V]) Len() int {
|
||||
return len(s.M)
|
||||
}
|
||||
|
||||
func (s *MultiMap[K, V]) Keys() iter.Seq[K] {
|
||||
return maps.Keys(s.M)
|
||||
}
|
||||
|
||||
func (s *MultiMap[K, V]) Values() iter.Seq[[]V] {
|
||||
return maps.Values(s.M)
|
||||
}
|
||||
|
||||
func (s *MultiMap[K, V]) Clear() {
|
||||
clear(s.M)
|
||||
}
|
||||
316
tools/tsgo/internal/collections/ordered_map.go
Normal file
316
tools/tsgo/internal/collections/ordered_map.go
Normal file
@@ -0,0 +1,316 @@
|
||||
package collections
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"errors"
|
||||
"iter"
|
||||
"maps"
|
||||
"reflect"
|
||||
"slices"
|
||||
"strconv"
|
||||
|
||||
"github.com/microsoft/typescript-go/internal/json"
|
||||
)
|
||||
|
||||
// OrderedMap is an insertion ordered map.
|
||||
type OrderedMap[K comparable, V any] struct {
|
||||
_ noCopy
|
||||
keys []K
|
||||
mp map[K]V
|
||||
}
|
||||
|
||||
// noCopy may be embedded into structs which must not be copied
|
||||
// after the first use.
|
||||
//
|
||||
// See https://golang.org/issues/8005#issuecomment-190753527
|
||||
// for details.
|
||||
type noCopy struct{}
|
||||
|
||||
// Lock is a no-op used by -copylocks checker from `go vet`.
|
||||
func (*noCopy) Lock() {}
|
||||
func (*noCopy) Unlock() {}
|
||||
|
||||
// NewOrderedMapWithSizeHint creates a new OrderedMap with a hint for the number of elements it will contain.
|
||||
func NewOrderedMapWithSizeHint[K comparable, V any](hint int) *OrderedMap[K, V] {
|
||||
m := newMapWithSizeHint[K, V](hint)
|
||||
return &m
|
||||
}
|
||||
|
||||
func newMapWithSizeHint[K comparable, V any](hint int) OrderedMap[K, V] {
|
||||
return OrderedMap[K, V]{
|
||||
keys: make([]K, 0, hint),
|
||||
mp: make(map[K]V, hint),
|
||||
}
|
||||
}
|
||||
|
||||
type MapEntry[K comparable, V any] struct {
|
||||
Key K
|
||||
Value V
|
||||
}
|
||||
|
||||
func NewOrderedMapFromList[K comparable, V any](items []MapEntry[K, V]) *OrderedMap[K, V] {
|
||||
mp := NewOrderedMapWithSizeHint[K, V](len(items))
|
||||
for _, item := range items {
|
||||
mp.Set(item.Key, item.Value)
|
||||
}
|
||||
return mp
|
||||
}
|
||||
|
||||
// Set sets a key-value pair in the map.
|
||||
func (m *OrderedMap[K, V]) Set(key K, value V) {
|
||||
if m.mp == nil {
|
||||
m.mp = make(map[K]V)
|
||||
}
|
||||
|
||||
if _, ok := m.mp[key]; !ok {
|
||||
m.keys = append(m.keys, key)
|
||||
}
|
||||
m.mp[key] = value
|
||||
}
|
||||
|
||||
// Get retrieves a value from the map.
|
||||
func (m *OrderedMap[K, V]) Get(key K) (V, bool) {
|
||||
v, ok := m.mp[key]
|
||||
return v, ok
|
||||
}
|
||||
|
||||
// GetOrZero retrieves a value from the map, or returns the zero value of the value type if the key is not present.
|
||||
func (m *OrderedMap[K, V]) GetOrZero(key K) V {
|
||||
return m.mp[key]
|
||||
}
|
||||
|
||||
// EntryAt retrieves the key-value pair at the specified index.
|
||||
func (m *OrderedMap[K, V]) EntryAt(index int) (K, V, bool) {
|
||||
if index < 0 || index >= len(m.keys) {
|
||||
var zero K
|
||||
var zeroV V
|
||||
return zero, zeroV, false
|
||||
}
|
||||
|
||||
key := m.keys[index]
|
||||
value := m.mp[key]
|
||||
return key, value, true
|
||||
}
|
||||
|
||||
// Has returns true if the map contains the key.
|
||||
func (m *OrderedMap[K, V]) Has(key K) bool {
|
||||
_, ok := m.mp[key]
|
||||
return ok
|
||||
}
|
||||
|
||||
// Delete removes a key-value pair from the map.
|
||||
func (m *OrderedMap[K, V]) Delete(key K) (V, bool) {
|
||||
v, ok := m.mp[key]
|
||||
if !ok {
|
||||
var zero V
|
||||
return zero, false
|
||||
}
|
||||
|
||||
delete(m.mp, key)
|
||||
i := slices.Index(m.keys, key)
|
||||
// If we're just removing the first or last element, avoid shifting everything around.
|
||||
if i == 0 {
|
||||
var zero K
|
||||
m.keys[0] = zero
|
||||
m.keys = m.keys[1:]
|
||||
} else if end := len(m.keys) - 1; i == end {
|
||||
var zero K
|
||||
m.keys[end] = zero
|
||||
m.keys = m.keys[:end]
|
||||
} else {
|
||||
m.keys = slices.Delete(m.keys, i, i+1)
|
||||
}
|
||||
|
||||
return v, true
|
||||
}
|
||||
|
||||
// Keys returns an iterator over the keys in the map.
|
||||
// A slice of the keys can be obtained by calling `slices.Collect`.
|
||||
func (m *OrderedMap[K, V]) Keys() iter.Seq[K] {
|
||||
return func(yield func(K) bool) {
|
||||
if m == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// We use a for loop here to ensure we enumerate new items added during iteration.
|
||||
//nolint:intrange
|
||||
for i := 0; i < len(m.keys); i++ {
|
||||
if !yield(m.keys[i]) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Values returns an iterator over the values in the map.
|
||||
// A slice of the values can be obtained by calling `slices.Collect`.
|
||||
func (m *OrderedMap[K, V]) Values() iter.Seq[V] {
|
||||
return func(yield func(V) bool) {
|
||||
if m == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// We use a for loop here to ensure we enumerate new items added during iteration.
|
||||
//nolint:intrange
|
||||
for i := 0; i < len(m.keys); i++ {
|
||||
if !yield(m.mp[m.keys[i]]) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Entries returns an iterator over the key-value pairs in the map.
|
||||
func (m *OrderedMap[K, V]) Entries() iter.Seq2[K, V] {
|
||||
return func(yield func(K, V) bool) {
|
||||
if m == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// We use a for loop here to ensure we enumerate new items added during iteration.
|
||||
//nolint:intrange
|
||||
for i := 0; i < len(m.keys); i++ {
|
||||
key := m.keys[i]
|
||||
if !yield(key, m.mp[key]) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clear removes all key-value pairs from the map.
|
||||
// The space allocated for the map will be reused.
|
||||
func (m *OrderedMap[K, V]) Clear() {
|
||||
clear(m.keys)
|
||||
m.keys = m.keys[:0]
|
||||
clear(m.mp)
|
||||
}
|
||||
|
||||
// Size returns the number of key-value pairs in the map.
|
||||
func (m *OrderedMap[K, V]) Size() int {
|
||||
if m == nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
return len(m.keys)
|
||||
}
|
||||
|
||||
// Clone returns a shallow copy of the map.
|
||||
func (m *OrderedMap[K, V]) Clone() *OrderedMap[K, V] {
|
||||
if m == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
m2 := m.clone()
|
||||
return &m2
|
||||
}
|
||||
|
||||
func (m *OrderedMap[K, V]) clone() OrderedMap[K, V] {
|
||||
return OrderedMap[K, V]{
|
||||
keys: slices.Clone(m.keys),
|
||||
mp: maps.Clone(m.mp),
|
||||
}
|
||||
}
|
||||
|
||||
var _ json.MarshalerTo = (*OrderedMap[string, string])(nil)
|
||||
|
||||
func (m *OrderedMap[K, V]) MarshalJSONTo(enc *json.Encoder) error {
|
||||
if err := enc.WriteToken(json.BeginObject); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, k := range m.keys {
|
||||
// TODO: is this needed? Can we just MarshalEncode k directly?
|
||||
keyString, err := resolveKeyName(reflect.ValueOf(k))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := json.MarshalEncode(enc, keyString); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := json.MarshalEncode(enc, m.mp[k]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return enc.WriteToken(json.EndObject)
|
||||
}
|
||||
|
||||
func resolveKeyName(k reflect.Value) (string, error) {
|
||||
if k.Kind() == reflect.String {
|
||||
return k.String(), nil
|
||||
}
|
||||
if tm, ok := reflect.TypeAssert[encoding.TextMarshaler](k); ok {
|
||||
if k.Kind() == reflect.Pointer && k.IsNil() {
|
||||
return "", nil
|
||||
}
|
||||
buf, err := tm.MarshalText()
|
||||
return string(buf), err
|
||||
}
|
||||
switch k.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return strconv.FormatInt(k.Int(), 10), nil
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
return strconv.FormatUint(k.Uint(), 10), nil
|
||||
}
|
||||
panic("unexpected map key type")
|
||||
}
|
||||
|
||||
var _ json.UnmarshalerFrom = (*OrderedMap[string, string])(nil)
|
||||
|
||||
func (m *OrderedMap[K, V]) UnmarshalJSONFrom(dec *json.Decoder) error {
|
||||
token, err := dec.ReadToken()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if token.Kind() == 'n' { // json.Null.Kind()
|
||||
// By convention, to approximate the behavior of Unmarshal itself,
|
||||
// Unmarshalers implement UnmarshalJSON([]byte("null")) as a no-op.
|
||||
// https://pkg.go.dev/encoding/json#Unmarshaler
|
||||
// TODO: reconsider
|
||||
return nil
|
||||
}
|
||||
if token.Kind() != '{' { // json.ObjectStart.Kind()
|
||||
return errors.New("cannot unmarshal non-object JSON value into Map")
|
||||
}
|
||||
for dec.PeekKind() != '}' { // json.ObjectEnd.Kind()
|
||||
var key K
|
||||
var value V
|
||||
if err := json.UnmarshalDecode(dec, &key); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := json.UnmarshalDecode(dec, &value); err != nil {
|
||||
return err
|
||||
}
|
||||
m.Set(key, value)
|
||||
}
|
||||
if _, err := dec.ReadToken(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func DiffOrderedMaps[K comparable, V comparable](m1 *OrderedMap[K, V], m2 *OrderedMap[K, V], onAdded func(key K, value V), onRemoved func(key K, value V), onModified func(key K, oldValue V, newValue V)) {
|
||||
DiffOrderedMapsFunc(m1, m2, func(a, b V) bool {
|
||||
return a == b
|
||||
}, onAdded, onRemoved, onModified)
|
||||
}
|
||||
|
||||
func DiffOrderedMapsFunc[K comparable, V any](m1 *OrderedMap[K, V], m2 *OrderedMap[K, V], equalValues func(a, b V) bool, onAdded func(key K, value V), onRemoved func(key K, value V), onModified func(key K, oldValue V, newValue V)) {
|
||||
for k, v2 := range m2.Entries() {
|
||||
if _, ok := m1.Get(k); !ok {
|
||||
onAdded(k, v2)
|
||||
}
|
||||
}
|
||||
for k, v1 := range m1.Entries() {
|
||||
if v2, ok := m2.Get(k); ok {
|
||||
if !equalValues(v1, v2) {
|
||||
onModified(k, v1, v2)
|
||||
}
|
||||
} else {
|
||||
onRemoved(k, v1)
|
||||
}
|
||||
}
|
||||
}
|
||||
187
tools/tsgo/internal/collections/ordered_map_test.go
Normal file
187
tools/tsgo/internal/collections/ordered_map_test.go
Normal file
@@ -0,0 +1,187 @@
|
||||
package collections_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"github.com/microsoft/typescript-go/internal/collections"
|
||||
"github.com/microsoft/typescript-go/internal/json"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestOrderedMap(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var m collections.OrderedMap[int, string]
|
||||
|
||||
assert.Assert(t, !m.Has(1))
|
||||
|
||||
const (
|
||||
N = 1000
|
||||
start = 1
|
||||
end = start + N
|
||||
)
|
||||
|
||||
// Seed the map with ascending keys and values for easier testing.
|
||||
for i := start; i < end; i++ {
|
||||
m.Set(i, padInt(i))
|
||||
}
|
||||
|
||||
assert.Equal(t, m.Size(), N)
|
||||
|
||||
// Attempt to overwrite existing keys in reverse order.
|
||||
for i := end - 1; i >= start; i-- {
|
||||
m.Set(i, padInt(i))
|
||||
}
|
||||
|
||||
assert.Equal(t, m.Size(), N)
|
||||
|
||||
for i := start; i < end; i++ {
|
||||
v, ok := m.Get(i)
|
||||
assert.Assert(t, ok)
|
||||
assert.Equal(t, v, padInt(i))
|
||||
}
|
||||
|
||||
for k, v := range m.Entries() {
|
||||
assert.Equal(t, v, padInt(k))
|
||||
}
|
||||
|
||||
keys := slices.Collect(m.Keys())
|
||||
assert.Equal(t, len(keys), N)
|
||||
assert.Assert(t, slices.IsSorted(keys))
|
||||
|
||||
values := slices.Collect(m.Values())
|
||||
assert.Equal(t, len(values), N)
|
||||
assert.Assert(t, slices.IsSorted(values))
|
||||
|
||||
var firstKey int
|
||||
for k := range m.Keys() {
|
||||
firstKey = k
|
||||
break
|
||||
}
|
||||
assert.Equal(t, firstKey, start)
|
||||
|
||||
var firstValue string
|
||||
for v := range m.Values() {
|
||||
firstValue = v
|
||||
break
|
||||
}
|
||||
assert.Equal(t, firstValue, padInt(start))
|
||||
|
||||
for k, v := range m.Entries() {
|
||||
firstKey = k
|
||||
firstValue = v
|
||||
break
|
||||
}
|
||||
|
||||
assert.Equal(t, firstKey, start)
|
||||
assert.Equal(t, firstValue, padInt(start))
|
||||
|
||||
for i := start + 1; i < end; i++ {
|
||||
v, ok := m.Delete(i)
|
||||
assert.Assert(t, ok)
|
||||
assert.Equal(t, v, padInt(i))
|
||||
assert.Assert(t, !m.Has(i))
|
||||
|
||||
v, ok = m.Get(i)
|
||||
assert.Assert(t, !ok)
|
||||
assert.Equal(t, v, "")
|
||||
|
||||
v, ok = m.Delete(i)
|
||||
assert.Assert(t, !ok)
|
||||
assert.Equal(t, v, "")
|
||||
}
|
||||
|
||||
assert.Equal(t, m.Size(), 1)
|
||||
assert.Assert(t, m.Has(start))
|
||||
|
||||
v, ok := m.Delete(start)
|
||||
assert.Assert(t, ok)
|
||||
assert.Equal(t, v, padInt(start))
|
||||
|
||||
assert.Equal(t, m.Size(), 0)
|
||||
}
|
||||
|
||||
func TestOrderedMapClone(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
m := &collections.OrderedMap[int, string]{}
|
||||
m.Set(1, "one")
|
||||
m.Set(2, "two")
|
||||
|
||||
clone := m.Clone()
|
||||
|
||||
assert.Assert(t, clone != m)
|
||||
assert.Equal(t, clone.Size(), 2)
|
||||
assert.DeepEqual(t, slices.Collect(clone.Keys()), []int{1, 2})
|
||||
assert.DeepEqual(t, slices.Collect(clone.Values()), []string{"one", "two"})
|
||||
|
||||
v, ok := clone.Get(1)
|
||||
assert.Assert(t, ok)
|
||||
assert.Equal(t, v, "one")
|
||||
|
||||
m.Delete(1)
|
||||
|
||||
assert.Equal(t, m.Size(), 1)
|
||||
assert.Equal(t, clone.Size(), 2)
|
||||
assert.DeepEqual(t, slices.Collect(clone.Keys()), []int{1, 2})
|
||||
assert.DeepEqual(t, slices.Collect(clone.Values()), []string{"one", "two"})
|
||||
}
|
||||
|
||||
func TestOrderedMapClear(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var m collections.OrderedMap[int, string]
|
||||
m.Set(1, "one")
|
||||
m.Set(2, "two")
|
||||
|
||||
m.Clear()
|
||||
|
||||
assert.Equal(t, m.Size(), 0)
|
||||
}
|
||||
|
||||
func padInt(n int) string {
|
||||
return fmt.Sprintf("%10d", n)
|
||||
}
|
||||
|
||||
func TestOrderedMapWithSizeHint(t *testing.T) { //nolint:paralleltest
|
||||
const N = 1024
|
||||
|
||||
allocs := testing.AllocsPerRun(10, func() {
|
||||
m := collections.NewOrderedMapWithSizeHint[int, int](N)
|
||||
for i := range N {
|
||||
m.Set(i, i)
|
||||
}
|
||||
})
|
||||
|
||||
assert.Assert(t, allocs < 10, "allocs = %v", allocs)
|
||||
}
|
||||
|
||||
func TestOrderedMapUnmarshalJSON(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("UnmarshalJSONV2", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
testOrderedMapUnmarshalJSON(t, func(in []byte, out any) error { return json.Unmarshal(in, out) })
|
||||
})
|
||||
}
|
||||
|
||||
func testOrderedMapUnmarshalJSON(t *testing.T, unmarshal func([]byte, any) error) {
|
||||
var m collections.OrderedMap[string, any]
|
||||
err := unmarshal([]byte(`{"a": 1, "b": "two", "c": { "d": 4 } }`), &m)
|
||||
assert.NilError(t, err)
|
||||
|
||||
assert.Equal(t, m.Size(), 3)
|
||||
assert.Equal(t, m.GetOrZero("a"), float64(1))
|
||||
|
||||
err = unmarshal([]byte(`null`), &m)
|
||||
assert.NilError(t, err)
|
||||
|
||||
err = unmarshal([]byte(`"foo"`), &m)
|
||||
assert.ErrorContains(t, err, "cannot unmarshal non-object JSON value into Map")
|
||||
|
||||
var invalidMap collections.OrderedMap[int, any]
|
||||
err = unmarshal([]byte(`{"a": 1, "b": "two"}`), &invalidMap)
|
||||
assert.ErrorContains(t, err, "unmarshal")
|
||||
}
|
||||
54
tools/tsgo/internal/collections/ordered_set.go
Normal file
54
tools/tsgo/internal/collections/ordered_set.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package collections
|
||||
|
||||
import "iter"
|
||||
|
||||
// OrderedSet an insertion ordered set.
|
||||
type OrderedSet[T comparable] struct {
|
||||
m OrderedMap[T, struct{}]
|
||||
}
|
||||
|
||||
// NewOrderedSetWithSizeHint creates a new OrderedSet with a hint for the number of elements it will contain.
|
||||
func NewOrderedSetWithSizeHint[T comparable](hint int) *OrderedSet[T] {
|
||||
return &OrderedSet[T]{
|
||||
m: newMapWithSizeHint[T, struct{}](hint),
|
||||
}
|
||||
}
|
||||
|
||||
// Add adds a value to the set.
|
||||
func (s *OrderedSet[T]) Add(value T) {
|
||||
s.m.Set(value, struct{}{})
|
||||
}
|
||||
|
||||
// Has returns true if the set contains the value.
|
||||
func (s *OrderedSet[T]) Has(value T) bool {
|
||||
return s.m.Has(value)
|
||||
}
|
||||
|
||||
// Delete removes a value from the set.
|
||||
func (s *OrderedSet[T]) Delete(value T) bool {
|
||||
_, ok := s.m.Delete(value)
|
||||
return ok
|
||||
}
|
||||
|
||||
// Values returns an iterator over the values in the set.
|
||||
func (s *OrderedSet[T]) Values() iter.Seq[T] {
|
||||
return s.m.Keys()
|
||||
}
|
||||
|
||||
// Clear removes all elements from the set.
|
||||
// The space allocated for the set will be reused.
|
||||
func (s *OrderedSet[T]) Clear() {
|
||||
s.m.Clear()
|
||||
}
|
||||
|
||||
// Size returns the number of elements in the set.
|
||||
func (s *OrderedSet[T]) Size() int {
|
||||
return s.m.Size()
|
||||
}
|
||||
|
||||
// Clone returns a shallow copy of the set.
|
||||
func (s *OrderedSet[T]) Clone() *OrderedSet[T] {
|
||||
return &OrderedSet[T]{
|
||||
m: s.m.clone(),
|
||||
}
|
||||
}
|
||||
53
tools/tsgo/internal/collections/ordered_set_test.go
Normal file
53
tools/tsgo/internal/collections/ordered_set_test.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package collections_test
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"github.com/microsoft/typescript-go/internal/collections"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestOrderedSet(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
s := &collections.OrderedSet[int]{}
|
||||
|
||||
s.Add(1)
|
||||
s.Add(2)
|
||||
s.Add(3)
|
||||
|
||||
assert.Assert(t, s.Has(1))
|
||||
assert.Assert(t, s.Has(2))
|
||||
assert.Assert(t, s.Has(3))
|
||||
|
||||
assert.Assert(t, s.Delete(2))
|
||||
|
||||
values := slices.Collect(s.Values())
|
||||
assert.Equal(t, len(values), 2)
|
||||
assert.Assert(t, slices.IsSorted(values))
|
||||
|
||||
s.Clear()
|
||||
|
||||
assert.Equal(t, s.Size(), 0)
|
||||
assert.Assert(t, !s.Has(1))
|
||||
assert.Assert(t, !s.Has(2))
|
||||
assert.Assert(t, !s.Has(3))
|
||||
|
||||
s2 := s.Clone()
|
||||
assert.Assert(t, s != s2)
|
||||
assert.Equal(t, s2.Size(), 0)
|
||||
}
|
||||
|
||||
func TestOrderedSetWithSizeHint(t *testing.T) { //nolint:paralleltest
|
||||
const N = 1024
|
||||
|
||||
allocs := testing.AllocsPerRun(10, func() {
|
||||
m := collections.NewOrderedSetWithSizeHint[int](N)
|
||||
for i := range N {
|
||||
m.Add(i)
|
||||
}
|
||||
})
|
||||
|
||||
assert.Assert(t, allocs < 10, "allocs = %v", allocs)
|
||||
}
|
||||
144
tools/tsgo/internal/collections/set.go
Normal file
144
tools/tsgo/internal/collections/set.go
Normal file
@@ -0,0 +1,144 @@
|
||||
package collections
|
||||
|
||||
import "maps"
|
||||
|
||||
type Set[T comparable] struct {
|
||||
M map[T]struct{}
|
||||
}
|
||||
|
||||
// NewSetWithSizeHint creates a new Set with a hint for the number of elements it will contain.
|
||||
func NewSetWithSizeHint[T comparable](hint int) *Set[T] {
|
||||
return &Set[T]{
|
||||
M: make(map[T]struct{}, hint),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Set[T]) Has(key T) bool {
|
||||
if s == nil {
|
||||
return false
|
||||
}
|
||||
_, ok := s.M[key]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (s *Set[T]) Add(key T) {
|
||||
if s.M == nil {
|
||||
s.M = make(map[T]struct{})
|
||||
}
|
||||
s.M[key] = struct{}{}
|
||||
}
|
||||
|
||||
func (s *Set[T]) Delete(key T) {
|
||||
delete(s.M, key)
|
||||
}
|
||||
|
||||
func (s *Set[T]) Len() int {
|
||||
if s == nil {
|
||||
return 0
|
||||
}
|
||||
return len(s.M)
|
||||
}
|
||||
|
||||
func (s *Set[T]) Keys() map[T]struct{} {
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
return s.M
|
||||
}
|
||||
|
||||
func (s *Set[T]) Clear() {
|
||||
if s == nil {
|
||||
return
|
||||
}
|
||||
clear(s.M)
|
||||
}
|
||||
|
||||
// Returns true if the key was not already present in the set.
|
||||
func (s *Set[T]) AddIfAbsent(key T) bool {
|
||||
if s.Has(key) {
|
||||
return false
|
||||
}
|
||||
s.Add(key)
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *Set[T]) Clone() *Set[T] {
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
clone := &Set[T]{M: maps.Clone(s.M)}
|
||||
return clone
|
||||
}
|
||||
|
||||
func (s *Set[T]) Union(other *Set[T]) {
|
||||
if s.Len() == 0 && other.Len() == 0 {
|
||||
return
|
||||
}
|
||||
if s == nil {
|
||||
panic("cannot modify nil Set")
|
||||
}
|
||||
if s.M == nil {
|
||||
s.M = maps.Clone(other.M)
|
||||
return
|
||||
}
|
||||
maps.Copy(s.M, other.M)
|
||||
}
|
||||
|
||||
func (s *Set[T]) UnionedWith(other *Set[T]) *Set[T] {
|
||||
if s == nil && other == nil {
|
||||
return nil
|
||||
}
|
||||
result := s.Clone()
|
||||
if other != nil {
|
||||
if result == nil {
|
||||
result = &Set[T]{}
|
||||
}
|
||||
if result.M == nil {
|
||||
result.M = make(map[T]struct{}, len(other.M))
|
||||
}
|
||||
maps.Copy(result.M, other.M)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (s *Set[T]) Equals(other *Set[T]) bool {
|
||||
if s == other {
|
||||
return true
|
||||
}
|
||||
if s == nil || other == nil {
|
||||
return false
|
||||
}
|
||||
return maps.Equal(s.M, other.M)
|
||||
}
|
||||
|
||||
func (s *Set[T]) IsSubsetOf(other *Set[T]) bool {
|
||||
if s == nil {
|
||||
return true
|
||||
}
|
||||
for key := range s.M {
|
||||
if !other.Has(key) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *Set[T]) Intersects(other *Set[T]) bool {
|
||||
if s == nil || other == nil {
|
||||
return false
|
||||
}
|
||||
for key := range s.M {
|
||||
if other.Has(key) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func NewSetFromItems[T comparable](items ...T) *Set[T] {
|
||||
s := &Set[T]{}
|
||||
for _, item := range items {
|
||||
s.Add(item)
|
||||
}
|
||||
return s
|
||||
}
|
||||
98
tools/tsgo/internal/collections/syncmap.go
Normal file
98
tools/tsgo/internal/collections/syncmap.go
Normal file
@@ -0,0 +1,98 @@
|
||||
package collections
|
||||
|
||||
import (
|
||||
"iter"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type SyncMap[K comparable, V any] struct {
|
||||
_ [0]K
|
||||
_ [0]V
|
||||
m sync.Map
|
||||
}
|
||||
|
||||
func (s *SyncMap[K, V]) Load(key K) (value V, ok bool) {
|
||||
val, ok := s.m.Load(key)
|
||||
if !ok || val == nil {
|
||||
return value, ok
|
||||
}
|
||||
return val.(V), true
|
||||
}
|
||||
|
||||
func (s *SyncMap[K, V]) Store(key K, value V) {
|
||||
s.m.Store(key, value)
|
||||
}
|
||||
|
||||
func (s *SyncMap[K, V]) LoadOrStore(key K, value V) (actual V, loaded bool) {
|
||||
actualAny, loaded := s.m.LoadOrStore(key, value)
|
||||
if actualAny == nil {
|
||||
return actual, loaded
|
||||
}
|
||||
|
||||
return actualAny.(V), loaded
|
||||
}
|
||||
|
||||
func (s *SyncMap[K, V]) Delete(key K) {
|
||||
s.m.Delete(key)
|
||||
}
|
||||
|
||||
func (s *SyncMap[K, V]) Clear() {
|
||||
s.m.Clear()
|
||||
}
|
||||
|
||||
func (s *SyncMap[K, V]) Range(f func(key K, value V) bool) {
|
||||
s.m.Range(func(key, value any) bool {
|
||||
var k K
|
||||
if key != nil {
|
||||
k = key.(K)
|
||||
}
|
||||
|
||||
var v V
|
||||
if value != nil {
|
||||
v = value.(V)
|
||||
}
|
||||
|
||||
return f(k, v)
|
||||
})
|
||||
}
|
||||
|
||||
// Size returns the approximate number of items in the map.
|
||||
// Note that this is not a precise count, as the map may be modified
|
||||
// concurrently while this method is running.
|
||||
func (s *SyncMap[K, V]) Size() int {
|
||||
count := 0
|
||||
s.m.Range(func(_, _ any) bool {
|
||||
count++
|
||||
return true
|
||||
})
|
||||
return count
|
||||
}
|
||||
|
||||
func (s *SyncMap[K, V]) ToMap() map[K]V {
|
||||
m := make(map[K]V, s.Size())
|
||||
s.m.Range(func(key, value any) bool {
|
||||
m[key.(K)] = value.(V)
|
||||
return true
|
||||
})
|
||||
return m
|
||||
}
|
||||
|
||||
func (s *SyncMap[K, V]) Keys() iter.Seq[K] {
|
||||
return func(yield func(K) bool) {
|
||||
s.m.Range(func(key, value any) bool {
|
||||
if !yield(key.(K)) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SyncMap[K, V]) Clone() *SyncMap[K, V] {
|
||||
clone := &SyncMap[K, V]{}
|
||||
s.m.Range(func(key, value any) bool {
|
||||
clone.m.Store(key, value)
|
||||
return true
|
||||
})
|
||||
return clone
|
||||
}
|
||||
32
tools/tsgo/internal/collections/syncmap_test.go
Normal file
32
tools/tsgo/internal/collections/syncmap_test.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package collections_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/microsoft/typescript-go/internal/collections"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestSyncMapWithNil(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var m collections.SyncMap[string, any]
|
||||
|
||||
got1, ok := m.Load("foo")
|
||||
assert.Assert(t, !ok)
|
||||
assert.Equal(t, got1, nil)
|
||||
|
||||
m.Store("foo", nil)
|
||||
|
||||
got2, ok := m.Load("foo")
|
||||
assert.Assert(t, ok)
|
||||
assert.Equal(t, got2, nil)
|
||||
|
||||
too, loaded := m.LoadOrStore("too", nil)
|
||||
assert.Assert(t, !loaded)
|
||||
assert.Equal(t, too, nil)
|
||||
|
||||
m.Range(func(k string, v any) bool {
|
||||
return true
|
||||
})
|
||||
}
|
||||
77
tools/tsgo/internal/collections/syncset.go
Normal file
77
tools/tsgo/internal/collections/syncset.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package collections
|
||||
|
||||
import "iter"
|
||||
|
||||
type SyncSet[T comparable] struct {
|
||||
m SyncMap[T, struct{}]
|
||||
}
|
||||
|
||||
func (s *SyncSet[T]) Has(key T) bool {
|
||||
_, ok := s.m.Load(key)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (s *SyncSet[T]) Add(key T) {
|
||||
s.AddIfAbsent(key)
|
||||
}
|
||||
|
||||
// AddIfAbsent adds the key to the set if it is not already present
|
||||
// using LoadOrStore. It returns true if the key was not already present
|
||||
// (opposite of the return value of LoadOrStore).
|
||||
func (s *SyncSet[T]) AddIfAbsent(key T) bool {
|
||||
_, loaded := s.m.LoadOrStore(key, struct{}{})
|
||||
return !loaded
|
||||
}
|
||||
|
||||
func (s *SyncSet[T]) Delete(key T) {
|
||||
s.m.Delete(key)
|
||||
}
|
||||
|
||||
func (s *SyncSet[T]) Range(fn func(key T) bool) {
|
||||
s.m.Range(func(key T, value struct{}) bool {
|
||||
return fn(key)
|
||||
})
|
||||
}
|
||||
|
||||
// Size returns the approximate number of items in the map.
|
||||
// Note that this is not a precise count, as the map may be modified
|
||||
// concurrently while this method is running.
|
||||
func (s *SyncSet[T]) Size() int {
|
||||
count := 0
|
||||
s.m.Range(func(_ T, _ struct{}) bool {
|
||||
count++
|
||||
return true
|
||||
})
|
||||
return count
|
||||
}
|
||||
|
||||
func (s *SyncSet[T]) IsEmpty() bool {
|
||||
empty := true
|
||||
s.m.Range(func(_ T, _ struct{}) bool {
|
||||
empty = false
|
||||
return false
|
||||
})
|
||||
|
||||
return empty
|
||||
}
|
||||
|
||||
func (s *SyncSet[T]) ToSlice() []T {
|
||||
var arr []T
|
||||
arr = make([]T, 0, s.m.Size())
|
||||
s.m.Range(func(key T, value struct{}) bool {
|
||||
arr = append(arr, key)
|
||||
return true
|
||||
})
|
||||
return arr
|
||||
}
|
||||
|
||||
func (s *SyncSet[T]) Keys() iter.Seq[T] {
|
||||
return func(yield func(T) bool) {
|
||||
s.m.Range(func(key T, value struct{}) bool {
|
||||
if !yield(key) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user