@cloudpss/template 0.6.14 → 0.6.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -1
- package/dist/builder.d.ts +3 -0
- package/dist/builder.d.ts.map +1 -0
- package/dist/builder.js +14 -0
- package/dist/builder.js.map +1 -0
- package/dist/{template/compiler.d.ts → compiler.d.ts} +5 -8
- package/dist/compiler.d.ts.map +1 -0
- package/dist/compiler.js +126 -0
- package/dist/compiler.js.map +1 -0
- package/dist/index.d.ts +16 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -2
- package/dist/index.js.map +1 -1
- package/dist/parser.d.ts +6 -23
- package/dist/parser.d.ts.map +1 -1
- package/dist/parser.js +27 -151
- package/dist/parser.js.map +1 -1
- package/dist/utils.d.ts +27 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +37 -0
- package/dist/utils.js.map +1 -0
- package/package.json +5 -1
- package/src/builder.ts +15 -0
- package/src/compiler.ts +121 -0
- package/src/index.ts +60 -4
- package/src/parser.ts +34 -172
- package/src/utils.ts +50 -0
- package/tests/template.ts +21 -60
- package/dist/template/compiler.d.ts.map +0 -1
- package/dist/template/compiler.js +0 -168
- package/dist/template/compiler.js.map +0 -1
- package/dist/template/index.d.ts +0 -42
- package/dist/template/index.d.ts.map +0 -1
- package/dist/template/index.js +0 -25
- package/dist/template/index.js.map +0 -1
- package/src/template/compiler.ts +0 -151
- package/src/template/index.ts +0 -74
- package/tests/parser.ts +0 -285
package/src/compiler.ts
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { unwrapFromVmValue } from '@mirascript/mirascript';
|
|
2
|
+
import { buildError } from './builder.js';
|
|
3
|
+
import type { TemplateFunction, TemplateOptions } from './index.js';
|
|
4
|
+
import { parseTemplate } from './parser.js';
|
|
5
|
+
import {
|
|
6
|
+
hasOwn,
|
|
7
|
+
isError,
|
|
8
|
+
isArrayBuffer,
|
|
9
|
+
copyArrayBuffer,
|
|
10
|
+
stringify,
|
|
11
|
+
toString,
|
|
12
|
+
isArrayBufferView,
|
|
13
|
+
isArray,
|
|
14
|
+
} from './utils.js';
|
|
15
|
+
|
|
16
|
+
/** 模板序列号 */
|
|
17
|
+
let seq = 0;
|
|
18
|
+
|
|
19
|
+
const $init = [unwrapFromVmValue] as const;
|
|
20
|
+
const $unwrapFromVmValue = 0;
|
|
21
|
+
|
|
22
|
+
/** 创建模板 */
|
|
23
|
+
export class TemplateCompiler {
|
|
24
|
+
constructor(
|
|
25
|
+
readonly template: unknown,
|
|
26
|
+
readonly options: Required<TemplateOptions>,
|
|
27
|
+
) {}
|
|
28
|
+
private readonly $: unknown[] = [...$init];
|
|
29
|
+
/** 放入 $,返回索引 */
|
|
30
|
+
private use(value: unknown): number {
|
|
31
|
+
this.$.push(value);
|
|
32
|
+
return this.$.length - 1;
|
|
33
|
+
}
|
|
34
|
+
/** 构建数组 */
|
|
35
|
+
private buildArray(arr: unknown[]): string {
|
|
36
|
+
let result = '[';
|
|
37
|
+
for (let i = 0, len = arr.length; i < len; i++) {
|
|
38
|
+
result += this.buildValue(arr[i]);
|
|
39
|
+
result += ',\n';
|
|
40
|
+
}
|
|
41
|
+
result += ']';
|
|
42
|
+
return result;
|
|
43
|
+
}
|
|
44
|
+
/** 构建 ArrayBuffer */
|
|
45
|
+
private buildArrayBuffer(buffer: ArrayBuffer | SharedArrayBuffer): string {
|
|
46
|
+
const copy = copyArrayBuffer(buffer, 0);
|
|
47
|
+
return `$[${this.use(copy)}].slice(0)`;
|
|
48
|
+
}
|
|
49
|
+
/** 构建 ArrayBufferView */
|
|
50
|
+
private buildArrayBufferView(view: ArrayBufferView): string {
|
|
51
|
+
const copy = copyArrayBuffer(view.buffer, view.byteOffset, view.byteLength);
|
|
52
|
+
return `new ${view.constructor.name}($[${this.use(copy)}].slice(0))`;
|
|
53
|
+
}
|
|
54
|
+
/** 构建可能为模板的字符串 */
|
|
55
|
+
private buildTemplate(value: string): string {
|
|
56
|
+
const template = parseTemplate(value);
|
|
57
|
+
if (typeof template == 'string') {
|
|
58
|
+
return stringify(template);
|
|
59
|
+
} else {
|
|
60
|
+
return `$[${$unwrapFromVmValue}]($[${this.use(template.value)}](context))`;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/** 构建对象 */
|
|
64
|
+
private buildObject(obj: Record<string, unknown>): string {
|
|
65
|
+
let result = '{';
|
|
66
|
+
for (const key in obj) {
|
|
67
|
+
if (!hasOwn(obj, key)) continue;
|
|
68
|
+
result += '[';
|
|
69
|
+
if (this.options.objectKeyMode === 'ignore') {
|
|
70
|
+
result += stringify(key);
|
|
71
|
+
} else {
|
|
72
|
+
result += this.buildTemplate(key);
|
|
73
|
+
}
|
|
74
|
+
result += ']:';
|
|
75
|
+
const value = obj[key];
|
|
76
|
+
result += this.buildValue(value);
|
|
77
|
+
result += ',\n';
|
|
78
|
+
}
|
|
79
|
+
result += '}';
|
|
80
|
+
return result;
|
|
81
|
+
}
|
|
82
|
+
/** 构建值 */
|
|
83
|
+
private buildValue(value: unknown): string {
|
|
84
|
+
if (value === null) return 'null';
|
|
85
|
+
if (value === undefined) return 'undefined';
|
|
86
|
+
if (value === true) return 'true';
|
|
87
|
+
if (value === false) return 'false';
|
|
88
|
+
if (typeof value == 'function') return 'undefined';
|
|
89
|
+
if (typeof value == 'symbol') return 'undefined';
|
|
90
|
+
if (typeof value == 'bigint') return `${value}n`;
|
|
91
|
+
if (typeof value == 'number') return String(value);
|
|
92
|
+
if (typeof value == 'string') return this.buildTemplate(value);
|
|
93
|
+
/* c8 ignore next */
|
|
94
|
+
if (typeof value != 'object') throw new Error(`Unsupported value: ${toString(value)}`);
|
|
95
|
+
if (value instanceof Date) return `new Date(${value.getTime()})`;
|
|
96
|
+
if (value instanceof RegExp) return value.toString();
|
|
97
|
+
if (isError(value)) return buildError(value);
|
|
98
|
+
if (isArray(value)) return this.buildArray(value);
|
|
99
|
+
if (isArrayBuffer(value)) return this.buildArrayBuffer(value);
|
|
100
|
+
if (isArrayBufferView(value)) return this.buildArrayBufferView(value);
|
|
101
|
+
return this.buildObject(value as Record<string, unknown>);
|
|
102
|
+
}
|
|
103
|
+
/** 构建模板 */
|
|
104
|
+
build(): TemplateFunction {
|
|
105
|
+
const source = this.buildValue(this.template);
|
|
106
|
+
try {
|
|
107
|
+
// eslint-disable-next-line @typescript-eslint/no-implied-eval, @typescript-eslint/no-unsafe-call
|
|
108
|
+
const result = new Function(
|
|
109
|
+
'$',
|
|
110
|
+
[
|
|
111
|
+
`//# sourceURL=cloudpss-template[${seq++}]`, // sourceURL 用于调试
|
|
112
|
+
`return (context) => (${source});`,
|
|
113
|
+
].join('\n'),
|
|
114
|
+
)(this.$) as TemplateFunction;
|
|
115
|
+
return result;
|
|
116
|
+
/* c8 ignore next 3 */
|
|
117
|
+
} catch (e) {
|
|
118
|
+
throw new Error(`Failed to compile template: ${source}\n${(e as Error).message}`, { cause: e });
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,60 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
export
|
|
1
|
+
import type { VmContext } from '@mirascript/mirascript';
|
|
2
|
+
import { TemplateCompiler } from './compiler.js';
|
|
3
|
+
|
|
4
|
+
export {
|
|
5
|
+
type VmContext,
|
|
6
|
+
VmError,
|
|
7
|
+
VmExtern,
|
|
8
|
+
VmModule,
|
|
9
|
+
VmFunction,
|
|
10
|
+
type VmAny,
|
|
11
|
+
type VmArray,
|
|
12
|
+
type VmConst,
|
|
13
|
+
type VmImmutable,
|
|
14
|
+
type VmPrimitive,
|
|
15
|
+
type VmScript,
|
|
16
|
+
type VmRecord,
|
|
17
|
+
type VmUninitialized,
|
|
18
|
+
type VmValue,
|
|
19
|
+
createVmContext,
|
|
20
|
+
isVmContext,
|
|
21
|
+
isVmExtern,
|
|
22
|
+
isVmModule,
|
|
23
|
+
isVmFunction,
|
|
24
|
+
isVmValue,
|
|
25
|
+
isVmAny,
|
|
26
|
+
isVmArray,
|
|
27
|
+
isVmCallable,
|
|
28
|
+
isVmConst,
|
|
29
|
+
isVmImmutable,
|
|
30
|
+
isVmPrimitive,
|
|
31
|
+
isVmRecord,
|
|
32
|
+
isVmScript,
|
|
33
|
+
isVmWrapper,
|
|
34
|
+
wrapToVmValue,
|
|
35
|
+
unwrapFromVmValue,
|
|
36
|
+
configCheckpoint,
|
|
37
|
+
} from '@mirascript/mirascript';
|
|
38
|
+
|
|
39
|
+
/** 模板选项 */
|
|
40
|
+
export interface TemplateOptions {
|
|
41
|
+
/**
|
|
42
|
+
* 对 object key 的处理方式
|
|
43
|
+
* - `template` 使用模板进行插值
|
|
44
|
+
* - `ignore` 原样输出
|
|
45
|
+
* @default 'template'
|
|
46
|
+
*/
|
|
47
|
+
objectKeyMode?: 'template' | 'ignore';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** 已编译的模板函数 */
|
|
51
|
+
export type TemplateFunction<T = unknown> = (context?: VmContext) => T;
|
|
52
|
+
|
|
53
|
+
/** 创建模板 */
|
|
54
|
+
export function template<T = unknown>(template: T, options: TemplateOptions = {}): TemplateFunction<T> {
|
|
55
|
+
const opt: Required<TemplateOptions> = {
|
|
56
|
+
objectKeyMode: 'template',
|
|
57
|
+
...options,
|
|
58
|
+
};
|
|
59
|
+
return new TemplateCompiler(template, opt).build() as TemplateFunction<T>;
|
|
60
|
+
}
|
package/src/parser.ts
CHANGED
|
@@ -1,173 +1,41 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
type: 'formula';
|
|
4
|
-
value: string;
|
|
5
|
-
};
|
|
6
|
-
|
|
7
|
-
/** 字符串插值模板 */
|
|
8
|
-
export type InterpolationTemplate = {
|
|
9
|
-
type: 'interpolation';
|
|
10
|
-
templates: string[];
|
|
11
|
-
values: string[];
|
|
12
|
-
};
|
|
1
|
+
import { compileSync, type VmScript } from '@mirascript/mirascript';
|
|
2
|
+
import { LRUCache } from 'lru-cache';
|
|
13
3
|
|
|
14
4
|
/** 字符串模板 */
|
|
15
|
-
export type Template =
|
|
5
|
+
export type Template =
|
|
6
|
+
| string
|
|
7
|
+
| {
|
|
8
|
+
type: TemplateType;
|
|
9
|
+
value: VmScript;
|
|
10
|
+
};
|
|
16
11
|
|
|
17
12
|
/** 字符串模板类型 */
|
|
18
|
-
export type TemplateType =
|
|
19
|
-
|
|
20
|
-
const PARSER_STATUS_TEXT = 0;
|
|
21
|
-
const PARSER_STATUS_EXPRESSION_SIMPLE = 1;
|
|
22
|
-
const PARSER_STATUS_EXPRESSION_COMPLEX = 2;
|
|
23
|
-
/** 解析状态机 */
|
|
24
|
-
type ParserStatus =
|
|
25
|
-
| typeof PARSER_STATUS_TEXT
|
|
26
|
-
| typeof PARSER_STATUS_EXPRESSION_SIMPLE
|
|
27
|
-
| typeof PARSER_STATUS_EXPRESSION_COMPLEX;
|
|
28
|
-
|
|
29
|
-
/** 检查字符满足 [a-zA-Z0-9_] */
|
|
30
|
-
function isIdentifierChar(char: string): boolean {
|
|
31
|
-
// eslint-disable-next-line unicorn/prefer-code-point
|
|
32
|
-
const charCode = char.charCodeAt(0);
|
|
33
|
-
return (
|
|
34
|
-
(charCode >= 97 && charCode <= 122) ||
|
|
35
|
-
(charCode >= 65 && charCode <= 90) ||
|
|
36
|
-
(charCode >= 48 && charCode <= 57) ||
|
|
37
|
-
charCode === 95
|
|
38
|
-
);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/** 检查字符满足 [a-zA-Z_] */
|
|
42
|
-
function isIdentifierFirstChar(char: string): boolean {
|
|
43
|
-
// eslint-disable-next-line unicorn/prefer-code-point
|
|
44
|
-
const charCode = char.charCodeAt(0);
|
|
45
|
-
return (charCode >= 97 && charCode <= 122) || (charCode >= 65 && charCode <= 90) || charCode === 95;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/** 字符串插值模板标识符 */
|
|
49
|
-
const INTERPOLATION_CHAR = '$';
|
|
50
|
-
/** 字符串插值模板表达式开始标识符 */
|
|
51
|
-
const INTERPOLATION_EXPRESSION_START = ['{', '[', '<', '('];
|
|
52
|
-
/** 字符串插值模板表达式结束标识符 */
|
|
53
|
-
const INTERPOLATION_EXPRESSION_END = ['}', ']', '>', ')'];
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* 解析字符串插值模板
|
|
57
|
-
* @example parseInterpolationImpl("hello $name! I am $age years old. I'll be ${age+1} next year. Give me $$100.")
|
|
58
|
-
* // {
|
|
59
|
-
* // type: 'interpolation',
|
|
60
|
-
* // templates: ['hello ', '! I am ', ' years old. I\'ll be ', ' next year. Give me $$100.'],
|
|
61
|
-
* // values: ['name', 'age', 'age+1']
|
|
62
|
-
* // }
|
|
63
|
-
*/
|
|
64
|
-
function parseInterpolationImpl(template: string, start: number, length: number): InterpolationTemplate {
|
|
65
|
-
const templates: string[] = [];
|
|
66
|
-
const values: string[] = [];
|
|
67
|
-
let currentTemplate = '';
|
|
68
|
-
let currentValue = '';
|
|
69
|
-
let expressionComplexDepth = 0;
|
|
70
|
-
let status: ParserStatus = PARSER_STATUS_TEXT;
|
|
71
|
-
let complexExpressionStartType = -1;
|
|
72
|
-
|
|
73
|
-
const end = start + length - 1;
|
|
74
|
-
for (let i = start; i <= end; i++) {
|
|
75
|
-
if (status === PARSER_STATUS_TEXT) {
|
|
76
|
-
const nextInterpolationChar = template.indexOf(INTERPOLATION_CHAR, i);
|
|
77
|
-
if (nextInterpolationChar < 0 || nextInterpolationChar >= end) {
|
|
78
|
-
// No more interpolation
|
|
79
|
-
currentTemplate += template.slice(i, end + 1);
|
|
80
|
-
break;
|
|
81
|
-
}
|
|
82
|
-
currentTemplate += template.slice(i, nextInterpolationChar);
|
|
83
|
-
i = nextInterpolationChar;
|
|
84
|
-
const nextChar = template.charAt(nextInterpolationChar + 1);
|
|
85
|
-
|
|
86
|
-
complexExpressionStartType = INTERPOLATION_EXPRESSION_START.indexOf(nextChar);
|
|
87
|
-
if (complexExpressionStartType >= 0) {
|
|
88
|
-
// Start of complex expression
|
|
89
|
-
templates.push(currentTemplate);
|
|
90
|
-
currentTemplate = '';
|
|
91
|
-
status = PARSER_STATUS_EXPRESSION_COMPLEX;
|
|
92
|
-
expressionComplexDepth = 1;
|
|
93
|
-
i++;
|
|
94
|
-
continue;
|
|
95
|
-
}
|
|
96
|
-
if (isIdentifierFirstChar(nextChar)) {
|
|
97
|
-
// Start of simple expression
|
|
98
|
-
templates.push(currentTemplate);
|
|
99
|
-
currentTemplate = '';
|
|
100
|
-
currentValue = nextChar;
|
|
101
|
-
status = PARSER_STATUS_EXPRESSION_SIMPLE;
|
|
102
|
-
i++;
|
|
103
|
-
continue;
|
|
104
|
-
}
|
|
105
|
-
// Not an expression
|
|
106
|
-
currentTemplate += INTERPOLATION_CHAR;
|
|
107
|
-
continue;
|
|
108
|
-
}
|
|
109
|
-
const char = template.charAt(i);
|
|
110
|
-
if (status === PARSER_STATUS_EXPRESSION_SIMPLE) {
|
|
111
|
-
if (isIdentifierChar(char)) {
|
|
112
|
-
currentValue += char;
|
|
113
|
-
continue;
|
|
114
|
-
}
|
|
115
|
-
// End of expression
|
|
116
|
-
values.push(currentValue);
|
|
117
|
-
currentValue = '';
|
|
118
|
-
status = PARSER_STATUS_TEXT;
|
|
119
|
-
i--;
|
|
120
|
-
continue;
|
|
121
|
-
}
|
|
122
|
-
if (status === PARSER_STATUS_EXPRESSION_COMPLEX) {
|
|
123
|
-
if (char === INTERPOLATION_EXPRESSION_START[complexExpressionStartType]) {
|
|
124
|
-
expressionComplexDepth++;
|
|
125
|
-
} else if (char === INTERPOLATION_EXPRESSION_END[complexExpressionStartType]) {
|
|
126
|
-
expressionComplexDepth--;
|
|
127
|
-
if (expressionComplexDepth === 0) {
|
|
128
|
-
// End of expression
|
|
129
|
-
values.push(currentValue.trim());
|
|
130
|
-
currentValue = '';
|
|
131
|
-
status = PARSER_STATUS_TEXT;
|
|
132
|
-
continue;
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
currentValue += char;
|
|
136
|
-
continue;
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
if (status === PARSER_STATUS_TEXT) {
|
|
140
|
-
templates.push(currentTemplate);
|
|
141
|
-
} else if (status === PARSER_STATUS_EXPRESSION_SIMPLE) {
|
|
142
|
-
values.push(currentValue);
|
|
143
|
-
templates.push('');
|
|
144
|
-
} else {
|
|
145
|
-
throw new Error('Unexpected end of input');
|
|
146
|
-
}
|
|
13
|
+
export type TemplateType = 'interpolation' | 'formula';
|
|
147
14
|
|
|
148
|
-
|
|
149
|
-
type: 'interpolation',
|
|
150
|
-
templates,
|
|
151
|
-
values,
|
|
152
|
-
};
|
|
153
|
-
}
|
|
15
|
+
const CACHE_SIZE = 100;
|
|
154
16
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
}
|
|
17
|
+
const formula = new LRUCache<string, Template & { type: 'formula' }>({
|
|
18
|
+
max: CACHE_SIZE,
|
|
19
|
+
memoMethod: (template: string) => {
|
|
20
|
+
const formula = template.slice(1);
|
|
21
|
+
const value = compileSync(formula, { input_mode: 'Script' });
|
|
22
|
+
return {
|
|
23
|
+
type: 'formula',
|
|
24
|
+
value: value,
|
|
25
|
+
};
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
const interpolation = new LRUCache<string, Template & { type: 'interpolation' }>({
|
|
29
|
+
max: CACHE_SIZE,
|
|
30
|
+
memoMethod: (template: string) => {
|
|
31
|
+
const expr = template.slice(1);
|
|
32
|
+
const value = compileSync(expr, { input_mode: 'Template' });
|
|
33
|
+
return {
|
|
34
|
+
type: 'interpolation',
|
|
35
|
+
value: value,
|
|
36
|
+
};
|
|
37
|
+
},
|
|
38
|
+
});
|
|
171
39
|
|
|
172
40
|
/**
|
|
173
41
|
* 解析字符串模板
|
|
@@ -177,18 +45,12 @@ export function parseInterpolation(template: string, start?: number, length?: nu
|
|
|
177
45
|
* - 否则表示是一个普通字符串
|
|
178
46
|
*/
|
|
179
47
|
export function parseTemplate(template: string): Template {
|
|
180
|
-
if (!template) return '';
|
|
181
48
|
if (template.length <= 1) return template;
|
|
182
49
|
if (template.startsWith('=')) {
|
|
183
|
-
return
|
|
184
|
-
type: 'formula',
|
|
185
|
-
value: template.slice(1),
|
|
186
|
-
};
|
|
50
|
+
return formula.memo(template);
|
|
187
51
|
}
|
|
188
52
|
if (template.startsWith('$')) {
|
|
189
|
-
|
|
190
|
-
if (result.values.length === 0) return result.templates[0]!;
|
|
191
|
-
return result;
|
|
53
|
+
return interpolation.memo(template);
|
|
192
54
|
}
|
|
193
55
|
return template;
|
|
194
56
|
}
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/** 是否为 ArrayBuffer */
|
|
2
|
+
export let isArrayBuffer: (value: object) => value is ArrayBuffer | SharedArrayBuffer;
|
|
3
|
+
|
|
4
|
+
/** 是否为 Error */
|
|
5
|
+
export let isError: (value: unknown) => value is Error;
|
|
6
|
+
|
|
7
|
+
declare global {
|
|
8
|
+
/** @inheritdoc */
|
|
9
|
+
interface ErrorConstructor {
|
|
10
|
+
/** @inheritdoc */
|
|
11
|
+
isError?(this: void, value: unknown): value is Error;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (typeof Error.isError == 'function') {
|
|
16
|
+
isError = Error.isError;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (typeof process == 'object' && typeof process.getBuiltinModule == 'function') {
|
|
20
|
+
const typeUtils = process.getBuiltinModule('node:util/types');
|
|
21
|
+
isArrayBuffer = typeUtils.isAnyArrayBuffer;
|
|
22
|
+
isError = typeUtils.isNativeError;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
isError ??= (value: unknown): value is Error => {
|
|
26
|
+
return value instanceof Error;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
isArrayBuffer ??= (value: object): value is ArrayBuffer | SharedArrayBuffer => {
|
|
30
|
+
const tag = toString(value);
|
|
31
|
+
return tag === '[object ArrayBuffer]' || tag === '[object SharedArrayBuffer]';
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Object.prototype.toString.call 的快捷方式
|
|
36
|
+
*/
|
|
37
|
+
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
38
|
+
export const toString = Function.call.bind(Object.prototype.toString) as (value: unknown) => string;
|
|
39
|
+
export const { hasOwn } = Object;
|
|
40
|
+
export const { isArray } = Array;
|
|
41
|
+
export const isArrayBufferView = ArrayBuffer.isView.bind(ArrayBuffer);
|
|
42
|
+
export const { stringify } = JSON;
|
|
43
|
+
/**
|
|
44
|
+
* 获取 ArrayBuffer 的拷贝
|
|
45
|
+
*/
|
|
46
|
+
export function copyArrayBuffer(buffer: ArrayBuffer | SharedArrayBuffer, start: number, length?: number): ArrayBuffer {
|
|
47
|
+
const copy = new ArrayBuffer(length ?? buffer.byteLength - start);
|
|
48
|
+
new Uint8Array(copy).set(new Uint8Array(buffer, start, length));
|
|
49
|
+
return copy;
|
|
50
|
+
}
|
package/tests/template.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { randomBytes, randomFillSync } from 'node:crypto';
|
|
2
|
-
import {
|
|
3
|
-
import { template } from '@cloudpss/template';
|
|
2
|
+
import { template, createVmContext } from '@cloudpss/template';
|
|
4
3
|
|
|
5
4
|
describe('template', () => {
|
|
6
5
|
it('should handle primitives', () => {
|
|
@@ -147,7 +146,7 @@ describe('template', () => {
|
|
|
147
146
|
randomFillSync(new Uint8Array(buffer));
|
|
148
147
|
const result = template(buffer)();
|
|
149
148
|
expect(new Uint8Array(result)).toEqual(new Uint8Array(buffer));
|
|
150
|
-
expect(result).toBeInstanceOf(
|
|
149
|
+
expect(result).toBeInstanceOf(ArrayBuffer);
|
|
151
150
|
expect(result).not.toBe(buffer);
|
|
152
151
|
});
|
|
153
152
|
});
|
|
@@ -199,9 +198,11 @@ describe('template', () => {
|
|
|
199
198
|
});
|
|
200
199
|
it('should build interpolated strings', () => {
|
|
201
200
|
const t = template({ hello: '$Hello, ${name}!', not: 'Hello $name', array: ['$$name', '$not'] });
|
|
202
|
-
const result = t(
|
|
203
|
-
|
|
204
|
-
|
|
201
|
+
const result = t(
|
|
202
|
+
createVmContext({
|
|
203
|
+
name: 'world',
|
|
204
|
+
}),
|
|
205
|
+
);
|
|
205
206
|
expect(result).toEqual({
|
|
206
207
|
hello: 'Hello, world!',
|
|
207
208
|
not: 'Hello $name',
|
|
@@ -227,7 +228,7 @@ describe('template', () => {
|
|
|
227
228
|
describe('should work while objectKeyMode is', () => {
|
|
228
229
|
it('template', () => {
|
|
229
230
|
const obj = { $: 1, _: 2, '=x': 3, $$y: 4 };
|
|
230
|
-
const result = template(obj, { objectKeyMode: 'template' })({ x: 11n, y: 12 });
|
|
231
|
+
const result = template(obj, { objectKeyMode: 'template' })(createVmContext({}, { x: 11n, y: 12 }));
|
|
231
232
|
expect(result).toEqual({
|
|
232
233
|
$: 1,
|
|
233
234
|
_: 2,
|
|
@@ -237,7 +238,7 @@ describe('template', () => {
|
|
|
237
238
|
});
|
|
238
239
|
it('ignore', () => {
|
|
239
240
|
const obj = { $: 1, _: 2, '=x': 3, $$y: 4 };
|
|
240
|
-
const result = template(obj, { objectKeyMode: 'ignore' })({ x: 11n, y: 12 });
|
|
241
|
+
const result = template(obj, { objectKeyMode: 'ignore' })(createVmContext({}, { x: 11n, y: 12 }));
|
|
241
242
|
expect(result).toEqual({
|
|
242
243
|
$: 1,
|
|
243
244
|
_: 2,
|
|
@@ -247,62 +248,22 @@ describe('template', () => {
|
|
|
247
248
|
});
|
|
248
249
|
});
|
|
249
250
|
|
|
250
|
-
describe('should work with
|
|
251
|
-
it('with formula', () => {
|
|
252
|
-
const obj = { a: '=a', b: '=b' };
|
|
253
|
-
const result = template(obj)({ a: 1 });
|
|
254
|
-
expect(result).toEqual({ a: 1, b: undefined });
|
|
255
|
-
});
|
|
256
|
-
it('with interpolation', () => {
|
|
257
|
-
const obj = { a: '$$a', b: '$$b' };
|
|
258
|
-
const result = template(obj)({ a: 1 });
|
|
259
|
-
expect(result).toEqual({ a: '1', b: '' });
|
|
260
|
-
});
|
|
261
|
-
});
|
|
262
|
-
describe('should work with custom evaluator', () => {
|
|
251
|
+
describe('should work with evaluator', () => {
|
|
263
252
|
it('with formula', () => {
|
|
264
|
-
const obj = { a: '=a', b: '=b' };
|
|
265
|
-
const result = template(obj,
|
|
266
|
-
|
|
267
|
-
inject: { a: 'abcd', b: 11n },
|
|
268
|
-
compile: (expression) => `evaluator.${expression}`,
|
|
269
|
-
},
|
|
270
|
-
})({ a: 1 });
|
|
271
|
-
expect(result).toEqual({ a: 'abcd', b: 11n });
|
|
253
|
+
const obj = { a: '=a', b: '=b', complex: '=a + (b ?? 1)' };
|
|
254
|
+
const result = template(obj)(createVmContext({ a: 1, b: undefined }));
|
|
255
|
+
expect(result).toEqual({ a: 1, b: null, complex: 2 });
|
|
272
256
|
});
|
|
273
257
|
it('with interpolation', () => {
|
|
274
|
-
const obj = { a: '$$a', b: '$$b' };
|
|
275
|
-
const result = template(obj,
|
|
276
|
-
|
|
277
|
-
inject: { a: 'abcd', b: 11n },
|
|
278
|
-
compile: (expression) => `evaluator.${expression}`,
|
|
279
|
-
},
|
|
280
|
-
})({ a: 1 });
|
|
281
|
-
expect(result).toEqual({ a: 'abcd', b: '11' });
|
|
282
|
-
});
|
|
283
|
-
});
|
|
284
|
-
describe('should work with async evaluator', () => {
|
|
285
|
-
it('with formula', async () => {
|
|
286
|
-
const obj = { a: '=a', b: '=b' };
|
|
287
|
-
const result = template(obj, {
|
|
288
|
-
evaluator: {
|
|
289
|
-
inject: { a: 'abcd', b: 11n, timeout },
|
|
290
|
-
async: true,
|
|
291
|
-
compile: (expression) => `await evaluator.timeout(100, evaluator.${expression})`,
|
|
292
|
-
},
|
|
293
|
-
})({ a: 1 });
|
|
294
|
-
await expect(result).resolves.toEqual({ a: 'abcd', b: 11n });
|
|
258
|
+
const obj = { a: '$$a', b: '$$b', complex: '$Value is ${a}-${b}' };
|
|
259
|
+
const result = template(obj)(createVmContext({ a: 1, b: undefined }));
|
|
260
|
+
expect(result).toEqual({ a: '1', b: '', complex: 'Value is 1-' });
|
|
295
261
|
});
|
|
296
|
-
it('
|
|
297
|
-
const obj = { a: '
|
|
298
|
-
const
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
async: true,
|
|
302
|
-
compile: (expression) => `await evaluator.timeout(100, evaluator.${expression})`,
|
|
303
|
-
},
|
|
304
|
-
})({ a: 1 });
|
|
305
|
-
await expect(result).resolves.toEqual({ a: 'abcd', b: '11' });
|
|
262
|
+
it('has extern values', () => {
|
|
263
|
+
const obj = { a: '=ab', b: '$$(ta.length)', t: '=ta' };
|
|
264
|
+
const externValues = { ab: new ArrayBuffer(8), ta: new Uint8Array(4) };
|
|
265
|
+
const result = template(obj)(createVmContext({}, externValues));
|
|
266
|
+
expect(result).toEqual({ a: externValues.ab, b: '4', t: externValues.ta });
|
|
306
267
|
});
|
|
307
268
|
});
|
|
308
269
|
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"compiler.d.ts","sourceRoot":"","sources":["../../src/template/compiler.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAuBpE,WAAW;AACX,qBAAa,gBAAgB;IAErB,QAAQ,CAAC,QAAQ,EAAE,OAAO;IAC1B,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,eAAe,CAAC;gBADlC,QAAQ,EAAE,OAAO,EACjB,OAAO,EAAE,QAAQ,CAAC,eAAe,CAAC;IAE/C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA8B;IACrD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAiB;IAC1C,WAAW;IACX,OAAO,CAAC,SAAS;IAOjB,YAAY;IACZ,OAAO,CAAC,WAAW;IAgBnB,eAAe;IACf,OAAO,CAAC,UAAU;IAUlB,WAAW;IACX,OAAO,CAAC,UAAU;IAGlB,qBAAqB;IACrB,OAAO,CAAC,gBAAgB;IAIxB,yBAAyB;IACzB,OAAO,CAAC,oBAAoB;IAI5B,WAAW;IACX,OAAO,CAAC,WAAW;IAqBnB,UAAU;IACV,OAAO,CAAC,UAAU;IAoBlB,WAAW;IACX,KAAK,IAAI,gBAAgB;CAwB5B"}
|