This commit is contained in:
Rafael Weinstein
2016-05-18 14:35:26 -07:00
parent 6e23e8f0e7
commit 4d47b9cf18
4 changed files with 237 additions and 4 deletions
+1 -1
View File
@@ -13,7 +13,7 @@
"tingodb": "^0.4.2"
},
"devDependencies": {
"@attic/eslintrc": "^1.0.0",
"@attic/eslintrc": "^2.0.0",
"babel-cli": "6.6.5",
"babel-core": "6.7.2",
"babel-generator": "6.7.2",
+2 -3
View File
@@ -47,9 +47,8 @@ export function newList<T: valueOrPrimitive>(values: Array<T>): Promise<List<T>>
export default class List<T: valueOrPrimitive> extends Collection<IndexedSequence> {
async get(idx: number): Promise<T> {
// TODO (when |length| works) invariant(idx < this.length, idx + ' >= ' + this.length);
const cursor = await this.sequence.newCursorAt(idx);
return cursor.getCurrent();
invariant(idx >= 0 && idx < this.length);
return this.sequence.newCursorAt(idx).then(cursor => cursor.getCurrent());
}
splice(idx: number, deleteCount: number, ...insert: Array<T>): Promise<List<T>> {
+103
View File
@@ -0,0 +1,103 @@
// @flow
import {assert} from 'chai';
import {suite, test} from 'mocha';
import {equals} from './compare.js';
import Path from './path.js';
import type {valueOrPrimitive} from './value.js';
import {newList} from './list.js';
import {newMap} from './map.js';
import {newStruct} from './struct.js';
suite('Path', () => {
async function assertPathEqual(expect: any, ref: valueOrPrimitive, path: Path):
Promise<void> {
// $FlowIssue: need to be able to pass in null for ref
const actual = await path.resolve(ref);
if (actual === undefined || expect === undefined) {
assert.strictEqual(expect, actual);
return;
}
assert.isTrue(equals(expect, actual));
}
test('struct', async () => {
const v = newStruct('', {
foo: 'foo',
bar: false,
baz: 203,
});
await assertPathEqual('foo', v, new Path().addField('foo'));
await assertPathEqual(false, v, new Path().addField('bar'));
await assertPathEqual(203, v, new Path().addField('baz'));
await assertPathEqual(undefined, v, new Path().addField('notHere'));
const v2 = newStruct('', {
v1: v,
});
await assertPathEqual('foo', v2, new Path().addField('v1').addField('foo'));
await assertPathEqual(false, v2, new Path().addField('v1').addField('bar'));
await assertPathEqual(203, v2, new Path().addField('v1').addField('baz'));
await assertPathEqual(undefined, v2, new Path().addField('v1').addField('notHere'));
await assertPathEqual(undefined, v2, new Path().addField('notHere').addField('foo'));
});
test('list', async () => {
const v = await newList([1, 3, 'foo', false]);
await assertPathEqual(1, v, new Path().addIndex(0));
await assertPathEqual(3, v, new Path().addIndex(1));
await assertPathEqual('foo', v, new Path().addIndex(2));
await assertPathEqual(false, v, new Path().addIndex(3));
await assertPathEqual(undefined, v, new Path().addIndex(4));
await assertPathEqual(undefined, v, new Path().addIndex(-4));
});
test('map', async () => {
const v = await newMap([1, 'foo', 'two', 'bar', false, 23, 2.3, 4.5]);
await assertPathEqual('foo', v, new Path().addIndex(1));
await assertPathEqual('bar', v, new Path().addIndex('two'));
await assertPathEqual(23, v, new Path().addIndex(false));
await assertPathEqual(4.5, v, new Path().addIndex(2.3));
await assertPathEqual(undefined, v, new Path().addIndex(4));
});
test('struct.list.map', async () => {
const m1 = await newMap(['a', 'foo', 'b','bar', 'c', 'car']);
const m2 = await newMap(['d', 'dar', false, 'earth']);
const l = await newList([m1, m2]);
const s = newStruct('', {
foo: l,
});
await assertPathEqual(l, s, new Path().addField('foo'));
await assertPathEqual(m1, s, new Path().addField('foo').addIndex(0));
await assertPathEqual('foo', s, new Path().addField('foo').addIndex(0).addIndex('a'));
await assertPathEqual('bar', s, new Path().addField('foo').addIndex(0).addIndex('b'));
await assertPathEqual('car', s, new Path().addField('foo').addIndex(0).addIndex('c'));
await assertPathEqual(undefined, s, new Path().addField('foo').addIndex(0).addIndex('x'));
await assertPathEqual(undefined, s, new Path().addField('foo').addIndex(2).addIndex('c'));
await assertPathEqual(undefined, s, new Path().addField('notHere').addIndex(2).addIndex('c'));
await assertPathEqual(m2, s, new Path().addField('foo').addIndex(1));
await assertPathEqual('dar', s, new Path().addField('foo').addIndex(1).addIndex('d'));
await assertPathEqual('earth', s, new Path().addField('foo').addIndex(1).addIndex(false));
});
function assertToString(expect: string, path: Path) {
assert.strictEqual(expect, path.toString());
}
test('toString()', () => {
assertToString('[0][1][100]', new Path().addIndex(0).addIndex(1).addIndex(100));
assertToString('["0"]["1"]["100"]',
new Path().addIndex('0').addIndex('1').addIndex('100'));
assertToString('.foo[0].bar[4.5][false]',
new Path().addField('foo').addIndex(0).addField('bar').addIndex(4.5).addIndex(false));
});
});
+131
View File
@@ -0,0 +1,131 @@
// @flow
import type {valueOrPrimitive} from './value.js';
import {Kind} from './noms-kind.js';
import List from './list.js';
import Map from './map.js';
import {getTypeOfValue, StructDesc} from './type.js';
interface Part {
resolve(v: Promise<?valueOrPrimitive>): Promise<?valueOrPrimitive>;
toString(): string;
}
class FieldPart {
name: string;
constructor(name: string) {
this.name = name;
}
resolve(v: Promise<?valueOrPrimitive>): Promise<?valueOrPrimitive> {
return v.then(value => {
if (value === null || value === undefined) {
return;
}
const t = getTypeOfValue(value);
if (t.kind !== Kind.Struct) {
return;
}
const f = (t.desc: StructDesc).fields[this.name];
if (!f) {
return; // non-present field
}
// $FlowIssue: Flow doesn't know that it's safe to just access the field name here.
return value[this.name];
});
}
toString(): string {
return `.${this.name}`;
}
}
// TODO: Support value
type indexType = boolean | number | string;
class IndexPart {
idx: indexType;
constructor(idx: indexType) {
const t = getTypeOfValue(idx);
switch (t.kind) {
case Kind.String:
case Kind.Bool:
case Kind.Number:
this.idx = idx;
break;
default:
throw new Error('Unsupported');
}
}
resolve(v: Promise<?valueOrPrimitive>): Promise<?valueOrPrimitive> {
return v.then(value => {
if (value === null || value === undefined) {
return;
}
if (value instanceof List) {
if (typeof this.idx !== 'number') {
return;
}
if (this.idx < 0 || this.idx >= value.length) {
return undefined; // index out of bounds
}
return value.get(this.idx);
}
if (value instanceof Map) {
return value.get(this.idx);
}
return;
});
}
toString(): string {
switch (typeof this.idx) {
case 'boolean':
case 'number':
case 'string':
return `[${JSON.stringify(this.idx)}]`;
default:
throw new Error('not reached');
}
}
}
export default class Path {
_parts: Array<Part>;
constructor() {
this._parts = [];
}
_addPart(part: Part): Path {
const p = new Path();
p._parts = this._parts.concat(part);
return p;
}
addField(name: string): Path {
return this._addPart(new FieldPart(name));
}
addIndex(idx: indexType): Path {
return this._addPart(new IndexPart(idx));
}
resolve(v: valueOrPrimitive): Promise<?valueOrPrimitive> {
return this._parts.reduce((v, p) => p.resolve(v), Promise.resolve(v));
}
toString(): string {
return this._parts.join('');
}
}