Skip to content

Commit bf6c262

Browse files
committed
feat: integrate real T-Ruby compiler via ruby.wasm
- Load T-Ruby library files dynamically in Web Worker - Use Base64 encoding for safe code transfer with special characters - Remove mock compiler fallback in playground - Add static/t-ruby-lib/ with core compilation files (19 files) - Exclude non-WASM-compatible modules (lsp, watcher, cli, etc.)
1 parent cbcbc32 commit bf6c262

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

64 files changed

+16703
-60
lines changed

src/lib/ruby-wasm.ts

Lines changed: 91 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,31 @@ function generateRequestId(): string {
4545
return `req_${++requestIdCounter}_${Date.now()}`;
4646
}
4747

48+
// T-Ruby library files in dependency order (only core compilation files)
49+
// Excluded: lsp_server, watcher, cli, cache, package_manager, bundler_integration, benchmark, doc_generator
50+
// These require external gems (listen, etc.) not available in WASM
51+
const T_RUBY_FILES = [
52+
't_ruby/version.rb',
53+
't_ruby/config.rb',
54+
't_ruby/ir.rb',
55+
't_ruby/parser_combinator.rb',
56+
't_ruby/smt_solver.rb',
57+
't_ruby/type_alias_registry.rb',
58+
't_ruby/parser.rb',
59+
't_ruby/union_type_parser.rb',
60+
't_ruby/generic_type_parser.rb',
61+
't_ruby/intersection_type_parser.rb',
62+
't_ruby/type_erasure.rb',
63+
't_ruby/error_handler.rb',
64+
't_ruby/rbs_generator.rb',
65+
't_ruby/declaration_generator.rb',
66+
't_ruby/compiler.rb',
67+
't_ruby/constraint_checker.rb',
68+
't_ruby/type_inferencer.rb',
69+
't_ruby/runtime_validator.rb',
70+
't_ruby/type_checker.rb',
71+
];
72+
4873
// Web Worker code as a string - runs in isolated thread
4974
const WORKER_CODE = `
5075
// Web Worker for T-Ruby WASM compilation
@@ -55,13 +80,23 @@ const RUBY_WASM_CDN = 'https://cdn.jsdelivr.net/npm/@ruby/wasm-wasi@2.7.1/dist/b
5580
// Use Ruby 3.4 WASM binary (more stable)
5681
const RUBY_WASM_BINARY = 'https://cdn.jsdelivr.net/npm/@ruby/3.4-wasm-wasi@2.7.1/dist/ruby+stdlib.wasm';
5782
83+
// T-Ruby library base URL (will be set from main thread)
84+
let T_RUBY_LIB_BASE = '';
85+
86+
// T-Ruby library files in dependency order
87+
const T_RUBY_FILES = ${JSON.stringify(T_RUBY_FILES)};
88+
5889
const BOOTSTRAP_CODE = \`
5990
require "json"
6091
92+
# Define TRuby module if not already defined
93+
module TRuby
94+
end unless defined?(TRuby)
95+
6196
$trb_compiler = nil
6297
6398
def get_compiler
64-
$trb_compiler ||= TRuby::Compiler.new
99+
$trb_compiler ||= TRuby::Compiler.new(nil, use_ir: true, optimize: false)
65100
end
66101
67102
def __trb_compile__(code)
@@ -88,7 +123,7 @@ def __trb_compile__(code)
88123
success: false,
89124
ruby: "",
90125
rbs: "",
91-
errors: ["Compilation error: " + e.message]
126+
errors: ["Compilation error: " + e.message + " at " + e.backtrace.first.to_s]
92127
}.to_json
93128
end
94129
end
@@ -110,34 +145,72 @@ function sendToMain(type, data, requestId) {
110145
self.postMessage({ type, data, requestId });
111146
}
112147
113-
// Initialize Ruby VM
148+
// Fetch T-Ruby library file
149+
async function fetchTRubyFile(filename) {
150+
const url = T_RUBY_LIB_BASE + filename;
151+
const response = await fetch(url);
152+
if (!response.ok) {
153+
throw new Error('Failed to fetch ' + filename + ': ' + response.status);
154+
}
155+
return await response.text();
156+
}
157+
158+
// Initialize Ruby VM with T-Ruby library
114159
async function initialize() {
115160
try {
116161
console.log('[WASM Worker] Step 1: Loading Ruby WASM module...');
117-
sendToMain('progress', { message: 'Loading Ruby runtime...', progress: 10 });
162+
sendToMain('progress', { message: 'Loading Ruby runtime...', progress: 5 });
118163
119164
const { DefaultRubyVM } = await import(RUBY_WASM_CDN);
120165
console.log('[WASM Worker] Step 1 complete');
121166
122167
console.log('[WASM Worker] Step 2: Fetching WASM binary...');
123-
sendToMain('progress', { message: 'Downloading Ruby WASM binary...', progress: 30 });
168+
sendToMain('progress', { message: 'Downloading Ruby WASM binary...', progress: 15 });
124169
125170
const response = await fetch(RUBY_WASM_BINARY);
126171
const wasmModule = await WebAssembly.compileStreaming(response);
127172
console.log('[WASM Worker] Step 2 complete');
128173
129174
console.log('[WASM Worker] Step 3: Initializing Ruby VM...');
130-
sendToMain('progress', { message: 'Initializing Ruby VM...', progress: 60 });
175+
sendToMain('progress', { message: 'Initializing Ruby VM...', progress: 30 });
131176
132177
const result = await DefaultRubyVM(wasmModule);
133178
vm = result.vm;
134179
console.log('[WASM Worker] Step 3 complete');
135180
136-
console.log('[WASM Worker] Step 4: Loading T-Ruby bootstrap...');
137-
sendToMain('progress', { message: 'Loading T-Ruby compiler...', progress: 80 });
181+
console.log('[WASM Worker] Step 4: Loading T-Ruby library files...');
182+
sendToMain('progress', { message: 'Loading T-Ruby compiler...', progress: 40 });
183+
184+
// Load each T-Ruby library file
185+
const totalFiles = T_RUBY_FILES.length;
186+
for (let i = 0; i < totalFiles; i++) {
187+
const filename = T_RUBY_FILES[i];
188+
const progress = 40 + Math.floor((i / totalFiles) * 50);
189+
sendToMain('progress', { message: 'Loading ' + filename + '...', progress });
190+
191+
try {
192+
const code = await fetchTRubyFile(filename);
193+
// Remove frozen_string_literal comment and require_relative statements
194+
// since we're loading files directly
195+
const processedCode = code
196+
.replace(/# frozen_string_literal: true\\n?/g, '')
197+
.replace(/require_relative\\s+["'][^"']+["']\\n?/g, '')
198+
.replace(/require\\s+["']fileutils["']\\n?/g, '');
199+
200+
console.log('[WASM Worker] Loading:', filename);
201+
vm.eval(processedCode);
202+
} catch (err) {
203+
console.error('[WASM Worker] Error loading ' + filename + ':', err);
204+
throw err;
205+
}
206+
}
207+
console.log('[WASM Worker] Step 4 complete');
208+
209+
console.log('[WASM Worker] Step 5: Running bootstrap code...');
210+
sendToMain('progress', { message: 'Initializing compiler...', progress: 95 });
138211
139212
vm.eval(BOOTSTRAP_CODE);
140-
console.log('[WASM Worker] Step 4 complete');
213+
console.log('[WASM Worker] Step 5 complete');
141214
142215
// Health check
143216
const healthResult = vm.eval('__trb_health_check__');
@@ -166,7 +239,10 @@ function compile(code, requestId) {
166239
167240
try {
168241
console.log('[WASM Worker] Compiling:', code.substring(0, 50) + '...');
169-
const resultJson = vm.eval('__trb_compile__(' + JSON.stringify(code) + ')');
242+
// Use Base64 encoding to safely pass code with any special characters
243+
const base64Code = btoa(unescape(encodeURIComponent(code)));
244+
const decodeAndCompile = 'require "base64"; __trb_compile__(Base64.decode64("' + base64Code + '").force_encoding("UTF-8"))';
245+
const resultJson = vm.eval(decodeAndCompile);
170246
const result = JSON.parse(resultJson.toString());
171247
console.log('[WASM Worker] Compile result:', result);
172248
sendToMain('compile-result', result, requestId);
@@ -187,6 +263,8 @@ self.addEventListener('message', (event) => {
187263
188264
switch (type) {
189265
case 'init':
266+
// Set base URL from main thread
267+
T_RUBY_LIB_BASE = data.origin + '/t-ruby-lib/';
190268
initialize();
191269
break;
192270
case 'compile':
@@ -237,7 +315,7 @@ async function doLoadCompiler(
237315
onProgress?.({
238316
state: 'loading',
239317
message: 'Initializing compiler worker...',
240-
progress: 5
318+
progress: 0
241319
});
242320

243321
// Create Web Worker from blob URL
@@ -257,7 +335,7 @@ async function doLoadCompiler(
257335
switch (type) {
258336
case 'loaded':
259337
console.log('[T-Ruby] Worker loaded, sending init command...');
260-
worker.postMessage({ type: 'init' });
338+
worker.postMessage({ type: 'init', data: { origin: window.location.origin } });
261339
break;
262340

263341
case 'progress':
@@ -351,7 +429,7 @@ async function doLoadCompiler(
351429
worker.terminate();
352430
reject(new Error('WASM worker initialization timeout'));
353431
}
354-
}, 60000); // 60 second timeout for initial load
432+
}, 120000); // 120 second timeout for initial load (loading many files)
355433

356434
// Clear timeout when ready
357435
const originalResolve = resolve;

src/pages/playground.tsx

Lines changed: 6 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -155,41 +155,6 @@ interface TRubyCompiler {
155155
compile(code: string): CompileResult | Promise<CompileResult>;
156156
}
157157

158-
// Fallback mock compiler (used when WASM fails to load)
159-
function createMockCompiler(): TRubyCompiler {
160-
return {
161-
compile(code: string): CompileResult {
162-
const lines = code.split('\n');
163-
const rubyLines: string[] = [];
164-
const rbsLines: string[] = [];
165-
166-
for (const line of lines) {
167-
let rubyLine = line
168-
.replace(/\):\s*\w+(\s*\|\s*\w+)*(\?)?(\s*{|$)/g, ')$3')
169-
.replace(/(\w+):\s*([A-Z]\w*(\s*\|\s*\w+)*\??)/g, '$1')
170-
.replace(/<[A-Z]\w*(,\s*[A-Z]\w*)*>/g, '')
171-
.replace(/^interface\s+.*$/g, '# interface removed')
172-
.replace(/^\s*implements\s+.*$/g, '')
173-
.replace(/@(\w+):\s*([A-Z]\w*(\s*\|\s*\w+)*\??)(\s*=.*)?$/g, '@$1$4');
174-
rubyLines.push(rubyLine);
175-
}
176-
177-
const methodMatches = code.matchAll(/def\s+(\w+)(\([^)]*\))?:\s*(\w+(\s*\|\s*\w+)*\??)/g);
178-
for (const match of methodMatches) {
179-
const [, name, params, returnType] = match;
180-
rbsLines.push(`def ${name}: ${params || '()'} -> ${returnType}`);
181-
}
182-
183-
return {
184-
success: true,
185-
ruby: rubyLines.join('\n'),
186-
rbs: rbsLines.length > 0 ? rbsLines.join('\n\n') : '# No type signatures generated',
187-
errors: [],
188-
};
189-
}
190-
};
191-
}
192-
193158
function PlaygroundContent(): JSX.Element {
194159
const [code, setCode] = useState(EXAMPLES['hello-world'].code);
195160
const [selectedExample, setSelectedExample] = useState<ExampleKey>('hello-world');
@@ -221,14 +186,10 @@ function PlaygroundContent(): JSX.Element {
221186
setCompiler(loadedCompiler);
222187
return loadedCompiler;
223188
} catch (error) {
224-
console.warn('WASM compiler failed to load, using mock compiler:', error);
189+
console.error('WASM compiler failed to load:', error);
225190
setCompilerState('error');
226-
setLoadingMessage('');
227-
228-
// Fall back to mock compiler
229-
const mockCompiler = createMockCompiler();
230-
setCompiler(mockCompiler);
231-
return mockCompiler;
191+
setLoadingMessage(error instanceof Error ? error.message : 'Failed to load compiler');
192+
throw error;
232193
}
233194
}, []);
234195

@@ -308,7 +269,7 @@ function PlaygroundContent(): JSX.Element {
308269
)}
309270
{compilerState === 'error' && (
310271
<span className={styles.compilerStatusFallback}>
311-
<Translate id="playground.status.fallback">Using simplified compiler</Translate>
272+
Compiler Error
312273
</span>
313274
)}
314275
</div>
@@ -422,10 +383,8 @@ function PlaygroundContent(): JSX.Element {
422383
<div className={styles.footer}>
423384
<p>
424385
{compilerState === 'error' ? (
425-
<strong>
426-
<Translate id="playground.footer.fallbackNote">
427-
Note: Using simplified compiler. For full functionality, install T-Ruby locally with gem install t-ruby.
428-
</Translate>
386+
<strong style={{color: 'var(--ifm-color-danger)'}}>
387+
{loadingMessage || 'Failed to load compiler. Please try refreshing the page.'}
429388
</strong>
430389
) : (
431390
<Translate id="playground.footer.wasmNote">

0 commit comments

Comments
 (0)