reign
Version:
A persistent, typed-objects implementation.
255 lines (227 loc) • 7.26 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.STRING_DATA_OFFSET = exports.STRING_HEADER_SIZE = exports.STRING_HASH_OFFSET = exports.STRING_LENGTH_OFFSET = undefined;
exports.make = make;
var _performance = require("../../performance");
var _string = require("../../random/string");
var _string2 = _interopRequireDefault(_string);
var _backing = require("backing");
var _backing2 = _interopRequireDefault(_backing);
var _ = require("../..");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
const STRING_LENGTH_OFFSET = exports.STRING_LENGTH_OFFSET = 0;
const STRING_HASH_OFFSET = exports.STRING_HASH_OFFSET = 4;
const STRING_HEADER_SIZE = exports.STRING_HEADER_SIZE = 8;
const STRING_DATA_OFFSET = exports.STRING_DATA_OFFSET = STRING_HEADER_SIZE;
/**
* Make a simple string type for the given realm.
*/
function make(realm) {
const StringType = realm.StringType;
const RawString = new StringType({
name: 'String',
byteLength: 8, // Pointer
byteAlignment: 8,
constructor: function constructor(input) {
if (this instanceof RawString) {
throw new TypeError(`String is not a constructor.`);
} else {
return input == null ? '' : '' + input;
}
},
cast: function cast(input) {
return input == null ? '' : '' + input;
},
accepts: function accepts(input) {
return typeof input === 'string';
},
initialize: function initialize(backing, pointerAddress, initialInput) {
if (!initialInput) {
backing.setFloat64(pointerAddress, 0);
} else {
backing.setFloat64(pointerAddress, createRawString(backing, initialInput));
}
},
store: function store(backing, pointerAddress, input) {
const existing = backing.getFloat64(pointerAddress);
if (!input) {
if (existing !== 0) {
backing.gc.unref(existing);
backing.setFloat64(pointerAddress, 0);
}
} else {
const address = createRawString(backing, input);
if (address !== existing) {
if (existing !== 0) {
backing.gc.unref(existing);
}
backing.setFloat64(pointerAddress, address);
}
}
},
load: function load(backing, address) {
return getString(backing, backing.getFloat64(address));
},
clear: function clear(backing, address) {
const existing = backing.getFloat64(address);
if (existing !== 0) {
backing.gc.unref(existing);
backing.setFloat64(address, 0);
}
},
randomValue: _string2.default,
emptyValue: function emptyValue() {
return '';
},
hashValue: hashString,
equal: function equal(valueA, valueB) {
return valueA === valueB;
},
flowType: function flowType() {
return `string`;
}
});
return RawString;
}
/**
* Store the given raw string and return the address.
* The string will NOT be interned.
*/
function createRawString(backing, input) {
let hash = 0x811c9dc5;
let allAscii = true;
const length = input.length;
for (let i = 0; i < length; i++) {
const code = input.charCodeAt(i);
if (code > 127) {
allAscii = false;
}
hash ^= code;
hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24);
}
hash = hash >>> 0;
return storeString(backing, input, hash, allAscii);
}
(0, _performance.forceInline)(createRawString);
/**
* Store a string and return its address.
*/
function storeString(backing, input, hash, allAscii) {
if (allAscii) {
return storeAsciiString(backing, input, hash);
} else {
return storeMultibyteString(backing, input, hash);
}
}
(0, _performance.forceInline)(storeString);
function storeAsciiString(backing, input, hash) {
const length = input.length;
const byteLength = length + STRING_HEADER_SIZE;
const address = backing.gc.alloc(byteLength, 0, 1);
backing.setInt32(address, length);
backing.setUint32(address + STRING_HASH_OFFSET, hash);
const offset = backing.offsetFor(address + STRING_DATA_OFFSET);
const chars = backing.arenaFor(address).uint8Array;
for (let i = 0; i < length; i++) {
chars[offset + i] = input.charCodeAt(i);
}
return address;
}
(0, _performance.forceInline)(storeAsciiString);
function storeMultibyteString(backing, input, hash) {
const length = input.length;
const byteLength = length + length + STRING_HEADER_SIZE;
const address = backing.gc.alloc(byteLength, 0, 1);
backing.setInt32(address, -length);
backing.setUint32(address + STRING_HASH_OFFSET, hash);
const offset = backing.offsetFor(address + STRING_DATA_OFFSET) >> 1;
const chars = backing.arenaFor(address).uint16Array;
for (let i = 0; i < length; i++) {
chars[offset + i] = input.charCodeAt(i);
}
return address;
}
(0, _performance.forceInline)(storeMultibyteString);
/**
* Returns the hash for the given string.
*/
function hashString(input) {
let hash = 0x811c9dc5;
for (let i = 0; i < input.length; i++) {
hash ^= input.charCodeAt(i);
hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24);
}
return hash >>> 0;
}
(0, _performance.forceInline)(hashString);
/**
* Check that the string stored at the given address matches the given input + hash.
*/
function checkEqual(backing, address, input, hash, allAscii) {
if (getStringHash(backing, address) !== hash) {
return false;
}
let length = backing.getInt32(address);
if (length < 0) {
if (allAscii) {
return false;
}
length = -length;
if (length !== input.length) {
return false;
}
const arena = backing.arenaFor(address);
const chars = arena.uint16Array;
const offset = backing.offsetFor(address + STRING_HEADER_SIZE) >> 1;
for (let i = 0; i < length; i++) {
if (input.charCodeAt(i) !== chars[offset + i]) {
return false;
}
}
return true;
} else {
if (!allAscii) {
return false;
} else if (length !== input.length) {
return false;
}
const arena = backing.arenaFor(address);
const chars = arena.uint8Array;
const offset = backing.offsetFor(address + STRING_HEADER_SIZE);
for (let i = 0; i < length; i++) {
if (input.charCodeAt(i) !== chars[offset + i]) {
return false;
}
}
return true;
}
}
(0, _performance.forceInline)(checkEqual);
/**
* Read the string at the given address.
*/
function getString(backing, address) {
if (address === 0) {
return '';
}
const arena = backing.arenaFor(address);
let offset = backing.offsetFor(address);
const length = arena.int32Array[offset >> 2];
if (length < 0) {
offset = offset + STRING_DATA_OFFSET >> 1;
return String.fromCharCode(...arena.uint16Array.slice(offset, offset + Math.abs(length)));
} else {
offset = offset + STRING_DATA_OFFSET;
return String.fromCharCode(...arena.uint8Array.slice(offset, offset + Math.abs(length)));
}
}
(0, _performance.forceInline)(getString);
/**
* Read the hash for the given string.
*/
function getStringHash(backing, address) {
return backing.getUint32(address + STRING_HASH_OFFSET);
}
(0, _performance.forceInline)(getStringHash);