-
Notifications
You must be signed in to change notification settings - Fork 3.9k
/
resolvable.ts
173 lines (152 loc) · 4.62 KB
/
resolvable.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
import { IConstruct } from "./construct-compat";
import { TokenString } from "./private/encoding";
import { TokenMap } from "./private/token-map";
import { TokenizedStringFragments } from "./string-fragments";
/**
* Current resolution context for tokens
*/
export interface IResolveContext {
/**
* The scope from which resolution has been initiated
*/
readonly scope: IConstruct;
/**
* True when we are still preparing, false if we're rendering the final output
*/
readonly preparing: boolean;
/**
* Resolve an inner object
*/
resolve(x: any): any;
/**
* Use this postprocessor after the entire token structure has been resolved
*/
registerPostProcessor(postProcessor: IPostProcessor): void;
}
/**
* Interface for values that can be resolvable later
*
* Tokens are special objects that participate in synthesis.
*/
export interface IResolvable {
/**
* The creation stack of this resolvable which will be appended to errors
* thrown during resolution.
*
* If this returns an empty array the stack will not be attached.
*/
readonly creationStack: string[];
/**
* Produce the Token's value at resolution time
*/
resolve(context: IResolveContext): any;
/**
* Return a string representation of this resolvable object.
*
* Returns a reversible string representation.
*/
toString(): string;
}
/**
* A Token that can post-process the complete resolved value, after resolve() has recursed over it
*/
export interface IPostProcessor {
/**
* Process the completely resolved value, after full recursion/resolution has happened
*/
postProcess(input: any, context: IResolveContext): any;
}
/**
* How to resolve tokens
*/
export interface ITokenResolver {
/**
* Resolve a single token
*/
resolveToken(t: IResolvable, context: IResolveContext, postProcessor: IPostProcessor): any;
/**
* Resolve a string with at least one stringified token in it
*
* (May use concatenation)
*/
resolveString(s: TokenizedStringFragments, context: IResolveContext): any;
/**
* Resolve a tokenized list
*/
resolveList(l: string[], context: IResolveContext): any;
}
/**
* Function used to concatenate symbols in the target document language
*
* Interface so it could potentially be exposed over jsii.
*
* @experimental
*/
export interface IFragmentConcatenator {
/**
* Join the fragment on the left and on the right
*/
join(left: any | undefined, right: any | undefined): any;
}
/**
* Converts all fragments to strings and concats those
*
* Drops 'undefined's.
*/
export class StringConcat implements IFragmentConcatenator {
public join(left: any | undefined, right: any | undefined): any {
if (left === undefined) { return right !== undefined ? `${right}` : undefined; }
if (right === undefined) { return `${left}`; }
return `${left}${right}`;
}
}
/**
* Default resolver implementation
*
* @experimental
*/
export class DefaultTokenResolver implements ITokenResolver {
constructor(private readonly concat: IFragmentConcatenator) {
}
/**
* Default Token resolution
*
* Resolve the Token, recurse into whatever it returns,
* then finally post-process it.
*/
public resolveToken(t: IResolvable, context: IResolveContext, postProcessor: IPostProcessor) {
try {
let resolved = t.resolve(context);
// The token might have returned more values that need resolving, recurse
resolved = context.resolve(resolved);
resolved = postProcessor.postProcess(resolved, context);
return resolved;
} catch (e) {
let message = `Resolution error: ${e.message}.`;
if (t.creationStack && t.creationStack.length > 0) {
message += `\nObject creation stack:\n at ${t.creationStack.join('\n at ')}`;
}
e.message = message;
throw e;
}
}
/**
* Resolve string fragments to Tokens
*/
public resolveString(fragments: TokenizedStringFragments, context: IResolveContext) {
return fragments.mapTokens({ mapToken: context.resolve }).join(this.concat);
}
public resolveList(xs: string[], context: IResolveContext) {
// Must be a singleton list token, because concatenation is not allowed.
if (xs.length !== 1) {
throw new Error(`Cannot add elements to list token, got: ${xs}`);
}
const str = TokenString.forListToken(xs[0]);
const tokenMap = TokenMap.instance();
const fragments = str.split(tokenMap.lookupToken.bind(tokenMap));
if (fragments.length !== 1) {
throw new Error(`Cannot concatenate strings in a tokenized string array, got: ${xs[0]}`);
}
return fragments.mapTokens({ mapToken: context.resolve }).firstValue;
}
}