diff --git a/js2/src/decode.js b/js2/src/decode.js index a3f12083ae..9b52bac44a 100644 --- a/js2/src/decode.js +++ b/js2/src/decode.js @@ -2,11 +2,13 @@ 'use strict'; -import {isPrimitiveKind, Kind} from './noms_kind.js'; -import {makeCompoundTypeRef, makePrimitiveTypeRef, makeTypeRef, TypeRef} from './type_ref.js'; import Ref from './ref.js'; import type {ChunkStore} from './chunk_store.js'; import type {NomsKind} from './noms_kind.js'; +import {isPrimitiveKind, Kind} from './noms_kind.js'; +import {makeCompoundTypeRef, makePrimitiveTypeRef, makeTypeRef, StructDesc, TypeRef} from './type_ref.js'; +import {lookupPackage, Package} from './package.js'; + class JsonArrayReader { _a: Array; @@ -108,7 +110,7 @@ class JsonArrayReader { throw new Error('Unreachable'); } - readList(t: TypeRef, pkg: ?Ref): Array { + readList(t: TypeRef, pkg: ?Package): Array { let elemType = t.elemTypes[0]; let list = []; while (!this.atEnd()) { @@ -118,7 +120,7 @@ class JsonArrayReader { return list; } - readSet(t: TypeRef, pkg: ?Ref): Set { + readSet(t: TypeRef, pkg: ?Package): Set { let elemType = t.elemTypes[0]; let s = new Set(); while (!this.atEnd()) { @@ -129,7 +131,7 @@ class JsonArrayReader { return s; } - readMap(t: TypeRef, pkg: ?Ref): Map { + readMap(t: TypeRef, pkg: ?Package): Map { let keyType = t.elemTypes[0]; let valueType = t.elemTypes[1]; let m = new Map(); @@ -147,7 +149,7 @@ class JsonArrayReader { return this.readValueWithoutTag(t); } - readValueWithoutTag(t: TypeRef, pkg: ?Ref = null): any { + readValueWithoutTag(t: TypeRef, pkg: ?Package = null): any { // TODO: Verify read values match tagged kinds. switch (t.kind) { case Kind.Blob: @@ -192,11 +194,70 @@ class JsonArrayReader { throw new Error('Not allowed'); case Kind.TypeRef: case Kind.Unresolved: - throw new Error('Not implemented'); + return this.readUnresolvedKindToValue(t, pkg); } throw new Error('Unreached'); } + + readUnresolvedKindToValue(t: TypeRef, pkg: ?Package = null): any { + let pkgRef = t.packageRef; + let ordinal = t.ordinal; + if (!pkgRef.isEmpty()) { + let pkg2 = lookupPackage(pkgRef); + if (!pkg2) { + throw new Error('Not implemented'); + } else { + pkg = pkg2; + } + } + + if (pkg) { + let typeDef = pkg.types[ordinal]; + if (typeDef.kind === Kind.Enum) { + throw new Error('Not implemented'); + } + + if (typeDef.kind !== Kind.Struct) { + throw new Error('Attempt to resolve non-struct struct kind'); + } + + return this.readStruct(typeDef, t, pkg); + } else { + throw new Error('Woah, got a null pkg. pkgRef: ' + pkgRef.toString() + ' ordinal: ' + ordinal); + } + } + + readStruct(typeDef: TypeRef, typeRef: TypeRef, pkg: Package): any { + // TODO FixupTypeRef? + let desc = typeDef.desc; + if (desc instanceof StructDesc) { + let s = Object.create(null); + + for (let i = 0; i < desc.fields.length; i++) { + let field = desc.fields[i]; + + if (field.optional) { + let b = this.readBool(); + if (b) { + let v = this.readValueWithoutTag(field.t, pkg); + s[field.name] = v; + } + } else { + let v = this.readValueWithoutTag(field.t, pkg); + s[field.name] = v; + } + } + + if (desc.union.length > 0) { + throw new Error('Not implemented'); + } + + return s; + } else { + throw new Error('Attempt to read struct without StructDesc typeref'); + } + } } -module.exports = {JsonArrayReader}; +export {JsonArrayReader}; diff --git a/js2/src/decode_test.js b/js2/src/decode_test.js index 5eb3534e89..b1205c1468 100644 --- a/js2/src/decode_test.js +++ b/js2/src/decode_test.js @@ -2,14 +2,15 @@ 'use strict'; -import {assert} from 'chai'; -import {JsonArrayReader} from './decode.js'; -import {Kind} from './noms_kind.js'; -import {suite} from 'mocha'; -import {TypeRef, makePrimitiveTypeRef, makeCompoundTypeRef, makeTypeRef} from './type_ref.js'; import MemoryStore from './memory_store.js'; import Ref from './ref.js'; import test from './async_test.js'; +import {assert} from 'chai'; +import {JsonArrayReader} from './decode.js'; +import {Kind} from './noms_kind.js'; +import {registerPackage, Package} from './package.js'; +import {suite} from 'mocha'; +import {Field, makeCompoundTypeRef, makePrimitiveTypeRef, makeStructTypeRef, makeTypeRef, TypeRef} from './type_ref.js'; suite('Decode', () => { test('read', async () => { @@ -167,4 +168,21 @@ suite('Decode', () => { assertSetsEqual(s, v); }); + + test('test read struct', async () => { + let ms = new MemoryStore(); + let tr = makeStructTypeRef('A1', [ + new Field('x', makePrimitiveTypeRef(Kind.Int16), false), + new Field('s', makePrimitiveTypeRef(Kind.String), false), + new Field('b', makePrimitiveTypeRef(Kind.Bool), false) + ], []); + + let pkg = new Package([tr], []); + registerPackage(pkg); + + let a = [Kind.Unresolved, pkg.ref.toString(), 0, 42, 'hi', true]; + let r = new JsonArrayReader(a, ms); + let v = r.readTopLevelValue(); + assert.deepEqual({x: 42, s: 'hi', b: true}, v); + }); }); diff --git a/js2/src/encode.js b/js2/src/encode.js index dd5ae1f1bd..0c266cca48 100644 --- a/js2/src/encode.js +++ b/js2/src/encode.js @@ -2,13 +2,14 @@ 'use strict'; -import type {ChunkStore} from './chunk_store.js'; -import type {NomsKind} from './noms_kind.js'; - import Chunk from './chunk.js'; import MemoryStore from './memory_store.js'; -import {Kind} from './noms_kind.js'; -import {TypeRef} from './type_ref.js'; +import Ref from './ref.js'; +import type {ChunkStore} from './chunk_store.js'; +import type {NomsKind} from './noms_kind.js'; +import {isPrimitiveKind, Kind} from './noms_kind.js'; +import {makePrimitiveTypeRef, StructDesc, TypeRef} from './type_ref.js'; +import {Package} from './package.js'; const typedTag = 't '; @@ -29,6 +30,10 @@ class JsonArrayWriter { this.write(k); } + writeRef(r: Ref) { + this.write(r.toString()); + } + writeTypeRefAsTag(t: TypeRef) { let k = t.kind; this.writeKind(k); @@ -75,43 +80,24 @@ class JsonArrayWriter { this.write(v); // TODO: Verify value fits in type break; case Kind.List: { - let w2 = new JsonArrayWriter(this._cs); - let elemType = t.elemTypes[0]; if (v instanceof Array) { + let w2 = new JsonArrayWriter(this._cs); + let elemType = t.elemTypes[0]; for (let i = 0; i < v.length; i++) { w2.writeValue(v[i], elemType); } + this.write(w2.array); } else { throw new Error('Attempt to serialize non-list as list'); } - this.write(w2.array); - break; - } - case Kind.Set: { - let w2 = new JsonArrayWriter(this._cs); - let elemType = t.elemTypes[0]; - if (v instanceof Set) { - let elems = []; - v.forEach(v => { - elems.push(v); - }); - elems = orderValuesByRef(elemType, elems); - for (let i = 0; i < elems.length; i++) { - w2.writeValue(elems[i], elemType); - } - } else { - throw new Error('Attempt to serialize non-set as set'); - } - - this.write(w2.array); break; } case Kind.Map: { - let w2 = new JsonArrayWriter(this._cs); - let keyType = t.elemTypes[0]; - let valueType = t.elemTypes[1]; if (v instanceof Map) { + let w2 = new JsonArrayWriter(this._cs); + let keyType = t.elemTypes[0]; + let valueType = t.elemTypes[1]; let elems = []; v.forEach((v, k) => { elems.push(k); @@ -121,15 +107,114 @@ class JsonArrayWriter { w2.writeValue(elems[i], keyType); w2.writeValue(v.get(elems[i]), valueType); } + this.write(w2.array); } else { throw new Error('Attempt to serialize non-map as maps'); } + break; + } + case Kind.Package: { + if (v instanceof Package) { + let ptr = makePrimitiveTypeRef(Kind.TypeRef); + let w2 = new JsonArrayWriter(this._cs); + for (let i = 0; i < v.types.length; i++) { + w2.writeValue(v.types[i], ptr); + } + this.write(w2.array); + let w3 = new JsonArrayWriter(this._cs); + for (let i = 0; i < v.dependencies.length; i++) { + w3.writeRef(v.dependencies[i]); + } + this.write(w3.array); + } else { + throw new Error('Attempt to serialize non-package as package'); + } + + break; + } + case Kind.Set: { + if (v instanceof Set) { + let w2 = new JsonArrayWriter(this._cs); + let elemType = t.elemTypes[0]; + let elems = []; + v.forEach(v => { + elems.push(v); + }); + elems = orderValuesByRef(elemType, elems); + for (let i = 0; i < elems.length; i++) { + w2.writeValue(elems[i], elemType); + } + this.write(w2.array); + } else { + throw new Error('Attempt to serialize non-set as set'); + } + + break; + } + case Kind.TypeRef: + if (v instanceof TypeRef) { + this.writeTypeRefAsValue(v); + } else { + throw new Error('Attempt to serialize non-typeref as typeref'); + } + break; + default: + throw new Error('Not implemented'); + } + } + + writeTypeRefAsValue(t: TypeRef) { + let k = t.kind; + this.writeKind(k); + switch (k) { + case Kind.Enum: + throw new Error('Not implemented'); + case Kind.List: + case Kind.Map: + case Kind.Ref: + case Kind.Set: { + this.write(t.name); + let w2 = new JsonArrayWriter(this._cs); + for (let i = 0; t.elemTypes.length; i++) { + w2.writeTypeRefAsValue(t.elemTypes[i]); + } this.write(w2.array); break; } - default: + case Kind.Struct: { + let desc = t.desc; + if (desc instanceof StructDesc) { + this.write(t.name); + let fieldWriter = new JsonArrayWriter(this._cs); + for (let i = 0; i < desc.fields.length; i++) { + let field = desc.fields[i]; + fieldWriter.write(field.name); + fieldWriter.writeTypeRefAsValue(field.t); + fieldWriter.write(field.optional); + } + this.write(fieldWriter.array); + let choiceWriter = new JsonArrayWriter(this._cs); + for (let i = 0; i < desc.union.length; i++) { + let choice = desc.union[i]; + choiceWriter.write(choice.name); + choiceWriter.writeTypeRefAsValue(choice.t); + choiceWriter.write(choice.optional); + } + this.write(choiceWriter.array); + } else { + throw new Error('Attempt to serialize non-struct typeref as struct type-ref'); + } + + break; + } + case Kind.Unresolved: throw new Error('Not implemented'); + default: { + if (!isPrimitiveKind(k)) { + throw new Error('Not implemented.'); + } + } } } } @@ -148,10 +233,16 @@ function orderValuesByRef(t: TypeRef, a: Array): Array { } function encodeNomsValue(v: any, t: TypeRef): Chunk { + // if (v instanceof Package) { + // if (v.dependencies.length > 0) { + // throw new Error('Not implemented'); + // } + // } + let ms = new MemoryStore(); // TODO: This should be passed in. let w = new JsonArrayWriter(ms); w.writeTopLevel(t, v); return new Chunk(typedTag + JSON.stringify(w.array)); } -module.exports = {JsonArrayWriter}; +export {encodeNomsValue, JsonArrayWriter}; diff --git a/js2/src/memory_store.js b/js2/src/memory_store.js index 2e37a8eba6..12ff97abd6 100644 --- a/js2/src/memory_store.js +++ b/js2/src/memory_store.js @@ -5,7 +5,7 @@ import Chunk from './chunk.js'; import Ref from './ref.js'; -class MemoryStore { +export default class MemoryStore { _data: { [key: string]: Chunk }; _root: Ref; @@ -50,5 +50,3 @@ class MemoryStore { close() {} } - -module.exports = MemoryStore; diff --git a/js2/src/package.js b/js2/src/package.js new file mode 100644 index 0000000000..6d5c9904e4 --- /dev/null +++ b/js2/src/package.js @@ -0,0 +1,45 @@ +/* @flow */ + +'use strict'; + +import Ref from './ref.js'; +import {Kind} from './noms_kind.js'; +import {makePrimitiveTypeRef, TypeRef} from './type_ref.js'; +import {encodeNomsValue} from './encode.js'; + +const packageTypeRef = makePrimitiveTypeRef(Kind.Package); + +class Package { + types: Array; + dependencies: Array; + _ref: Ref; + + constructor(types: Array, dependencies: Array) { + this.types = types; + this.dependencies = dependencies; + } + + get ref(): Ref { + if (!this._ref) { + this._ref = encodeNomsValue(this, this.typeRef).ref; + } + return this._ref; + } + + get typeRef(): TypeRef { + return packageTypeRef; + } +} + +const packageRegistry: { [key: string]: Package } = Object.create(null); + +function lookupPackage(r: Ref): ?Package { + return packageRegistry[r.toString()]; +} + +// TODO: Compute ref rather than setting +function registerPackage(p: Package) { + packageRegistry[p.ref.toString()] = p; +} + +export {lookupPackage, Package, registerPackage}; diff --git a/js2/src/type_ref.js b/js2/src/type_ref.js index 2b995ca80c..aff2a4cb9d 100644 --- a/js2/src/type_ref.js +++ b/js2/src/type_ref.js @@ -71,6 +71,61 @@ class CompoundDesc { } } +class StructDesc { + fields: Array; + union: Array; + + constructor(fields: Array, union: Array) { + this.fields = fields; + this.union = union; + } + + get kind(): NomsKind { + return Kind.Struct; + } + + equals(other: TypeDesc): boolean { + if (other instanceof StructDesc) { + if (this.fields.length !== other.fields.length || this.union.length !== other.union.length) { + return false; + } + + for (let i = 0; i < this.fields.length; i++) { + if (!this.fields[i].equals(other.fields[i])) { + return false; + } + } + + for (let i = 0; i < this.union.length; i++) { + if (!this.union[i].equals(other.union[i])) { + return false; + } + } + + return true; + } + + return false; + } +} + +class Field { + name: string; + t: TypeRef; + optional: boolean; + + constructor(name: string, t: TypeRef, optional: boolean) { + this.name = name; + this.t = t; + this.optional = optional; + } + + equals(other: Field): boolean { + return this.name === other.name && this.t.equals(other.t) && this.optional === other.optional; + } +} + + class TypeRef { _namespace: string; _name: string; @@ -192,8 +247,12 @@ function makeCompoundTypeRef(k: NomsKind, ...elemTypes: Array): TypeRef return buildType('', new CompoundDesc(k, elemTypes)); } +function makeStructTypeRef(name: string, fields: Array, choices: Array): TypeRef { + return buildType(name, new StructDesc(fields, choices)); +} + function makeTypeRef(pkgRef: Ref, ordinal: number): TypeRef { return new TypeRef('', '', new UnresolvedDesc(pkgRef, ordinal)); } -export {CompoundDesc, makeCompoundTypeRef, makePrimitiveTypeRef, makeTypeRef, TypeRef}; +export {CompoundDesc, Field, makeCompoundTypeRef, makePrimitiveTypeRef, makeStructTypeRef, makeTypeRef, StructDesc, TypeRef};