jslike 1.0.0
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/LICENSE +21 -0
- package/README.md +348 -0
- package/bin/jslike.js +33 -0
- package/bin/repl.js +48 -0
- package/package.json +39 -0
- package/src/ast/nodes.js +236 -0
- package/src/cli/wang-run.js +89 -0
- package/src/cli/wang-validate.js +88 -0
- package/src/errors/enhanced-error.js +124 -0
- package/src/index.js +337 -0
- package/src/interpreter/index.js +174 -0
- package/src/interpreter/interpreter.js +1795 -0
- package/src/metadata/index.js +531 -0
- package/src/parser.js +6241 -0
- package/src/resolvers/memory.js +17 -0
- package/src/runtime/builtins.js +517 -0
- package/src/runtime/environment.js +75 -0
package/src/ast/nodes.js
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
// AST Node Constructors
|
|
2
|
+
|
|
3
|
+
export const Program = (body) => ({
|
|
4
|
+
type: 'Program',
|
|
5
|
+
body
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
export const Literal = (value) => ({
|
|
9
|
+
type: 'Literal',
|
|
10
|
+
value
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
export const Identifier = (name) => ({
|
|
14
|
+
type: 'Identifier',
|
|
15
|
+
name
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
export const BinaryExpression = (operator, left, right) => ({
|
|
19
|
+
type: 'BinaryExpression',
|
|
20
|
+
operator,
|
|
21
|
+
left,
|
|
22
|
+
right
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export const UnaryExpression = (operator, argument, prefix = true) => ({
|
|
26
|
+
type: 'UnaryExpression',
|
|
27
|
+
operator,
|
|
28
|
+
argument,
|
|
29
|
+
prefix
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
export const UpdateExpression = (operator, argument, prefix = true) => ({
|
|
33
|
+
type: 'UpdateExpression',
|
|
34
|
+
operator,
|
|
35
|
+
argument,
|
|
36
|
+
prefix
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
export const AssignmentExpression = (operator, left, right) => ({
|
|
40
|
+
type: 'AssignmentExpression',
|
|
41
|
+
operator,
|
|
42
|
+
left,
|
|
43
|
+
right
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
export const LogicalExpression = (operator, left, right) => ({
|
|
47
|
+
type: 'LogicalExpression',
|
|
48
|
+
operator,
|
|
49
|
+
left,
|
|
50
|
+
right
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
export const ConditionalExpression = (test, consequent, alternate) => ({
|
|
54
|
+
type: 'ConditionalExpression',
|
|
55
|
+
test,
|
|
56
|
+
consequent,
|
|
57
|
+
alternate
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
export const CallExpression = (callee, args) => ({
|
|
61
|
+
type: 'CallExpression',
|
|
62
|
+
callee,
|
|
63
|
+
arguments: args
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
export const MemberExpression = (object, property, computed = false) => ({
|
|
67
|
+
type: 'MemberExpression',
|
|
68
|
+
object,
|
|
69
|
+
property,
|
|
70
|
+
computed
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
export const ArrayExpression = (elements) => ({
|
|
74
|
+
type: 'ArrayExpression',
|
|
75
|
+
elements
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
export const ObjectExpression = (properties) => ({
|
|
79
|
+
type: 'ObjectExpression',
|
|
80
|
+
properties
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
export const Property = (key, value, shorthand = false) => ({
|
|
84
|
+
type: 'Property',
|
|
85
|
+
key,
|
|
86
|
+
value,
|
|
87
|
+
shorthand
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
export const FunctionExpression = (id, params, body) => ({
|
|
91
|
+
type: 'FunctionExpression',
|
|
92
|
+
id,
|
|
93
|
+
params,
|
|
94
|
+
body
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
export const ArrowFunctionExpression = (params, body, expression = false) => ({
|
|
98
|
+
type: 'ArrowFunctionExpression',
|
|
99
|
+
params,
|
|
100
|
+
body,
|
|
101
|
+
expression
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
export const VariableDeclaration = (kind, declarations) => ({
|
|
105
|
+
type: 'VariableDeclaration',
|
|
106
|
+
kind,
|
|
107
|
+
declarations
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
export const VariableDeclarator = (id, init = null) => ({
|
|
111
|
+
type: 'VariableDeclarator',
|
|
112
|
+
id,
|
|
113
|
+
init
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
export const FunctionDeclaration = (id, params, body) => ({
|
|
117
|
+
type: 'FunctionDeclaration',
|
|
118
|
+
id,
|
|
119
|
+
params,
|
|
120
|
+
body
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
export const BlockStatement = (body) => ({
|
|
124
|
+
type: 'BlockStatement',
|
|
125
|
+
body
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
export const ExpressionStatement = (expression) => ({
|
|
129
|
+
type: 'ExpressionStatement',
|
|
130
|
+
expression
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
export const ReturnStatement = (argument = null) => ({
|
|
134
|
+
type: 'ReturnStatement',
|
|
135
|
+
argument
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
export const IfStatement = (test, consequent, alternate = null) => ({
|
|
139
|
+
type: 'IfStatement',
|
|
140
|
+
test,
|
|
141
|
+
consequent,
|
|
142
|
+
alternate
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
export const WhileStatement = (test, body) => ({
|
|
146
|
+
type: 'WhileStatement',
|
|
147
|
+
test,
|
|
148
|
+
body
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
export const DoWhileStatement = (body, test) => ({
|
|
152
|
+
type: 'DoWhileStatement',
|
|
153
|
+
body,
|
|
154
|
+
test
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
export const ForStatement = (init, test, update, body) => ({
|
|
158
|
+
type: 'ForStatement',
|
|
159
|
+
init,
|
|
160
|
+
test,
|
|
161
|
+
update,
|
|
162
|
+
body
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
export const ForInStatement = (left, right, body) => ({
|
|
166
|
+
type: 'ForInStatement',
|
|
167
|
+
left,
|
|
168
|
+
right,
|
|
169
|
+
body
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
export const ForOfStatement = (left, right, body) => ({
|
|
173
|
+
type: 'ForOfStatement',
|
|
174
|
+
left,
|
|
175
|
+
right,
|
|
176
|
+
body
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
export const BreakStatement = (label = null) => ({
|
|
180
|
+
type: 'BreakStatement',
|
|
181
|
+
label
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
export const ContinueStatement = (label = null) => ({
|
|
185
|
+
type: 'ContinueStatement',
|
|
186
|
+
label
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
export const ThrowStatement = (argument) => ({
|
|
190
|
+
type: 'ThrowStatement',
|
|
191
|
+
argument
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
export const TryStatement = (block, handler = null, finalizer = null) => ({
|
|
195
|
+
type: 'TryStatement',
|
|
196
|
+
block,
|
|
197
|
+
handler,
|
|
198
|
+
finalizer
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
export const CatchClause = (param, body) => ({
|
|
202
|
+
type: 'CatchClause',
|
|
203
|
+
param,
|
|
204
|
+
body
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
export const SwitchStatement = (discriminant, cases) => ({
|
|
208
|
+
type: 'SwitchStatement',
|
|
209
|
+
discriminant,
|
|
210
|
+
cases
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
export const SwitchCase = (test, consequent) => ({
|
|
214
|
+
type: 'SwitchCase',
|
|
215
|
+
test,
|
|
216
|
+
consequent
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
export const NewExpression = (callee, args) => ({
|
|
220
|
+
type: 'NewExpression',
|
|
221
|
+
callee,
|
|
222
|
+
arguments: args
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
export const ThisExpression = () => ({
|
|
226
|
+
type: 'ThisExpression'
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
export const SequenceExpression = (expressions) => ({
|
|
230
|
+
type: 'SequenceExpression',
|
|
231
|
+
expressions
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
export const EmptyStatement = () => ({
|
|
235
|
+
type: 'EmptyStatement'
|
|
236
|
+
});
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { readFile } from 'fs/promises';
|
|
4
|
+
import { stdin } from 'process';
|
|
5
|
+
import { execute } from '../index.js';
|
|
6
|
+
|
|
7
|
+
const args = process.argv.slice(2);
|
|
8
|
+
|
|
9
|
+
// Parse flags
|
|
10
|
+
const verbose = args.includes('--verbose');
|
|
11
|
+
const quiet = args.includes('--quiet');
|
|
12
|
+
const help = args.includes('--help') || args.includes('-h');
|
|
13
|
+
|
|
14
|
+
// Filter out flags to get file path
|
|
15
|
+
const filePath = args.find(arg => !arg.startsWith('--') && arg !== '-h');
|
|
16
|
+
|
|
17
|
+
if (help) {
|
|
18
|
+
console.log(`
|
|
19
|
+
Wang Language Runtime
|
|
20
|
+
|
|
21
|
+
Usage:
|
|
22
|
+
wang-run <file> Execute a Wang file
|
|
23
|
+
wang-run - Read from stdin
|
|
24
|
+
wang-run --help Show this help message
|
|
25
|
+
|
|
26
|
+
Options:
|
|
27
|
+
--verbose Show detailed execution information
|
|
28
|
+
--quiet Suppress extra output
|
|
29
|
+
-h, --help Show this help message
|
|
30
|
+
|
|
31
|
+
Examples:
|
|
32
|
+
wang-run script.wang
|
|
33
|
+
echo "console.log('test')" | wang-run -
|
|
34
|
+
`.trim());
|
|
35
|
+
process.exit(0);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function readStdin() {
|
|
39
|
+
const chunks = [];
|
|
40
|
+
for await (const chunk of stdin) {
|
|
41
|
+
chunks.push(chunk);
|
|
42
|
+
}
|
|
43
|
+
return Buffer.concat(chunks).toString('utf8');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function run() {
|
|
47
|
+
try {
|
|
48
|
+
let code;
|
|
49
|
+
|
|
50
|
+
if (filePath === '-') {
|
|
51
|
+
// Read from stdin
|
|
52
|
+
code = await readStdin();
|
|
53
|
+
} else if (filePath) {
|
|
54
|
+
// Read from file
|
|
55
|
+
code = await readFile(filePath, 'utf8');
|
|
56
|
+
} else {
|
|
57
|
+
console.error('Error: No input file specified');
|
|
58
|
+
console.error('Use --help for usage information');
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (verbose) {
|
|
63
|
+
console.log('🚀 Executing Wang code...');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Execute the code
|
|
67
|
+
await execute(code);
|
|
68
|
+
|
|
69
|
+
if (verbose) {
|
|
70
|
+
console.log('✅ Execution completed successfully');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
process.exit(0);
|
|
74
|
+
} catch (error) {
|
|
75
|
+
// For file not found errors, show generic error message
|
|
76
|
+
if (error.code === 'ENOENT') {
|
|
77
|
+
console.error('Error:', error.message);
|
|
78
|
+
} else {
|
|
79
|
+
console.error('❌ Execution failed:');
|
|
80
|
+
console.error(error.message);
|
|
81
|
+
if (verbose) {
|
|
82
|
+
console.error(error.stack);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
process.exit(1);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
run();
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { readFile } from 'fs/promises';
|
|
4
|
+
import { stdin } from 'process';
|
|
5
|
+
import { parse } from 'acorn';
|
|
6
|
+
import { preprocessCode, isTopLevelAwait } from '../index.js';
|
|
7
|
+
|
|
8
|
+
const args = process.argv.slice(2);
|
|
9
|
+
|
|
10
|
+
// Parse flags
|
|
11
|
+
const showAst = args.includes('--ast');
|
|
12
|
+
const help = args.includes('--help') || args.includes('-h');
|
|
13
|
+
|
|
14
|
+
// Filter out flags to get file path
|
|
15
|
+
const filePath = args.find(arg => !arg.startsWith('--') && arg !== '-h');
|
|
16
|
+
|
|
17
|
+
if (help) {
|
|
18
|
+
console.log(`
|
|
19
|
+
Wang Language Validator
|
|
20
|
+
|
|
21
|
+
Usage:
|
|
22
|
+
wang-validate <file> Validate a Wang file
|
|
23
|
+
wang-validate - Read from stdin
|
|
24
|
+
wang-validate --help Show this help message
|
|
25
|
+
|
|
26
|
+
Options:
|
|
27
|
+
--ast Show the Abstract Syntax Tree
|
|
28
|
+
-h, --help Show this help message
|
|
29
|
+
|
|
30
|
+
Examples:
|
|
31
|
+
wang-validate script.wang
|
|
32
|
+
echo "let x = 1" | wang-validate -
|
|
33
|
+
wang-validate script.wang --ast
|
|
34
|
+
`.trim());
|
|
35
|
+
process.exit(0);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function readStdin() {
|
|
39
|
+
const chunks = [];
|
|
40
|
+
for await (const chunk of stdin) {
|
|
41
|
+
chunks.push(chunk);
|
|
42
|
+
}
|
|
43
|
+
return Buffer.concat(chunks).toString('utf8');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function validate() {
|
|
47
|
+
try {
|
|
48
|
+
let code;
|
|
49
|
+
|
|
50
|
+
if (filePath === '-') {
|
|
51
|
+
// Read from stdin
|
|
52
|
+
code = await readStdin();
|
|
53
|
+
} else if (filePath) {
|
|
54
|
+
// Read from file
|
|
55
|
+
code = await readFile(filePath, 'utf8');
|
|
56
|
+
} else {
|
|
57
|
+
console.error('Error: No input file specified');
|
|
58
|
+
console.error('Use --help for usage information');
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Preprocess the code
|
|
63
|
+
const processedCode = preprocessCode(code);
|
|
64
|
+
const hasAwait = isTopLevelAwait(processedCode);
|
|
65
|
+
|
|
66
|
+
// Parse the code to validate syntax
|
|
67
|
+
const ast = parse(processedCode, {
|
|
68
|
+
ecmaVersion: 'latest',
|
|
69
|
+
sourceType: hasAwait ? 'module' : 'script',
|
|
70
|
+
locations: true
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
console.log('✅ Valid Wang syntax');
|
|
74
|
+
|
|
75
|
+
if (showAst) {
|
|
76
|
+
console.log('\nAST:');
|
|
77
|
+
console.log(JSON.stringify(ast, null, 2));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
process.exit(0);
|
|
81
|
+
} catch (error) {
|
|
82
|
+
console.error('❌ Invalid Wang syntax:');
|
|
83
|
+
console.error(error.message);
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
validate();
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enhanced error classes for better developer experience
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// Simple Levenshtein distance for "did you mean" suggestions
|
|
6
|
+
function levenshteinDistance(a, b) {
|
|
7
|
+
const matrix = [];
|
|
8
|
+
|
|
9
|
+
for (let i = 0; i <= b.length; i++) {
|
|
10
|
+
matrix[i] = [i];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
for (let j = 0; j <= a.length; j++) {
|
|
14
|
+
matrix[0][j] = j;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
for (let i = 1; i <= b.length; i++) {
|
|
18
|
+
for (let j = 1; j <= a.length; j++) {
|
|
19
|
+
if (b.charAt(i - 1) === a.charAt(j - 1)) {
|
|
20
|
+
matrix[i][j] = matrix[i - 1][j - 1];
|
|
21
|
+
} else {
|
|
22
|
+
matrix[i][j] = Math.min(
|
|
23
|
+
matrix[i - 1][j - 1] + 1,
|
|
24
|
+
matrix[i][j - 1] + 1,
|
|
25
|
+
matrix[i - 1][j] + 1
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return matrix[b.length][a.length];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Find similar strings for suggestions
|
|
35
|
+
function findSimilar(target, candidates, maxDistance = 3) {
|
|
36
|
+
const suggestions = [];
|
|
37
|
+
|
|
38
|
+
for (const candidate of candidates) {
|
|
39
|
+
const distance = levenshteinDistance(target.toLowerCase(), candidate.toLowerCase());
|
|
40
|
+
if (distance <= maxDistance && distance > 0) {
|
|
41
|
+
suggestions.push({ name: candidate, distance });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return suggestions
|
|
46
|
+
.sort((a, b) => a.distance - b.distance)
|
|
47
|
+
.slice(0, 3)
|
|
48
|
+
.map(s => s.name);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Get available methods/properties on an object
|
|
52
|
+
function getAvailableMethods(obj) {
|
|
53
|
+
if (obj === null || obj === undefined) return [];
|
|
54
|
+
|
|
55
|
+
const methods = new Set();
|
|
56
|
+
|
|
57
|
+
// Get own properties
|
|
58
|
+
Object.getOwnPropertyNames(obj).forEach(name => {
|
|
59
|
+
if (typeof obj[name] === 'function') {
|
|
60
|
+
methods.add(name);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Get prototype methods
|
|
65
|
+
let proto = Object.getPrototypeOf(obj);
|
|
66
|
+
while (proto && proto !== Object.prototype) {
|
|
67
|
+
Object.getOwnPropertyNames(proto).forEach(name => {
|
|
68
|
+
if (typeof proto[name] === 'function' && name !== 'constructor') {
|
|
69
|
+
methods.add(name);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
proto = Object.getPrototypeOf(proto);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return Array.from(methods).sort();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export class EnhancedTypeError extends TypeError {
|
|
79
|
+
constructor(message, context = {}) {
|
|
80
|
+
super(message);
|
|
81
|
+
this.name = 'TypeError';
|
|
82
|
+
this.objectName = context.objectName || 'object';
|
|
83
|
+
this.methodName = context.methodName || 'method';
|
|
84
|
+
this.objectValue = context.objectValue;
|
|
85
|
+
this.availableMethods = context.availableMethods || [];
|
|
86
|
+
this.suggestions = context.suggestions || [];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
getFormattedMessage() {
|
|
90
|
+
let formatted = this.message;
|
|
91
|
+
|
|
92
|
+
// Add available methods
|
|
93
|
+
if (this.availableMethods.length > 0) {
|
|
94
|
+
const methodList = this.availableMethods.slice(0, 10).join(', ');
|
|
95
|
+
const more = this.availableMethods.length > 10
|
|
96
|
+
? ` (and ${this.availableMethods.length - 10} more)`
|
|
97
|
+
: '';
|
|
98
|
+
formatted += `\n\nAvailable methods on '${this.objectName}': ${methodList}${more}`;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Add suggestions
|
|
102
|
+
if (this.suggestions.length > 0) {
|
|
103
|
+
formatted += `\n\nDid you mean: ${this.suggestions.join(', ')}?`;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return formatted;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function createMethodNotFoundError(objectName, methodName, objectValue) {
|
|
111
|
+
const availableMethods = getAvailableMethods(objectValue);
|
|
112
|
+
const suggestions = findSimilar(methodName, availableMethods);
|
|
113
|
+
|
|
114
|
+
const message = `TypeError when calling method '${methodName}' on object '${objectName}': ` +
|
|
115
|
+
`Expected: function, Received: undefined`;
|
|
116
|
+
|
|
117
|
+
return new EnhancedTypeError(message, {
|
|
118
|
+
objectName,
|
|
119
|
+
methodName,
|
|
120
|
+
objectValue,
|
|
121
|
+
availableMethods,
|
|
122
|
+
suggestions
|
|
123
|
+
});
|
|
124
|
+
}
|