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.
data/README.md ADDED
@@ -0,0 +1,248 @@
1
+ # Infornoid Compiler
2
+
3
+ > 混合架构智能编译器 — 确定性前端 + LLM 顾问优化 + 确定性代码生成
4
+
5
+ Infornoid 是一个采用**混合架构**设计的智能编译器:确定性编译器保证安全性与可预测性,LLM 作为**顾问**提供优化建议,不直接生成可执行代码。
6
+
7
+ **零第三方依赖,纯 Ruby 标准库实现。**
8
+
9
+ ---
10
+
11
+ ## 特性
12
+
13
+ - **确定性前端**:手写词法分析器 + 递归下降语法分析器
14
+ - **规则引擎语义**:符号表 + 作用域 + 类型推导
15
+ - **LLM 顾问优化**(可选):通过 [Ollama](https://ollama.com/) 本地调用大模型提供优化建议
16
+ - **确定性代码生成**:模板驱动的目标代码输出(JavaScript / Python)
17
+ - **优雅降级**:Ollama 不可用时自动回退到纯确定性编译
18
+
19
+ ---
20
+
21
+ ## 快速开始
22
+
23
+ ### 环境要求
24
+
25
+ - **Ruby 2.5+**(仅使用标准库,无需安装 gem)
26
+ - **可选**:本地 Ollama 服务(用于 `--llm` 顾问功能)
27
+
28
+ ### 安装
29
+
30
+ ```bash
31
+ gem build infornoid.gemspec
32
+ gem install infornoid-0.1.0.gem
33
+ ```
34
+
35
+ 安装后可直接使用 `infornoid` 命令:
36
+
37
+ ```bash
38
+ infornoid examples/hello.inf # 编译为 JavaScript
39
+ infornoid compile examples/hello.inf -o out.js
40
+ infornoid examples/hello.inf --target py -o out.py
41
+ ```
42
+
43
+ ### 本地开发(无需安装)
44
+
45
+ ```bash
46
+ # 编译为 JavaScript (默认子命令为 compile)
47
+ ruby lib/infornoid.rb examples/hello.inf
48
+
49
+ # 指定输出文件
50
+ ruby lib/infornoid.rb examples/hello.inf -o out.js
51
+
52
+ # 编译为 Python
53
+ ruby lib/infornoid.rb examples/hello.inf --target py -o out.py
54
+ ```
55
+
56
+ ### 命令行工具 (本地开发)
57
+
58
+ 将 `bin/`(Unix/macOS)或仓库根目录(Windows)加入 `PATH` 后,即可直接使用 `infornoid` 命令:
59
+
60
+ ```bash
61
+ # Windows: infornoid.cmd
62
+ # Unix/macOS: bin/infornoid (chmod +x bin/infornoid)
63
+
64
+ infornoid examples/hello.inf # 编译为 JavaScript
65
+ infornoid compile examples/hello.inf -o out.js
66
+ infornoid examples/hello.inf --target py -o out.py
67
+ ```
68
+
69
+ ### 子命令风格(类似 gcc/clang 分阶段调用)
70
+
71
+ ```bash
72
+ infornoid lex examples/hello.inf # 输出 Token 列表
73
+ infornoid parse examples/hello.inf --json # 输出 AST JSON
74
+ infornoid semantic examples/hello.inf # 输出符号表 / 类型 / 错误
75
+ infornoid optimize examples/hello.inf # 输出优化后 AST
76
+ infornoid codegen examples/hello.inf --target py # 仅生成代码
77
+ infornoid check examples/hello.inf # 语法检查 (不输出代码)
78
+ ```
79
+
80
+ > 兼容旧用法:`--tokens` 等同 `lex`,`--ast` 等同 `parse`,无子命令时默认 `compile`。
81
+
82
+ ---
83
+
84
+ ## 语言示例
85
+
86
+ ```inf
87
+ // 函数声明
88
+ fn add(a: Number, b: Number) -> Number {
89
+ return a + b;
90
+ }
91
+
92
+ // 变量声明 + 类型推断
93
+ let x = 1 + 2 * 3;
94
+ let y = add(x, 10);
95
+
96
+ // 条件分支
97
+ if (x > 5) {
98
+ let z = x + y;
99
+ } else {
100
+ let z = x - y;
101
+ }
102
+
103
+ // 循环
104
+ let i = 0;
105
+ while (i < 10) {
106
+ i = i + 1;
107
+ }
108
+ ```
109
+
110
+ ---
111
+
112
+ ## 架构
113
+
114
+ ```
115
+ 源码 (.inf)
116
+
117
+ [Lexer] → Token
118
+
119
+ [Parser] → AST
120
+
121
+ [Semantic] → 类型检查、符号表验证
122
+
123
+ [Optimizer] → 常量折叠、死代码消除、LLM 建议
124
+
125
+ [Codegen] → 目标代码(JavaScript / Python)
126
+ ```
127
+
128
+ | 模块 | 功能 |
129
+ |------|------|
130
+ | `lib/infornoid.rb` | 主驱动,CLI 参数解析与阶段编排 |
131
+ | `lib/lexer.rb` | 词法分析器 |
132
+ | `lib/parser.rb` | 递归下降语法分析器 |
133
+ | `lib/semantic.rb` | 语义分析:符号表 + 类型检查 |
134
+ | `lib/optimizer.rb` | 优化器:常量折叠、死代码消除 |
135
+ | `lib/codegen.rb` | 代码生成器 |
136
+ | `lib/advisor.rb` | LLM 顾问层:Ollama 集成 + 优雅降级 |
137
+
138
+ ---
139
+
140
+ ## 项目结构
141
+
142
+ ```
143
+ infornoid/
144
+ ├── bin/
145
+ │ └── infornoid # Unix/macOS 命令入口
146
+ ├── infornoid.cmd # Windows 命令入口
147
+ ├── infornoid.gemspec # gem 构建文件
148
+ ├── lib/
149
+ │ ├── infornoid.rb # 主入口(子命令风格 CLI)
150
+ │ ├── lexer.rb # 词法分析器
151
+ │ ├── parser.rb # 语法分析器
152
+ │ ├── semantic.rb # 语义分析器
153
+ │ ├── optimizer.rb # 优化器
154
+ │ ├── codegen.rb # 代码生成器
155
+ │ └── advisor.rb # LLM 顾问层
156
+ ├── spec/
157
+ │ ├── base_spec.rb # 测试套件
158
+ │ └── spec_helper.rb # 测试辅助
159
+ ├── examples/
160
+ │ ├── hello.inf # 示例源文件
161
+ │ └── hello.js # 编译输出示例
162
+ ├── documents/ # 调研文档与方案设计
163
+ │ ├── 01_GitHub项目调研/
164
+ │ ├── 02_大模型构建编译器方案/
165
+ │ ├── 03_相关SKILL分析/
166
+ │ └── 04_下一步计划/
167
+ ├── README.md # 本文件
168
+ └── index.html # 交互式演示页面
169
+ ```
170
+
171
+ ---
172
+
173
+ ## 测试
174
+
175
+ ```bash
176
+ bundle exec rspec spec/base_spec.rb
177
+ ```
178
+
179
+ 测试覆盖:Lexer、Parser、Semantic、Optimizer、Codegen、端到端。
180
+
181
+ ---
182
+
183
+ ## CLI 选项
184
+
185
+ | 选项 | 说明 |
186
+ |------|------|
187
+ | `-o <file>` | 指定输出文件路径 |
188
+ | `--target <lang>` | 目标语言:`js`(默认)、`py` |
189
+ | `--json` | 以 JSON 输出调试信息(`lex` / `parse` / `semantic` / `optimize`) |
190
+ | `--ast` | 输出 AST JSON(等同 `parse`) |
191
+ | `--tokens` | 输出 Token 列表(等同 `lex`) |
192
+ | `--no-opt` | 跳过优化阶段 |
193
+ | `--llm` | 启用 LLM 顾问(需要 Ollama) |
194
+ | `--help`, `-h` | 显示帮助 |
195
+ | `--version`, `-v` | 显示版本 |
196
+
197
+ ## CLI 子命令
198
+
199
+ | 子命令 | 说明 |
200
+ |--------|------|
201
+ | `compile`(默认) | 完整流水线:词法 → 语法 → 语义 → 优化 → 代码生成 |
202
+ | `lex` | 仅词法分析,输出 Token 列表(`--json` 输出 JSON) |
203
+ | `parse` | 仅语法分析,输出 AST |
204
+ | `semantic` | 语义分析,输出符号表 / 类型 / 错误 |
205
+ | `optimize` | 优化阶段,输出优化后 AST |
206
+ | `codegen` | 仅代码生成(默认目标 JS,可用 `--target` 切换) |
207
+ | `check` | 语法检查,通过时退出码 0,出错时退出码 1 |
208
+
209
+ ---
210
+
211
+ ## 语言规范速查
212
+
213
+ ### 数据类型
214
+
215
+ `Number` · `String` · `Boolean` · `Void`
216
+
217
+ ### 关键字
218
+
219
+ ```
220
+ let fn if else while return true false
221
+ ```
222
+
223
+ ### 运算符优先级
224
+
225
+ 1. `=` 赋值
226
+ 2. `||` 逻辑或
227
+ 3. `&&` 逻辑与
228
+ 4. `==` `!=` 等于/不等于
229
+ 5. `<` `>` `<=` `>=` 比较
230
+ 6. `+` `-` 加减
231
+ 7. `*` `/` `%` 乘除模
232
+ 8. `!` `-`(一元)逻辑非、取负
233
+
234
+ ---
235
+
236
+ ## 设计文档
237
+
238
+ 本项目的设计与调研文档位于 `documents/` 目录,涵盖:
239
+
240
+ - **GitHub 项目调研**:AI 优化传统编译器、深度学习专用编译器
241
+ - **三条技术路线**:LLM 作编码助手 / LLM 充当编译器 / 混合架构 AgentCompile
242
+ - **推荐路线**:混合架构(路线三)— LLM 顾问 + 确定性编译器
243
+
244
+ ---
245
+
246
+ ## 许可证
247
+
248
+ AGPL-3.0
data/bin/infornoid ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+ #coding:utf-8
3
+ # infornoid — 命令行入口
4
+ # 安装为 gem 后: infornoid examples/hello.inf
5
+ # 本地开发: ruby bin/infornoid examples/hello.inf
6
+
7
+ $LOAD_PATH.unshift(File.expand_path('../lib', __dir__)) unless $LOADED_FEATURES.any?{|f| f.end_with?('infornoid.rb')}
8
+ require 'infornoid'
9
+
10
+ exit(Infor::Compiler.main(ARGV) || 0)
data/lib/advisor.rb ADDED
@@ -0,0 +1,151 @@
1
+ #coding:utf-8
2
+ # infornoid LLM 顾问层 — Ollama 本地集成 + 优雅降级
3
+ # 零第三方依赖, 通过 net/http 调用本地 Ollama API
4
+ # 设计哲学: LLM 只输出建议性元数据, 不直接生成可执行代码
5
+ # CALL: Advisor.suggest(ast, phase) → suggestions Hash
6
+ # RETN: {ok:true/false, suggestions:[...], source:"llm"/"fallback"}
7
+
8
+ require 'net/http'
9
+ require 'json'
10
+
11
+ module Infor
12
+ module Advisor
13
+ module_function
14
+
15
+ # ☆ Ollama 默认配置, 可通过环境变量覆盖
16
+ DEF_HOST = ENV['OLLAMA_HOST'] || '127.0.0.1'
17
+ DEF_PORT = (ENV['OLLAMA_PORT'] || 11434).to_i
18
+ DEF_MODEL = ENV['INFORNOID_MODEL'] || 'qwen2.5-coder:7b'
19
+
20
+ #### 探活 ####
21
+
22
+ # 检查 Ollama 是否可用
23
+ def alive? host = DEF_HOST, port = DEF_PORT
24
+ s = TCPSocket.new(host, port)
25
+ s.close
26
+ true
27
+ rescue
28
+ false
29
+ end
30
+
31
+ #### 核心调用 ####
32
+
33
+ # 向 Ollama 发送 prompt, 返回文本响应
34
+ # CALL: ask(prompt) → String
35
+ def ask prompt, model = DEF_MODEL, host = DEF_HOST, port = DEF_PORT
36
+ return '' unless alive?(host, port)
37
+ uri = URI("http://#{host}:#{port}/api/generate")
38
+ req = Net::HTTP::Post.new(uri, 'Content-Type'=>'application/json')
39
+ req.body = JSON.generate({
40
+ model: model,
41
+ prompt: prompt,
42
+ stream: false,
43
+ options: { temperature: 0, seed: 42 }
44
+ })
45
+ http = Net::HTTP.new(host, port)
46
+ http.read_timeout = 60
47
+ res = http.request(req)
48
+ return '' unless res.is_a? Net::HTTPSuccess
49
+ data = JSON.parse(res.body)
50
+ data['response'] || ''
51
+ rescue => e
52
+ $stderr.puts "[advisor] LLM 调用失败: #{e.message}"
53
+ ''
54
+ end
55
+
56
+ #### 语义分析顾问 ####
57
+
58
+ # 让 LLM 对 AST 做类型推断建议
59
+ # RETN: {ok, suggestions, source}
60
+ def typecheck_advice ast
61
+ prompt = <<-PROMPT
62
+ 你是类型推断顾问, 分析以下 AST, 对需要推断类型的节点给出建议.
63
+ 只输出 JSON, 格式: {"suggestions":[{"node_path":"...","type":"Number|String|Boolean|Void","confidence":0.0-1.0,"reason":"..."}]}
64
+
65
+ AST(JSON):
66
+ #{JSON.pretty_generate(ast)}
67
+ PROMPT
68
+ raw = ask(prompt)
69
+ return fallback_typecheck(ast) if raw.empty?
70
+ parse_suggestions(raw, :typecheck)
71
+ end
72
+
73
+ #### 优化顾问 ####
74
+
75
+ # 让 LLM 分析 AST/IR 输出优化建议
76
+ # RETN: {ok, suggestions, source}
77
+ def optimize_advice ast
78
+ prompt = <<-PROMPT
79
+ 你是编译优化顾问, 分析以下 AST, 列出优化建议.
80
+ 可用 Pass: constant_folding, dead_code_elimination, inline_call, loop_unroll, strength_reduction
81
+ 只输出 JSON, 格式: {"suggestions":[{"pass":"...","target":"...","params":{},"expected_gain":"...","reason":"..."}]}
82
+
83
+ AST(JSON):
84
+ #{JSON.pretty_generate(ast)}
85
+ PROMPT
86
+ raw = ask(prompt)
87
+ return fallback_optimize(ast) if raw.empty?
88
+ parse_suggestions(raw, :optimize)
89
+ end
90
+
91
+ #### 代码生成顾问 ####
92
+
93
+ # 让 LLM 建议代码模板
94
+ # RETN: {ok, suggestions, source}
95
+ def codegen_advice node, target = 'JavaScript'
96
+ prompt = <<-PROMPT
97
+ 你是代码生成顾问, 为以下 AST 节点建议 #{target} 代码模板.
98
+ 只输出 JSON, 格式: {"templates":[{"node_type":"...","template":"..."}]}
99
+
100
+ AST 节点(JSON):
101
+ #{JSON.pretty_generate(node)}
102
+ PROMPT
103
+ raw = ask(prompt)
104
+ return fallback_codegen(node) if raw.empty?
105
+ parse_suggestions(raw, :codegen)
106
+ end
107
+
108
+ #### JSON 解析 ####
109
+
110
+ # 尝试从 LLM 响应中提取 JSON
111
+ def parse_suggestions raw, phase
112
+ # 尝试直接解析
113
+ begin
114
+ data = JSON.parse(raw)
115
+ return { ok:true, suggestions:data['suggestions'] || data['templates'] || [data], source:'llm' }
116
+ rescue
117
+ end
118
+ # 尝试提取 JSON 块
119
+ if raw =~ /\{[\s\S]*\}/m
120
+ begin
121
+ data = JSON.parse($&)
122
+ return { ok:true, suggestions:data['suggestions'] || data['templates'] || [data], source:'llm' }
123
+ rescue
124
+ end
125
+ end
126
+ # 降级
127
+ case phase
128
+ when :typecheck then fallback_typecheck(nil)
129
+ when :optimize then fallback_optimize(nil)
130
+ when :codegen then fallback_codegen(nil)
131
+ end
132
+ end
133
+
134
+ #### 降级策略 — 零 LLM 时的确定性兜底 ####
135
+
136
+ def fallback_typecheck ast
137
+ { ok:false, suggestions:[], source:'fallback', reason:'LLM 不可用, 使用规则引擎兜底' }
138
+ end
139
+
140
+ def fallback_optimize ast
141
+ { ok:false, suggestions:[
142
+ { pass:'constant_folding', target:'all', params:{}, expected_gain:'+5%', reason:'确定性常量折叠, 无需 LLM' }
143
+ ], source:'fallback' }
144
+ end
145
+
146
+ def fallback_codegen node
147
+ { ok:false, suggestions:[], source:'fallback', reason:'LLM 不可用, 使用默认模板兜底' }
148
+ end
149
+
150
+ end
151
+ end
data/lib/codegen.rb ADDED
@@ -0,0 +1,109 @@
1
+ #coding:utf-8
2
+ # infornoid 代码生成器 — LLM 建议模板 + 确定性填充
3
+ # 默认目标: JavaScript
4
+ # 确定性模板优先, LLM 模板仅在编译验证通过时采纳
5
+ # CALL: Codegen.gen(ast, target, use_llm) → String (目标代码)
6
+
7
+ module Infor
8
+ module Codegen
9
+ module_function
10
+
11
+ # ☆ 确定性默认模板
12
+ TMPL = {
13
+ 'LetDecl' => 'let %s = %s;',
14
+ 'FnDecl' => "function %s(%s) {\n%s\n}",
15
+ 'IfStmt' => "if (%s) {\n%s\n} else {\n%s\n}",
16
+ 'WhileStmt' => "while (%s) {\n%s\n}",
17
+ 'ReturnStmt' => 'return %s;',
18
+ 'ExprStmt' => '%s;',
19
+ 'Assign' => '%s = %s',
20
+ 'Binary' => '(%s %s %s)',
21
+ 'Unary' => '%s%s',
22
+ 'NumLit' => '%s',
23
+ 'StrLit' => '"%s"',
24
+ 'BoolLit' => '%s',
25
+ 'Ident' => '%s',
26
+ 'Call' => '%s(%s)',
27
+ }
28
+
29
+ @ind = 0 #缩进层级
30
+
31
+ def gen ast, target = 'JavaScript', use_llm = false
32
+ @ind = 0
33
+ @log = []
34
+ code = gen_node(ast)
35
+ { code:code, log:@log }
36
+ end
37
+
38
+ #### 递归生成 ####
39
+
40
+ def gen_node node
41
+ return '' unless node
42
+ case node[:type]
43
+ when 'Program'
44
+ node[:body].map{|s| gen_node(s) }.join("\n\n")
45
+ when 'Block'
46
+ node[:body].map{|s| gen_node(s) }.join("\n")
47
+ when 'LetDecl'
48
+ pad TMPL['LetDecl'] % [node[:name], gen_node(node[:value])]
49
+ when 'FnDecl'
50
+ params = node[:params].map{|p| p[:name] }.join(', ')
51
+ body = indent { gen_node(node[:body]) }
52
+ pad TMPL['FnDecl'] % [node[:name], params, body]
53
+ when 'IfStmt'
54
+ cond = gen_node(node[:cond])
55
+ then_b = indent { gen_node(node[:then]) }
56
+ if node[:els]
57
+ els_b = indent { gen_node(node[:els]) }
58
+ pad TMPL['IfStmt'] % [cond, then_b, els_b]
59
+ else
60
+ pad "if (%s) {\n%s\n}" % [cond, then_b]
61
+ end
62
+ when 'WhileStmt'
63
+ cond = gen_node(node[:cond])
64
+ body = indent { gen_node(node[:body]) }
65
+ pad TMPL['WhileStmt'] % [cond, body]
66
+ when 'ReturnStmt'
67
+ val = node[:value] ? gen_node(node[:value]) : ''
68
+ pad TMPL['ReturnStmt'] % val
69
+ when 'ExprStmt'
70
+ pad TMPL['ExprStmt'] % gen_node(node[:expr])
71
+ when 'Assign'
72
+ pad TMPL['Assign'] % [gen_node(node[:target]), gen_node(node[:value])]
73
+ when 'Binary'
74
+ TMPL['Binary'] % [gen_node(node[:left]), node[:op], gen_node(node[:right])]
75
+ when 'Unary'
76
+ TMPL['Unary'] % [node[:op], gen_node(node[:operand])]
77
+ when 'NumLit'
78
+ node[:value]
79
+ when 'StrLit'
80
+ '"%s"' % node[:value].gsub('"', '\\"')
81
+ when 'BoolLit'
82
+ node[:value] ? 'true' : 'false'
83
+ when 'Ident'
84
+ node[:name]
85
+ when 'Call'
86
+ args = node[:args].map{|a| gen_node(a) }.join(', ')
87
+ TMPL['Call'] % [node[:name], args]
88
+ when 'Error'
89
+ '/* ERROR */'
90
+ else
91
+ "/* unknown: #{node[:type]} */"
92
+ end
93
+ end
94
+
95
+ #### 缩进辅助 ####
96
+
97
+ def pad str
98
+ ' ' * @ind + str
99
+ end
100
+
101
+ def indent
102
+ @ind += 1
103
+ r = yield
104
+ @ind -= 1
105
+ r
106
+ end
107
+
108
+ end
109
+ end