Skip to content

Commit 965b70d

Browse files
committed
fix: resolve WASM initialization failures with heredoc and stubs
- Replace escape-based eval with heredoc syntax to avoid complex escaping issues in TypeScript → JavaScript → Ruby eval chain - Add Net::HTTP, OpenSSL, and FileUtils stubs for WASM environment where socket is not available - Add TRuby::WASM_ENV flag to detect WASM runtime - Add TRubyInitScript tests for validation Fixes #2
1 parent 59c2ba9 commit 965b70d

2 files changed

Lines changed: 234 additions & 16 deletions

File tree

src/vm/TRubyInitScript.ts

Lines changed: 184 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,32 +11,200 @@ import { T_RUBY_BUNDLE, HAS_T_RUBY_BUNDLE } from "./TRubyBundle.js";
1111
*/
1212
export function generateInitScript(): string {
1313
if (HAS_T_RUBY_BUNDLE) {
14-
// Generate script that loads bundled files
14+
// Generate script that directly evals bundled T-Ruby files
15+
// This avoids file system operations which may not work in all WASM environments
16+
17+
// T-Ruby library files in dependency order (complete list)
18+
// Based on t_ruby.rb require_relative order + parser_combinator submodules
19+
// Paths match bundle keys: lib/t_ruby/...
20+
// NOTE: version_checker.rb excluded - uses net/http which requires socket (not available in WASM)
21+
const T_RUBY_LOAD_ORDER = [
22+
// Core
23+
'lib/t_ruby/version.rb',
24+
// 'lib/t_ruby/version_checker.rb', // Excluded: uses net/http (socket not available in WASM)
25+
'lib/t_ruby/ruby_version.rb',
26+
'lib/t_ruby/code_emitter.rb',
27+
'lib/t_ruby/config.rb',
28+
'lib/t_ruby/string_utils.rb',
29+
'lib/t_ruby/ir.rb',
30+
31+
// Parser Combinator - Base
32+
'lib/t_ruby/parser_combinator/parse_result.rb',
33+
'lib/t_ruby/parser_combinator/parser.rb',
34+
35+
// Parser Combinator - Primitives
36+
'lib/t_ruby/parser_combinator/primitives/literal.rb',
37+
'lib/t_ruby/parser_combinator/primitives/satisfy.rb',
38+
'lib/t_ruby/parser_combinator/primitives/regex.rb',
39+
'lib/t_ruby/parser_combinator/primitives/end_of_input.rb',
40+
'lib/t_ruby/parser_combinator/primitives/pure.rb',
41+
'lib/t_ruby/parser_combinator/primitives/fail.rb',
42+
'lib/t_ruby/parser_combinator/primitives/lazy.rb',
43+
44+
// Parser Combinator - Combinators
45+
'lib/t_ruby/parser_combinator/combinators/sequence.rb',
46+
'lib/t_ruby/parser_combinator/combinators/alternative.rb',
47+
'lib/t_ruby/parser_combinator/combinators/map.rb',
48+
'lib/t_ruby/parser_combinator/combinators/flat_map.rb',
49+
'lib/t_ruby/parser_combinator/combinators/many.rb',
50+
'lib/t_ruby/parser_combinator/combinators/many1.rb',
51+
'lib/t_ruby/parser_combinator/combinators/optional.rb',
52+
'lib/t_ruby/parser_combinator/combinators/sep_by.rb',
53+
'lib/t_ruby/parser_combinator/combinators/sep_by1.rb',
54+
'lib/t_ruby/parser_combinator/combinators/skip_right.rb',
55+
'lib/t_ruby/parser_combinator/combinators/label.rb',
56+
'lib/t_ruby/parser_combinator/combinators/lookahead.rb',
57+
'lib/t_ruby/parser_combinator/combinators/not_followed_by.rb',
58+
'lib/t_ruby/parser_combinator/combinators/choice.rb',
59+
'lib/t_ruby/parser_combinator/combinators/chain_left.rb',
60+
61+
// Parser Combinator - DSL
62+
'lib/t_ruby/parser_combinator/dsl.rb',
63+
64+
// Parser Combinator - Token parsers
65+
'lib/t_ruby/parser_combinator/token/token_parse_result.rb',
66+
'lib/t_ruby/parser_combinator/token/token_parser.rb',
67+
'lib/t_ruby/parser_combinator/token/token_matcher.rb',
68+
'lib/t_ruby/parser_combinator/token/token_sequence.rb',
69+
'lib/t_ruby/parser_combinator/token/token_alternative.rb',
70+
'lib/t_ruby/parser_combinator/token/token_map.rb',
71+
'lib/t_ruby/parser_combinator/token/token_many.rb',
72+
'lib/t_ruby/parser_combinator/token/token_many1.rb',
73+
'lib/t_ruby/parser_combinator/token/token_optional.rb',
74+
'lib/t_ruby/parser_combinator/token/token_sep_by.rb',
75+
'lib/t_ruby/parser_combinator/token/token_sep_by1.rb',
76+
'lib/t_ruby/parser_combinator/token/token_skip_right.rb',
77+
'lib/t_ruby/parser_combinator/token/token_label.rb',
78+
'lib/t_ruby/parser_combinator/token/token_dsl.rb',
79+
80+
// Parser Combinator - High-level parsers
81+
'lib/t_ruby/parser_combinator/token/expression_parser.rb',
82+
'lib/t_ruby/parser_combinator/token/token_body_parser.rb',
83+
'lib/t_ruby/parser_combinator/token/statement_parser.rb',
84+
'lib/t_ruby/parser_combinator/token/token_declaration_parser.rb',
85+
86+
// Parser Combinator - Type and declaration parsers
87+
'lib/t_ruby/parser_combinator/type_parser.rb',
88+
'lib/t_ruby/parser_combinator/declaration_parser.rb',
89+
90+
// Parser Combinator - Error
91+
'lib/t_ruby/parser_combinator/parse_error.rb',
92+
93+
// Main parser_combinator module (defines the module structure)
94+
'lib/t_ruby/parser_combinator.rb',
95+
96+
// Scanner and more parsers
97+
'lib/t_ruby/scanner.rb',
98+
'lib/t_ruby/smt_solver.rb',
99+
'lib/t_ruby/type_alias_registry.rb',
100+
'lib/t_ruby/heredoc_detector.rb',
101+
'lib/t_ruby/parser.rb',
102+
'lib/t_ruby/union_type_parser.rb',
103+
'lib/t_ruby/generic_type_parser.rb',
104+
'lib/t_ruby/intersection_type_parser.rb',
105+
'lib/t_ruby/type_erasure.rb',
106+
'lib/t_ruby/error_handler.rb',
107+
'lib/t_ruby/diagnostic.rb',
108+
'lib/t_ruby/diagnostic_formatter.rb',
109+
'lib/t_ruby/error_reporter.rb',
110+
'lib/t_ruby/declaration_generator.rb',
111+
'lib/t_ruby/compiler.rb',
112+
113+
// Type system
114+
'lib/t_ruby/constraint_checker.rb',
115+
'lib/t_ruby/type_inferencer.rb',
116+
'lib/t_ruby/runtime_validator.rb',
117+
'lib/t_ruby/type_checker.rb',
118+
'lib/t_ruby/type_env.rb',
119+
'lib/t_ruby/ast_type_inferrer.rb',
120+
'lib/t_ruby/cache.rb',
121+
];
122+
15123
let script = `
16124
# T-Ruby WASM Bundle Loader
17-
$LOAD_PATH.unshift('/t-ruby')
125+
# Directly eval bundled files instead of writing to file system
126+
127+
# WASM 환경 표시
128+
module TRuby
129+
WASM_ENV = true
130+
end unless defined?(TRuby)
131+
132+
# Net::HTTP 스텁 (WASM에서 socket 사용 불가)
133+
module Net
134+
class HTTP
135+
class << self
136+
def new(*); self; end
137+
def start(*); yield self if block_given?; end
138+
def get_response(*); nil; end
139+
end
140+
141+
attr_accessor :use_ssl, :verify_mode, :open_timeout, :read_timeout
18142
19-
# Create virtual file system for bundled files
143+
def request(*); nil; end
144+
145+
class Get
146+
def initialize(*); end
147+
end
148+
149+
class Post
150+
def initialize(*); end
151+
attr_accessor :body
152+
def []=(*); end
153+
end
154+
155+
class Delete
156+
def initialize(*); end
157+
def set_form_data(*); end
158+
end
159+
end
160+
161+
HTTPSuccess = Class.new
162+
HTTPNotFound = Class.new
163+
end unless defined?(Net::HTTP)
164+
165+
# OpenSSL 스텁
166+
module OpenSSL
167+
module SSL
168+
VERIFY_PEER = 0
169+
VERIFY_NONE = 1
170+
end
171+
end unless defined?(OpenSSL::SSL::VERIFY_PEER)
172+
173+
# FileUtils 스텁
174+
module FileUtils
175+
def self.mkdir_p(*); true; end
176+
def self.rm_rf(*); true; end
177+
def self.cp(*); true; end
178+
def self.mv(*); true; end
179+
end unless defined?(FileUtils) && FileUtils.respond_to?(:mkdir_p)
20180
`;
21181

22-
// Add each bundled file to the virtual file system
23-
for (const [path, content] of Object.entries(T_RUBY_BUNDLE)) {
24-
const escapedContent = content
25-
.replace(/\\/g, "\\\\")
26-
.replace(/'/g, "\\'")
27-
.replace(/\n/g, "\\n");
182+
// Load each bundled file in dependency order by directly evaling the content
183+
// Using heredoc syntax to avoid complex escaping issues
184+
for (let i = 0; i < T_RUBY_LOAD_ORDER.length; i++) {
185+
const path = T_RUBY_LOAD_ORDER[i];
186+
const content = T_RUBY_BUNDLE[path];
187+
if (!content) continue;
188+
189+
// Process the content: remove frozen_string_literal and require_relative
190+
const processedContent = content
191+
.replace(/# frozen_string_literal: true\n?/g, '')
192+
.replace(/require_relative\s+["'][^"']+["']\n?/g, '')
193+
.replace(/require\s+["']fileutils["']\n?/g, '');
194+
195+
// Use heredoc with single quotes to avoid interpolation and escape issues
196+
// The delimiter is unique per file to prevent conflicts
197+
const delimiter = `__T_RUBY_CODE_${i}__`;
198+
28199
script += `
29-
begin
30-
dir = File.dirname('/t-ruby/${path}')
31-
FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
32-
rescue; end
33-
File.write('/t-ruby/${path}', '${escapedContent}')
200+
eval(<<~'${delimiter}', binding, '${path}')
201+
${processedContent}
202+
${delimiter}
34203
`;
35204
}
36205

37206
script += `
38-
require 'rubygems'
39-
require 't_ruby'
207+
require 'json'
40208
`;
41209

42210
return script;

tests/TRubyInitScript.test.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { describe, it, expect } from "vitest";
2+
import { generateInitScript } from "../src/vm/TRubyInitScript.js";
3+
4+
describe("TRubyInitScript", () => {
5+
describe("generateInitScript", () => {
6+
it("should generate script with TRuby module and WASM_ENV flag", () => {
7+
const script = generateInitScript();
8+
expect(script).toContain("module TRuby");
9+
expect(script).toContain("WASM_ENV = true");
10+
});
11+
12+
it("should include Net::HTTP stub for WASM environment", () => {
13+
const script = generateInitScript();
14+
expect(script).toContain("module Net");
15+
expect(script).toContain("class HTTP");
16+
expect(script).toContain("HTTPSuccess = Class.new");
17+
expect(script).toContain("HTTPNotFound = Class.new");
18+
});
19+
20+
it("should include OpenSSL stub", () => {
21+
const script = generateInitScript();
22+
expect(script).toContain("module OpenSSL");
23+
expect(script).toContain("VERIFY_PEER = 0");
24+
});
25+
26+
it("should include FileUtils stub", () => {
27+
const script = generateInitScript();
28+
expect(script).toContain("module FileUtils");
29+
expect(script).toContain("def self.mkdir_p");
30+
});
31+
32+
it("should use heredoc for file loading to avoid escape issues", () => {
33+
const script = generateInitScript();
34+
// Heredoc delimiter pattern: __T_RUBY_CODE_N__
35+
expect(script).toMatch(/<<~'__T_RUBY_CODE_\d+__'/);
36+
});
37+
38+
it("should require json at the end", () => {
39+
const script = generateInitScript();
40+
expect(script).toContain("require 'json'");
41+
});
42+
43+
it("should not contain double-quoted eval strings (old escape method)", () => {
44+
const script = generateInitScript();
45+
// Old pattern: eval("escaped content", binding, 'path')
46+
// Should not exist anymore since we use heredoc
47+
expect(script).not.toMatch(/eval\("[^"]*\\n[^"]*", binding/);
48+
});
49+
});
50+
});

0 commit comments

Comments
 (0)