infornoid 0.2.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.
- checksums.yaml +7 -0
- data/LICENSE +661 -0
- data/README.md +248 -0
- data/bin/infornoid +10 -0
- data/lib/advisor.rb +151 -0
- data/lib/codegen.rb +109 -0
- data/lib/infornoid.rb +312 -0
- data/lib/lexer.rb +144 -0
- data/lib/optimizer.rb +238 -0
- data/lib/parser.rb +273 -0
- data/lib/semantic.rb +214 -0
- metadata +57 -0
data/lib/infornoid.rb
ADDED
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
#coding:utf-8
|
|
2
|
+
# infornoid 智能编译器 — 主驱动
|
|
3
|
+
# 混合架构: 确定性前端 + 规则引擎语义 + LLM 顾问优化 + 确定性代码生成
|
|
4
|
+
# 零第三方依赖, Ruby 标准库实现
|
|
5
|
+
#
|
|
6
|
+
# 子命令风格 CLI (类似 gcc/clang 的分阶段调用):
|
|
7
|
+
# infornoid compile <source.inf> [-o out] [--target js|py] [--no-opt] [--llm]
|
|
8
|
+
# infornoid lex <source.inf> [--json] # 输出 Token 列表
|
|
9
|
+
# infornoid parse <source.inf> [--json] # 输出 AST
|
|
10
|
+
# infornoid semantic <source.inf> [--json] # 输出符号表/类型/错误
|
|
11
|
+
# infornoid optimize <source.inf> [--json] # 输出优化后 AST
|
|
12
|
+
# infornoid codegen <source.inf> [--target js|py] [--llm]
|
|
13
|
+
# infornoid check <source.inf> # 仅做语义检查
|
|
14
|
+
#
|
|
15
|
+
# 兼容旧用法 (无子命令时等同 compile):
|
|
16
|
+
# ruby lib/infornoid.rb <source.inf> [-o out] [--target py] [--ast] [--tokens] [--no-opt] [--llm]
|
|
17
|
+
#
|
|
18
|
+
# 数据流:
|
|
19
|
+
# 源码 → Lexer → Token → Parser → AST → Semantic → Optimizer → Codegen → 目标代码
|
|
20
|
+
|
|
21
|
+
%w[lexer parser advisor semantic optimizer codegen].each{|f| require_relative f }
|
|
22
|
+
|
|
23
|
+
module Infor
|
|
24
|
+
module Compiler
|
|
25
|
+
module_function
|
|
26
|
+
|
|
27
|
+
VERSION = '0.2.0'
|
|
28
|
+
|
|
29
|
+
# 子命令 → 处理函数
|
|
30
|
+
COMMANDS = {
|
|
31
|
+
'compile' => :cmd_compile,
|
|
32
|
+
'lex' => :cmd_lex,
|
|
33
|
+
'parse' => :cmd_parse,
|
|
34
|
+
'semantic' => :cmd_semantic,
|
|
35
|
+
'optimize' => :cmd_optimize,
|
|
36
|
+
'codegen' => :cmd_codegen,
|
|
37
|
+
'check' => :cmd_check,
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
def main argv
|
|
41
|
+
args = argv.dup
|
|
42
|
+
cmd = nil
|
|
43
|
+
|
|
44
|
+
# 首个非选项参数若不是已存在文件, 则视为子命令 (兼容: 文件路径 → compile)
|
|
45
|
+
if args[0] && !args[0].start_with?('-')
|
|
46
|
+
if COMMANDS.key?(args[0])
|
|
47
|
+
cmd = args.shift
|
|
48
|
+
elsif args[0] == 'help'
|
|
49
|
+
print_help; return 0
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# 无子命令 → 默认 compile (兼容旧用法: ruby infornoid.rb <file> ...)
|
|
54
|
+
cmd ||= 'compile'
|
|
55
|
+
|
|
56
|
+
# 兼容旧调试标志: --ast / --tokens → 映射为对应子命令
|
|
57
|
+
if cmd == 'compile'
|
|
58
|
+
if args.include?('--tokens')
|
|
59
|
+
cmd = 'lex'; args.delete('--tokens')
|
|
60
|
+
elsif args.include?('--ast')
|
|
61
|
+
cmd = 'parse'; args.delete('--ast')
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
send(COMMANDS[cmd], args)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
#### 子命令实现 ####
|
|
69
|
+
|
|
70
|
+
# 编译: 完整流水线
|
|
71
|
+
def cmd_compile argv
|
|
72
|
+
src_path, out_path, flags = parse_args(argv)
|
|
73
|
+
unless src_path
|
|
74
|
+
return err
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
src = read_source(src_path)
|
|
78
|
+
return 1 unless src
|
|
79
|
+
|
|
80
|
+
# 词法
|
|
81
|
+
ts = Lexer.lex(src)
|
|
82
|
+
# 语法
|
|
83
|
+
ast = Parser.parse(ts)
|
|
84
|
+
# 语义
|
|
85
|
+
sem = Semantic.analyze(ast)
|
|
86
|
+
report_errors(sem[:errors]) if sem[:errors].any?
|
|
87
|
+
|
|
88
|
+
# 优化
|
|
89
|
+
unless flags[:no_opt]
|
|
90
|
+
opt = Optimizer.optimize(ast, flags[:llm])
|
|
91
|
+
ast = opt[:ast]
|
|
92
|
+
opt[:log].each{|l| $stderr.puts l }
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# 代码生成
|
|
96
|
+
target = map_target(flags[:target])
|
|
97
|
+
result = Codegen.gen(ast, target, flags[:llm])
|
|
98
|
+
result[:log].each{|l| $stderr.puts l }
|
|
99
|
+
|
|
100
|
+
code = result[:code]
|
|
101
|
+
if out_path
|
|
102
|
+
File.open(out_path, 'w:utf-8'){|f| f.puts code }
|
|
103
|
+
$stderr.puts "[infornoid] 编译完成 → #{out_path}"
|
|
104
|
+
else
|
|
105
|
+
$stdout.puts code
|
|
106
|
+
end
|
|
107
|
+
0
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# 词法分析: in [--json]
|
|
111
|
+
def cmd_lex argv
|
|
112
|
+
src_path, _, flags = parse_args(argv)
|
|
113
|
+
return err unless src_path
|
|
114
|
+
src = read_source(src_path)
|
|
115
|
+
return 1 unless src
|
|
116
|
+
ts = Lexer.lex(src)
|
|
117
|
+
if flags[:json]
|
|
118
|
+
require 'json'
|
|
119
|
+
$stdout.puts JSON.pretty_generate(ts)
|
|
120
|
+
else
|
|
121
|
+
ts.each{|tk| $stdout.puts "%-6s | %-12s | L%d:C%d"%[tk[:t], tk[:v], tk[:l], tk[:c]] }
|
|
122
|
+
end
|
|
123
|
+
0
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# 语法分析: parse [--json]
|
|
127
|
+
def cmd_parse argv
|
|
128
|
+
src_path, _, flags = parse_args(argv)
|
|
129
|
+
return err unless src_path
|
|
130
|
+
src = read_source(src_path)
|
|
131
|
+
return 1 unless src
|
|
132
|
+
ast = Parser.parse(Lexer.lex(src))
|
|
133
|
+
require 'json'
|
|
134
|
+
$stdout.puts flags[:json] ? JSON.pretty_generate(ast) : ast.inspect
|
|
135
|
+
0
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# 语义分析: semantic [--json]
|
|
139
|
+
def cmd_semantic argv
|
|
140
|
+
src_path, _, flags = parse_args(argv)
|
|
141
|
+
return err unless src_path
|
|
142
|
+
src = read_source(src_path)
|
|
143
|
+
return 1 unless src
|
|
144
|
+
sem = Semantic.analyze(Parser.parse(Lexer.lex(src)))
|
|
145
|
+
report_errors(sem[:errors]) if sem[:errors].any?
|
|
146
|
+
if flags[:json]
|
|
147
|
+
require 'json'
|
|
148
|
+
$stdout.puts JSON.pretty_generate(sem)
|
|
149
|
+
else
|
|
150
|
+
$stdout.puts "符号表: #{sem[:types].inspect}" if sem[:types]
|
|
151
|
+
$stdout.puts "错误数: #{sem[:errors].length}"
|
|
152
|
+
end
|
|
153
|
+
sem[:errors].any? ? 1 : 0
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# 优化: optimize [--json]
|
|
157
|
+
def cmd_optimize argv
|
|
158
|
+
src_path, _, flags = parse_args(argv)
|
|
159
|
+
return err unless src_path
|
|
160
|
+
src = read_source(src_path)
|
|
161
|
+
return 1 unless src
|
|
162
|
+
ast = Parser.parse(Lexer.lex(src))
|
|
163
|
+
opt = Optimizer.optimize(ast, flags[:llm])
|
|
164
|
+
opt[:log].each{|l| $stderr.puts l }
|
|
165
|
+
require 'json'
|
|
166
|
+
$stdout.puts flags[:json] ? JSON.pretty_generate(opt[:ast]) : opt[:ast].inspect
|
|
167
|
+
0
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# 代码生成: codegen [--target js|py] [--llm]
|
|
171
|
+
def cmd_codegen argv
|
|
172
|
+
src_path, _, flags = parse_args(argv)
|
|
173
|
+
return err unless src_path
|
|
174
|
+
src = read_source(src_path)
|
|
175
|
+
return 1 unless src
|
|
176
|
+
ast = Parser.parse(Lexer.lex(src))
|
|
177
|
+
opt = Optimizer.optimize(ast, flags[:llm])
|
|
178
|
+
result = Codegen.gen(opt[:ast], map_target(flags[:target]), flags[:llm])
|
|
179
|
+
result[:log].each{|l| $stderr.puts l }
|
|
180
|
+
$stdout.puts result[:code]
|
|
181
|
+
0
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# 语法检查: check (只跑词法+语法+语义, 不输出代码)
|
|
185
|
+
def cmd_check argv
|
|
186
|
+
src_path, _, flags = parse_args(argv)
|
|
187
|
+
return err unless src_path
|
|
188
|
+
src = read_source(src_path)
|
|
189
|
+
return 1 unless src
|
|
190
|
+
sem = Semantic.analyze(Parser.parse(Lexer.lex(src)))
|
|
191
|
+
if sem[:errors].any?
|
|
192
|
+
report_errors(sem[:errors])
|
|
193
|
+
1
|
|
194
|
+
else
|
|
195
|
+
$stderr.puts "[infornoid] 语法检查通过 (#{src_path})"
|
|
196
|
+
0
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
#### 参数解析 ####
|
|
201
|
+
|
|
202
|
+
def parse_args argv
|
|
203
|
+
src_path = nil
|
|
204
|
+
out_path = nil
|
|
205
|
+
flags = { json:false, no_opt:false, llm:false, target:'js' }
|
|
206
|
+
nxt = nil
|
|
207
|
+
|
|
208
|
+
argv.each do|a|
|
|
209
|
+
case a
|
|
210
|
+
when '-o' then nxt = :out
|
|
211
|
+
when '--target' then nxt = :target
|
|
212
|
+
when '--json' then flags[:json] = true
|
|
213
|
+
when '--no-opt' then flags[:no_opt] = true
|
|
214
|
+
when '--llm' then flags[:llm] = true
|
|
215
|
+
when '--help', '-h' then print_help; exit 0
|
|
216
|
+
when '--version', '-v' then $stdout.puts VERSION; exit 0
|
|
217
|
+
else
|
|
218
|
+
case nxt
|
|
219
|
+
when :out then out_path = a; nxt = nil
|
|
220
|
+
when :target then flags[:target] = a; nxt = nil
|
|
221
|
+
else src_path = a
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
[src_path, out_path, flags]
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
#### 辅助 ####
|
|
230
|
+
|
|
231
|
+
def err
|
|
232
|
+
$stderr.puts "用法: infornoid <source.inf> [选项]"
|
|
233
|
+
$stderr.puts "运行 --help 查看完整帮助"
|
|
234
|
+
1
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def read_source path
|
|
238
|
+
braw = File.binread(path)
|
|
239
|
+
begin
|
|
240
|
+
braw.force_encoding('UTF-8')
|
|
241
|
+
rescue
|
|
242
|
+
begin
|
|
243
|
+
braw.force_encoding('GBK').encode('UTF-8')
|
|
244
|
+
rescue
|
|
245
|
+
braw.force_encoding('ASCII-8BIT')
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
rescue => e
|
|
249
|
+
$stderr.puts "[infornoid] 无法读取文件 #{path}: #{e.message}"
|
|
250
|
+
nil
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def map_target t
|
|
254
|
+
case t.to_s.downcase
|
|
255
|
+
when 'js', 'javascript' then 'JavaScript'
|
|
256
|
+
when 'py', 'python' then 'Python'
|
|
257
|
+
else 'JavaScript'
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def report_errors errors
|
|
262
|
+
$stderr.puts "[infornoid] 语义错误 (#{errors.length} 个):"
|
|
263
|
+
errors.each{|e| $stderr.puts " line #{e[:line]}: #{e[:msg]}" }
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def print_help
|
|
267
|
+
$stdout.puts <<~HELP
|
|
268
|
+
infornoid v#{VERSION} — LLM 辅助编译器 (混合架构)
|
|
269
|
+
|
|
270
|
+
用法 (子命令风格):
|
|
271
|
+
infornoid compile <source.inf> [-o out] [--target js|py] [--no-opt] [--llm]
|
|
272
|
+
infornoid lex <source.inf> [--json] 输出 Token 列表
|
|
273
|
+
infornoid parse <source.inf> [--json] 输出 AST
|
|
274
|
+
infornoid semantic <source.inf> [--json] 输出语义分析结果
|
|
275
|
+
infornoid optimize <source.inf> [--json] 输出优化后 AST
|
|
276
|
+
infornoid codegen <source.inf> [--target js|py] [--llm] 仅生成代码
|
|
277
|
+
infornoid check <source.inf> 语法检查 (不输出代码)
|
|
278
|
+
|
|
279
|
+
兼容用法 (无子命令时等同 compile):
|
|
280
|
+
infornoid <source.inf> [-o out] [--target py] [--ast] [--tokens] [--no-opt] [--llm]
|
|
281
|
+
|
|
282
|
+
选项:
|
|
283
|
+
-o <file> 指定输出文件路径
|
|
284
|
+
--target <lang> 目标语言 (js/javascript, py/python), 默认 js
|
|
285
|
+
--json 以 JSON 输出调试信息 (lex/parse/semantic/optimize)
|
|
286
|
+
--ast 输出 AST JSON (调试用, 等同 parse)
|
|
287
|
+
--tokens 输出 Token 列表 (调试用, 等同 lex)
|
|
288
|
+
--no-opt 跳过优化阶段
|
|
289
|
+
--llm 启用 LLM 顾问 (需要 Ollama 运行)
|
|
290
|
+
--help, -h 显示帮助
|
|
291
|
+
--version, -v 显示版本
|
|
292
|
+
|
|
293
|
+
架构:
|
|
294
|
+
源码 → [Lexer] → Token → [Parser] → AST
|
|
295
|
+
→ [Semantic 规则引擎] → Typed AST
|
|
296
|
+
→ [Optimizer LLM顾问+确定性] → Optimized AST
|
|
297
|
+
→ [Codegen 确定性模板] → 目标代码
|
|
298
|
+
|
|
299
|
+
LLM 顾问:
|
|
300
|
+
--llm 标志启用后, 在优化和代码生成阶段调用本地 Ollama.
|
|
301
|
+
LLM 只输出建议性元数据, 不直接生成可执行代码.
|
|
302
|
+
无 Ollama 时自动降级为纯确定性编译.
|
|
303
|
+
HELP
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
end
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
if :main == :main && $0 == __FILE__
|
|
310
|
+
$rc = Infor::Compiler.main(ARGV)
|
|
311
|
+
exit($rc || 0)
|
|
312
|
+
end
|
data/lib/lexer.rb
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
#coding:utf-8
|
|
2
|
+
# infornoid 词法分析器 — 确定性前端
|
|
3
|
+
# 零依赖, 手写扫描器, Token 列表输出
|
|
4
|
+
# CALL: Lexer.lex(source) → [Token]
|
|
5
|
+
# RETN: [{type, value, line, col}]
|
|
6
|
+
|
|
7
|
+
module Infor
|
|
8
|
+
module Lexer
|
|
9
|
+
module_function
|
|
10
|
+
|
|
11
|
+
#### token 定义 ####
|
|
12
|
+
|
|
13
|
+
KWS = {
|
|
14
|
+
'let'=>:LET, 'fn'=>:FN, 'if'=>:IF, 'else'=>:ELS,
|
|
15
|
+
'while'=>:WHL, 'return'=>:RTN, 'true'=>:TRU, 'false'=>:FLS
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
TYS = %w[Number String Boolean Void].inject({}){|h,t| h[t]=:TY; h}
|
|
19
|
+
|
|
20
|
+
# 单字符符号表 → token type
|
|
21
|
+
SMAP = {
|
|
22
|
+
'('=>:LPR, ')'=>:RPR, '{'=>:LBR, '}'=>:RBR,
|
|
23
|
+
';'=>:SCL, ','=>:CMA, ':'=>:COL
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
def lex src
|
|
27
|
+
ts = []
|
|
28
|
+
i = 0
|
|
29
|
+
ln = 1
|
|
30
|
+
co = 1
|
|
31
|
+
sl = src.length
|
|
32
|
+
while i < sl
|
|
33
|
+
c = src[i]
|
|
34
|
+
# 空白
|
|
35
|
+
if c =~ /\s/
|
|
36
|
+
c == "\n" ? (ln += 1; co = 1) : (co += 1)
|
|
37
|
+
i += 1
|
|
38
|
+
next
|
|
39
|
+
end
|
|
40
|
+
# 注释 //...
|
|
41
|
+
if c == '/' && src[i+1] == '/'
|
|
42
|
+
while i < sl && src[i] != "\n"
|
|
43
|
+
i += 1; co += 1
|
|
44
|
+
end
|
|
45
|
+
next
|
|
46
|
+
end
|
|
47
|
+
# 注释 /*...*/
|
|
48
|
+
if c == '/' && src[i+1] == '*'
|
|
49
|
+
i += 2; co += 2
|
|
50
|
+
while i < sl
|
|
51
|
+
src[i] == "\n" ? (ln += 1; co = 1) : (co += 1)
|
|
52
|
+
break if src[i] == '*' && src[i+1] == '/'
|
|
53
|
+
i += 1
|
|
54
|
+
end
|
|
55
|
+
i += 2; co += 2
|
|
56
|
+
next
|
|
57
|
+
end
|
|
58
|
+
# 字符串
|
|
59
|
+
if c == '"'
|
|
60
|
+
i += 1; co += 1
|
|
61
|
+
v = ''
|
|
62
|
+
while i < sl && src[i] != '"'
|
|
63
|
+
if src[i] == '\\' && i+1 < sl
|
|
64
|
+
n = src[i+1]
|
|
65
|
+
v += {'n'=>"\n",'t'=>"\t",'r'=>"\r",'\\'=>'\\','"'=>'"'}[n] || n
|
|
66
|
+
i += 2; co += 2
|
|
67
|
+
else
|
|
68
|
+
v += src[i]; i += 1; co += 1
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
err "unterminated string", ln, co and return ts if i >= sl
|
|
72
|
+
i += 1; co += 1
|
|
73
|
+
ts << {t: :STR, v:v, l:ln, c:co}
|
|
74
|
+
next
|
|
75
|
+
end
|
|
76
|
+
# 数字
|
|
77
|
+
if c =~ /[0-9]/
|
|
78
|
+
s = co
|
|
79
|
+
v = ''
|
|
80
|
+
while i < sl && src[i] =~ /[0-9.]/
|
|
81
|
+
v += src[i]; i += 1; co += 1
|
|
82
|
+
end
|
|
83
|
+
ts << {t: :NUM, v:v, l:ln, c:s}
|
|
84
|
+
next
|
|
85
|
+
end
|
|
86
|
+
# 标识符 / 关键字
|
|
87
|
+
if c =~ /[a-zA-Z_]/
|
|
88
|
+
s = co
|
|
89
|
+
v = ''
|
|
90
|
+
while i < sl && src[i] =~ /[a-zA-Z0-9_]/
|
|
91
|
+
v += src[i]; i += 1; co += 1
|
|
92
|
+
end
|
|
93
|
+
tt = KWS[v] || TYS[v] || :IDT
|
|
94
|
+
ts << {t:tt, v:v, l:ln, c:s}
|
|
95
|
+
next
|
|
96
|
+
end
|
|
97
|
+
# 多字符运算符
|
|
98
|
+
if c == '-' && src[i+1] == '>'
|
|
99
|
+
ts << {t: :ARR, v:'->', l:ln, c:co}
|
|
100
|
+
i += 2; co += 2
|
|
101
|
+
next
|
|
102
|
+
end
|
|
103
|
+
m2 = { '=='=>:EQ, '!='=>:NE, '<='=>:LE, '>='=>:GE,
|
|
104
|
+
'&&'=>:AND, '||'=>:OR }[src[i,2]]
|
|
105
|
+
if m2
|
|
106
|
+
ts << {t:m2, v:src[i,2], l:ln, c:co}
|
|
107
|
+
i += 2; co += 2
|
|
108
|
+
next
|
|
109
|
+
end
|
|
110
|
+
# 单字符运算符
|
|
111
|
+
m1 = { '='=>:ASN, '+'=>:ADD, '-'=>:SUB, '*'=>:MUL, '/'=>:DIV,
|
|
112
|
+
'%'=>:MOD, '<'=>:LT, '>'=>:GT, '!'=>:NOT }[c]
|
|
113
|
+
if m1
|
|
114
|
+
ts << {t:m1, v:c, l:ln, c:co}
|
|
115
|
+
i += 1; co += 1
|
|
116
|
+
next
|
|
117
|
+
end
|
|
118
|
+
# 分隔符
|
|
119
|
+
if SMAP[c]
|
|
120
|
+
ts << {t:SMAP[c], v:c, l:ln, c:co}
|
|
121
|
+
i += 1; co += 1
|
|
122
|
+
next
|
|
123
|
+
end
|
|
124
|
+
err "unexpected char '#{c}'", ln, co
|
|
125
|
+
i += 1; co += 1
|
|
126
|
+
end
|
|
127
|
+
ts << {t: :EOF, v:'', l:ln, c:co}
|
|
128
|
+
ts
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def err msg, ln, co
|
|
132
|
+
$stderr.puts "[lex error] line %d col %d: %s"%[ln, co, msg]
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
if :main == :main && $0 == __FILE__
|
|
139
|
+
#### 自测 ####
|
|
140
|
+
while line = ARGF.gets
|
|
141
|
+
ts = Infor::Lexer.lex(line)
|
|
142
|
+
ts.each{|tk| puts "%-6s | %-12s | L%d:C%d"%[tk[:t], tk[:v], tk[:l], tk[:c]] }
|
|
143
|
+
end
|
|
144
|
+
end
|
data/lib/optimizer.rb
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
#coding:utf-8
|
|
2
|
+
# infornoid 优化器 — LLM 顾问 + 确定性执行
|
|
3
|
+
# 确定性 Pass: 常量折叠, 死代码消除
|
|
4
|
+
# LLM 角色: 分析 IR 输出优化建议元数据, 编译器验证后执行
|
|
5
|
+
# CALL: Optimizer.optimize(ast, use_llm) → {ast, passes, log}
|
|
6
|
+
|
|
7
|
+
module Infor
|
|
8
|
+
module Optimizer
|
|
9
|
+
module_function
|
|
10
|
+
|
|
11
|
+
def optimize ast, use_llm = false
|
|
12
|
+
@log = []
|
|
13
|
+
@passes = []
|
|
14
|
+
|
|
15
|
+
# 1. 确定性常量折叠
|
|
16
|
+
ast = constant_fold(ast)
|
|
17
|
+
log_pass 'constant_folding', '常量折叠 (确定性)'
|
|
18
|
+
|
|
19
|
+
# 2. 确定性死代码消除
|
|
20
|
+
before = count_nodes(ast)
|
|
21
|
+
ast = dead_code_elim(ast)
|
|
22
|
+
after = count_nodes(ast)
|
|
23
|
+
if after < before
|
|
24
|
+
log_pass 'dead_code_elimination', "死代码消除: #{before} → #{after} 节点"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# 3. LLM 顾问 (可选)
|
|
28
|
+
if use_llm
|
|
29
|
+
advice = Advisor.optimize_advice(ast)
|
|
30
|
+
if advice[:ok]
|
|
31
|
+
advice[:suggestions].each do|sg|
|
|
32
|
+
r = apply_llm_pass(ast, sg)
|
|
33
|
+
if r && r[:applied]
|
|
34
|
+
ast = r[:ast]
|
|
35
|
+
log_pass sg['pass'] || sg[:pass], "LLM 建议: #{sg['reason'] || sg[:reason]}"
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
else
|
|
39
|
+
@log << "[advisor] LLM 降级: #{advice[:reason]}" if advice[:reason]
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
{ ast:ast, passes:@passes, log:@log }
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
#### 常量折叠 — 确定性 ####
|
|
47
|
+
|
|
48
|
+
def constant_fold node
|
|
49
|
+
return nil unless node
|
|
50
|
+
case node[:type]
|
|
51
|
+
when 'Program'
|
|
52
|
+
node[:body] = node[:body].map{|s| constant_fold(s) }.compact
|
|
53
|
+
node
|
|
54
|
+
when 'Block'
|
|
55
|
+
node[:body] = node[:body].map{|s| constant_fold(s) }.compact
|
|
56
|
+
node
|
|
57
|
+
when 'FnDecl'
|
|
58
|
+
node[:body] = constant_fold(node[:body])
|
|
59
|
+
node
|
|
60
|
+
when 'LetDecl'
|
|
61
|
+
node[:value] = constant_fold(node[:value])
|
|
62
|
+
node
|
|
63
|
+
when 'IfStmt'
|
|
64
|
+
node[:cond] = constant_fold(node[:cond])
|
|
65
|
+
node[:then] = constant_fold(node[:then])
|
|
66
|
+
node[:els] = constant_fold(node[:els]) if node[:els]
|
|
67
|
+
# 条件为常量 → 编译时分支消除
|
|
68
|
+
if node[:cond][:type] == 'BoolLit'
|
|
69
|
+
return node[:cond][:value] ? node[:then] : (node[:els] || { type:'Block', body:[], line:node[:line] })
|
|
70
|
+
end
|
|
71
|
+
node
|
|
72
|
+
when 'WhileStmt'
|
|
73
|
+
node[:cond] = constant_fold(node[:cond])
|
|
74
|
+
node[:body] = constant_fold(node[:body])
|
|
75
|
+
# 条件为 false → 整个循环消除
|
|
76
|
+
if node[:cond][:type] == 'BoolLit' && !node[:cond][:value]
|
|
77
|
+
return nil
|
|
78
|
+
end
|
|
79
|
+
node
|
|
80
|
+
when 'ReturnStmt'
|
|
81
|
+
node[:value] = constant_fold(node[:value]) if node[:value]
|
|
82
|
+
node
|
|
83
|
+
when 'ExprStmt'
|
|
84
|
+
node[:expr] = constant_fold(node[:expr])
|
|
85
|
+
node
|
|
86
|
+
when 'Assign'
|
|
87
|
+
node[:value] = constant_fold(node[:value])
|
|
88
|
+
node
|
|
89
|
+
when 'Binary'
|
|
90
|
+
node[:left] = constant_fold(node[:left])
|
|
91
|
+
node[:right] = constant_fold(node[:right])
|
|
92
|
+
try_fold(node)
|
|
93
|
+
when 'Unary'
|
|
94
|
+
node[:operand] = constant_fold(node[:operand])
|
|
95
|
+
try_fold_unary(node)
|
|
96
|
+
else
|
|
97
|
+
node
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
#尝试折叠二元常量
|
|
102
|
+
def try_fold node
|
|
103
|
+
l = node[:left]
|
|
104
|
+
r = node[:right]
|
|
105
|
+
return node unless l[:type] == 'NumLit' && r[:type] == 'NumLit'
|
|
106
|
+
lv = eval_num(l[:value])
|
|
107
|
+
rv = eval_num(r[:value])
|
|
108
|
+
result = case node[:op]
|
|
109
|
+
when '+' then lv + rv
|
|
110
|
+
when '-' then lv - rv
|
|
111
|
+
when '*' then lv * rv
|
|
112
|
+
when '/' then rv == 0 ? nil : lv / rv
|
|
113
|
+
when '%' then rv == 0 ? nil : lv % rv
|
|
114
|
+
when '<' then lv < rv
|
|
115
|
+
when '>' then lv > rv
|
|
116
|
+
when '<=' then lv <= rv
|
|
117
|
+
when '>=' then lv >= rv
|
|
118
|
+
when '==' then lv == rv
|
|
119
|
+
when '!=' then lv != rv
|
|
120
|
+
end
|
|
121
|
+
return node unless result
|
|
122
|
+
if [true, false].include?(result)
|
|
123
|
+
{ type:'BoolLit', value:result, line:node[:line] }
|
|
124
|
+
else
|
|
125
|
+
{ type:'NumLit', value:result.to_s, line:node[:line] }
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def try_fold_unary node
|
|
130
|
+
o = node[:operand]
|
|
131
|
+
return node unless o[:type] == 'NumLit' || o[:type] == 'BoolLit'
|
|
132
|
+
case node[:op]
|
|
133
|
+
when '-'
|
|
134
|
+
v = eval_num(o[:value])
|
|
135
|
+
{ type:'NumLit', value:(-v).to_s, line:node[:line] }
|
|
136
|
+
when '!'
|
|
137
|
+
{ type:'BoolLit', value:!o[:value], line:node[:line] }
|
|
138
|
+
else
|
|
139
|
+
node
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
#### 死代码消除 — 确定性 ####
|
|
144
|
+
|
|
145
|
+
def dead_code_elim node
|
|
146
|
+
return nil unless node
|
|
147
|
+
case node[:type]
|
|
148
|
+
when 'Program', 'Block'
|
|
149
|
+
node[:body] = node[:body].map{|s| dead_code_elim(s) }.compact
|
|
150
|
+
# 移除 return 之后的语句
|
|
151
|
+
ret_idx = node[:body].index{ |s| s[:type] == 'ReturnStmt' }
|
|
152
|
+
if ret_idx && ret_idx < node[:body].length - 1
|
|
153
|
+
node[:body] = node[:body][0..ret_idx]
|
|
154
|
+
@log << "[dce] 移除 return 后的死代码 (line #{node[:line]})"
|
|
155
|
+
end
|
|
156
|
+
node
|
|
157
|
+
when 'FnDecl'
|
|
158
|
+
node[:body] = dead_code_elim(node[:body])
|
|
159
|
+
node
|
|
160
|
+
when 'IfStmt'
|
|
161
|
+
node[:then] = dead_code_elim(node[:then])
|
|
162
|
+
node[:els] = dead_code_elim(node[:els]) if node[:els]
|
|
163
|
+
node
|
|
164
|
+
when 'WhileStmt'
|
|
165
|
+
node[:body] = dead_code_elim(node[:body])
|
|
166
|
+
node
|
|
167
|
+
else
|
|
168
|
+
node
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
#### LLM 顾问建议执行 ####
|
|
173
|
+
|
|
174
|
+
def apply_llm_pass ast, suggestion
|
|
175
|
+
pass = suggestion['pass'] || suggestion[:pass]
|
|
176
|
+
case pass
|
|
177
|
+
when 'constant_folding'
|
|
178
|
+
# 已在确定性阶段完成, 跳过
|
|
179
|
+
nil
|
|
180
|
+
when 'dead_code_elimination'
|
|
181
|
+
# 已在确定性阶段完成, 跳过
|
|
182
|
+
nil
|
|
183
|
+
when 'inline_call'
|
|
184
|
+
# ※ 确定性内联: 仅内联单行函数体
|
|
185
|
+
r = inline_single_line(ast)
|
|
186
|
+
r ? { applied:true, ast:r } : nil
|
|
187
|
+
else
|
|
188
|
+
# 未实现的 Pass, 跳过
|
|
189
|
+
nil
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
#内联仅含单个 return 的函数
|
|
194
|
+
def inline_single_line ast
|
|
195
|
+
# 简化实现: 标记但不实际执行内联
|
|
196
|
+
# 复杂内联需 SSA + 控制流图, 此处保守不执行
|
|
197
|
+
nil
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
#### 辅助 ####
|
|
201
|
+
|
|
202
|
+
def eval_num v
|
|
203
|
+
v.to_s.include?('.') ? v.to_f : v.to_i
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def count_nodes node
|
|
207
|
+
return 0 unless node
|
|
208
|
+
n = 1
|
|
209
|
+
case node[:type]
|
|
210
|
+
when 'Program', 'Block'
|
|
211
|
+
n += node[:body].inject(0){ |s, c| s + count_nodes(c) }
|
|
212
|
+
when 'LetDecl', 'ReturnStmt', 'ExprStmt'
|
|
213
|
+
n += count_nodes(node[:value]) if node[:value]
|
|
214
|
+
n += count_nodes(node[:expr]) if node[:expr]
|
|
215
|
+
when 'IfStmt'
|
|
216
|
+
n += count_nodes(node[:cond]) + count_nodes(node[:then])
|
|
217
|
+
n += count_nodes(node[:els]) if node[:els]
|
|
218
|
+
when 'WhileStmt'
|
|
219
|
+
n += count_nodes(node[:cond]) + count_nodes(node[:body])
|
|
220
|
+
when 'Binary'
|
|
221
|
+
n += count_nodes(node[:left]) + count_nodes(node[:right])
|
|
222
|
+
when 'Unary'
|
|
223
|
+
n += count_nodes(node[:operand])
|
|
224
|
+
when 'Assign'
|
|
225
|
+
n += count_nodes(node[:target]) + count_nodes(node[:value])
|
|
226
|
+
when 'Call'
|
|
227
|
+
n += node[:args].inject(0){ |s, a| s + count_nodes(a) }
|
|
228
|
+
end
|
|
229
|
+
n
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def log_pass name, desc
|
|
233
|
+
@passes << name
|
|
234
|
+
@log << "[opt] #{name}: #{desc}"
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
end
|
|
238
|
+
end
|