@weborigami/language 0.6.4 → 0.6.6
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/main.js +4 -0
- package/package.json +2 -2
- package/src/compiler/optimize.js +17 -11
- package/src/compiler/origami.pegjs +192 -58
- package/src/compiler/parse.js +2182 -1172
- package/src/compiler/parserHelpers.js +172 -10
- package/src/project/projectConfig.js +2 -2
- package/src/project/projectGlobals.js +2 -2
- package/src/project/projectRoot.js +5 -54
- package/src/project/projectRootFromPath.js +58 -0
- package/src/protocols/fetchAndHandleExtension.js +5 -0
- package/src/protocols/package.js +26 -39
- package/src/runtime/expressionObject.js +162 -150
- package/src/runtime/handleExtension.js +18 -2
- package/src/runtime/ops.js +30 -9
- package/test/compiler/compile.test.js +39 -0
- package/test/compiler/optimize.test.js +2 -1
- package/test/compiler/parse.test.js +355 -112
- package/test/project/{projectRoot.test.js → projectRootFromPath.test.js} +5 -5
- package/test/protocols/package.test.js +6 -1
- package/test/runtime/expressionObject.test.js +25 -10
- package/test/runtime/ops.test.js +32 -4
|
@@ -9,20 +9,22 @@ import {
|
|
|
9
9
|
import handleExtension from "./handleExtension.js";
|
|
10
10
|
import { evaluate, ops } from "./internal.js";
|
|
11
11
|
|
|
12
|
+
export const KEY_TYPE = {
|
|
13
|
+
STRING: 0, // Simple string key: `a: 1`
|
|
14
|
+
COMPUTED: 1, // Computed key: `[code]: 1`
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const VALUE_TYPE = {
|
|
18
|
+
PRIMITIVE: 0, // Primitive value: `a: 1`
|
|
19
|
+
EAGER: 1, // Calculated immediately: `a: 1 + 1`
|
|
20
|
+
GETTER: 2, // Calculated on demand: `a = fn()`
|
|
21
|
+
};
|
|
22
|
+
|
|
12
23
|
/**
|
|
13
24
|
* Given an array of entries with string keys and Origami code values (arrays of
|
|
14
25
|
* ops and operands), return an object with the same keys defining properties
|
|
15
26
|
* whose getters evaluate the code.
|
|
16
|
-
|
|
17
|
-
* The value can take three forms:
|
|
18
|
-
*
|
|
19
|
-
* 1. A primitive value (string, etc.). This will be defined directly as an
|
|
20
|
-
* object property.
|
|
21
|
-
* 1. An eager (as opposed to lazy) code entry. This will be evaluated during
|
|
22
|
-
* this call and its result defined as an object property.
|
|
23
|
-
* 1. A code entry that starts with ops.getter. This will be defined as a
|
|
24
|
-
* property getter on the object.
|
|
25
|
-
*
|
|
27
|
+
|
|
26
28
|
* @param {*} entries
|
|
27
29
|
* @param {import("../../index.ts").RuntimeState} [state]
|
|
28
30
|
*/
|
|
@@ -35,191 +37,201 @@ export default async function expressionObject(entries, state = {}) {
|
|
|
35
37
|
}
|
|
36
38
|
setParent(object, parent);
|
|
37
39
|
|
|
38
|
-
//
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
let hasLazyProperties = false;
|
|
49
|
-
for (let i = 0; i < entries.length; i++) {
|
|
50
|
-
let key = computedKeys[i];
|
|
51
|
-
let value = entries[i][1];
|
|
52
|
-
|
|
53
|
-
// Determine if we need to define a getter or a regular property. If the key
|
|
54
|
-
// has an extension, we need to define a getter. If the value is code (an
|
|
55
|
-
// array), we need to define a getter -- but if that code takes the form
|
|
56
|
-
// [ops.getter, <primitive>] or [ops.literal, <value>], we can define a
|
|
57
|
-
// regular property.
|
|
58
|
-
let defineProperty;
|
|
59
|
-
const extname = extension.extname(key);
|
|
60
|
-
if (extname) {
|
|
61
|
-
defineProperty = false;
|
|
62
|
-
} else if (!(value instanceof Array)) {
|
|
63
|
-
defineProperty = true;
|
|
64
|
-
} else if (value[0] === ops.getter && !(value[1] instanceof Array)) {
|
|
65
|
-
defineProperty = true;
|
|
66
|
-
value = value[1];
|
|
67
|
-
} else if (value[0] === ops.literal) {
|
|
68
|
-
defineProperty = true;
|
|
69
|
-
value = value[1];
|
|
70
|
-
} else {
|
|
71
|
-
defineProperty = false;
|
|
40
|
+
// The object in Map form for use on the stack
|
|
41
|
+
const map = new ObjectMap(object);
|
|
42
|
+
|
|
43
|
+
// Preparation: gather information about all properties
|
|
44
|
+
const infos = entries.map(([key, value]) => propertyInfo(key, value));
|
|
45
|
+
|
|
46
|
+
// First pass: define all properties with plain string keys
|
|
47
|
+
for (const info of infos) {
|
|
48
|
+
if (info.keyType === KEY_TYPE.STRING) {
|
|
49
|
+
defineProperty(object, info, state, map);
|
|
72
50
|
}
|
|
51
|
+
}
|
|
73
52
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if (
|
|
77
|
-
|
|
78
|
-
|
|
53
|
+
// Second pass: redefine eager string-keyed properties with actual values.
|
|
54
|
+
for (const info of infos) {
|
|
55
|
+
if (
|
|
56
|
+
info.keyType === KEY_TYPE.STRING &&
|
|
57
|
+
info.valueType === VALUE_TYPE.EAGER
|
|
58
|
+
) {
|
|
59
|
+
await redefineProperty(object, info);
|
|
79
60
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
// Property getter
|
|
92
|
-
let code;
|
|
93
|
-
if (value[0] === ops.getter) {
|
|
94
|
-
hasLazyProperties = true;
|
|
95
|
-
code = value[1];
|
|
96
|
-
} else {
|
|
97
|
-
eagerProperties.push(key);
|
|
98
|
-
code = value;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const get = async () => {
|
|
102
|
-
tree ??= new ObjectMap(object);
|
|
103
|
-
const newState = Object.assign({}, state, { object: tree });
|
|
104
|
-
const result = await evaluate(code, newState);
|
|
105
|
-
return extname ? handleExtension(result, key, tree) : result;
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
Object.defineProperty(object, key, {
|
|
109
|
-
configurable: true,
|
|
110
|
-
enumerable,
|
|
111
|
-
get,
|
|
112
|
-
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Third pass: define all computed properties. These may refer to the
|
|
64
|
+
// properties we just defined.
|
|
65
|
+
for (const info of infos) {
|
|
66
|
+
if (info.keyType === KEY_TYPE.COMPUTED) {
|
|
67
|
+
const newState = Object.assign({}, state, { object: map });
|
|
68
|
+
const key = await evaluate(/** @type {any} */ (info.key), newState);
|
|
69
|
+
// Destructively update the property info with the computed key
|
|
70
|
+
info.key = key;
|
|
71
|
+
defineProperty(object, info, state, map);
|
|
113
72
|
}
|
|
114
73
|
}
|
|
115
74
|
|
|
116
|
-
//
|
|
75
|
+
// Fourth pass: redefine eager computed-keyed properties with actual values.
|
|
76
|
+
for (const info of infos) {
|
|
77
|
+
if (
|
|
78
|
+
info.keyType === KEY_TYPE.COMPUTED &&
|
|
79
|
+
info.valueType === VALUE_TYPE.EAGER
|
|
80
|
+
) {
|
|
81
|
+
await redefineProperty(object, info);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Attach a keys method, where keys for primitive/eager properties with
|
|
86
|
+
// maplike values get a trailing slash.
|
|
117
87
|
Object.defineProperty(object, symbols.keys, {
|
|
118
88
|
configurable: true,
|
|
119
89
|
enumerable: false,
|
|
120
90
|
value: () =>
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
eagerProperties,
|
|
125
|
-
propertyIsEnumerable,
|
|
126
|
-
entries
|
|
127
|
-
),
|
|
91
|
+
infos
|
|
92
|
+
.filter((info) => info.enumerable)
|
|
93
|
+
.map((info) => normalizeKey(info, object)),
|
|
128
94
|
writable: true,
|
|
129
95
|
});
|
|
130
96
|
|
|
131
|
-
//
|
|
132
|
-
//
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
97
|
+
// TODO: If there are any getters, mark the object as async. Note: this code
|
|
98
|
+
// was added so that Tree.from() could know whether to return an ObjectMap or
|
|
99
|
+
// a hypothetical AsyncObjectMap, which in turn would let a map operation know
|
|
100
|
+
// whether to expect async property values. const hasGetters =
|
|
101
|
+
// infos.some((info) => info.valueType === VALUE_TYPE.GETTER); if (hasGetters)
|
|
102
|
+
// { Object.defineProperty(object, symbols.async, { configurable: true,
|
|
103
|
+
// enumerable: false, value: true, writable: true,
|
|
104
|
+
// });
|
|
105
|
+
// }
|
|
106
|
+
|
|
107
|
+
return object;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Define a single property on the object
|
|
112
|
+
*/
|
|
113
|
+
function defineProperty(object, propertyInfo, state, map) {
|
|
114
|
+
let { enumerable, hasExtension, key, value, valueType } = propertyInfo;
|
|
115
|
+
if (valueType == VALUE_TYPE.PRIMITIVE) {
|
|
116
|
+
// Define simple property
|
|
136
117
|
Object.defineProperty(object, key, {
|
|
137
118
|
configurable: true,
|
|
138
119
|
enumerable,
|
|
139
120
|
value,
|
|
140
121
|
writable: true,
|
|
141
122
|
});
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
if (hasLazyProperties) {
|
|
146
|
-
Object.defineProperty(object, symbols.async, {
|
|
123
|
+
} else {
|
|
124
|
+
// Eager or getter; will evaluate eager property later
|
|
125
|
+
Object.defineProperty(object, key, {
|
|
147
126
|
configurable: true,
|
|
148
|
-
enumerable
|
|
149
|
-
|
|
150
|
-
|
|
127
|
+
enumerable,
|
|
128
|
+
get: async () => {
|
|
129
|
+
const newState = Object.assign({}, state, { object: map });
|
|
130
|
+
const result = await evaluate(value, newState);
|
|
131
|
+
return hasExtension ? handleExtension(result, key, map) : result;
|
|
132
|
+
},
|
|
151
133
|
});
|
|
152
134
|
}
|
|
153
|
-
|
|
154
|
-
return object;
|
|
155
135
|
}
|
|
156
136
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
if (key[0] === "(" && key[key.length - 1] === ")") {
|
|
166
|
-
// Non-enumerable property, remove parentheses. This doesn't come up in the
|
|
167
|
-
// constructor, but can happen in situations encountered by the compiler's
|
|
168
|
-
// optimizer.
|
|
169
|
-
key = key.slice(1, -1);
|
|
170
|
-
}
|
|
137
|
+
/**
|
|
138
|
+
* Return a normalized version of the property key for use in the keys() method.
|
|
139
|
+
* Among other things, this adds trailing slashes to keys that correspond to
|
|
140
|
+
* maplike values.
|
|
141
|
+
*/
|
|
142
|
+
export function normalizeKey(propertyInfo, object = null) {
|
|
143
|
+
const { key, value, valueType } = propertyInfo;
|
|
171
144
|
|
|
172
145
|
if (trailingSlash.has(key)) {
|
|
173
146
|
// Explicit trailing slash, return as is
|
|
174
147
|
return key;
|
|
175
148
|
}
|
|
176
149
|
|
|
177
|
-
// If
|
|
178
|
-
if (
|
|
150
|
+
// If actual property value is maplike, add slash
|
|
151
|
+
if (
|
|
152
|
+
(valueType === VALUE_TYPE.EAGER || valueType === VALUE_TYPE.PRIMITIVE) &&
|
|
153
|
+
Tree.isMaplike(object?.[key])
|
|
154
|
+
) {
|
|
179
155
|
return trailingSlash.add(key);
|
|
180
156
|
}
|
|
181
157
|
|
|
158
|
+
// Look at value code to see if it will produce a maplike value
|
|
182
159
|
if (!(value instanceof Array)) {
|
|
183
160
|
// Can't be a subtree
|
|
184
161
|
return trailingSlash.remove(key);
|
|
185
162
|
}
|
|
186
|
-
|
|
187
|
-
// If we're dealing with a getter, work with what that gets
|
|
188
|
-
if (value[0] === ops.getter) {
|
|
189
|
-
value = value[1];
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// If entry will definitely create a subtree, add a trailing slash
|
|
193
163
|
if (value[0] === ops.object) {
|
|
194
|
-
//
|
|
164
|
+
// Creates an object; maplike
|
|
195
165
|
return trailingSlash.add(key);
|
|
196
166
|
}
|
|
197
|
-
|
|
198
|
-
// See if it looks a merged object
|
|
199
167
|
if (value[1] === "_result" && value[0][0] === ops.object) {
|
|
200
|
-
//
|
|
168
|
+
// Merges an object; maplike
|
|
201
169
|
return trailingSlash.add(key);
|
|
202
170
|
}
|
|
203
171
|
|
|
172
|
+
// Return as is
|
|
204
173
|
return key;
|
|
205
174
|
}
|
|
206
175
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
176
|
+
/**
|
|
177
|
+
* Given a key and the code for its value, determine some basic aspects of the
|
|
178
|
+
* property. This may return an updated key and/or value as well.
|
|
179
|
+
*/
|
|
180
|
+
export function propertyInfo(key, value) {
|
|
181
|
+
// If the key is wrapped in parentheses, it is not enumerable.
|
|
182
|
+
let enumerable = true;
|
|
183
|
+
if (
|
|
184
|
+
typeof key === "string" &&
|
|
185
|
+
key[0] === "(" &&
|
|
186
|
+
key[key.length - 1] === ")"
|
|
187
|
+
) {
|
|
188
|
+
key = key.slice(1, -1);
|
|
189
|
+
enumerable = false;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const keyType = key instanceof Array ? KEY_TYPE.COMPUTED : KEY_TYPE.STRING;
|
|
193
|
+
|
|
194
|
+
let valueType;
|
|
195
|
+
if (!(value instanceof Array)) {
|
|
196
|
+
// Primitive, no code to evaluate
|
|
197
|
+
valueType = VALUE_TYPE.PRIMITIVE;
|
|
198
|
+
} else if (value[0] !== ops.getter) {
|
|
199
|
+
// Code will be eagerly evaluated when object is constructed
|
|
200
|
+
valueType = VALUE_TYPE.EAGER;
|
|
201
|
+
} else {
|
|
202
|
+
// Defined as a getter
|
|
203
|
+
value = value[1]; // The actual code
|
|
204
|
+
if (!(value instanceof Array)) {
|
|
205
|
+
// Getter returns a primitive value; treat as regular property
|
|
206
|
+
valueType = VALUE_TYPE.PRIMITIVE;
|
|
207
|
+
} else if (value[0] === ops.literal) {
|
|
208
|
+
// Getter returns a literal value; treat as eager property
|
|
209
|
+
valueType = VALUE_TYPE.EAGER;
|
|
210
|
+
} else {
|
|
211
|
+
valueType = VALUE_TYPE.GETTER;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Special case: a key with an extension has to be a getter
|
|
216
|
+
const hasExtension =
|
|
217
|
+
typeof key === "string" && extension.extname(key).length > 0;
|
|
218
|
+
if (hasExtension) {
|
|
219
|
+
valueType = VALUE_TYPE.GETTER;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return { enumerable, hasExtension, key, keyType, value, valueType };
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Get the value of the indicated eager property and overwrite the property
|
|
227
|
+
* definition with the actual value.
|
|
228
|
+
*/
|
|
229
|
+
async function redefineProperty(object, info) {
|
|
230
|
+
const value = await object[info.key];
|
|
231
|
+
Object.defineProperty(object, info.key, {
|
|
232
|
+
configurable: true,
|
|
233
|
+
enumerable: info.enumerable,
|
|
234
|
+
value,
|
|
235
|
+
writable: true,
|
|
236
|
+
});
|
|
225
237
|
}
|
|
@@ -21,7 +21,6 @@ let projectGlobals;
|
|
|
21
21
|
* @param {import("@weborigami/async-tree").SyncOrAsyncMap} [parent]
|
|
22
22
|
*/
|
|
23
23
|
export default async function handleExtension(value, key, parent) {
|
|
24
|
-
projectGlobals ??= await globals();
|
|
25
24
|
if (isPacked(value) && isStringlike(key) && value.unpack === undefined) {
|
|
26
25
|
const hasSlash = trailingSlash.has(key);
|
|
27
26
|
if (hasSlash) {
|
|
@@ -34,7 +33,8 @@ export default async function handleExtension(value, key, parent) {
|
|
|
34
33
|
: extension.extname(key);
|
|
35
34
|
if (extname) {
|
|
36
35
|
const handlerName = `${extname.slice(1)}_handler`;
|
|
37
|
-
|
|
36
|
+
const handlers = await getHandlers(parent);
|
|
37
|
+
let handler = await handlers[handlerName];
|
|
38
38
|
if (handler) {
|
|
39
39
|
if (isUnpackable(handler)) {
|
|
40
40
|
// The extension handler itself needs to be unpacked
|
|
@@ -66,3 +66,19 @@ export default async function handleExtension(value, key, parent) {
|
|
|
66
66
|
}
|
|
67
67
|
return value;
|
|
68
68
|
}
|
|
69
|
+
|
|
70
|
+
async function getHandlers(parent) {
|
|
71
|
+
// Walk up the parent chain to find first `handlers` property
|
|
72
|
+
let current = parent;
|
|
73
|
+
|
|
74
|
+
while (current) {
|
|
75
|
+
if (current.handlers) {
|
|
76
|
+
return current.handlers;
|
|
77
|
+
}
|
|
78
|
+
current = current.parent;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Fall back to project globals
|
|
82
|
+
projectGlobals ??= await globals();
|
|
83
|
+
return projectGlobals;
|
|
84
|
+
}
|
package/src/runtime/ops.js
CHANGED
|
@@ -123,6 +123,18 @@ export async function construct(constructor, ...args) {
|
|
|
123
123
|
return Reflect.construct(constructor, args);
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
+
/**
|
|
127
|
+
* Default value for a parameter: if the value is defined, return that;
|
|
128
|
+
* otherwise, return the result of invoking the initializer.
|
|
129
|
+
*/
|
|
130
|
+
export async function defaultValue(value, initializer) {
|
|
131
|
+
if (value !== undefined) {
|
|
132
|
+
return value;
|
|
133
|
+
}
|
|
134
|
+
return initializer instanceof Function ? await initializer() : initializer;
|
|
135
|
+
}
|
|
136
|
+
addOpLabel(defaultValue, "«ops.defaultValue»");
|
|
137
|
+
|
|
126
138
|
export function division(a, b) {
|
|
127
139
|
return a / b;
|
|
128
140
|
}
|
|
@@ -219,7 +231,7 @@ addOpLabel(instanceOf, "«ops.instanceOf»");
|
|
|
219
231
|
* @param {string[]} parameters
|
|
220
232
|
* @param {AnnotatedCode} code
|
|
221
233
|
*/
|
|
222
|
-
export function lambda(parameters, code, state = {}) {
|
|
234
|
+
export function lambda(length, parameters, code, state = {}) {
|
|
223
235
|
const stack = state.stack ?? [];
|
|
224
236
|
|
|
225
237
|
async function invoke(...args) {
|
|
@@ -228,12 +240,11 @@ export function lambda(parameters, code, state = {}) {
|
|
|
228
240
|
// No parameters
|
|
229
241
|
newState = state;
|
|
230
242
|
} else {
|
|
231
|
-
// Create a stack frame for the parameters
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
}
|
|
243
|
+
// Create a stack frame for the parameters. Add the arguments as an
|
|
244
|
+
// interim stack frame.
|
|
245
|
+
const interimStack = stack.slice();
|
|
246
|
+
interimStack.push(args);
|
|
247
|
+
const frame = await expressionObject(parameters, { stack: interimStack });
|
|
237
248
|
// Record which code this stack frame is associated with
|
|
238
249
|
Object.defineProperty(frame, codeSymbol, {
|
|
239
250
|
value: code,
|
|
@@ -255,9 +266,8 @@ export function lambda(parameters, code, state = {}) {
|
|
|
255
266
|
// We set the `length` property on the function so that Tree.traverseOrThrow()
|
|
256
267
|
// will correctly identify how many parameters it wants. This is unorthodox
|
|
257
268
|
// but doesn't appear to affect other behavior.
|
|
258
|
-
const fnLength = parameters.length;
|
|
259
269
|
Object.defineProperty(invoke, "length", {
|
|
260
|
-
value:
|
|
270
|
+
value: length,
|
|
261
271
|
});
|
|
262
272
|
|
|
263
273
|
return invoke;
|
|
@@ -403,6 +413,17 @@ addOpLabel(object, "«ops.object»");
|
|
|
403
413
|
object.unevaluatedArgs = true;
|
|
404
414
|
object.needsState = true;
|
|
405
415
|
|
|
416
|
+
export async function objectRest(source, excludeKeys) {
|
|
417
|
+
const result = {};
|
|
418
|
+
for (const [key, value] of Object.entries(source)) {
|
|
419
|
+
if (!excludeKeys.includes(key)) {
|
|
420
|
+
result[key] = value; // might be a promise
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
return result;
|
|
424
|
+
}
|
|
425
|
+
addOpLabel(objectRest, "«ops.objectRest»");
|
|
426
|
+
|
|
406
427
|
/**
|
|
407
428
|
* Return the stack frame that's `depth` levels up the stack.
|
|
408
429
|
*
|
|
@@ -100,6 +100,45 @@ describe("compile", () => {
|
|
|
100
100
|
assert.deepEqual(await object.a.b, "Alice");
|
|
101
101
|
});
|
|
102
102
|
|
|
103
|
+
test("lambda", async () => {
|
|
104
|
+
const fn = compile.expression("(name) => greet(name)", { globals });
|
|
105
|
+
const lambda = await fn();
|
|
106
|
+
const result = await lambda("Bob");
|
|
107
|
+
assert.equal(result, "Hello, Bob!");
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test("lambda with default parameter", async () => {
|
|
111
|
+
const fn = compile.expression("(name = 'Guest') => greet(name)", {
|
|
112
|
+
globals,
|
|
113
|
+
});
|
|
114
|
+
const lambda = await fn();
|
|
115
|
+
const result1 = await lambda();
|
|
116
|
+
assert.equal(result1, "Hello, Guest!");
|
|
117
|
+
const result2 = await lambda("Bob");
|
|
118
|
+
assert.equal(result2, "Hello, Bob!");
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test("lambda with rest parameter", async () => {
|
|
122
|
+
const fn = compile.expression("(head, ...rest) => { head, rest }", {
|
|
123
|
+
globals,
|
|
124
|
+
});
|
|
125
|
+
const lambda = await fn();
|
|
126
|
+
const result = await lambda(1, 2, 3, 4);
|
|
127
|
+
assert.deepEqual(result, { head: 1, rest: [2, 3, 4] });
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test("lambda with object destructuring", async () => {
|
|
131
|
+
const fn = compile.expression("({ name, ...rest }) => { name, rest }", {
|
|
132
|
+
globals,
|
|
133
|
+
});
|
|
134
|
+
const lambda = await fn();
|
|
135
|
+
const result = await lambda({ name: "Bob", age: 30, city: "New York" });
|
|
136
|
+
assert.deepEqual(result, {
|
|
137
|
+
name: "Bob",
|
|
138
|
+
rest: { age: 30, city: "New York" },
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
103
142
|
test("templateDocument", async () => {
|
|
104
143
|
const defineTemplateFn = compile.templateDocument(
|
|
105
144
|
"Documents can contain ` backticks"
|