Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

179 changes: 158 additions & 21 deletions src/tokens.js
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,10 @@ function parseSpaces(input, offset) {
* @return { { token: string, offset: number, term: number } | null }
*/
function parseName(input, variables) {
const contextKeys = variables.contextKeys();
const {
keys,
prefixes
} = variables.contextCache();

const start = variables.tokens;

Expand Down Expand Up @@ -298,7 +301,7 @@ function parseName(input, variables) {

const name = [ ...start, ...tokens ].join(' ');

if (contextKeys.some(el => el === name)) {
if (keys.has(name)) {
const token = tokens[0];

nextMatch = {
Expand All @@ -308,7 +311,7 @@ function parseName(input, variables) {
};
}

if (contextKeys.some(el => el.startsWith(name))) {
if (prefixes.has(name)) {
continue;
}

Expand Down Expand Up @@ -476,6 +479,85 @@ const dateTimeLiterals = {

const dateTimeIdentifiers = Object.keys(dateTimeLiterals);

/**
* @typedef { {
* keys: Set<string>,
* prefixes: Set<string>,
* originalMap: Map<string, string>
* } } ContextCache
*
* @typedef { WeakMap<VariableContext, ContextCache> } CacheMap
*/

/**
* Get all prefixes for a given string.
* Used to build a prefix set for fast startsWith() checks.
*
* @param {string} str
* @returns {string[]}
*/
function getPrefixes(str) {
const prefixes = [];
for (let i = 1; i <= str.length; i++) {
prefixes.push(str.substring(0, i));
}
return prefixes;
}

/**
* @param {string} key
* @param {ContextCache} cache
*
* @return {ContextCache} cache
*/
function cacheKey(key, cache) {
const normalizedKey = normalizeContextKey(key);
const prefixes = getPrefixes(normalizedKey);

cache.keys.add(normalizedKey);

for (const prefix of prefixes) {
cache.prefixes.add(prefix);
}

cache.originalMap.set(normalizedKey, key);

return cache;
}

/**
* Compute the normalized keys cache for a context.
*
* @param {VariableContext} context
*
* @returns {ContextCache}
*/
function computeContextCache(context) {

const cache = createContextCache();

for (const key of context.getKeys()) {
cacheKey(key, cache);
}

return cache;
}


/**
* Copy an existing context cache
*
* @param {ContextCache} [from]
*
* @returns {ContextCache}
*/
function createContextCache(from) {
return {
keys: new Set(from?.keys),
prefixes: new Set(from?.prefixes),
originalMap: new Map(from?.originalMap)
};
}

/**
* A basic key-value store to hold context values.
Expand All @@ -498,7 +580,7 @@ export class VariableContext {
/**
* Return all defined keys of the context.
*
* @returns {Array<string>} the keys of the context
* @returns {string[] } the keys of the context
*/
getKeys() {
return Object.keys(this.value);
Expand All @@ -510,7 +592,7 @@ export class VariableContext {
* If the value represents a context itself, it should be wrapped in a
* context class.
*
* @param {String} key
* @param {string} key
* @returns {VariableContext|ValueProducer|null}
*/
get(key) {
Expand All @@ -528,7 +610,7 @@ export class VariableContext {
/**
* Creates a new context with the given key added.
*
* @param {String} key
* @param {string} key
* @param {any} value
*
* @returns {VariableContext} new context with the given key added
Expand All @@ -537,7 +619,7 @@ export class VariableContext {

const constructor = /** @type { typeof VariableContext } */ (this.constructor);

return constructor.of({
return new constructor({
...this.value,
[key]: value
});
Expand Down Expand Up @@ -657,7 +739,8 @@ class Variables {
* parent: Variables | null
* context: VariableContext,
* value?: any,
* raw?: any
* raw?: any,
* __cache?: CacheMap
* } } options
*/
constructor({
Expand All @@ -667,7 +750,8 @@ class Variables {
parent = null,
context,
value,
raw
raw,
__cache
}) {
this.name = name;
this.tokens = tokens;
Expand All @@ -676,6 +760,36 @@ class Variables {
this.context = context;
this.value = value;
this.raw = raw;
this.__cache = __cache;
}

/**
* Get the root Variables instance by traversing up the parent chain.
*
* @returns {Variables}
*/
get root() {
let current = /** @type {Variables} */ (this);
while (current.parent) {
current = current.parent;
}
return current;
}

/**
* Get the root Variables instance by traversing up the parent chain.
*
* @returns {CacheMap}
*/
get cache() {

const root = this.root;

if (!root.__cache) {
root.__cache = new WeakMap();
}

return root.__cache;
}

enterScope(name) {
Expand Down Expand Up @@ -740,8 +854,22 @@ class Variables {
}
}

contextKeys() {
return this.context.getKeys().map(normalizeContextKey);
/**
* Get or compute the context cache for fast retrival
* of keys, prefixes and original mappings.
*
* @returns {ContextCache}
*/
contextCache() {

let cache = this.cache.get(this.context);
if (!cache) {
cache = computeContextCache(this.context);

this.cache.set(this.context, cache);
}

return cache;
}

get path() {
Expand All @@ -755,12 +883,9 @@ class Variables {
* @return { any } value
*/
get(variable) {
const normalizedVariable = variable && normalizeContextKey(variable);

const names = [ variable, variable && normalizeContextKey(variable) ];

const contextKey = this.context.getKeys().find(
key => names.includes(normalizeContextKey(key))
);
const contextKey = this.contextCache().originalMap.get(normalizedVariable);

if (typeof contextKey === 'undefined') {
return undefined;
Expand Down Expand Up @@ -855,10 +980,19 @@ class Variables {

LOG_VARS && console.log('[%s] define <%s=%s>', this.path, name, value);

const context = this.context.set(name, value);
const oldContext = this.context;
const newContext = oldContext.set(name, value);

const oldCache = this.cache.get(oldContext) || computeContextCache(oldContext);
const newCache = cacheKey(
name,
createContextCache(oldCache)
);

this.cache.set(newContext, newCache);

return this.assign({
context
context: newContext
});
}

Expand Down Expand Up @@ -901,7 +1035,8 @@ class Variables {
* parent?: Variables | null
* context: VariableContext,
* value?: any,
* raw?: any
* raw?: any,
* __cache?: CacheMap
* } } options
*
* @return {Variables}
Expand All @@ -915,7 +1050,8 @@ class Variables {
parent = null,
context,
value,
raw
raw,
__cache
} = options;

if (!context) {
Expand All @@ -929,7 +1065,8 @@ class Variables {
context,
parent,
value,
raw
raw,
__cache
});
}

Expand Down
Loading