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.
Files changed (80) hide show
  1. package/.cursor/rules/rip-agent-onboarding.md +681 -0
  2. package/.github/ISSUE_TEMPLATE/bug_report.yml +98 -0
  3. package/.github/ISSUE_TEMPLATE/coffeescript_compatibility.yml +86 -0
  4. package/.github/ISSUE_TEMPLATE/config.yml +9 -0
  5. package/.github/ISSUE_TEMPLATE/feature_request.yml +82 -0
  6. package/.github/ISSUE_TEMPLATE/question.yml +55 -0
  7. package/.github/pull_request_template.md +84 -0
  8. package/AGENT.md +623 -0
  9. package/CONTRIBUTING.md +331 -0
  10. package/LICENSE +21 -0
  11. package/README.md +1245 -0
  12. package/SETUP.md +144 -0
  13. package/bar.coffee +394 -0
  14. package/bin/rip +162 -0
  15. package/bunfig.toml +11 -0
  16. package/docs/BROWSER.md +983 -0
  17. package/docs/CODEGEN.md +1023 -0
  18. package/docs/COFFEESCRIPT-COMPARISON.md +740 -0
  19. package/docs/COMPREHENSIONS.md +572 -0
  20. package/docs/REGEX-PLUS.md +441 -0
  21. package/docs/SOLAR.md +846 -0
  22. package/docs/SPECIAL-OPERATORS.md +769 -0
  23. package/docs/STRING.md +363 -0
  24. package/docs/WHY-NOT-COFFEESCRIPT.md +184 -0
  25. package/docs/WHY-YES-RIP.md +690 -0
  26. package/docs/WORKFLOW.md +306 -0
  27. package/docs/dist/rip.browser.js +6798 -0
  28. package/docs/dist/rip.browser.min.js +242 -0
  29. package/docs/dist/rip.browser.min.js.br +0 -0
  30. package/docs/example.html +177 -0
  31. package/docs/examples/README.md +154 -0
  32. package/docs/examples/arrows.rip +84 -0
  33. package/docs/examples/async-await.rip +59 -0
  34. package/docs/examples/existential.rip +86 -0
  35. package/docs/examples/fibonacci.rip +12 -0
  36. package/docs/examples/module.rip +38 -0
  37. package/docs/examples/object-syntax.rip +74 -0
  38. package/docs/examples/prototype.rip +30 -0
  39. package/docs/examples/ranges.rip +45 -0
  40. package/docs/examples/switch.rip +50 -0
  41. package/docs/examples/ternary.rip +36 -0
  42. package/docs/examples/use-loader.js +9 -0
  43. package/docs/examples/utils.rip +20 -0
  44. package/docs/index.html +65 -0
  45. package/docs/repl.html +914 -0
  46. package/docs/rip-1280w.png +0 -0
  47. package/docs/rip.svg +4 -0
  48. package/package.json +52 -0
  49. package/rip-loader.ts +27 -0
  50. package/scripts/build-browser.js +76 -0
  51. package/scripts/serve.js +71 -0
  52. package/src/browser.js +97 -0
  53. package/src/codegen.js +4808 -0
  54. package/src/compiler.js +270 -0
  55. package/src/grammar/grammar.rip +801 -0
  56. package/src/grammar/solar.rip +1056 -0
  57. package/src/lexer.js +3145 -0
  58. package/src/parser.js +352 -0
  59. package/src/repl.js +423 -0
  60. package/test/rip/assignment.rip +115 -0
  61. package/test/rip/async.rip +361 -0
  62. package/test/rip/basic.rip +171 -0
  63. package/test/rip/classes.rip +167 -0
  64. package/test/rip/compatibility.rip +338 -0
  65. package/test/rip/comprehensions.rip +104 -0
  66. package/test/rip/control.rip +177 -0
  67. package/test/rip/data.rip +215 -0
  68. package/test/rip/errors.rip +129 -0
  69. package/test/rip/functions.rip +443 -0
  70. package/test/rip/guards.rip +247 -0
  71. package/test/rip/literals.rip +131 -0
  72. package/test/rip/loops.rip +117 -0
  73. package/test/rip/modules.rip +87 -0
  74. package/test/rip/operators.rip +158 -0
  75. package/test/rip/optional.rip +184 -0
  76. package/test/rip/properties.rip +94 -0
  77. package/test/rip/regex.rip +301 -0
  78. package/test/rip/stabilization.rip +825 -0
  79. package/test/rip/strings.rip +483 -0
  80. 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 };