schema.ts 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. /**
  2. * Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author David Sehnal <david.sehnal@gmail.com>
  5. */
  6. import * as Data from './data-model'
  7. import * as Column from '../common/column'
  8. import StringPool from '../../utils/short-string-pool'
  9. /**
  10. * A schema defines the shape of categories and fields.
  11. *
  12. * @example:
  13. * const atom_site = {
  14. * '@alias': '_atom_site',
  15. * label_atom_id: Field.str(),
  16. * Cartn_x: Field.float(),
  17. * Cartn_y: Field.float(),
  18. * Cartn_z: Field.float(),
  19. * }
  20. *
  21. * const mmCIF = { atom_site };
  22. */
  23. //////////////////////////////////////////////
  24. export function toTypedFrame<Schema extends FrameSchema>(schema: Schema, frame: Data.Frame): TypedFrame<Schema> {
  25. return createTypedFrame(schema, frame) as TypedFrame<Schema>;
  26. }
  27. export function toTypedCategory<Schema extends CategorySchema>(schema: Schema, category: Data.Category): TypedCategory<Schema> {
  28. return new _TypedCategory(category, schema, true) as TypedCategory<any>;
  29. }
  30. export type FrameSchema = { [category: string]: CategorySchema }
  31. export type TypedFrame<Schema extends FrameSchema> = {
  32. readonly _header?: string,
  33. readonly _frame: Data.Frame
  34. } & { [C in keyof Schema]: TypedCategory<Schema[C]> }
  35. export type CategorySchema = { [field: string]: Field.Schema<any> }
  36. export type TypedCategory<Schema extends CategorySchema> = {
  37. readonly _rowCount: number,
  38. readonly _isDefined: boolean,
  39. readonly _category: Data.Category
  40. } & { [F in keyof Schema]: Column.Column<Schema[F]['T']> }
  41. export namespace Field {
  42. export interface Schema<T> { T: T, ctor: (field: Data.Field, category: Data.Category, key: string) => Column.Column<T>, undefinedField: (c: number) => Data.Field, alias?: string };
  43. export interface Spec { undefinedField?: (c: number) => Data.Field, alias?: string }
  44. export function alias(name: string): Schema<any> { return { alias: name } as any; }
  45. export function pooledStr(spec?: Spec) { return createSchema(spec, PooledStr); }
  46. export function str(spec?: Spec) { return createSchema(spec, Str); }
  47. export function int(spec?: Spec) { return createSchema(spec, Int); }
  48. export function float(spec?: Spec) { return createSchema(spec, Float); }
  49. export function vector(rows: number, spec?: Spec) { return createSchema(spec, Vector(rows)); }
  50. export function matrix(rows: number, cols: number, spec?: Spec) { return createSchema(spec, Matrix(rows, cols)); }
  51. function create<T>(field: Data.Field, value: (row: number) => T, toArray: Column.Column<T>['toArray']): Column.Column<T> {
  52. const presence = field.presence;
  53. return {
  54. isDefined: field.isDefined,
  55. rowCount: field.rowCount,
  56. value,
  57. isValueDefined: row => presence(row) === Data.ValuePresence.Present,
  58. stringEquals: field.stringEquals,
  59. areValuesEqual: field.areValuesEqual,
  60. toArray
  61. };
  62. }
  63. function PooledStr(field: Data.Field) {
  64. const pool = StringPool.create();
  65. const value = (row: number) => StringPool.get(pool, field.str(row));
  66. const array = (params?: Column.ToArrayParams) => Column.createAndFillArray(field.rowCount, value, params);
  67. return create<string>(field, value, array);
  68. }
  69. function Str(field: Data.Field) { return create(field, field.str, field.toStringArray); }
  70. function Int(field: Data.Field) { return create(field, field.int, field.toIntArray); }
  71. function Float(field: Data.Field) { return create(field, field.float, field.toFloatArray); }
  72. function Vector(rows: number) {
  73. return function(field: Data.Field, category: Data.Category, key: string) {
  74. const value = (row: number) => Data.getVector(category, key, rows, row);
  75. return create(field, value, params => Column.createAndFillArray(field.rowCount, value, params));
  76. }
  77. }
  78. function Matrix(rows: number, cols: number) {
  79. return function(field: Data.Field, category: Data.Category, key: string) {
  80. const value = (row: number) => Data.getMatrix(category, key, rows, cols, row);
  81. return create(field, value, params => Column.createAndFillArray(field.rowCount, value, params));
  82. }
  83. }
  84. // spec argument is to allow for specialised implementation for undefined fields
  85. function createSchema<T>(spec: Spec | undefined, ctor: (field: Data.Field, category: Data.Category, key: string) => Column.Column<T>): Schema<T> {
  86. return { T: 0 as any, ctor, undefinedField: (spec && spec.undefinedField) || Data.DefaultUndefinedField, alias: spec && spec.alias };
  87. }
  88. }
  89. class _TypedFrame implements TypedFrame<any> { // tslint:disable-line:class-name
  90. header = this._frame.header;
  91. constructor(public _frame: Data.Frame, schema: FrameSchema) {
  92. for (const k of Object.keys(schema)) {
  93. Object.defineProperty(this, k, { value: createTypedCategory(k, schema[k], _frame), enumerable: true, writable: false, configurable: false });
  94. }
  95. }
  96. }
  97. class _TypedCategory implements TypedCategory<any> { // tslint:disable-line:class-name
  98. _rowCount = this._category.rowCount;
  99. constructor(public _category: Data.Category, schema: CategorySchema, public _isDefined: boolean) {
  100. const fieldKeys = Object.keys(schema).filter(k => k !== '@alias');
  101. const cache = Object.create(null);
  102. for (const k of fieldKeys) {
  103. const s = schema[k];
  104. Object.defineProperty(this, k, {
  105. get: function() {
  106. if (cache[k]) return cache[k];
  107. const name = s.alias || k;
  108. const field = _category.getField(name) || s.undefinedField(_category.rowCount);
  109. cache[k] = s.ctor(field, _category, name);
  110. return cache[k];
  111. },
  112. enumerable: true,
  113. configurable: false
  114. });
  115. }
  116. }
  117. }
  118. function createTypedFrame(schema: FrameSchema, frame: Data.Frame): any {
  119. return new _TypedFrame(frame, schema);
  120. }
  121. function createTypedCategory(key: string, schema: CategorySchema, frame: Data.Frame) {
  122. const alias = (schema['@alias'] && schema['@alias'].alias) || key;
  123. const name = alias[0] === '_' ? alias : '_' + alias;
  124. const cat = frame.categories[name];
  125. return new _TypedCategory(cat || Data.Category.Empty, schema, !!cat);
  126. }