t-ruby 0.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.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +221 -0
- data/bin/trc +6 -0
- data/lib/t_ruby/benchmark.rb +592 -0
- data/lib/t_ruby/bundler_integration.rb +569 -0
- data/lib/t_ruby/cache.rb +774 -0
- data/lib/t_ruby/cli.rb +106 -0
- data/lib/t_ruby/compiler.rb +299 -0
- data/lib/t_ruby/config.rb +53 -0
- data/lib/t_ruby/constraint_checker.rb +441 -0
- data/lib/t_ruby/declaration_generator.rb +298 -0
- data/lib/t_ruby/doc_generator.rb +474 -0
- data/lib/t_ruby/error_handler.rb +132 -0
- data/lib/t_ruby/generic_type_parser.rb +68 -0
- data/lib/t_ruby/intersection_type_parser.rb +30 -0
- data/lib/t_ruby/ir.rb +1301 -0
- data/lib/t_ruby/lsp_server.rb +994 -0
- data/lib/t_ruby/package_manager.rb +735 -0
- data/lib/t_ruby/parser.rb +245 -0
- data/lib/t_ruby/parser_combinator.rb +942 -0
- data/lib/t_ruby/rbs_generator.rb +71 -0
- data/lib/t_ruby/runtime_validator.rb +367 -0
- data/lib/t_ruby/smt_solver.rb +1076 -0
- data/lib/t_ruby/type_alias_registry.rb +102 -0
- data/lib/t_ruby/type_checker.rb +770 -0
- data/lib/t_ruby/type_erasure.rb +26 -0
- data/lib/t_ruby/type_inferencer.rb +580 -0
- data/lib/t_ruby/union_type_parser.rb +38 -0
- data/lib/t_ruby/version.rb +5 -0
- data/lib/t_ruby/watcher.rb +320 -0
- data/lib/t_ruby.rb +42 -0
- metadata +87 -0
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "fileutils"
|
|
5
|
+
|
|
6
|
+
module TRuby
|
|
7
|
+
# API Documentation Generator
|
|
8
|
+
class DocGenerator
|
|
9
|
+
attr_reader :docs, :config
|
|
10
|
+
|
|
11
|
+
def initialize(config = nil)
|
|
12
|
+
@config = config || Config.new
|
|
13
|
+
@docs = {
|
|
14
|
+
types: {},
|
|
15
|
+
interfaces: {},
|
|
16
|
+
functions: {},
|
|
17
|
+
modules: {}
|
|
18
|
+
}
|
|
19
|
+
@parser = nil
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Generate documentation from source files
|
|
23
|
+
def generate(paths, output_dir: "docs")
|
|
24
|
+
puts "Generating T-Ruby API Documentation..."
|
|
25
|
+
|
|
26
|
+
# Parse all files
|
|
27
|
+
files = collect_files(paths)
|
|
28
|
+
files.each { |file| parse_file(file) }
|
|
29
|
+
|
|
30
|
+
# Generate output
|
|
31
|
+
FileUtils.mkdir_p(output_dir)
|
|
32
|
+
|
|
33
|
+
generate_index(output_dir)
|
|
34
|
+
generate_type_docs(output_dir)
|
|
35
|
+
generate_interface_docs(output_dir)
|
|
36
|
+
generate_function_docs(output_dir)
|
|
37
|
+
generate_module_docs(output_dir)
|
|
38
|
+
generate_search_index(output_dir)
|
|
39
|
+
|
|
40
|
+
puts "Documentation generated in #{output_dir}/"
|
|
41
|
+
@docs
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Generate single-file markdown documentation
|
|
45
|
+
def generate_markdown(paths, output_path: "API.md")
|
|
46
|
+
files = collect_files(paths)
|
|
47
|
+
files.each { |file| parse_file(file) }
|
|
48
|
+
|
|
49
|
+
md = []
|
|
50
|
+
md << "# T-Ruby API Documentation"
|
|
51
|
+
md << ""
|
|
52
|
+
md << "**Generated:** #{Time.now}"
|
|
53
|
+
md << ""
|
|
54
|
+
md << "## Table of Contents"
|
|
55
|
+
md << ""
|
|
56
|
+
md << "- [Types](#types)"
|
|
57
|
+
md << "- [Interfaces](#interfaces)"
|
|
58
|
+
md << "- [Functions](#functions)"
|
|
59
|
+
md << ""
|
|
60
|
+
|
|
61
|
+
# Types section
|
|
62
|
+
md << "## Types"
|
|
63
|
+
md << ""
|
|
64
|
+
@docs[:types].each do |name, info|
|
|
65
|
+
md << "### `#{name}`"
|
|
66
|
+
md << ""
|
|
67
|
+
md << "```typescript"
|
|
68
|
+
md << "type #{name} = #{info[:definition]}"
|
|
69
|
+
md << "```"
|
|
70
|
+
md << ""
|
|
71
|
+
md << info[:description] if info[:description]
|
|
72
|
+
md << ""
|
|
73
|
+
md << "**Source:** `#{info[:source]}`" if info[:source]
|
|
74
|
+
md << ""
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Interfaces section
|
|
78
|
+
md << "## Interfaces"
|
|
79
|
+
md << ""
|
|
80
|
+
@docs[:interfaces].each do |name, info|
|
|
81
|
+
md << "### `#{name}`"
|
|
82
|
+
md << ""
|
|
83
|
+
md << info[:description] if info[:description]
|
|
84
|
+
md << ""
|
|
85
|
+
|
|
86
|
+
if info[:type_params]&.any?
|
|
87
|
+
md << "**Type Parameters:** `<#{info[:type_params].join(', ')}>`"
|
|
88
|
+
md << ""
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
if info[:members]&.any?
|
|
92
|
+
md << "#### Members"
|
|
93
|
+
md << ""
|
|
94
|
+
md << "| Name | Type | Description |"
|
|
95
|
+
md << "|------|------|-------------|"
|
|
96
|
+
info[:members].each do |member|
|
|
97
|
+
md << "| `#{member[:name]}` | `#{member[:type]}` | #{member[:description] || '-'} |"
|
|
98
|
+
end
|
|
99
|
+
md << ""
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
md << "**Source:** `#{info[:source]}`" if info[:source]
|
|
103
|
+
md << ""
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Functions section
|
|
107
|
+
md << "## Functions"
|
|
108
|
+
md << ""
|
|
109
|
+
@docs[:functions].each do |name, info|
|
|
110
|
+
md << "### `#{name}`"
|
|
111
|
+
md << ""
|
|
112
|
+
md << info[:description] if info[:description]
|
|
113
|
+
md << ""
|
|
114
|
+
|
|
115
|
+
# Signature
|
|
116
|
+
params = info[:params]&.map { |p| "#{p[:name]}: #{p[:type]}" }&.join(", ") || ""
|
|
117
|
+
type_params = info[:type_params]&.any? ? "<#{info[:type_params].join(', ')}>" : ""
|
|
118
|
+
md << "```ruby"
|
|
119
|
+
md << "def #{name}#{type_params}(#{params}): #{info[:return_type] || 'void'}"
|
|
120
|
+
md << "```"
|
|
121
|
+
md << ""
|
|
122
|
+
|
|
123
|
+
if info[:params]&.any?
|
|
124
|
+
md << "#### Parameters"
|
|
125
|
+
md << ""
|
|
126
|
+
md << "| Name | Type | Description |"
|
|
127
|
+
md << "|------|------|-------------|"
|
|
128
|
+
info[:params].each do |param|
|
|
129
|
+
md << "| `#{param[:name]}` | `#{param[:type]}` | #{param[:description] || '-'} |"
|
|
130
|
+
end
|
|
131
|
+
md << ""
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
md << "**Returns:** `#{info[:return_type]}`" if info[:return_type]
|
|
135
|
+
md << ""
|
|
136
|
+
md << "**Source:** `#{info[:source]}`" if info[:source]
|
|
137
|
+
md << ""
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
File.write(output_path, md.join("\n"))
|
|
141
|
+
puts "Documentation generated: #{output_path}"
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Generate JSON documentation
|
|
145
|
+
def generate_json(paths, output_path: "api.json")
|
|
146
|
+
files = collect_files(paths)
|
|
147
|
+
files.each { |file| parse_file(file) }
|
|
148
|
+
|
|
149
|
+
File.write(output_path, JSON.pretty_generate({
|
|
150
|
+
generated_at: Time.now.iso8601,
|
|
151
|
+
version: TRuby::VERSION,
|
|
152
|
+
types: @docs[:types],
|
|
153
|
+
interfaces: @docs[:interfaces],
|
|
154
|
+
functions: @docs[:functions],
|
|
155
|
+
modules: @docs[:modules]
|
|
156
|
+
}))
|
|
157
|
+
|
|
158
|
+
puts "JSON documentation generated: #{output_path}"
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
private
|
|
162
|
+
|
|
163
|
+
def parser
|
|
164
|
+
@parser ||= Parser.new("")
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def collect_files(paths)
|
|
168
|
+
files = []
|
|
169
|
+
paths.each do |path|
|
|
170
|
+
if File.directory?(path)
|
|
171
|
+
files.concat(Dir.glob(File.join(path, "**", "*.trb")))
|
|
172
|
+
files.concat(Dir.glob(File.join(path, "**", "*.d.trb")))
|
|
173
|
+
elsif File.file?(path)
|
|
174
|
+
files << path
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
files.uniq
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def parse_file(file_path)
|
|
181
|
+
content = File.read(file_path)
|
|
182
|
+
relative_path = file_path.sub("#{Dir.pwd}/", "")
|
|
183
|
+
|
|
184
|
+
# Extract documentation comments
|
|
185
|
+
doc_comments = extract_doc_comments(content)
|
|
186
|
+
|
|
187
|
+
# Parse type aliases
|
|
188
|
+
content.scan(/^type\s+(\w+)(?:<([^>]+)>)?\s*=\s*(.+)$/) do |name, type_params, definition|
|
|
189
|
+
@docs[:types][name] = {
|
|
190
|
+
name: name,
|
|
191
|
+
type_params: type_params&.split(/\s*,\s*/),
|
|
192
|
+
definition: definition.strip,
|
|
193
|
+
description: doc_comments["type:#{name}"],
|
|
194
|
+
source: relative_path
|
|
195
|
+
}
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Parse interfaces
|
|
199
|
+
parse_interfaces(content, relative_path, doc_comments)
|
|
200
|
+
|
|
201
|
+
# Parse functions
|
|
202
|
+
parse_functions(content, relative_path, doc_comments)
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def extract_doc_comments(content)
|
|
206
|
+
comments = {}
|
|
207
|
+
current_comment = []
|
|
208
|
+
current_target = nil
|
|
209
|
+
|
|
210
|
+
content.each_line do |line|
|
|
211
|
+
if line =~ /^\s*#\s*@doc\s+(\w+):(\w+)/
|
|
212
|
+
current_target = "#{Regexp.last_match(1)}:#{Regexp.last_match(2)}"
|
|
213
|
+
current_comment = []
|
|
214
|
+
elsif line =~ /^\s*#\s*(.+)/ && current_target
|
|
215
|
+
current_comment << Regexp.last_match(1).strip
|
|
216
|
+
elsif line !~ /^\s*#/ && current_target
|
|
217
|
+
comments[current_target] = current_comment.join(" ")
|
|
218
|
+
current_target = nil
|
|
219
|
+
current_comment = []
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
# Also check inline comments before definitions
|
|
223
|
+
if line =~ /^\s*#\s*(.+)$/
|
|
224
|
+
current_comment << Regexp.last_match(1).strip
|
|
225
|
+
elsif line =~ /^(type|interface|def)\s+(\w+)/
|
|
226
|
+
type = Regexp.last_match(1)
|
|
227
|
+
name = Regexp.last_match(2)
|
|
228
|
+
unless current_comment.empty?
|
|
229
|
+
comments["#{type}:#{name}"] = current_comment.join(" ")
|
|
230
|
+
end
|
|
231
|
+
current_comment = []
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
comments
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def parse_interfaces(content, source, doc_comments)
|
|
239
|
+
# Match interface blocks
|
|
240
|
+
content.scan(/interface\s+(\w+)(?:<([^>]+)>)?\s*\n((?:(?!^end).)*?)^end/m) do |name, type_params, body|
|
|
241
|
+
members = []
|
|
242
|
+
|
|
243
|
+
body.scan(/^\s*(\w+[\?\!]?)\s*:\s*(.+)$/) do |member_name, member_type|
|
|
244
|
+
members << {
|
|
245
|
+
name: member_name,
|
|
246
|
+
type: member_type.strip,
|
|
247
|
+
description: doc_comments["member:#{name}.#{member_name}"]
|
|
248
|
+
}
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
@docs[:interfaces][name] = {
|
|
252
|
+
name: name,
|
|
253
|
+
type_params: type_params&.split(/\s*,\s*/),
|
|
254
|
+
members: members,
|
|
255
|
+
description: doc_comments["interface:#{name}"],
|
|
256
|
+
source: source
|
|
257
|
+
}
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def parse_functions(content, source, doc_comments)
|
|
262
|
+
# Match function definitions
|
|
263
|
+
content.scan(/def\s+(\w+[\?\!]?)(?:<([^>]+)>)?\s*\(([^)]*)\)(?:\s*:\s*(.+?))?(?:\n|$)/) do |name, type_params, params_str, return_type|
|
|
264
|
+
params = []
|
|
265
|
+
|
|
266
|
+
params_str.scan(/(\w+)\s*:\s*([^,]+)/) do |param_name, param_type|
|
|
267
|
+
params << {
|
|
268
|
+
name: param_name,
|
|
269
|
+
type: param_type.strip,
|
|
270
|
+
description: doc_comments["param:#{name}.#{param_name}"]
|
|
271
|
+
}
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
@docs[:functions][name] = {
|
|
275
|
+
name: name,
|
|
276
|
+
type_params: type_params&.split(/\s*,\s*/),
|
|
277
|
+
params: params,
|
|
278
|
+
return_type: return_type&.strip,
|
|
279
|
+
description: doc_comments["def:#{name}"],
|
|
280
|
+
source: source
|
|
281
|
+
}
|
|
282
|
+
end
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
def generate_index(output_dir)
|
|
286
|
+
html = <<~HTML
|
|
287
|
+
<!DOCTYPE html>
|
|
288
|
+
<html>
|
|
289
|
+
<head>
|
|
290
|
+
<title>T-Ruby API Documentation</title>
|
|
291
|
+
<style>
|
|
292
|
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; margin: 40px; }
|
|
293
|
+
h1 { color: #cc342d; }
|
|
294
|
+
.section { margin: 20px 0; }
|
|
295
|
+
a { color: #0366d6; text-decoration: none; }
|
|
296
|
+
a:hover { text-decoration: underline; }
|
|
297
|
+
.item { padding: 5px 0; }
|
|
298
|
+
code { background: #f6f8fa; padding: 2px 6px; border-radius: 3px; }
|
|
299
|
+
</style>
|
|
300
|
+
</head>
|
|
301
|
+
<body>
|
|
302
|
+
<h1>T-Ruby API Documentation</h1>
|
|
303
|
+
<p>Generated: #{Time.now}</p>
|
|
304
|
+
|
|
305
|
+
<div class="section">
|
|
306
|
+
<h2>Types (#{@docs[:types].size})</h2>
|
|
307
|
+
#{@docs[:types].keys.sort.map { |name| "<div class='item'><a href='types/#{name}.html'><code>#{name}</code></a></div>" }.join}
|
|
308
|
+
</div>
|
|
309
|
+
|
|
310
|
+
<div class="section">
|
|
311
|
+
<h2>Interfaces (#{@docs[:interfaces].size})</h2>
|
|
312
|
+
#{@docs[:interfaces].keys.sort.map { |name| "<div class='item'><a href='interfaces/#{name}.html'><code>#{name}</code></a></div>" }.join}
|
|
313
|
+
</div>
|
|
314
|
+
|
|
315
|
+
<div class="section">
|
|
316
|
+
<h2>Functions (#{@docs[:functions].size})</h2>
|
|
317
|
+
#{@docs[:functions].keys.sort.map { |name| "<div class='item'><a href='functions/#{name}.html'><code>#{name}</code></a></div>" }.join}
|
|
318
|
+
</div>
|
|
319
|
+
</body>
|
|
320
|
+
</html>
|
|
321
|
+
HTML
|
|
322
|
+
|
|
323
|
+
File.write(File.join(output_dir, "index.html"), html)
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
def generate_type_docs(output_dir)
|
|
327
|
+
types_dir = File.join(output_dir, "types")
|
|
328
|
+
FileUtils.mkdir_p(types_dir)
|
|
329
|
+
|
|
330
|
+
@docs[:types].each do |name, info|
|
|
331
|
+
html = generate_type_html(name, info)
|
|
332
|
+
File.write(File.join(types_dir, "#{name}.html"), html)
|
|
333
|
+
end
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
def generate_interface_docs(output_dir)
|
|
337
|
+
interfaces_dir = File.join(output_dir, "interfaces")
|
|
338
|
+
FileUtils.mkdir_p(interfaces_dir)
|
|
339
|
+
|
|
340
|
+
@docs[:interfaces].each do |name, info|
|
|
341
|
+
html = generate_interface_html(name, info)
|
|
342
|
+
File.write(File.join(interfaces_dir, "#{name}.html"), html)
|
|
343
|
+
end
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
def generate_function_docs(output_dir)
|
|
347
|
+
functions_dir = File.join(output_dir, "functions")
|
|
348
|
+
FileUtils.mkdir_p(functions_dir)
|
|
349
|
+
|
|
350
|
+
@docs[:functions].each do |name, info|
|
|
351
|
+
html = generate_function_html(name, info)
|
|
352
|
+
File.write(File.join(functions_dir, "#{name}.html"), html)
|
|
353
|
+
end
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
def generate_module_docs(output_dir)
|
|
357
|
+
# Placeholder for module documentation
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
def generate_search_index(output_dir)
|
|
361
|
+
search_data = []
|
|
362
|
+
|
|
363
|
+
@docs[:types].each do |name, info|
|
|
364
|
+
search_data << { type: "type", name: name, url: "types/#{name}.html" }
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
@docs[:interfaces].each do |name, info|
|
|
368
|
+
search_data << { type: "interface", name: name, url: "interfaces/#{name}.html" }
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
@docs[:functions].each do |name, info|
|
|
372
|
+
search_data << { type: "function", name: name, url: "functions/#{name}.html" }
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
File.write(File.join(output_dir, "search-index.json"), JSON.generate(search_data))
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
def generate_type_html(name, info)
|
|
379
|
+
<<~HTML
|
|
380
|
+
<!DOCTYPE html>
|
|
381
|
+
<html>
|
|
382
|
+
<head>
|
|
383
|
+
<title>#{name} - T-Ruby API</title>
|
|
384
|
+
<style>
|
|
385
|
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; margin: 40px; }
|
|
386
|
+
h1 { color: #cc342d; }
|
|
387
|
+
pre { background: #f6f8fa; padding: 16px; border-radius: 6px; overflow-x: auto; }
|
|
388
|
+
.meta { color: #6a737d; font-size: 14px; }
|
|
389
|
+
a { color: #0366d6; }
|
|
390
|
+
</style>
|
|
391
|
+
</head>
|
|
392
|
+
<body>
|
|
393
|
+
<a href="../index.html">← Back to Index</a>
|
|
394
|
+
<h1>type #{name}</h1>
|
|
395
|
+
#{info[:description] ? "<p>#{info[:description]}</p>" : ""}
|
|
396
|
+
<pre>type #{name}#{info[:type_params] ? "<#{info[:type_params].join(', ')}>" : ""} = #{info[:definition]}</pre>
|
|
397
|
+
<p class="meta">Source: <code>#{info[:source]}</code></p>
|
|
398
|
+
</body>
|
|
399
|
+
</html>
|
|
400
|
+
HTML
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
def generate_interface_html(name, info)
|
|
404
|
+
members_html = info[:members]&.map do |m|
|
|
405
|
+
"<tr><td><code>#{m[:name]}</code></td><td><code>#{m[:type]}</code></td><td>#{m[:description] || '-'}</td></tr>"
|
|
406
|
+
end&.join || ""
|
|
407
|
+
|
|
408
|
+
<<~HTML
|
|
409
|
+
<!DOCTYPE html>
|
|
410
|
+
<html>
|
|
411
|
+
<head>
|
|
412
|
+
<title>#{name} - T-Ruby API</title>
|
|
413
|
+
<style>
|
|
414
|
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; margin: 40px; }
|
|
415
|
+
h1 { color: #cc342d; }
|
|
416
|
+
table { border-collapse: collapse; width: 100%; }
|
|
417
|
+
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
|
|
418
|
+
th { background: #f6f8fa; }
|
|
419
|
+
pre { background: #f6f8fa; padding: 16px; border-radius: 6px; }
|
|
420
|
+
.meta { color: #6a737d; font-size: 14px; }
|
|
421
|
+
a { color: #0366d6; }
|
|
422
|
+
</style>
|
|
423
|
+
</head>
|
|
424
|
+
<body>
|
|
425
|
+
<a href="../index.html">← Back to Index</a>
|
|
426
|
+
<h1>interface #{name}#{info[:type_params] ? "<#{info[:type_params].join(', ')}>" : ""}</h1>
|
|
427
|
+
#{info[:description] ? "<p>#{info[:description]}</p>" : ""}
|
|
428
|
+
#{"<h2>Members</h2><table><tr><th>Name</th><th>Type</th><th>Description</th></tr>#{members_html}</table>" unless members_html.empty?}
|
|
429
|
+
<p class="meta">Source: <code>#{info[:source]}</code></p>
|
|
430
|
+
</body>
|
|
431
|
+
</html>
|
|
432
|
+
HTML
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
def generate_function_html(name, info)
|
|
436
|
+
params_html = info[:params]&.map do |p|
|
|
437
|
+
"<tr><td><code>#{p[:name]}</code></td><td><code>#{p[:type]}</code></td><td>#{p[:description] || '-'}</td></tr>"
|
|
438
|
+
end&.join || ""
|
|
439
|
+
|
|
440
|
+
params_sig = info[:params]&.map { |p| "#{p[:name]}: #{p[:type]}" }&.join(", ") || ""
|
|
441
|
+
type_params = info[:type_params]&.any? ? "<#{info[:type_params].join(', ')}>" : ""
|
|
442
|
+
|
|
443
|
+
<<~HTML
|
|
444
|
+
<!DOCTYPE html>
|
|
445
|
+
<html>
|
|
446
|
+
<head>
|
|
447
|
+
<title>#{name} - T-Ruby API</title>
|
|
448
|
+
<style>
|
|
449
|
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; margin: 40px; }
|
|
450
|
+
h1 { color: #cc342d; }
|
|
451
|
+
table { border-collapse: collapse; width: 100%; }
|
|
452
|
+
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
|
|
453
|
+
th { background: #f6f8fa; }
|
|
454
|
+
pre { background: #f6f8fa; padding: 16px; border-radius: 6px; }
|
|
455
|
+
.meta { color: #6a737d; font-size: 14px; }
|
|
456
|
+
a { color: #0366d6; }
|
|
457
|
+
</style>
|
|
458
|
+
</head>
|
|
459
|
+
<body>
|
|
460
|
+
<a href="../index.html">← Back to Index</a>
|
|
461
|
+
<h1>#{name}</h1>
|
|
462
|
+
#{info[:description] ? "<p>#{info[:description]}</p>" : ""}
|
|
463
|
+
<h2>Signature</h2>
|
|
464
|
+
<pre>def #{name}#{type_params}(#{params_sig}): #{info[:return_type] || 'void'}</pre>
|
|
465
|
+
#{"<h2>Parameters</h2><table><tr><th>Name</th><th>Type</th><th>Description</th></tr>#{params_html}</table>" unless params_html.empty?}
|
|
466
|
+
<h2>Returns</h2>
|
|
467
|
+
<p><code>#{info[:return_type] || 'void'}</code></p>
|
|
468
|
+
<p class="meta">Source: <code>#{info[:source]}</code></p>
|
|
469
|
+
</body>
|
|
470
|
+
</html>
|
|
471
|
+
HTML
|
|
472
|
+
end
|
|
473
|
+
end
|
|
474
|
+
end
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TRuby
|
|
4
|
+
class ErrorHandler
|
|
5
|
+
VALID_TYPES = %w[String Integer Boolean Array Hash Symbol void nil].freeze
|
|
6
|
+
|
|
7
|
+
def initialize(source)
|
|
8
|
+
@source = source
|
|
9
|
+
@lines = source.split("\n")
|
|
10
|
+
@errors = []
|
|
11
|
+
@functions = {}
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def check
|
|
15
|
+
@errors = []
|
|
16
|
+
@functions = {}
|
|
17
|
+
@type_aliases = {}
|
|
18
|
+
@interfaces = {}
|
|
19
|
+
|
|
20
|
+
check_type_alias_errors
|
|
21
|
+
check_interface_errors
|
|
22
|
+
check_syntax_errors
|
|
23
|
+
check_type_validation
|
|
24
|
+
check_duplicate_definitions
|
|
25
|
+
|
|
26
|
+
@errors
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def check_interface_errors
|
|
32
|
+
@lines.each_with_index do |line, idx|
|
|
33
|
+
next unless line.match?(/^\s*interface\s+\w+/)
|
|
34
|
+
|
|
35
|
+
match = line.match(/^\s*interface\s+(\w+)/)
|
|
36
|
+
next unless match
|
|
37
|
+
|
|
38
|
+
interface_name = match[1]
|
|
39
|
+
|
|
40
|
+
if @interfaces[interface_name]
|
|
41
|
+
@errors << "Line #{idx + 1}: Interface '#{interface_name}' is already defined at line #{@interfaces[interface_name]}"
|
|
42
|
+
else
|
|
43
|
+
@interfaces[interface_name] = idx + 1
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def check_type_alias_errors
|
|
49
|
+
@lines.each_with_index do |line, idx|
|
|
50
|
+
next unless line.match?(/^\s*type\s+\w+/)
|
|
51
|
+
|
|
52
|
+
match = line.match(/^\s*type\s+(\w+)\s*=\s*(.+)$/)
|
|
53
|
+
next unless match
|
|
54
|
+
|
|
55
|
+
alias_name = match[1]
|
|
56
|
+
|
|
57
|
+
if @type_aliases[alias_name]
|
|
58
|
+
@errors << "Line #{idx + 1}: Type alias '#{alias_name}' is already defined at line #{@type_aliases[alias_name]}"
|
|
59
|
+
else
|
|
60
|
+
@type_aliases[alias_name] = idx + 1
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def check_syntax_errors
|
|
66
|
+
@lines.each_with_index do |line, idx|
|
|
67
|
+
next unless line.match?(/^\s*def\s+/)
|
|
68
|
+
|
|
69
|
+
# Check for unclosed parenthesis
|
|
70
|
+
if line.match?(/def\s+\w+\([^)]*$/) && !@lines[idx + 1..].any? { |l| l.match?(/\)/) }
|
|
71
|
+
@errors << "Line #{idx + 1}: Potential unclosed parenthesis in function definition"
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Check for invalid parameter syntax (e.g., "def test(: String)")
|
|
75
|
+
if line.match?(/def\s+\w+\(\s*:\s*\w+/)
|
|
76
|
+
@errors << "Line #{idx + 1}: Invalid parameter syntax - parameter name missing"
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def check_type_validation
|
|
82
|
+
@lines.each_with_index do |line, idx|
|
|
83
|
+
next unless line.match?(/^\s*def\s+/)
|
|
84
|
+
|
|
85
|
+
# Extract types from function definition
|
|
86
|
+
match = line.match(/def\s+\w+\s*\((.*?)\)\s*(?::\s*(\w+))?/)
|
|
87
|
+
next unless match
|
|
88
|
+
|
|
89
|
+
params_str = match[1]
|
|
90
|
+
return_type = match[2]
|
|
91
|
+
|
|
92
|
+
# Check return type
|
|
93
|
+
if return_type && !VALID_TYPES.include?(return_type) && !@type_aliases.key?(return_type)
|
|
94
|
+
@errors << "Line #{idx + 1}: Unknown return type '#{return_type}'"
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Check parameter types
|
|
98
|
+
check_parameter_types(params_str, idx)
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def check_parameter_types(params_str, line_idx)
|
|
103
|
+
return if params_str.empty?
|
|
104
|
+
|
|
105
|
+
param_list = params_str.split(",").map(&:strip)
|
|
106
|
+
param_list.each do |param|
|
|
107
|
+
match = param.match(/^(\w+)(?::\s*(\w+))?$/)
|
|
108
|
+
next unless match
|
|
109
|
+
|
|
110
|
+
param_type = match[2]
|
|
111
|
+
next unless param_type
|
|
112
|
+
next if VALID_TYPES.include?(param_type) || @type_aliases.key?(param_type)
|
|
113
|
+
|
|
114
|
+
@errors << "Line #{line_idx + 1}: Unknown parameter type '#{param_type}'"
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def check_duplicate_definitions
|
|
119
|
+
@lines.each_with_index do |line, idx|
|
|
120
|
+
next unless line.match?(/^\s*def\s+(\w+)/)
|
|
121
|
+
|
|
122
|
+
func_name = line.match(/def\s+(\w+)/)[1]
|
|
123
|
+
|
|
124
|
+
if @functions[func_name]
|
|
125
|
+
@errors << "Line #{idx + 1}: Function '#{func_name}' is already defined at line #{@functions[func_name]}"
|
|
126
|
+
else
|
|
127
|
+
@functions[func_name] = idx + 1
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TRuby
|
|
4
|
+
class GenericTypeParser
|
|
5
|
+
def initialize(type_string)
|
|
6
|
+
@type_string = type_string.strip
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def parse
|
|
10
|
+
if @type_string.include?("<") && @type_string.include?(">")
|
|
11
|
+
parse_generic
|
|
12
|
+
else
|
|
13
|
+
{ type: :simple, value: @type_string }
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def parse_generic
|
|
20
|
+
# Match: BaseName<Params>
|
|
21
|
+
match = @type_string.match(/^(\w+)<(.+)>$/)
|
|
22
|
+
return { type: :simple, value: @type_string } unless match
|
|
23
|
+
|
|
24
|
+
base_name = match[1]
|
|
25
|
+
params_str = match[2]
|
|
26
|
+
|
|
27
|
+
# Parse parameters, handling nested generics
|
|
28
|
+
params = parse_params(params_str)
|
|
29
|
+
|
|
30
|
+
{
|
|
31
|
+
type: :generic,
|
|
32
|
+
base: base_name,
|
|
33
|
+
params: params
|
|
34
|
+
}
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def parse_params(params_str)
|
|
38
|
+
# Simple comma-based splitting (doesn't handle nested generics fully)
|
|
39
|
+
# For nested generics like Array<Array<String>>, we need careful parsing
|
|
40
|
+
params = []
|
|
41
|
+
current = ""
|
|
42
|
+
depth = 0
|
|
43
|
+
|
|
44
|
+
params_str.each_char do |char|
|
|
45
|
+
case char
|
|
46
|
+
when "<"
|
|
47
|
+
depth += 1
|
|
48
|
+
current += char
|
|
49
|
+
when ">"
|
|
50
|
+
depth -= 1
|
|
51
|
+
current += char
|
|
52
|
+
when ","
|
|
53
|
+
if depth == 0
|
|
54
|
+
params << current.strip
|
|
55
|
+
current = ""
|
|
56
|
+
else
|
|
57
|
+
current += char
|
|
58
|
+
end
|
|
59
|
+
else
|
|
60
|
+
current += char
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
params << current.strip if current.length > 0
|
|
65
|
+
params
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TRuby
|
|
4
|
+
class IntersectionTypeParser
|
|
5
|
+
def initialize(type_string)
|
|
6
|
+
@type_string = type_string.strip
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def parse
|
|
10
|
+
if @type_string.include?("&")
|
|
11
|
+
parse_intersection
|
|
12
|
+
else
|
|
13
|
+
{ type: :simple, value: @type_string }
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def parse_intersection
|
|
20
|
+
members = @type_string.split("&").map { |m| m.strip }.compact
|
|
21
|
+
|
|
22
|
+
{
|
|
23
|
+
type: :intersection,
|
|
24
|
+
members: members,
|
|
25
|
+
has_duplicates: members.length != members.uniq.length,
|
|
26
|
+
unique_members: members.uniq
|
|
27
|
+
}
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|