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/lib/parser.rb ADDED
@@ -0,0 +1,273 @@
1
+ #coding:utf-8
2
+ # infornoid 递归下降语法分析器 — 确定性前端
3
+ # 零依赖, 手写 Parser, AST 为 Ruby Hash
4
+ # CALL: Parser.parse(tokens) → AST Hash
5
+ # RETN: {type:"Program", body:[...]}
6
+
7
+ module Infor
8
+ module Parser
9
+ module_function
10
+
11
+ def parse ts
12
+ @ts = ts
13
+ @i = 0
14
+ prog = { type:'Program', body:[] }
15
+ while !at?(:EOF)
16
+ node = parse_decl
17
+ node and prog[:body] << node
18
+ end
19
+ prog
20
+ end
21
+
22
+ #### 前瞻 / 消费 ####
23
+
24
+ def cur; @ts[@i]; end
25
+ def nxt; @ts[@i+1]; end
26
+ def at? tt; cur && cur[:t] == tt; end
27
+
28
+ # eat: 无参 → 消费当前 token 并返回; 有参 → 校验类型后消费
29
+ def eat tt = nil
30
+ tk = cur
31
+ return nil unless tk
32
+ if tt && tk[:t] != tt
33
+ err "expected %s but got %s '%s'"%[tt, tk[:t], tk[:v]], tk
34
+ return nil
35
+ end
36
+ @i += 1
37
+ tk
38
+ end
39
+
40
+ #消费当前 token 并返回其 value
41
+ def eatv
42
+ tk = eat
43
+ tk ? tk[:v] : nil
44
+ end
45
+
46
+ def err msg, tk
47
+ $stderr.puts "[parse error] line %d col %d: %s"%[tk[:l], tk[:c], msg]
48
+ end
49
+
50
+ #### 声明 ####
51
+
52
+ def parse_decl
53
+ return parse_let if at? :LET
54
+ return parse_fn if at? :FN
55
+ parse_stmt
56
+ end
57
+
58
+ def parse_let
59
+ tk = eat :LET
60
+ id = eat :IDT
61
+ vt = nil
62
+ if at? :COL
63
+ eat :COL
64
+ vt = eatv
65
+ end
66
+ eat :ASN
67
+ val = parse_expr
68
+ eat :SCL
69
+ { type:'LetDecl', name:(id && id[:v]), varType:vt, value:val, line:(tk[:l]) }
70
+ end
71
+
72
+ def parse_fn
73
+ tk = eat :FN
74
+ id = eat :IDT
75
+ eat :LPR
76
+ params = []
77
+ while !at?(:RPR) && !at?(:EOF)
78
+ p = eat :IDT
79
+ eat :COL
80
+ pt = eatv
81
+ params << { name:(p[:v]), type:pt } rescue nil
82
+ eat :CMA if at? :CMA
83
+ end
84
+ eat :RPR
85
+ rt = nil
86
+ if at? :ARR
87
+ eat :ARR
88
+ rt = eatv
89
+ end
90
+ body = parse_block
91
+ { type:'FnDecl', name:(id[:v]), params:params, retType:rt, body:body, line:(tk[:l]) }
92
+ end
93
+
94
+ #### 语句 ####
95
+
96
+ def parse_stmt
97
+ return parse_let if at? :LET
98
+ return parse_if if at? :IF
99
+ return parse_while if at? :WHL
100
+ return parse_return if at? :RTN
101
+ return parse_block if at? :LBR
102
+ # 表达式语句
103
+ e = parse_expr
104
+ eat :SCL
105
+ { type:'ExprStmt', expr:e, line:(e && e[:line]) }
106
+ end
107
+
108
+ def parse_block
109
+ tk = eat :LBR
110
+ stmts = []
111
+ while !at?(:RBR) && !at?(:EOF)
112
+ stmts << parse_stmt
113
+ end
114
+ eat :RBR
115
+ { type:'Block', body:stmts, line:(tk[:l]) }
116
+ end
117
+
118
+ def parse_if
119
+ tk = eat :IF
120
+ eat :LPR
121
+ cond = parse_expr
122
+ eat :RPR
123
+ then_blk = parse_block
124
+ else_blk = nil
125
+ if at? :ELS
126
+ eat :ELS
127
+ else_blk = parse_block
128
+ end
129
+ { type:'IfStmt', cond:cond, then:then_blk, els:else_blk, line:(tk[:l]) }
130
+ end
131
+
132
+ def parse_while
133
+ tk = eat :WHL
134
+ eat :LPR
135
+ cond = parse_expr
136
+ eat :RPR
137
+ body = parse_block
138
+ { type:'WhileStmt', cond:cond, body:body, line:(tk[:l]) }
139
+ end
140
+
141
+ def parse_return
142
+ tk = eat :RTN
143
+ val = nil
144
+ val = parse_expr unless at?(:SCL)
145
+ eat :SCL
146
+ { type:'ReturnStmt', value:val, line:(tk[:l]) }
147
+ end
148
+
149
+ #### 表达式: 优先级递降链 ####
150
+
151
+ def parse_expr; parse_assign; end
152
+
153
+ def parse_assign
154
+ left = parse_or
155
+ if at?(:ASN) && left && left[:type] == 'Ident'
156
+ eat :ASN
157
+ right = parse_assign
158
+ return { type:'Assign', target:left, value:right, line:left[:line] }
159
+ end
160
+ left
161
+ end
162
+
163
+ def parse_or
164
+ left = parse_and
165
+ while at? :OR
166
+ eat; right = parse_and
167
+ left = mkbin('||', left, right)
168
+ end
169
+ left
170
+ end
171
+
172
+ def parse_and
173
+ left = parse_eq
174
+ while at? :AND
175
+ eat; right = parse_eq
176
+ left = mkbin('&&', left, right)
177
+ end
178
+ left
179
+ end
180
+
181
+ def parse_eq
182
+ left = parse_cmp
183
+ while at?(:EQ) || at?(:NE)
184
+ op = eatv; right = parse_cmp
185
+ left = mkbin(op, left, right)
186
+ end
187
+ left
188
+ end
189
+
190
+ def parse_cmp
191
+ left = parse_add
192
+ while at?(:LT) || at?(:GT) || at?(:LE) || at?(:GE)
193
+ op = eatv; right = parse_add
194
+ left = mkbin(op, left, right)
195
+ end
196
+ left
197
+ end
198
+
199
+ def parse_add
200
+ left = parse_mul
201
+ while at?(:ADD) || at?(:SUB)
202
+ op = eatv; right = parse_mul
203
+ left = mkbin(op, left, right)
204
+ end
205
+ left
206
+ end
207
+
208
+ def parse_mul
209
+ left = parse_unary
210
+ while at?(:MUL) || at?(:DIV) || at?(:MOD)
211
+ op = eatv; right = parse_unary
212
+ left = mkbin(op, left, right)
213
+ end
214
+ left
215
+ end
216
+
217
+ def parse_unary
218
+ if at?(:NOT) || at?(:SUB)
219
+ op = eatv; operand = parse_unary
220
+ return { type:'Unary', op:op, operand:operand, line:(operand[:line]) }
221
+ end
222
+ parse_primary
223
+ end
224
+
225
+ def parse_primary
226
+ tk = cur
227
+ return { type:'Error', line:1 } unless tk
228
+ case tk[:t]
229
+ when :NUM
230
+ eat
231
+ { type:'NumLit', value:tk[:v], line:tk[:l] }
232
+ when :STR
233
+ eat
234
+ { type:'StrLit', value:tk[:v], line:tk[:l] }
235
+ when :TRU
236
+ eat
237
+ { type:'BoolLit', value:true, line:tk[:l] }
238
+ when :FLS
239
+ eat
240
+ { type:'BoolLit', value:false, line:tk[:l] }
241
+ when :IDT
242
+ eat
243
+ if at? :LPR
244
+ eat :LPR
245
+ args = []
246
+ while !at?(:RPR) && !at?(:EOF)
247
+ args << parse_expr
248
+ eat :CMA if at? :CMA
249
+ end
250
+ eat :RPR
251
+ { type:'Call', name:tk[:v], args:args, line:tk[:l] }
252
+ else
253
+ { type:'Ident', name:tk[:v], line:tk[:l] }
254
+ end
255
+ when :LPR
256
+ eat :LPR
257
+ e = parse_expr
258
+ eat :RPR
259
+ e
260
+ else
261
+ err "unexpected token %s '%s'"%[tk[:t], tk[:v]], tk
262
+ eat
263
+ { type:'Error', line:tk[:l] }
264
+ end
265
+ end
266
+
267
+ #构造二元表达式节点
268
+ def mkbin op, l, r
269
+ { type:'Binary', op:op, left:l, right:r, line:(l[:line]) }
270
+ end
271
+
272
+ end
273
+ end
data/lib/semantic.rb ADDED
@@ -0,0 +1,214 @@
1
+ #coding:utf-8
2
+ # infornoid 语义分析 — 确定性规则引擎为主, LLM 顾问辅助
3
+ # 零第三方依赖, 符号表 + 作用域 + 类型推导
4
+ # CALL: Semantic.analyze(ast) → {ast, errors, types}
5
+ # 设计: 规则引擎做确定性推导, LLM 只在规则未覆盖时给建议
6
+
7
+ module Infor
8
+ module Semantic
9
+ module_function
10
+
11
+ #### 类型规则表 ####
12
+
13
+ # (leftType, op, rightType) → resultType
14
+ RULES = {
15
+ ['Number','+','Number']=>'Number',
16
+ ['Number','-','Number']=>'Number',
17
+ ['Number','*','Number']=>'Number',
18
+ ['Number','/','Number']=>'Number',
19
+ ['Number','%','Number']=>'Number',
20
+ ['String','+','String']=>'String',
21
+ ['Number','<','Number']=>'Boolean',
22
+ ['Number','>','Number']=>'Boolean',
23
+ ['Number','<=','Number']=>'Boolean',
24
+ ['Number','>=','Number']=>'Boolean',
25
+ ['Number','==','Number']=>'Boolean',
26
+ ['Number','!=','Number']=>'Boolean',
27
+ ['String','==','String']=>'Boolean',
28
+ ['String','!=','String']=>'Boolean',
29
+ ['Boolean','==','Boolean']=>'Boolean',
30
+ ['Boolean','!=','Boolean']=>'Boolean',
31
+ ['Boolean','&&','Boolean']=>'Boolean',
32
+ ['Boolean','||','Boolean']=>'Boolean',
33
+ }
34
+
35
+ UNARY = {
36
+ ['-', 'Number']=>'Number',
37
+ ['!', 'Boolean']=>'Boolean',
38
+ }
39
+
40
+ #### 符号表 / 作用域 ####
41
+
42
+ Scope = Struct.new(:vars, :funcs, :parent) do
43
+ def initialize(parent=nil)
44
+ self.vars = {}
45
+ self.funcs = {}
46
+ self.parent = parent
47
+ end
48
+ def lookup_var name
49
+ vars[name] || (parent && parent.lookup_var(name))
50
+ end
51
+ def lookup_fn name
52
+ funcs[name] || (parent && parent.lookup_fn(name))
53
+ end
54
+ def define_var name, type, line
55
+ if vars[name]
56
+ return [:error, "变量 '#{name}' 在同一作用域内重复声明 (line #{line})"]
57
+ end
58
+ vars[name] = type
59
+ nil
60
+ end
61
+ def define_fn name, sig, line
62
+ funcs[name] = sig
63
+ nil
64
+ end
65
+ end
66
+
67
+ #### 主入口 ####
68
+
69
+ def analyze ast
70
+ @errors = []
71
+ @scope = Scope.new
72
+ @advisor_suggestions = []
73
+ walk ast, @scope
74
+ { ast:ast, errors:@errors, types:@scope, suggestions:@advisor_suggestions }
75
+ end
76
+
77
+ #### AST 遍历 ####
78
+
79
+ def walk node, sc
80
+ return nil unless node
81
+ case node[:type]
82
+ when 'Program'
83
+ node[:body].each{|s| walk(s, sc) }
84
+ when 'LetDecl'
85
+ walk_let(node, sc)
86
+ when 'FnDecl'
87
+ walk_fn(node, sc)
88
+ when 'IfStmt'
89
+ walk_if(node, sc)
90
+ when 'WhileStmt'
91
+ ct = walk(node[:cond], sc)
92
+ err node[:line], "while 条件必须为 Boolean, 实际为 #{ct}" if ct && ct != 'Boolean'
93
+ walk(node[:body], Scope.new(sc))
94
+ when 'ReturnStmt'
95
+ walk(node[:value], sc) if node[:value]
96
+ when 'Block'
97
+ inner = Scope.new(sc)
98
+ node[:body].each{|s| walk(s, inner) }
99
+ when 'ExprStmt'
100
+ walk(node[:expr], sc)
101
+ when 'Assign'
102
+ walk_assign(node, sc)
103
+ when 'Binary'
104
+ walk_binary(node, sc)
105
+ when 'Unary'
106
+ walk_unary(node, sc)
107
+ when 'NumLit'
108
+ 'Number'
109
+ when 'StrLit'
110
+ 'String'
111
+ when 'BoolLit'
112
+ 'Boolean'
113
+ when 'Ident'
114
+ t = sc.lookup_var(node[:name])
115
+ err node[:line], "变量 '#{node[:name]}' 未声明" unless t
116
+ t
117
+ when 'Call'
118
+ walk_call(node, sc)
119
+ end
120
+ end
121
+
122
+ #### 各节点处理 ####
123
+
124
+ def walk_let node, sc
125
+ vt = node[:varType]
126
+ it = walk(node[:value], sc)
127
+ if vt && it && vt != it
128
+ # ※ 尝试 LLM 顾问: 隐式类型转换建议
129
+ err node[:line], "类型不匹配: 声明为 #{vt}, 推断为 #{it}"
130
+ end
131
+ t = vt || it || 'Unknown'
132
+ r = sc.define_var(node[:name], t, node[:line])
133
+ err(*r) if r
134
+ t
135
+ end
136
+
137
+ def walk_fn node, sc
138
+ params = node[:params].inject({}){|h,p| h[p[:name]]=p[:type]; h }
139
+ sc.define_fn(node[:name], {
140
+ params:params, retType:node[:retType]
141
+ }, node[:line])
142
+ inner = Scope.new(sc)
143
+ params.each{|n,t| inner.vars[n] = t }
144
+ walk(node[:body], inner)
145
+ node[:retType] || 'Void'
146
+ end
147
+
148
+ def walk_if node, sc
149
+ ct = walk(node[:cond], sc)
150
+ err node[:line], "if 条件必须为 Boolean, 实际为 #{ct}" if ct && ct != 'Boolean'
151
+ walk(node[:then], Scope.new(sc))
152
+ walk(node[:els], Scope.new(sc)) if node[:els]
153
+ end
154
+
155
+ def walk_assign node, sc
156
+ lt = walk(node[:target], sc)
157
+ rt = walk(node[:value], sc)
158
+ if lt && rt && lt != rt
159
+ err node[:line], "赋值类型不匹配: #{lt} ≠ #{rt}"
160
+ end
161
+ lt
162
+ end
163
+
164
+ def walk_binary node, sc
165
+ lt = walk(node[:left], sc)
166
+ rt = walk(node[:right], sc)
167
+ key = [lt, node[:op], rt]
168
+ r = RULES[key]
169
+ unless r
170
+ # ★ 规则未覆盖 → 尝试 LLM 顾问 (可选, 默认降级)
171
+ # 这里确定性兜底: 标记为 Unknown
172
+ r = 'Unknown'
173
+ end
174
+ node[:inferredType] = r
175
+ r
176
+ end
177
+
178
+ def walk_unary node, sc
179
+ ot = walk(node[:operand], sc)
180
+ r = UNARY[[node[:op], ot]] || 'Unknown'
181
+ node[:inferredType] = r
182
+ r
183
+ end
184
+
185
+ def walk_call node, sc
186
+ fn = sc.lookup_fn(node[:name])
187
+ unless fn
188
+ err node[:line], "函数 '#{node[:name]}' 未声明"
189
+ return 'Unknown'
190
+ end
191
+ # ※ 参数数量检查
192
+ decl_params = fn[:params].keys
193
+ if node[:args].length != decl_params.length
194
+ err node[:line], "函数 '#{node[:name]}' 参数数量不匹配: 期望 #{decl_params.length} 实际 #{node[:args].length}"
195
+ end
196
+ # 参数类型检查
197
+ node[:args].each_with_index do|arg, i|
198
+ at = walk(arg, sc)
199
+ pn = decl_params[i]
200
+ pt = fn[:params][pn]
201
+ if at && pt && at != pt
202
+ err node[:line], "参数类型不匹配: '#{pn}' 期望 #{pt} 实际 #{at}"
203
+ end
204
+ end
205
+ fn[:retType] || 'Unknown'
206
+ end
207
+
208
+ def err line, msg
209
+ @errors << { line:line, msg:msg }
210
+ $stderr.puts "[semantic] line #{line}: #{msg}"
211
+ end
212
+
213
+ end
214
+ end
metadata ADDED
@@ -0,0 +1,57 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: infornoid
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - numeron
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies: []
12
+ description: |
13
+ Infornoid 是一个采用混合架构设计的智能编译器:确定性编译器保证安全性与
14
+ 可预测性,LLM 作为顾问提供优化建议,不直接生成可执行代码。零第三方依赖,
15
+ 纯 Ruby 标准库实现。支持子命令风格 CLI(compile/lex/parse/semantic/
16
+ optimize/codegen/check),可编译为 JavaScript 或 Python 目标代码。
17
+ email:
18
+ - numeron@example.com
19
+ executables:
20
+ - infornoid
21
+ extensions: []
22
+ extra_rdoc_files: []
23
+ files:
24
+ - LICENSE
25
+ - README.md
26
+ - bin/infornoid
27
+ - lib/advisor.rb
28
+ - lib/codegen.rb
29
+ - lib/infornoid.rb
30
+ - lib/lexer.rb
31
+ - lib/optimizer.rb
32
+ - lib/parser.rb
33
+ - lib/semantic.rb
34
+ homepage: https://github.com/numeron/infornoid
35
+ licenses:
36
+ - AGPL-3.0-only
37
+ metadata:
38
+ source_code_uri: https://github.com/numeron/infornoid
39
+ changelog_uri: https://github.com/numeron/infornoid/releases
40
+ rdoc_options: []
41
+ require_paths:
42
+ - lib
43
+ required_ruby_version: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: 2.5.0
48
+ required_rubygems_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: '0'
53
+ requirements: []
54
+ rubygems_version: 4.0.19
55
+ specification_version: 4
56
+ summary: 混合架构智能编译器 — 确定性前端 + LLM 顾问优化 + 确定性代码生成
57
+ test_files: []