rip-lang 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/.cursor/rules/rip-agent-onboarding.md +681 -0
- package/.github/ISSUE_TEMPLATE/bug_report.yml +98 -0
- package/.github/ISSUE_TEMPLATE/coffeescript_compatibility.yml +86 -0
- package/.github/ISSUE_TEMPLATE/config.yml +9 -0
- package/.github/ISSUE_TEMPLATE/feature_request.yml +82 -0
- package/.github/ISSUE_TEMPLATE/question.yml +55 -0
- package/.github/pull_request_template.md +84 -0
- package/AGENT.md +623 -0
- package/CONTRIBUTING.md +331 -0
- package/LICENSE +21 -0
- package/README.md +1245 -0
- package/SETUP.md +144 -0
- package/bar.coffee +394 -0
- package/bin/rip +162 -0
- package/bunfig.toml +11 -0
- package/docs/BROWSER.md +983 -0
- package/docs/CODEGEN.md +1023 -0
- package/docs/COFFEESCRIPT-COMPARISON.md +740 -0
- package/docs/COMPREHENSIONS.md +572 -0
- package/docs/REGEX-PLUS.md +441 -0
- package/docs/SOLAR.md +846 -0
- package/docs/SPECIAL-OPERATORS.md +769 -0
- package/docs/STRING.md +363 -0
- package/docs/WHY-NOT-COFFEESCRIPT.md +184 -0
- package/docs/WHY-YES-RIP.md +690 -0
- package/docs/WORKFLOW.md +306 -0
- package/docs/dist/rip.browser.js +6798 -0
- package/docs/dist/rip.browser.min.js +242 -0
- package/docs/dist/rip.browser.min.js.br +0 -0
- package/docs/example.html +177 -0
- package/docs/examples/README.md +154 -0
- package/docs/examples/arrows.rip +84 -0
- package/docs/examples/async-await.rip +59 -0
- package/docs/examples/existential.rip +86 -0
- package/docs/examples/fibonacci.rip +12 -0
- package/docs/examples/module.rip +38 -0
- package/docs/examples/object-syntax.rip +74 -0
- package/docs/examples/prototype.rip +30 -0
- package/docs/examples/ranges.rip +45 -0
- package/docs/examples/switch.rip +50 -0
- package/docs/examples/ternary.rip +36 -0
- package/docs/examples/use-loader.js +9 -0
- package/docs/examples/utils.rip +20 -0
- package/docs/index.html +65 -0
- package/docs/repl.html +914 -0
- package/docs/rip-1280w.png +0 -0
- package/docs/rip.svg +4 -0
- package/package.json +52 -0
- package/rip-loader.ts +27 -0
- package/scripts/build-browser.js +76 -0
- package/scripts/serve.js +71 -0
- package/src/browser.js +97 -0
- package/src/codegen.js +4808 -0
- package/src/compiler.js +270 -0
- package/src/grammar/grammar.rip +801 -0
- package/src/grammar/solar.rip +1056 -0
- package/src/lexer.js +3145 -0
- package/src/parser.js +352 -0
- package/src/repl.js +423 -0
- package/test/rip/assignment.rip +115 -0
- package/test/rip/async.rip +361 -0
- package/test/rip/basic.rip +171 -0
- package/test/rip/classes.rip +167 -0
- package/test/rip/compatibility.rip +338 -0
- package/test/rip/comprehensions.rip +104 -0
- package/test/rip/control.rip +177 -0
- package/test/rip/data.rip +215 -0
- package/test/rip/errors.rip +129 -0
- package/test/rip/functions.rip +443 -0
- package/test/rip/guards.rip +247 -0
- package/test/rip/literals.rip +131 -0
- package/test/rip/loops.rip +117 -0
- package/test/rip/modules.rip +87 -0
- package/test/rip/operators.rip +158 -0
- package/test/rip/optional.rip +184 -0
- package/test/rip/properties.rip +94 -0
- package/test/rip/regex.rip +301 -0
- package/test/rip/stabilization.rip +825 -0
- package/test/rip/strings.rip +483 -0
- package/test/runner.js +329 -0
package/test/runner.js
ADDED
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Rip Test Runner
|
|
5
|
+
*
|
|
6
|
+
* Runs .ripp test files with three test types:
|
|
7
|
+
* - test(name, code, expected) - Execute and compare result
|
|
8
|
+
* - code(name, code, expected) - Compile and compare generated code
|
|
9
|
+
* - fail(name, code) - Expect compilation/execution to fail
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* bun test/runner.js test/rip # Run directory
|
|
13
|
+
* bun test/runner.js test/rip/operators.rip # Run file
|
|
14
|
+
* bun test/runner.js test/rip test/cs2 # Multiple paths
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { readFileSync, readdirSync, statSync } from 'fs';
|
|
18
|
+
import { join, extname, relative } from 'path';
|
|
19
|
+
import { compile } from '../src/compiler.js';
|
|
20
|
+
|
|
21
|
+
// ANSI colors
|
|
22
|
+
const colors = {
|
|
23
|
+
reset: '\x1b[0m',
|
|
24
|
+
bright: '\x1b[1m',
|
|
25
|
+
red: '\x1b[31m',
|
|
26
|
+
green: '\x1b[32m',
|
|
27
|
+
yellow: '\x1b[33m',
|
|
28
|
+
blue: '\x1b[34m',
|
|
29
|
+
cyan: '\x1b[36m',
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// Test tracking
|
|
33
|
+
let currentFile = '';
|
|
34
|
+
let fileTests = { pass: 0, fail: 0 };
|
|
35
|
+
let totalTests = { pass: 0, fail: 0 };
|
|
36
|
+
let failures = [];
|
|
37
|
+
|
|
38
|
+
// Normalize code for comparison (remove extra whitespace, normalize semicolons)
|
|
39
|
+
function normalizeCode(code) {
|
|
40
|
+
return code
|
|
41
|
+
.trim()
|
|
42
|
+
.replace(/^\/\/.*\n/gm, '') // Remove comment lines
|
|
43
|
+
.replace(/;\s*$/gm, '') // Remove trailing semicolons from lines
|
|
44
|
+
.replace(/\s+/g, ' ') // Collapse whitespace
|
|
45
|
+
.replace(/\s*([{}();,=])\s*/g, '$1') // Remove spaces around punctuation
|
|
46
|
+
.replace(/;}/g, '}') // Remove semicolon before closing brace
|
|
47
|
+
.trim();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Test helper: Execute code and compare result
|
|
51
|
+
// Note: This is async to support await - but await on non-promises is instant,
|
|
52
|
+
// so synchronous tests have zero performance impact
|
|
53
|
+
async function test(name, code, expected) {
|
|
54
|
+
try {
|
|
55
|
+
const result = compile(code);
|
|
56
|
+
|
|
57
|
+
// Check if code contains for-await or top-level await (needs async wrapper)
|
|
58
|
+
const needsAsyncWrapper = result.code.includes('for await') ||
|
|
59
|
+
(result.code.includes('await ') && !result.code.includes('async function'));
|
|
60
|
+
|
|
61
|
+
let actual;
|
|
62
|
+
if (needsAsyncWrapper) {
|
|
63
|
+
// Wrap in async function for for-await support
|
|
64
|
+
const AsyncFunction = Object.getPrototypeOf(async function(){}).constructor;
|
|
65
|
+
const fn = new AsyncFunction(result.code);
|
|
66
|
+
actual = await fn();
|
|
67
|
+
} else {
|
|
68
|
+
// Regular eval (preserves function types like AsyncFunction)
|
|
69
|
+
actual = eval(result.code);
|
|
70
|
+
// If result is a Promise, await it
|
|
71
|
+
actual = await actual;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (JSON.stringify(actual) === JSON.stringify(expected)) {
|
|
75
|
+
fileTests.pass++;
|
|
76
|
+
totalTests.pass++;
|
|
77
|
+
console.log(` ${colors.green}✓${colors.reset} ${name}`);
|
|
78
|
+
} else {
|
|
79
|
+
fileTests.fail++;
|
|
80
|
+
totalTests.fail++;
|
|
81
|
+
console.log(` ${colors.red}✗${colors.reset} ${name}`);
|
|
82
|
+
failures.push({
|
|
83
|
+
file: currentFile,
|
|
84
|
+
test: name,
|
|
85
|
+
type: 'test',
|
|
86
|
+
expected: JSON.stringify(expected),
|
|
87
|
+
actual: JSON.stringify(actual),
|
|
88
|
+
code
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
} catch (error) {
|
|
92
|
+
fileTests.fail++;
|
|
93
|
+
totalTests.fail++;
|
|
94
|
+
console.log(` ${colors.red}✗${colors.reset} ${name}`);
|
|
95
|
+
failures.push({
|
|
96
|
+
file: currentFile,
|
|
97
|
+
test: name,
|
|
98
|
+
type: 'test',
|
|
99
|
+
error: error.message,
|
|
100
|
+
code
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Test helper: Compile and compare generated code
|
|
106
|
+
function code(name, sourceCode, expectedCode) {
|
|
107
|
+
try {
|
|
108
|
+
const result = compile(sourceCode);
|
|
109
|
+
const actualNorm = normalizeCode(result.code);
|
|
110
|
+
const expectedNorm = normalizeCode(expectedCode);
|
|
111
|
+
|
|
112
|
+
if (actualNorm === expectedNorm) {
|
|
113
|
+
fileTests.pass++;
|
|
114
|
+
totalTests.pass++;
|
|
115
|
+
console.log(` ${colors.green}✓${colors.reset} ${name}`);
|
|
116
|
+
} else {
|
|
117
|
+
fileTests.fail++;
|
|
118
|
+
totalTests.fail++;
|
|
119
|
+
console.log(` ${colors.red}✗${colors.reset} ${name}`);
|
|
120
|
+
failures.push({
|
|
121
|
+
file: currentFile,
|
|
122
|
+
test: name,
|
|
123
|
+
type: 'code',
|
|
124
|
+
expected: expectedCode,
|
|
125
|
+
actual: result.code,
|
|
126
|
+
normalized: {
|
|
127
|
+
expected: expectedNorm,
|
|
128
|
+
actual: actualNorm
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
} catch (error) {
|
|
133
|
+
fileTests.fail++;
|
|
134
|
+
totalTests.fail++;
|
|
135
|
+
console.log(` ${colors.red}✗${colors.reset} ${name}`);
|
|
136
|
+
failures.push({
|
|
137
|
+
file: currentFile,
|
|
138
|
+
test: name,
|
|
139
|
+
type: 'code',
|
|
140
|
+
error: error.message,
|
|
141
|
+
sourceCode
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Test helper: Expect failure (compilation or execution)
|
|
147
|
+
function fail(name, sourceCode) {
|
|
148
|
+
try {
|
|
149
|
+
const result = compile(sourceCode);
|
|
150
|
+
|
|
151
|
+
// Try to execute - should fail
|
|
152
|
+
try {
|
|
153
|
+
eval(result.code);
|
|
154
|
+
|
|
155
|
+
// If we get here, test failed (should have thrown)
|
|
156
|
+
fileTests.fail++;
|
|
157
|
+
totalTests.fail++;
|
|
158
|
+
console.log(` ${colors.red}✗${colors.reset} ${name}`);
|
|
159
|
+
failures.push({
|
|
160
|
+
file: currentFile,
|
|
161
|
+
test: name,
|
|
162
|
+
type: 'fail',
|
|
163
|
+
reason: 'Expected failure but succeeded',
|
|
164
|
+
code: result.code
|
|
165
|
+
});
|
|
166
|
+
} catch (execError) {
|
|
167
|
+
// Execution failed as expected
|
|
168
|
+
fileTests.pass++;
|
|
169
|
+
totalTests.pass++;
|
|
170
|
+
console.log(` ${colors.green}✓${colors.reset} ${name}`);
|
|
171
|
+
}
|
|
172
|
+
} catch (compileError) {
|
|
173
|
+
// Compilation failed as expected
|
|
174
|
+
fileTests.pass++;
|
|
175
|
+
totalTests.pass++;
|
|
176
|
+
console.log(` ${colors.green}✓${colors.reset} ${name}`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Run a single test file
|
|
181
|
+
async function runTestFile(filePath) {
|
|
182
|
+
currentFile = relative(process.cwd(), filePath);
|
|
183
|
+
fileTests = { pass: 0, fail: 0 };
|
|
184
|
+
|
|
185
|
+
console.log(`\n${colors.cyan}${currentFile}${colors.reset}`);
|
|
186
|
+
|
|
187
|
+
try {
|
|
188
|
+
const source = readFileSync(filePath, 'utf-8');
|
|
189
|
+
const result = compile(source);
|
|
190
|
+
|
|
191
|
+
// Create test environment with test helpers
|
|
192
|
+
const testEnv = {
|
|
193
|
+
test,
|
|
194
|
+
code,
|
|
195
|
+
fail,
|
|
196
|
+
console,
|
|
197
|
+
// For async tests
|
|
198
|
+
Promise,
|
|
199
|
+
async: true,
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
// Execute test file as async function
|
|
203
|
+
const testFn = new (async function(){}).constructor(...Object.keys(testEnv), result.code);
|
|
204
|
+
await testFn(...Object.values(testEnv));
|
|
205
|
+
|
|
206
|
+
} catch (error) {
|
|
207
|
+
console.log(` ${colors.red}✗ File failed to compile/execute${colors.reset}`);
|
|
208
|
+
console.log(` ${error.message}`);
|
|
209
|
+
fileTests.fail++;
|
|
210
|
+
totalTests.fail++;
|
|
211
|
+
failures.push({
|
|
212
|
+
file: currentFile,
|
|
213
|
+
test: 'File execution',
|
|
214
|
+
type: 'file',
|
|
215
|
+
error: error.message
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return fileTests;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Recursively find all test files
|
|
223
|
+
function findTestFiles(path) {
|
|
224
|
+
try {
|
|
225
|
+
const stats = statSync(path);
|
|
226
|
+
|
|
227
|
+
if (stats.isFile()) {
|
|
228
|
+
return ['.rip'].includes(extname(path)) ? [path] : [];
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (stats.isDirectory()) {
|
|
232
|
+
// Skip fixtures directory
|
|
233
|
+
if (path.includes('/fixtures')) {
|
|
234
|
+
return [];
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const entries = readdirSync(path).sort();
|
|
238
|
+
return entries.flatMap(entry =>
|
|
239
|
+
findTestFiles(join(path, entry))
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
} catch (error) {
|
|
243
|
+
console.error(`${colors.red}Error reading path: ${path}${colors.reset}`);
|
|
244
|
+
console.error(` ${error.message}`);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return [];
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Print failure details
|
|
251
|
+
function printFailures() {
|
|
252
|
+
if (failures.length === 0) return;
|
|
253
|
+
|
|
254
|
+
console.log(`\n${colors.bright}${colors.red}Failure Details:${colors.reset}\n`);
|
|
255
|
+
|
|
256
|
+
failures.forEach((failure, index) => {
|
|
257
|
+
console.log(`${colors.bright}${index + 1}. ${failure.file} - ${failure.test}${colors.reset}`);
|
|
258
|
+
|
|
259
|
+
if (failure.error) {
|
|
260
|
+
console.log(` Error: ${failure.error}`);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (failure.type === 'test') {
|
|
264
|
+
console.log(` Expected: ${failure.expected}`);
|
|
265
|
+
console.log(` Actual: ${failure.actual}`);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (failure.type === 'code') {
|
|
269
|
+
console.log(` Expected code:`);
|
|
270
|
+
console.log(` ${failure.expected}`);
|
|
271
|
+
console.log(` Actual code:`);
|
|
272
|
+
console.log(` ${failure.actual}`);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (failure.type === 'fail') {
|
|
276
|
+
console.log(` ${failure.reason}`);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
console.log('');
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Main entry point
|
|
284
|
+
async function main(args) {
|
|
285
|
+
// Default to test/rip if no arguments provided
|
|
286
|
+
if (args.length === 0) {
|
|
287
|
+
args = ['test/rip'];
|
|
288
|
+
console.log(`${colors.cyan}Running default: test/rip${colors.reset}\n`);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Collect all test files from arguments
|
|
292
|
+
const testFiles = args.flatMap(arg => findTestFiles(arg));
|
|
293
|
+
|
|
294
|
+
if (testFiles.length === 0) {
|
|
295
|
+
console.log(`${colors.yellow}No test files found${colors.reset}`);
|
|
296
|
+
process.exit(0);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
console.log(`${colors.bright}Running ${testFiles.length} test file(s)...${colors.reset}`);
|
|
300
|
+
|
|
301
|
+
// Run each test file (await for async support)
|
|
302
|
+
for (const file of testFiles) {
|
|
303
|
+
await runTestFile(file);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Summary
|
|
307
|
+
console.log(`\n${colors.bright}${'─'.repeat(60)}${colors.reset}`);
|
|
308
|
+
|
|
309
|
+
if (totalTests.fail > 0) {
|
|
310
|
+
console.log(`${colors.red}${colors.bright}Test failures detected${colors.reset}`);
|
|
311
|
+
printFailures();
|
|
312
|
+
} else {
|
|
313
|
+
console.log(`${colors.green}${colors.bright}All tests passed!${colors.reset}`);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
console.log(`${colors.green}✓ ${totalTests.pass} passing${colors.reset}`);
|
|
317
|
+
if (totalTests.fail > 0) {
|
|
318
|
+
console.log(`${colors.red}✗ ${totalTests.fail} failing${colors.reset}`);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
process.exit(totalTests.fail > 0 ? 1 : 0);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Run if called directly
|
|
325
|
+
if (import.meta.main) {
|
|
326
|
+
await main(process.argv.slice(2));
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
export { test, code, fail };
|