kimchilang 1.0.1
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/.github/workflows/ci.yml +66 -0
- package/README.md +1547 -0
- package/create-kimchi-app/README.md +44 -0
- package/create-kimchi-app/index.js +214 -0
- package/create-kimchi-app/package.json +22 -0
- package/editors/README.md +121 -0
- package/editors/sublime/KimchiLang.sublime-syntax +138 -0
- package/editors/vscode/README.md +90 -0
- package/editors/vscode/kimchilang-1.1.0.vsix +0 -0
- package/editors/vscode/language-configuration.json +37 -0
- package/editors/vscode/package.json +55 -0
- package/editors/vscode/src/extension.js +354 -0
- package/editors/vscode/syntaxes/kimchi.tmLanguage.json +215 -0
- package/examples/api/client.km +36 -0
- package/examples/async_pipe.km +58 -0
- package/examples/basic.kimchi +109 -0
- package/examples/cli_framework/README.md +92 -0
- package/examples/cli_framework/calculator.km +61 -0
- package/examples/cli_framework/deploy.km +126 -0
- package/examples/cli_framework/greeter.km +26 -0
- package/examples/config.static +27 -0
- package/examples/config.static.js +10 -0
- package/examples/env_test.km +37 -0
- package/examples/fibonacci.kimchi +17 -0
- package/examples/greeter.km +15 -0
- package/examples/hello.js +1 -0
- package/examples/hello.kimchi +3 -0
- package/examples/js_interop.km +42 -0
- package/examples/logger_example.km +34 -0
- package/examples/memo_fibonacci.km +17 -0
- package/examples/myapp/lib/http.js +14 -0
- package/examples/myapp/lib/http.km +16 -0
- package/examples/myapp/main.km +16 -0
- package/examples/myapp/main_with_mock.km +42 -0
- package/examples/myapp/services/api.js +18 -0
- package/examples/myapp/services/api.km +18 -0
- package/examples/new_features.kimchi +52 -0
- package/examples/project_example.static +20 -0
- package/examples/readme_examples.km +240 -0
- package/examples/reduce_pattern_match.km +85 -0
- package/examples/regex_match.km +46 -0
- package/examples/sample.js +45 -0
- package/examples/sample.km +39 -0
- package/examples/secrets.static +35 -0
- package/examples/secrets.static.js +30 -0
- package/examples/shell-example.mjs +144 -0
- package/examples/shell_example.km +19 -0
- package/examples/stdlib_test.km +22 -0
- package/examples/test_example.km +69 -0
- package/examples/testing/README.md +88 -0
- package/examples/testing/http_client.km +18 -0
- package/examples/testing/math.km +48 -0
- package/examples/testing/math.test.km +93 -0
- package/examples/testing/user_service.km +29 -0
- package/examples/testing/user_service.test.km +72 -0
- package/examples/use-config.mjs +141 -0
- package/examples/use_config.km +13 -0
- package/install.sh +59 -0
- package/package.json +29 -0
- package/pantry/acorn/index.km +1 -0
- package/pantry/is_number/index.km +1 -0
- package/pantry/is_odd/index.km +2 -0
- package/project.static +6 -0
- package/src/cli.js +1245 -0
- package/src/generator.js +1241 -0
- package/src/index.js +141 -0
- package/src/js2km.js +568 -0
- package/src/lexer.js +822 -0
- package/src/linter.js +810 -0
- package/src/package-manager.js +307 -0
- package/src/parser.js +1876 -0
- package/src/static-parser.js +500 -0
- package/src/typechecker.js +950 -0
- package/stdlib/array.km +0 -0
- package/stdlib/bitwise.km +38 -0
- package/stdlib/console.km +49 -0
- package/stdlib/date.km +97 -0
- package/stdlib/function.km +44 -0
- package/stdlib/http.km +197 -0
- package/stdlib/http.md +333 -0
- package/stdlib/index.km +26 -0
- package/stdlib/json.km +17 -0
- package/stdlib/logger.js +114 -0
- package/stdlib/logger.km +104 -0
- package/stdlib/math.km +120 -0
- package/stdlib/object.km +41 -0
- package/stdlib/promise.km +33 -0
- package/stdlib/string.km +93 -0
- package/stdlib/testing.md +265 -0
- package/test/test.js +599 -0
|
@@ -0,0 +1,500 @@
|
|
|
1
|
+
// KimchiLang Static File Parser
|
|
2
|
+
// Parses .static files containing arrays, objects, and enums
|
|
3
|
+
|
|
4
|
+
export class StaticLexer {
|
|
5
|
+
constructor(source) {
|
|
6
|
+
this.source = source;
|
|
7
|
+
this.pos = 0;
|
|
8
|
+
this.line = 1;
|
|
9
|
+
this.column = 1;
|
|
10
|
+
this.tokens = [];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
error(message) {
|
|
14
|
+
throw new Error(`Static Lexer Error at ${this.line}:${this.column}: ${message}`);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
peek(offset = 0) {
|
|
18
|
+
const pos = this.pos + offset;
|
|
19
|
+
if (pos >= this.source.length) return '\0';
|
|
20
|
+
return this.source[pos];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
advance() {
|
|
24
|
+
const char = this.peek();
|
|
25
|
+
this.pos++;
|
|
26
|
+
if (char === '\n') {
|
|
27
|
+
this.line++;
|
|
28
|
+
this.column = 1;
|
|
29
|
+
} else {
|
|
30
|
+
this.column++;
|
|
31
|
+
}
|
|
32
|
+
return char;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
match(expected) {
|
|
36
|
+
if (this.peek() === expected) {
|
|
37
|
+
this.advance();
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
skipWhitespace() {
|
|
44
|
+
while (this.peek() === ' ' || this.peek() === '\t' || this.peek() === '\r') {
|
|
45
|
+
this.advance();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
skipLineComment() {
|
|
50
|
+
while (this.peek() !== '\n' && this.peek() !== '\0') {
|
|
51
|
+
this.advance();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
readString(quote) {
|
|
56
|
+
let value = '';
|
|
57
|
+
while (this.peek() !== quote && this.peek() !== '\0') {
|
|
58
|
+
if (this.peek() === '\\') {
|
|
59
|
+
this.advance();
|
|
60
|
+
const escaped = this.advance();
|
|
61
|
+
switch (escaped) {
|
|
62
|
+
case 'n': value += '\n'; break;
|
|
63
|
+
case 't': value += '\t'; break;
|
|
64
|
+
case 'r': value += '\r'; break;
|
|
65
|
+
case '\\': value += '\\'; break;
|
|
66
|
+
case '"': value += '"'; break;
|
|
67
|
+
case "'": value += "'"; break;
|
|
68
|
+
default: value += escaped;
|
|
69
|
+
}
|
|
70
|
+
} else {
|
|
71
|
+
value += this.advance();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
this.advance(); // closing quote
|
|
75
|
+
return { type: 'STRING', value };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
readNumber() {
|
|
79
|
+
let value = '';
|
|
80
|
+
const isNegative = this.peek() === '-';
|
|
81
|
+
if (isNegative) value += this.advance();
|
|
82
|
+
|
|
83
|
+
while (/[0-9]/.test(this.peek())) {
|
|
84
|
+
value += this.advance();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (this.peek() === '.' && /[0-9]/.test(this.peek(1))) {
|
|
88
|
+
value += this.advance();
|
|
89
|
+
while (/[0-9]/.test(this.peek())) {
|
|
90
|
+
value += this.advance();
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return { type: 'NUMBER', value: parseFloat(value) };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
readIdentifier() {
|
|
98
|
+
let value = '';
|
|
99
|
+
while (/[a-zA-Z0-9_$.]/.test(this.peek())) {
|
|
100
|
+
value += this.advance();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Check for boolean/null keywords
|
|
104
|
+
if (value === 'true') return { type: 'BOOLEAN', value: true };
|
|
105
|
+
if (value === 'false') return { type: 'BOOLEAN', value: false };
|
|
106
|
+
if (value === 'null') return { type: 'NULL', value: null };
|
|
107
|
+
if (value === 'secret') return { type: 'SECRET' };
|
|
108
|
+
|
|
109
|
+
return { type: 'IDENTIFIER', value };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
tokenize() {
|
|
113
|
+
while (this.pos < this.source.length) {
|
|
114
|
+
this.skipWhitespace();
|
|
115
|
+
|
|
116
|
+
if (this.pos >= this.source.length) break;
|
|
117
|
+
|
|
118
|
+
const char = this.peek();
|
|
119
|
+
|
|
120
|
+
// Comments
|
|
121
|
+
if (char === '/' && this.peek(1) === '/') {
|
|
122
|
+
this.advance();
|
|
123
|
+
this.advance();
|
|
124
|
+
this.skipLineComment();
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Newlines
|
|
129
|
+
if (char === '\n') {
|
|
130
|
+
this.advance();
|
|
131
|
+
this.tokens.push({ type: 'NEWLINE' });
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Strings
|
|
136
|
+
if (char === '"' || char === "'") {
|
|
137
|
+
this.advance();
|
|
138
|
+
this.tokens.push(this.readString(char));
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Numbers (including negative)
|
|
143
|
+
if (/[0-9]/.test(char) || (char === '-' && /[0-9]/.test(this.peek(1)))) {
|
|
144
|
+
this.tokens.push(this.readNumber());
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Identifiers (including dotted paths like foo.bar.Baz)
|
|
149
|
+
if (/[a-zA-Z_$]/.test(char)) {
|
|
150
|
+
this.tokens.push(this.readIdentifier());
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Single character tokens
|
|
155
|
+
this.advance();
|
|
156
|
+
switch (char) {
|
|
157
|
+
case '[': this.tokens.push({ type: 'LBRACKET' }); break;
|
|
158
|
+
case ']': this.tokens.push({ type: 'RBRACKET' }); break;
|
|
159
|
+
case '{': this.tokens.push({ type: 'LBRACE' }); break;
|
|
160
|
+
case '}': this.tokens.push({ type: 'RBRACE' }); break;
|
|
161
|
+
case '`': this.tokens.push({ type: 'BACKTICK' }); break;
|
|
162
|
+
case '=': this.tokens.push({ type: 'EQUALS' }); break;
|
|
163
|
+
case ',': this.tokens.push({ type: 'COMMA' }); break;
|
|
164
|
+
default:
|
|
165
|
+
this.error(`Unexpected character: ${char}`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
this.tokens.push({ type: 'EOF' });
|
|
170
|
+
return this.tokens;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export class StaticParser {
|
|
175
|
+
constructor(tokens, modulePath = '') {
|
|
176
|
+
this.tokens = tokens;
|
|
177
|
+
this.pos = 0;
|
|
178
|
+
this.modulePath = modulePath;
|
|
179
|
+
this.declarations = {};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
error(message) {
|
|
183
|
+
const token = this.peek();
|
|
184
|
+
throw new Error(`Static Parse Error: ${message} (got ${token.type})`);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
peek() {
|
|
188
|
+
return this.tokens[this.pos] || { type: 'EOF' };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
advance() {
|
|
192
|
+
return this.tokens[this.pos++];
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
check(type) {
|
|
196
|
+
return this.peek().type === type;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
match(type) {
|
|
200
|
+
if (this.check(type)) {
|
|
201
|
+
this.advance();
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
expect(type, message) {
|
|
208
|
+
if (!this.check(type)) {
|
|
209
|
+
this.error(message || `Expected ${type}`);
|
|
210
|
+
}
|
|
211
|
+
return this.advance();
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
skipNewlines() {
|
|
215
|
+
while (this.match('NEWLINE')) {}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
parse() {
|
|
219
|
+
this.skipNewlines();
|
|
220
|
+
|
|
221
|
+
while (!this.check('EOF')) {
|
|
222
|
+
this.parseDeclaration();
|
|
223
|
+
this.skipNewlines();
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return this.declarations;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
parseDeclaration() {
|
|
230
|
+
// Check for secret modifier
|
|
231
|
+
let isSecret = false;
|
|
232
|
+
if (this.match('SECRET')) {
|
|
233
|
+
isSecret = true;
|
|
234
|
+
this.skipNewlines();
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Expect an identifier (the name)
|
|
238
|
+
const nameToken = this.expect('IDENTIFIER', 'Expected declaration name');
|
|
239
|
+
const name = nameToken.value;
|
|
240
|
+
|
|
241
|
+
this.skipNewlines();
|
|
242
|
+
|
|
243
|
+
// Determine type by next token
|
|
244
|
+
if (this.check('LBRACKET')) {
|
|
245
|
+
// Array declaration: Name [value1, value2, ...]
|
|
246
|
+
this.declarations[name] = this.parseArray();
|
|
247
|
+
} else if (this.check('LBRACE')) {
|
|
248
|
+
// Object declaration: Name { key = value, ... }
|
|
249
|
+
this.declarations[name] = this.parseObject();
|
|
250
|
+
} else if (this.check('BACKTICK')) {
|
|
251
|
+
// Enum declaration: Name `ENUM1 = foo, ENUM2 = bar`
|
|
252
|
+
this.declarations[name] = this.parseEnum();
|
|
253
|
+
} else if (this.check('STRING')) {
|
|
254
|
+
// String primitive: Name "value"
|
|
255
|
+
const token = this.advance();
|
|
256
|
+
this.declarations[name] = { type: 'literal', value: token.value, secret: isSecret };
|
|
257
|
+
return;
|
|
258
|
+
} else if (this.check('NUMBER')) {
|
|
259
|
+
// Number primitive: Name 123
|
|
260
|
+
const token = this.advance();
|
|
261
|
+
this.declarations[name] = { type: 'literal', value: token.value, secret: isSecret };
|
|
262
|
+
return;
|
|
263
|
+
} else if (this.check('BOOLEAN')) {
|
|
264
|
+
// Boolean primitive: Name true/false
|
|
265
|
+
const token = this.advance();
|
|
266
|
+
this.declarations[name] = { type: 'literal', value: token.value, secret: isSecret };
|
|
267
|
+
return;
|
|
268
|
+
} else {
|
|
269
|
+
this.error(`Expected [, {, \`, string, or number after declaration name "${name}"`);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Mark the declaration as secret if needed
|
|
273
|
+
if (isSecret && this.declarations[name]) {
|
|
274
|
+
this.declarations[name].secret = true;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
parseArray() {
|
|
279
|
+
this.expect('LBRACKET', 'Expected [');
|
|
280
|
+
const values = [];
|
|
281
|
+
|
|
282
|
+
this.skipNewlines();
|
|
283
|
+
|
|
284
|
+
while (!this.check('RBRACKET') && !this.check('EOF')) {
|
|
285
|
+
values.push(this.parseValue());
|
|
286
|
+
|
|
287
|
+
// Comma or newline separates values
|
|
288
|
+
if (this.match('COMMA')) {
|
|
289
|
+
this.skipNewlines();
|
|
290
|
+
} else if (this.check('NEWLINE')) {
|
|
291
|
+
this.skipNewlines();
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
this.expect('RBRACKET', 'Expected ]');
|
|
296
|
+
|
|
297
|
+
return { type: 'array', values };
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
parseObject() {
|
|
301
|
+
this.expect('LBRACE', 'Expected {');
|
|
302
|
+
const properties = {};
|
|
303
|
+
|
|
304
|
+
this.skipNewlines();
|
|
305
|
+
|
|
306
|
+
while (!this.check('RBRACE') && !this.check('EOF')) {
|
|
307
|
+
// Check for secret modifier on property
|
|
308
|
+
let isSecret = false;
|
|
309
|
+
if (this.match('SECRET')) {
|
|
310
|
+
isSecret = true;
|
|
311
|
+
this.skipNewlines();
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const keyToken = this.expect('IDENTIFIER', 'Expected property name');
|
|
315
|
+
const key = keyToken.value;
|
|
316
|
+
|
|
317
|
+
this.expect('EQUALS', 'Expected = after property name');
|
|
318
|
+
|
|
319
|
+
const value = this.parseValue();
|
|
320
|
+
if (isSecret) {
|
|
321
|
+
value.secret = true;
|
|
322
|
+
}
|
|
323
|
+
properties[key] = value;
|
|
324
|
+
|
|
325
|
+
// Comma or newline separates properties
|
|
326
|
+
if (this.match('COMMA')) {
|
|
327
|
+
this.skipNewlines();
|
|
328
|
+
} else if (this.check('NEWLINE')) {
|
|
329
|
+
this.skipNewlines();
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
this.expect('RBRACE', 'Expected }');
|
|
334
|
+
|
|
335
|
+
return { type: 'object', properties };
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
parseEnum() {
|
|
339
|
+
this.expect('BACKTICK', 'Expected `');
|
|
340
|
+
const members = {};
|
|
341
|
+
|
|
342
|
+
while (!this.check('BACKTICK') && !this.check('EOF')) {
|
|
343
|
+
const nameToken = this.expect('IDENTIFIER', 'Expected enum member name');
|
|
344
|
+
const name = nameToken.value;
|
|
345
|
+
|
|
346
|
+
this.expect('EQUALS', 'Expected = after enum member name');
|
|
347
|
+
|
|
348
|
+
const value = this.parseValue();
|
|
349
|
+
members[name] = value;
|
|
350
|
+
|
|
351
|
+
// Comma or newline separates members
|
|
352
|
+
if (this.match('COMMA')) {
|
|
353
|
+
this.skipNewlines();
|
|
354
|
+
} else if (this.check('NEWLINE')) {
|
|
355
|
+
this.skipNewlines();
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
this.expect('BACKTICK', 'Expected closing `');
|
|
360
|
+
|
|
361
|
+
return { type: 'enum', members };
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
parseValue() {
|
|
365
|
+
this.skipNewlines();
|
|
366
|
+
|
|
367
|
+
const token = this.peek();
|
|
368
|
+
|
|
369
|
+
switch (token.type) {
|
|
370
|
+
case 'STRING':
|
|
371
|
+
this.advance();
|
|
372
|
+
return { type: 'literal', value: token.value };
|
|
373
|
+
|
|
374
|
+
case 'NUMBER':
|
|
375
|
+
this.advance();
|
|
376
|
+
return { type: 'literal', value: token.value };
|
|
377
|
+
|
|
378
|
+
case 'BOOLEAN':
|
|
379
|
+
this.advance();
|
|
380
|
+
return { type: 'literal', value: token.value };
|
|
381
|
+
|
|
382
|
+
case 'NULL':
|
|
383
|
+
this.advance();
|
|
384
|
+
return { type: 'literal', value: null };
|
|
385
|
+
|
|
386
|
+
case 'IDENTIFIER':
|
|
387
|
+
this.advance();
|
|
388
|
+
// Could be a reference to another static file's data (e.g., foo.bar.Baz)
|
|
389
|
+
if (token.value.includes('.')) {
|
|
390
|
+
return { type: 'reference', path: token.value };
|
|
391
|
+
}
|
|
392
|
+
// Or a local reference
|
|
393
|
+
return { type: 'reference', path: token.value };
|
|
394
|
+
|
|
395
|
+
case 'LBRACKET':
|
|
396
|
+
return this.parseArray();
|
|
397
|
+
|
|
398
|
+
case 'LBRACE':
|
|
399
|
+
return this.parseObject();
|
|
400
|
+
|
|
401
|
+
default:
|
|
402
|
+
this.error(`Unexpected token in value: ${token.type}`);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Generate JavaScript code from parsed static file
|
|
408
|
+
export function generateStaticCode(declarations, modulePath) {
|
|
409
|
+
let code = '// Generated from .static file\n\n';
|
|
410
|
+
|
|
411
|
+
// Check if any declarations use secrets
|
|
412
|
+
const hasSecrets = checkForSecrets(declarations);
|
|
413
|
+
|
|
414
|
+
if (hasSecrets) {
|
|
415
|
+
// Add _secret helper for secret values
|
|
416
|
+
code += `// Secret wrapper class\n`;
|
|
417
|
+
code += `class _Secret {\n`;
|
|
418
|
+
code += ` constructor(value) { this._value = value; }\n`;
|
|
419
|
+
code += ` toString() { return "********"; }\n`;
|
|
420
|
+
code += ` valueOf() { return this._value; }\n`;
|
|
421
|
+
code += ` get value() { return this._value; }\n`;
|
|
422
|
+
code += ` [Symbol.toPrimitive](hint) { return hint === "string" ? "********" : this._value; }\n`;
|
|
423
|
+
code += `}\n`;
|
|
424
|
+
code += `function _secret(value) { return new _Secret(value); }\n\n`;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
for (const [name, decl] of Object.entries(declarations)) {
|
|
428
|
+
code += `export const ${name} = ${generateValue(decl)};\n\n`;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
return code;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Check if any node in the declarations tree has secret: true
|
|
435
|
+
function checkForSecrets(obj) {
|
|
436
|
+
if (!obj || typeof obj !== 'object') return false;
|
|
437
|
+
if (obj.secret === true) return true;
|
|
438
|
+
|
|
439
|
+
for (const value of Object.values(obj)) {
|
|
440
|
+
if (checkForSecrets(value)) return true;
|
|
441
|
+
}
|
|
442
|
+
return false;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function generateValue(node) {
|
|
446
|
+
let value;
|
|
447
|
+
|
|
448
|
+
switch (node.type) {
|
|
449
|
+
case 'literal':
|
|
450
|
+
if (typeof node.value === 'string') {
|
|
451
|
+
value = JSON.stringify(node.value);
|
|
452
|
+
} else {
|
|
453
|
+
value = String(node.value);
|
|
454
|
+
}
|
|
455
|
+
break;
|
|
456
|
+
|
|
457
|
+
case 'reference':
|
|
458
|
+
// References are resolved at runtime via the dependency system
|
|
459
|
+
value = node.path;
|
|
460
|
+
break;
|
|
461
|
+
|
|
462
|
+
case 'array':
|
|
463
|
+
const arrayItems = node.values.map(v => generateValue(v)).join(', ');
|
|
464
|
+
value = `[${arrayItems}]`;
|
|
465
|
+
break;
|
|
466
|
+
|
|
467
|
+
case 'object':
|
|
468
|
+
const objProps = Object.entries(node.properties)
|
|
469
|
+
.map(([k, v]) => `${k}: ${generateValue(v)}`)
|
|
470
|
+
.join(', ');
|
|
471
|
+
value = `{ ${objProps} }`;
|
|
472
|
+
break;
|
|
473
|
+
|
|
474
|
+
case 'enum':
|
|
475
|
+
// Enums are frozen objects
|
|
476
|
+
const enumProps = Object.entries(node.members)
|
|
477
|
+
.map(([k, v]) => `${k}: ${generateValue(v)}`)
|
|
478
|
+
.join(', ');
|
|
479
|
+
value = `Object.freeze({ ${enumProps} })`;
|
|
480
|
+
break;
|
|
481
|
+
|
|
482
|
+
default:
|
|
483
|
+
throw new Error(`Unknown node type: ${node.type}`);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// Wrap in _secret() if marked as secret
|
|
487
|
+
if (node.secret) {
|
|
488
|
+
return `_secret(${value})`;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
return value;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// Parse a static file and return declarations
|
|
495
|
+
export function parseStaticFile(source, modulePath = '') {
|
|
496
|
+
const lexer = new StaticLexer(source);
|
|
497
|
+
const tokens = lexer.tokenize();
|
|
498
|
+
const parser = new StaticParser(tokens, modulePath);
|
|
499
|
+
return parser.parse();
|
|
500
|
+
}
|