From dbd4c572df48940c97cc2cf7dfb6751d5206639f Mon Sep 17 00:00:00 2001 From: Erik Arvidsson Date: Fri, 27 May 2016 14:49:22 -0700 Subject: [PATCH] Add ListBuilder (#1669) A ListBuilder allows creating large lists efficiently --- js/package.json | 2 +- js/src/list-test.js | 53 +++++++++++++++++++++++++++++++++++++- js/src/list.js | 46 +++++++++++++++++++++++++++++++-- js/src/noms.js | 2 +- js/src/sequence-chunker.js | 2 +- 5 files changed, 99 insertions(+), 6 deletions(-) diff --git a/js/package.json b/js/package.json index 22530dee47..fdcfaf4e16 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "@attic/noms", - "version": "41.4.0", + "version": "41.5.0", "description": "Noms JS SDK", "repository": "https://github.com/attic-labs/noms", "main": "dist/commonjs/noms.js", diff --git a/js/src/list-test.js b/js/src/list-test.js index 07389ab739..abd02c74ae 100644 --- a/js/src/list-test.js +++ b/js/src/list-test.js @@ -26,7 +26,7 @@ import { } from './test-util.js'; import {MetaTuple, newListMetaSequence} from './meta-sequence.js'; import {invariant, notNull} from './assert.js'; -import List from './list.js'; +import List, {ListWriter} from './list.js'; import {equals} from './compare.js'; const testListSize = 5000; @@ -492,4 +492,55 @@ suite('Diff List', () => { assert.strictEqual('Load limit exceeded', exMessage); }); + + test('ListWriter', async () => { + const values = intSequence(15); + const l = new List(values); + + const w = new ListWriter(); + for (let i = 0; i < values.length; i++) { + w.write(values[i]); + } + + w.close(); + const l2 = w.list; + const l3 = w.list; + assert.isTrue(equals(l, l2)); + assert.strictEqual(l2, l3); + }); + + test('ListWriter close throws', () => { + const values = intSequence(15); + const w = new ListWriter(); + for (let i = 0; i < values.length; i++) { + w.write(values[i]); + } + w.close(); + + let ex; + try { + w.close(); // Cannot close twice. + } catch (e) { + ex = e; + } + assert.instanceOf(ex, TypeError); + }); + + test('ListWriter write throws', () => { + const values = intSequence(15); + const w = new ListWriter(); + for (let i = 0; i < values.length; i++) { + w.write(values[i]); + } + w.close(); + + let ex; + try { + w.write(42); // Cannot write after close. + } catch (e) { + ex = e; + } + assert.instanceOf(ex, TypeError); + }); + }); diff --git a/js/src/list.js b/js/src/list.js index 9f9dabb3fb..0c8e43a42e 100644 --- a/js/src/list.js +++ b/js/src/list.js @@ -12,14 +12,18 @@ import {IndexedSequence, IndexedSequenceIterator} from './indexed-sequence.js'; import {diff} from './indexed-sequence-diff.js'; import {getHashOfValue} from './get-hash.js'; import {invariant} from './assert.js'; -import {MetaTuple, newIndexedMetaSequenceBoundaryChecker, - newIndexedMetaSequenceChunkFn} from './meta-sequence.js'; +import { + MetaTuple, + newIndexedMetaSequenceBoundaryChecker, + newIndexedMetaSequenceChunkFn, +} from './meta-sequence.js'; import {sha1Size} from './hash.js'; import Ref from './ref.js'; import {getValueChunks} from './sequence.js'; import {makeListType, makeUnionType, getTypeOfValue} from './type.js'; import {equals} from './compare.js'; import {Kind} from './noms-kind.js'; +import SequenceChunker from './sequence-chunker.js'; const listWindowSize = 64; const listPattern = ((1 << 6) | 0) - 1; @@ -154,3 +158,41 @@ function clampIndex(idx: number, length: number): number { return idx < 0 ? Math.max(0, length + idx) : idx; } + +type ListWriterState = 'writable' | 'closed'; + +export class ListWriter { + _state: ListWriterState; + _list: ?List; + _chunker: SequenceChunker, T, ListLeafSequence>; + + constructor() { + this._state = 'writable'; + this._chunker = new SequenceChunker(null, newListLeafChunkFn(), + newIndexedMetaSequenceChunkFn(Kind.List, null), newListLeafBoundaryChecker(), + newIndexedMetaSequenceBoundaryChecker); + } + + write(item: T) { + assert(this._state === 'writable'); + this._chunker.append(item); + } + + close() { + assert(this._state === 'writable'); + this._list = this._chunker.doneSync(); + this._state = 'closed'; + } + + get list(): List { + assert(this._state === 'closed'); + invariant(this._list); + return this._list; + } +} + +function assert(v: any) { + if (!v) { + throw new TypeError('Invalid usage of ListWriter'); + } +} diff --git a/js/src/noms.js b/js/src/noms.js index 083af0dd80..06aaf90aad 100644 --- a/js/src/noms.js +++ b/js/src/noms.js @@ -23,7 +23,7 @@ export { export {encodeNomsValue} from './encode.js'; export {invariant, notNull} from './assert.js'; export {isPrimitiveKind, Kind, kindToString} from './noms-kind.js'; -export {default as List, ListLeafSequence} from './list.js'; +export {default as List, ListWriter, ListLeafSequence} from './list.js'; export {default as Map, MapLeafSequence} from './map.js'; export {default as Set, SetLeafSequence} from './set.js'; export {IndexedSequence} from './indexed-sequence.js'; diff --git a/js/src/sequence-chunker.js b/js/src/sequence-chunker.js index e149c9ff78..c429aa035b 100644 --- a/js/src/sequence-chunker.js +++ b/js/src/sequence-chunker.js @@ -60,7 +60,7 @@ export function chunkSequenceSync( return chunker.doneSync(); } -export default class SequenceChunker { +export default class SequenceChunker { _cursor: ?SequenceCursor; _isOnChunkBoundary: boolean; _parent: ?SequenceChunker;