-
Notifications
You must be signed in to change notification settings - Fork 3.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(cdk): support encoding Tokens as numbers #2534
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -200,9 +200,79 @@ export function containsListTokenElement(xs: any[]) { | |
export function unresolved(obj: any): boolean { | ||
if (typeof(obj) === 'string') { | ||
return TokenString.forStringToken(obj).test(); | ||
} else if (typeof obj === 'number') { | ||
return extractTokenDouble(obj) !== undefined; | ||
} else if (Array.isArray(obj) && obj.length === 1) { | ||
return typeof(obj[0]) === 'string' && TokenString.forListToken(obj[0]).test(); | ||
} else { | ||
return obj && typeof(obj[RESOLVE_METHOD]) === 'function'; | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Bit pattern in the top 16 bits of a double to indicate a Token | ||
* | ||
* An IEEE double in LE memory order looks like this (grouped | ||
* into octets, then grouped into 32-bit words): | ||
* | ||
* mmmmmmm.mmmmmmm.mmmmmmm.mmmmmmm | mmmmmmm.mmmmmmm.EEEEEmm.sEEEEEE | ||
* | ||
* - m: mantissa (52 bits) | ||
* - E: exponent (11 bits) | ||
* - s: sign (1 bit) | ||
* | ||
* We put the following marker into the top 16 bits (exponent and sign), and | ||
* use the mantissa part to encode the token index. To save some bit twiddling | ||
* we use all top 16 bits for the tag. That loses us 2 mantissa bits to store | ||
* information in but we still have 50, which is going to be plenty for any | ||
* number of tokens to be created during the lifetime of any CDK application. | ||
* | ||
* Can't have all bits set because that makes a NaN, so unset the least | ||
* significant exponent bit. | ||
* | ||
* Currently not supporting BE architectures. | ||
*/ | ||
// tslint:disable-next-line:no-bitwise | ||
const DOUBLE_TOKEN_MARKER_BITS = 0xFBFF << 16; | ||
|
||
/** | ||
* Return a special Double value that encodes the given integer | ||
*/ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mention in the docstring that this is used to encode an index to the token map and therefore it's okay that we don't support negative numbers. Also, mention the limit on the number of number tokens we can support. |
||
export function createTokenDouble(x: number) { | ||
if (Math.floor(x) !== x || x < 0) { | ||
throw new Error('Can only encode positive integers'); | ||
} | ||
|
||
const buf = new ArrayBuffer(8); | ||
const ints = new Uint32Array(buf); | ||
|
||
// tslint:disable:no-bitwise | ||
ints[0] = x & 0x0000FFFFFFFF; // Bottom 32 bits of number | ||
ints[1] = (x & 0xFFFF00000000) >> 32 | DOUBLE_TOKEN_MARKER_BITS; // Top 16 bits of number and the mask | ||
// tslint:enable:no-bitwise | ||
|
||
return (new Float64Array(buf))[0]; | ||
|
||
} | ||
|
||
/** | ||
* Extract the encoded integer out of the special Double value | ||
* | ||
* Returns undefined if the float is a not an encoded token. | ||
*/ | ||
export function extractTokenDouble(encoded: number): number | undefined { | ||
const buf = new ArrayBuffer(8); | ||
(new Float64Array(buf))[0] = encoded; | ||
|
||
const ints = new Uint32Array(buf); | ||
|
||
// tslint:disable:no-bitwise | ||
if ((ints[1] & 0xFFFF0000) !== DOUBLE_TOKEN_MARKER_BITS) { | ||
return undefined; | ||
} | ||
|
||
// Must use + instead of | here (bitwise operations | ||
// will force 32-bits integer arithmetic, + will not). | ||
return ints[0] + (ints[1] & 0xFFFF0000) << 16; | ||
// tslint:enable:no-bitwise | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
import { BEGIN_LIST_TOKEN_MARKER, BEGIN_STRING_TOKEN_MARKER, END_TOKEN_MARKER, TokenString, VALID_KEY_CHARS } from "./encoding"; | ||
import { BEGIN_LIST_TOKEN_MARKER, BEGIN_STRING_TOKEN_MARKER, createTokenDouble, | ||
END_TOKEN_MARKER, extractTokenDouble, TokenString, VALID_KEY_CHARS } from "./encoding"; | ||
import { Token } from "./token"; | ||
|
||
const glob = global as any; | ||
|
@@ -23,7 +24,9 @@ export class TokenMap { | |
return glob.__cdkTokenMap; | ||
} | ||
|
||
private readonly tokenMap = new Map<string, Token>(); | ||
private readonly stringTokenMap = new Map<string, Token>(); | ||
private readonly numberTokenMap = new Map<number, Token>(); | ||
private tokenCounter = 0; | ||
|
||
/** | ||
* Generate a unique string for this Token, returning a key | ||
|
@@ -49,6 +52,15 @@ export class TokenMap { | |
return [`${BEGIN_LIST_TOKEN_MARKER}${key}${END_TOKEN_MARKER}`]; | ||
} | ||
|
||
/** | ||
* Create a unique number representation for this Token and return it | ||
*/ | ||
public registerNumber(token: Token): number { | ||
const tokenIndex = this.tokenCounter++; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Error if we reached the limit. |
||
this.numberTokenMap.set(tokenIndex, token); | ||
return createTokenDouble(tokenIndex); | ||
} | ||
|
||
/** | ||
* Reverse a string representation into a Token object | ||
*/ | ||
|
@@ -76,24 +88,35 @@ export class TokenMap { | |
return undefined; | ||
} | ||
|
||
/** | ||
* Reverse a number encoding into a Token, or undefined if the number wasn't a Token | ||
*/ | ||
public lookupNumberToken(x: number): Token | undefined { | ||
const tokenIndex = extractTokenDouble(x); | ||
if (tokenIndex === undefined) { return undefined; } | ||
const t = this.numberTokenMap.get(tokenIndex); | ||
if (t === undefined) { throw new Error('Encoded representation of unknown number Token found'); } | ||
return t; | ||
} | ||
|
||
/** | ||
* Find a Token by key. | ||
* | ||
* This excludes the token markers. | ||
*/ | ||
public lookupToken(key: string): Token { | ||
const token = this.tokenMap.get(key); | ||
const token = this.stringTokenMap.get(key); | ||
if (!token) { | ||
throw new Error(`Unrecognized token key: ${key}`); | ||
} | ||
return token; | ||
} | ||
|
||
private register(token: Token, representationHint?: string): string { | ||
const counter = this.tokenMap.size; | ||
const counter = this.tokenCounter++; | ||
const representation = (representationHint || `TOKEN`).replace(new RegExp(`[^${VALID_KEY_CHARS}]`, 'g'), '.'); | ||
const key = `${representation}.${counter}`; | ||
this.tokenMap.set(key, token); | ||
this.stringTokenMap.set(key, token); | ||
return key; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"encode the index to the token in the token map"