mirror of
				https://github.com/SillyTavern/SillyTavern.git
				synced 2025-06-05 21:59:27 +02:00 
			
		
		
		
	
		
			
				
	
	
		
			211 lines
		
	
	
		
			4.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			211 lines
		
	
	
		
			4.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/*
 | 
						|
  # Implementation strategy
 | 
						|
 | 
						|
  Create a tree of `Map`s, such that indexing the tree recursively (with items
 | 
						|
  of a key array, sequentially), traverses the tree, so that when the key array
 | 
						|
  is exhausted, the tree node we arrive at contains the value for that key
 | 
						|
  array under the guaranteed-unique `Symbol` key `dataSymbol`.
 | 
						|
 | 
						|
  ## Example
 | 
						|
 | 
						|
  Start with an empty `ArrayKeyedMap` tree:
 | 
						|
 | 
						|
      {
 | 
						|
      }
 | 
						|
 | 
						|
  Add ['a'] → 1:
 | 
						|
 | 
						|
      {
 | 
						|
        'a': {
 | 
						|
          [dataSymbol]: 1,
 | 
						|
        },
 | 
						|
      }
 | 
						|
 | 
						|
  Add [] → 0:
 | 
						|
 | 
						|
      {
 | 
						|
        [dataSymbol]: 0,
 | 
						|
        'a': {
 | 
						|
          [dataSymbol]: 1,
 | 
						|
        },
 | 
						|
      }
 | 
						|
 | 
						|
  Add ['a', 'b', 'c', 'd'] → 4:
 | 
						|
 | 
						|
      {
 | 
						|
        [dataSymbol]: 0,
 | 
						|
        'a': {
 | 
						|
          [dataSymbol]: 1,
 | 
						|
          'b': {
 | 
						|
            'c': {
 | 
						|
              'd': {
 | 
						|
                [dataSymbol]: 4,
 | 
						|
              },
 | 
						|
            },
 | 
						|
          },
 | 
						|
        },
 | 
						|
      }
 | 
						|
 | 
						|
  String array keys are used in the above example for simplicity.  In reality,
 | 
						|
  we can support any values in array keys, because `Map`s do.
 | 
						|
*/
 | 
						|
 | 
						|
const dataSymbol = Symbol('path-store-trunk')
 | 
						|
 | 
						|
//
 | 
						|
// This class represents the external API
 | 
						|
//
 | 
						|
 | 
						|
class ArrayKeyedMap {
 | 
						|
  constructor (initialEntries = []) {
 | 
						|
    this._root = new Map()
 | 
						|
    this._size = 0
 | 
						|
    for (const [k, v] of initialEntries) { this.set(k, v) }
 | 
						|
  }
 | 
						|
 | 
						|
  set (path, value) { return set.call(this, path, value) }
 | 
						|
 | 
						|
  has (path) { return has.call(this, path) }
 | 
						|
 | 
						|
  get (path) { return get.call(this, path) }
 | 
						|
 | 
						|
  delete (path) { return del.call(this, path) }
 | 
						|
 | 
						|
  get size () { return this._size }
 | 
						|
 | 
						|
  clear () {
 | 
						|
    this._root.clear()
 | 
						|
    this._size = 0
 | 
						|
  }
 | 
						|
 | 
						|
  hasPrefix (path) { return hasPrefix.call(this, path) }
 | 
						|
 | 
						|
  get [Symbol.toStringTag] () { return 'ArrayKeyedMap' }
 | 
						|
 | 
						|
  * [Symbol.iterator] () { yield * entries.call(this) }
 | 
						|
 | 
						|
  * entries () { yield * entries.call(this) }
 | 
						|
 | 
						|
  * keys () { yield * keys.call(this) }
 | 
						|
 | 
						|
  * values () { yield * values.call(this) }
 | 
						|
 | 
						|
  forEach (callback, thisArg) { forEach.call(this, callback, thisArg) }
 | 
						|
}
 | 
						|
 | 
						|
//
 | 
						|
// These stateless functions implement the internals
 | 
						|
//
 | 
						|
 | 
						|
function set (path, value) {
 | 
						|
  let map = this._root
 | 
						|
  for (const item of path) {
 | 
						|
    let nextMap = map.get(item)
 | 
						|
    if (!nextMap) {
 | 
						|
      // Create next map if none exists
 | 
						|
      nextMap = new Map()
 | 
						|
      map.set(item, nextMap)
 | 
						|
    }
 | 
						|
    map = nextMap
 | 
						|
  }
 | 
						|
 | 
						|
  // Reached end of path.  Set the data symbol to the given value, and
 | 
						|
  // increment size if nothing was here before.
 | 
						|
  if (!map.has(dataSymbol)) this._size += 1
 | 
						|
  map.set(dataSymbol, value)
 | 
						|
  return this
 | 
						|
}
 | 
						|
 | 
						|
function has (path) {
 | 
						|
  let map = this._root
 | 
						|
  for (const item of path) {
 | 
						|
    const nextMap = map.get(item)
 | 
						|
    if (nextMap) {
 | 
						|
      map = nextMap
 | 
						|
    } else {
 | 
						|
      return false
 | 
						|
    }
 | 
						|
  }
 | 
						|
  return map.has(dataSymbol)
 | 
						|
}
 | 
						|
 | 
						|
function get (path) {
 | 
						|
  let map = this._root
 | 
						|
  for (const item of path) {
 | 
						|
    map = map.get(item)
 | 
						|
    if (!map) return undefined
 | 
						|
  }
 | 
						|
  return map.get(dataSymbol)
 | 
						|
}
 | 
						|
 | 
						|
function del (path) {
 | 
						|
  let map = this._root
 | 
						|
 | 
						|
  // Maintain a stack of maps we visited, so we can go back and trim empty ones
 | 
						|
  // if we delete something.
 | 
						|
  const stack = []
 | 
						|
 | 
						|
  for (const item of path) {
 | 
						|
    const nextMap = map.get(item)
 | 
						|
    if (nextMap) {
 | 
						|
      stack.unshift({ parent: map, child: nextMap, item })
 | 
						|
      map = nextMap
 | 
						|
    } else {
 | 
						|
      // Nothing to delete
 | 
						|
      return false
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // Reached end of path.  Delete data, if it exists.
 | 
						|
  const hadPreviousValue = map.delete(dataSymbol)
 | 
						|
 | 
						|
  // If something was deleted, decrement size and go through the stack of
 | 
						|
  // visited maps, trimming any that are now empty.
 | 
						|
  if (hadPreviousValue) {
 | 
						|
    this._size -= 1
 | 
						|
 | 
						|
    for (const { parent, child, item } of stack) {
 | 
						|
      if (child.size === 0) {
 | 
						|
        parent.delete(item)
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
  return hadPreviousValue
 | 
						|
}
 | 
						|
 | 
						|
function hasPrefix (path) {
 | 
						|
  let map = this._root
 | 
						|
  for (const item of path) {
 | 
						|
    map = map.get(item)
 | 
						|
    if (!map) return false
 | 
						|
  }
 | 
						|
  return true
 | 
						|
}
 | 
						|
 | 
						|
function * entries () {
 | 
						|
  const stack = [{ path: [], map: this._root }]
 | 
						|
  while (stack.length > 0) {
 | 
						|
    const { path, map } = stack.pop()
 | 
						|
    for (const [k, v] of map.entries()) {
 | 
						|
      if (k === dataSymbol) yield [path, v]
 | 
						|
      else stack.push({ path: path.concat([k]), map: v })
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
function * keys () {
 | 
						|
  for (const [k] of this.entries()) yield k
 | 
						|
}
 | 
						|
 | 
						|
function * values () {
 | 
						|
  for (const [, v] of this.entries()) yield v
 | 
						|
}
 | 
						|
 | 
						|
function forEach (callback, thisArg) {
 | 
						|
  for (const [k, v] of this.entries()) callback.call(thisArg, v, k, this)
 | 
						|
}
 | 
						|
 | 
						|
export {
 | 
						|
    ArrayKeyedMap
 | 
						|
}
 |