grongigo 1.0.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/MIT +21 -0
- data/README.md +266 -0
- data/Rakefile +8 -0
- data/bin/grongigo +157 -0
- data/examples/calc.grg +17 -0
- data/examples/factorial.grg +21 -0
- data/examples/fizzbuzz.grg +86 -0
- data/examples/hello.grg +5 -0
- data/grongigo.gemspec +30 -0
- data/lib/grongigo/ast/assign_expr.rb +18 -0
- data/lib/grongigo/ast/binary_expr.rb +19 -0
- data/lib/grongigo/ast/block_stmt.rb +17 -0
- data/lib/grongigo/ast/break_stmt.rb +14 -0
- data/lib/grongigo/ast/call_expr.rb +18 -0
- data/lib/grongigo/ast/case_clause.rb +18 -0
- data/lib/grongigo/ast/char_literal.rb +17 -0
- data/lib/grongigo/ast/continue_stmt.rb +14 -0
- data/lib/grongigo/ast/expr_stmt.rb +17 -0
- data/lib/grongigo/ast/for_stmt.rb +20 -0
- data/lib/grongigo/ast/function_decl.rb +20 -0
- data/lib/grongigo/ast/identifier.rb +17 -0
- data/lib/grongigo/ast/if_stmt.rb +19 -0
- data/lib/grongigo/ast/index_expr.rb +18 -0
- data/lib/grongigo/ast/node.rb +15 -0
- data/lib/grongigo/ast/number_literal.rb +17 -0
- data/lib/grongigo/ast/parameter.rb +18 -0
- data/lib/grongigo/ast/program.rb +17 -0
- data/lib/grongigo/ast/return_stmt.rb +17 -0
- data/lib/grongigo/ast/string_literal.rb +17 -0
- data/lib/grongigo/ast/switch_stmt.rb +19 -0
- data/lib/grongigo/ast/unary_expr.rb +19 -0
- data/lib/grongigo/ast/var_decl.rb +19 -0
- data/lib/grongigo/ast/while_stmt.rb +18 -0
- data/lib/grongigo/ast.rb +30 -0
- data/lib/grongigo/codegen.rb +357 -0
- data/lib/grongigo/compiler.rb +156 -0
- data/lib/grongigo/constants.rb +129 -0
- data/lib/grongigo/jp2grg.rb +117 -0
- data/lib/grongigo/lexer.rb +349 -0
- data/lib/grongigo/parse_error.rb +13 -0
- data/lib/grongigo/parser.rb +572 -0
- data/lib/grongigo/token.rb +23 -0
- data/lib/grongigo.rb +12 -0
- metadata +90 -0
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'parser'
|
|
4
|
+
|
|
5
|
+
module Grongigo
|
|
6
|
+
# C code generator
|
|
7
|
+
class CodeGenerator
|
|
8
|
+
def initialize
|
|
9
|
+
@indent_level = 0
|
|
10
|
+
@output = []
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def generate(ast)
|
|
14
|
+
@output = []
|
|
15
|
+
|
|
16
|
+
# Standard headers
|
|
17
|
+
emit '#include <stdio.h>'
|
|
18
|
+
emit '#include <stdlib.h>'
|
|
19
|
+
emit '#include <string.h>'
|
|
20
|
+
emit ''
|
|
21
|
+
|
|
22
|
+
# Generate each declaration
|
|
23
|
+
ast.declarations.each do |decl|
|
|
24
|
+
generate_declaration(decl)
|
|
25
|
+
emit ''
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
@output.join("\n")
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def emit(code)
|
|
34
|
+
@output << (' ' * @indent_level + code)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def indent
|
|
38
|
+
@indent_level += 1
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def dedent
|
|
42
|
+
@indent_level -= 1
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def generate_declaration(node)
|
|
46
|
+
case node
|
|
47
|
+
when AST::FunctionDecl
|
|
48
|
+
generate_function(node)
|
|
49
|
+
when AST::VarDecl
|
|
50
|
+
generate_var_decl(node)
|
|
51
|
+
else
|
|
52
|
+
generate_statement(node)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def generate_function(node)
|
|
57
|
+
params = node.params.map { |p| "#{p.type} #{sanitize_name(p.name)}" }.join(', ')
|
|
58
|
+
params = 'void' if params.empty? && node.name == 'main'
|
|
59
|
+
|
|
60
|
+
emit "#{node.return_type} #{sanitize_name(node.name)}(#{params})"
|
|
61
|
+
generate_block(node.body)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def generate_block(node)
|
|
65
|
+
emit '{'
|
|
66
|
+
indent
|
|
67
|
+
node.statements.each { |stmt| generate_statement(stmt) }
|
|
68
|
+
dedent
|
|
69
|
+
emit '}'
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def generate_statement(node)
|
|
73
|
+
case node
|
|
74
|
+
when AST::BlockStmt
|
|
75
|
+
generate_block(node)
|
|
76
|
+
when AST::VarDecl
|
|
77
|
+
generate_var_decl(node)
|
|
78
|
+
when AST::IfStmt
|
|
79
|
+
generate_if(node)
|
|
80
|
+
when AST::WhileStmt
|
|
81
|
+
generate_while(node)
|
|
82
|
+
when AST::ForStmt
|
|
83
|
+
generate_for(node)
|
|
84
|
+
when AST::SwitchStmt
|
|
85
|
+
generate_switch(node)
|
|
86
|
+
when AST::ReturnStmt
|
|
87
|
+
generate_return(node)
|
|
88
|
+
when AST::BreakStmt
|
|
89
|
+
emit 'break;'
|
|
90
|
+
when AST::ContinueStmt
|
|
91
|
+
emit 'continue;'
|
|
92
|
+
when AST::ExprStmt
|
|
93
|
+
emit "#{generate_expr(node.expression)};"
|
|
94
|
+
else
|
|
95
|
+
raise "Unknown statement type: #{node.class}"
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def generate_var_decl(node)
|
|
100
|
+
name = sanitize_name(node.name)
|
|
101
|
+
type = node.type
|
|
102
|
+
|
|
103
|
+
# Handle array types
|
|
104
|
+
if type.end_with?('[]')
|
|
105
|
+
base_type = type[0..-3]
|
|
106
|
+
if node.initializer
|
|
107
|
+
emit "#{base_type} #{name}[] = #{generate_expr(node.initializer)};"
|
|
108
|
+
else
|
|
109
|
+
emit "#{base_type} #{name}[];"
|
|
110
|
+
end
|
|
111
|
+
elsif node.initializer
|
|
112
|
+
emit "#{type} #{name} = #{generate_expr(node.initializer)};"
|
|
113
|
+
else
|
|
114
|
+
emit "#{type} #{name};"
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def generate_if(node)
|
|
119
|
+
emit "if (#{generate_expr(node.condition)})"
|
|
120
|
+
if node.then_branch.is_a?(AST::BlockStmt)
|
|
121
|
+
generate_block(node.then_branch)
|
|
122
|
+
else
|
|
123
|
+
indent
|
|
124
|
+
generate_statement(node.then_branch)
|
|
125
|
+
dedent
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
return unless node.else_branch
|
|
129
|
+
|
|
130
|
+
if node.else_branch.is_a?(AST::IfStmt)
|
|
131
|
+
@output[-1] = @output[-1] # Keep previous closing brace
|
|
132
|
+
# Put else if on same line
|
|
133
|
+
@output << (' ' * @indent_level + 'else')
|
|
134
|
+
generate_if_as_else(node.else_branch)
|
|
135
|
+
elsif node.else_branch.is_a?(AST::BlockStmt)
|
|
136
|
+
emit 'else'
|
|
137
|
+
generate_block(node.else_branch)
|
|
138
|
+
else
|
|
139
|
+
emit 'else'
|
|
140
|
+
indent
|
|
141
|
+
generate_statement(node.else_branch)
|
|
142
|
+
dedent
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def generate_if_as_else(node)
|
|
147
|
+
@output[-1] += " if (#{generate_expr(node.condition)})"
|
|
148
|
+
if node.then_branch.is_a?(AST::BlockStmt)
|
|
149
|
+
generate_block(node.then_branch)
|
|
150
|
+
else
|
|
151
|
+
indent
|
|
152
|
+
generate_statement(node.then_branch)
|
|
153
|
+
dedent
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
return unless node.else_branch
|
|
157
|
+
|
|
158
|
+
if node.else_branch.is_a?(AST::IfStmt)
|
|
159
|
+
@output << (' ' * @indent_level + 'else')
|
|
160
|
+
generate_if_as_else(node.else_branch)
|
|
161
|
+
elsif node.else_branch.is_a?(AST::BlockStmt)
|
|
162
|
+
emit 'else'
|
|
163
|
+
generate_block(node.else_branch)
|
|
164
|
+
else
|
|
165
|
+
emit 'else'
|
|
166
|
+
indent
|
|
167
|
+
generate_statement(node.else_branch)
|
|
168
|
+
dedent
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def generate_while(node)
|
|
173
|
+
emit "while (#{generate_expr(node.condition)})"
|
|
174
|
+
if node.body.is_a?(AST::BlockStmt)
|
|
175
|
+
generate_block(node.body)
|
|
176
|
+
else
|
|
177
|
+
indent
|
|
178
|
+
generate_statement(node.body)
|
|
179
|
+
dedent
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def generate_for(node)
|
|
184
|
+
init = node.init ? generate_for_init(node.init) : ''
|
|
185
|
+
cond = node.condition ? generate_expr(node.condition) : ''
|
|
186
|
+
update = node.update ? generate_expr(node.update) : ''
|
|
187
|
+
|
|
188
|
+
emit "for (#{init}; #{cond}; #{update})"
|
|
189
|
+
if node.body.is_a?(AST::BlockStmt)
|
|
190
|
+
generate_block(node.body)
|
|
191
|
+
else
|
|
192
|
+
indent
|
|
193
|
+
generate_statement(node.body)
|
|
194
|
+
dedent
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def generate_for_init(node)
|
|
199
|
+
case node
|
|
200
|
+
when AST::VarDecl
|
|
201
|
+
name = sanitize_name(node.name)
|
|
202
|
+
if node.initializer
|
|
203
|
+
"#{node.type} #{name} = #{generate_expr(node.initializer)}"
|
|
204
|
+
else
|
|
205
|
+
"#{node.type} #{name}"
|
|
206
|
+
end
|
|
207
|
+
else
|
|
208
|
+
generate_expr(node)
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def generate_switch(node)
|
|
213
|
+
emit "switch (#{generate_expr(node.expression)})"
|
|
214
|
+
emit '{'
|
|
215
|
+
indent
|
|
216
|
+
|
|
217
|
+
node.cases.each do |c|
|
|
218
|
+
dedent
|
|
219
|
+
emit "case #{generate_expr(c.value)}:"
|
|
220
|
+
indent
|
|
221
|
+
c.statements.each { |stmt| generate_statement(stmt) }
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
if node.default_case
|
|
225
|
+
dedent
|
|
226
|
+
emit 'default:'
|
|
227
|
+
indent
|
|
228
|
+
node.default_case.each { |stmt| generate_statement(stmt) }
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
dedent
|
|
232
|
+
emit '}'
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def generate_return(node)
|
|
236
|
+
if node.value
|
|
237
|
+
emit "return #{generate_expr(node.value)};"
|
|
238
|
+
else
|
|
239
|
+
emit 'return;'
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def generate_expr(node)
|
|
244
|
+
case node
|
|
245
|
+
when AST::BinaryExpr
|
|
246
|
+
"(#{generate_expr(node.left)} #{node.operator} #{generate_expr(node.right)})"
|
|
247
|
+
when AST::UnaryExpr
|
|
248
|
+
if node.prefix
|
|
249
|
+
"#{node.operator}#{generate_expr(node.operand)}"
|
|
250
|
+
else
|
|
251
|
+
"#{generate_expr(node.operand)}#{node.operator}"
|
|
252
|
+
end
|
|
253
|
+
when AST::AssignExpr
|
|
254
|
+
"#{generate_expr(node.target)} = #{generate_expr(node.value)}"
|
|
255
|
+
when AST::CallExpr
|
|
256
|
+
args = node.arguments.map { |a| generate_expr(a) }.join(', ')
|
|
257
|
+
"#{generate_expr(node.callee)}(#{args})"
|
|
258
|
+
when AST::IndexExpr
|
|
259
|
+
"#{generate_expr(node.array)}[#{generate_expr(node.index)}]"
|
|
260
|
+
when AST::Identifier
|
|
261
|
+
sanitize_name(node.name)
|
|
262
|
+
when AST::NumberLiteral
|
|
263
|
+
node.value.to_s
|
|
264
|
+
when AST::StringLiteral
|
|
265
|
+
"\"#{escape_string(node.value)}\""
|
|
266
|
+
when AST::CharLiteral
|
|
267
|
+
"'#{escape_char(node.value)}'"
|
|
268
|
+
else
|
|
269
|
+
raise "Unknown expression type: #{node.class}"
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def sanitize_name(name)
|
|
274
|
+
# Convert Grongigo variable names to valid C names
|
|
275
|
+
return name if name =~ /^[a-zA-Z_][a-zA-Z0-9_]*$/
|
|
276
|
+
|
|
277
|
+
# Special keywords
|
|
278
|
+
return name if %w[main printf scanf NULL].include?(name)
|
|
279
|
+
|
|
280
|
+
# Convert katakana to roman alphabets
|
|
281
|
+
katakana2roman(name)
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
def katakana2roman(text)
|
|
285
|
+
# Simple katakana to roman alphabet mapping
|
|
286
|
+
map = {
|
|
287
|
+
'ア' => 'a', 'イ' => 'i', 'ウ' => 'u', 'エ' => 'e', 'オ' => 'o',
|
|
288
|
+
'カ' => 'ka', 'キ' => 'ki', 'ク' => 'ku', 'ケ' => 'ke', 'コ' => 'ko',
|
|
289
|
+
'サ' => 'sa', 'シ' => 'si', 'ス' => 'su', 'セ' => 'se', 'ソ' => 'so',
|
|
290
|
+
'タ' => 'ta', 'チ' => 'ti', 'ツ' => 'tu', 'テ' => 'te', 'ト' => 'to',
|
|
291
|
+
'ナ' => 'na', 'ニ' => 'ni', 'ヌ' => 'nu', 'ネ' => 'ne', 'ノ' => 'no',
|
|
292
|
+
'ハ' => 'ha', 'ヒ' => 'hi', 'フ' => 'hu', 'ヘ' => 'he', 'ホ' => 'ho',
|
|
293
|
+
'マ' => 'ma', 'ミ' => 'mi', 'ム' => 'mu', 'メ' => 'me', 'モ' => 'mo',
|
|
294
|
+
'ヤ' => 'ya', 'ユ' => 'yu', 'ヨ' => 'yo',
|
|
295
|
+
'ラ' => 'ra', 'リ' => 'ri', 'ル' => 'ru', 'レ' => 're', 'ロ' => 'ro',
|
|
296
|
+
'ワ' => 'wa', 'ヲ' => 'wo', 'ン' => 'n',
|
|
297
|
+
'ガ' => 'ga', 'ギ' => 'gi', 'グ' => 'gu', 'ゲ' => 'ge', 'ゴ' => 'go',
|
|
298
|
+
'ザ' => 'za', 'ジ' => 'zi', 'ズ' => 'zu', 'ゼ' => 'ze', 'ゾ' => 'zo',
|
|
299
|
+
'ダ' => 'da', 'ヂ' => 'di', 'ヅ' => 'du', 'デ' => 'de', 'ド' => 'do',
|
|
300
|
+
'バ' => 'ba', 'ビ' => 'bi', 'ブ' => 'bu', 'ベ' => 'be', 'ボ' => 'bo',
|
|
301
|
+
'パ' => 'pa', 'ピ' => 'pi', 'プ' => 'pu', 'ペ' => 'pe', 'ポ' => 'po',
|
|
302
|
+
'ジャ' => 'ja', 'ジュ' => 'ju', 'ジョ' => 'jo',
|
|
303
|
+
'ー' => '_'
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
result = ''
|
|
307
|
+
i = 0
|
|
308
|
+
while i < text.length
|
|
309
|
+
# Check for 2-character combinations
|
|
310
|
+
if i + 1 < text.length && map.key?(text[i, 2])
|
|
311
|
+
result += map[text[i, 2]]
|
|
312
|
+
i += 2
|
|
313
|
+
elsif map.key?(text[i])
|
|
314
|
+
result += map[text[i]]
|
|
315
|
+
i += 1
|
|
316
|
+
else
|
|
317
|
+
# Convert unmappable characters to underscore
|
|
318
|
+
result += '_'
|
|
319
|
+
i += 1
|
|
320
|
+
end
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
# Ensure result doesn't start with a digit
|
|
324
|
+
result = "_#{result}" if result =~ /^[0-9]/
|
|
325
|
+
result = 'var' if result.empty?
|
|
326
|
+
result
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
def escape_string(str)
|
|
330
|
+
str.gsub('\\', '\\\\')
|
|
331
|
+
.gsub('"', '\\"')
|
|
332
|
+
.gsub("\n", '\\n')
|
|
333
|
+
.gsub("\t", '\\t')
|
|
334
|
+
.gsub("\r", '\\r')
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
def escape_char(str)
|
|
338
|
+
return '\\0' if str.empty?
|
|
339
|
+
|
|
340
|
+
char = str[0]
|
|
341
|
+
case char
|
|
342
|
+
when '\\'
|
|
343
|
+
'\\\\'
|
|
344
|
+
when "'"
|
|
345
|
+
"\\'"
|
|
346
|
+
when "\n"
|
|
347
|
+
'\\n'
|
|
348
|
+
when "\t"
|
|
349
|
+
'\\t'
|
|
350
|
+
when "\r"
|
|
351
|
+
'\\r'
|
|
352
|
+
else
|
|
353
|
+
char
|
|
354
|
+
end
|
|
355
|
+
end
|
|
356
|
+
end
|
|
357
|
+
end
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'lexer'
|
|
4
|
+
require_relative 'parser'
|
|
5
|
+
require_relative 'codegen'
|
|
6
|
+
|
|
7
|
+
module Grongigo
|
|
8
|
+
# Main compiler class
|
|
9
|
+
class Compiler
|
|
10
|
+
attr_reader :errors
|
|
11
|
+
|
|
12
|
+
def initialize(options = {})
|
|
13
|
+
@options = {
|
|
14
|
+
verbose: false,
|
|
15
|
+
output_tokens: false,
|
|
16
|
+
output_ast: false
|
|
17
|
+
}.merge(options)
|
|
18
|
+
@errors = []
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def compile(source)
|
|
22
|
+
@errors = []
|
|
23
|
+
|
|
24
|
+
# Lexical analysis
|
|
25
|
+
log 'Starting lexical analysis...'
|
|
26
|
+
lexer = Lexer.new(source)
|
|
27
|
+
tokens = lexer.tokenize
|
|
28
|
+
|
|
29
|
+
if @options[:output_tokens]
|
|
30
|
+
puts '=== Tokens ==='
|
|
31
|
+
tokens.each { |t| puts t }
|
|
32
|
+
puts ''
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Parsing
|
|
36
|
+
log 'Starting parsing...'
|
|
37
|
+
parser = Parser.new(tokens)
|
|
38
|
+
ast = parser.parse
|
|
39
|
+
|
|
40
|
+
if @options[:output_ast]
|
|
41
|
+
puts '=== AST ==='
|
|
42
|
+
print_ast(ast)
|
|
43
|
+
puts ''
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Code generation
|
|
47
|
+
log 'Generating C code...'
|
|
48
|
+
generator = CodeGenerator.new
|
|
49
|
+
c_code = generator.generate(ast)
|
|
50
|
+
|
|
51
|
+
log 'Compilation successful!'
|
|
52
|
+
c_code
|
|
53
|
+
rescue ParseError => e
|
|
54
|
+
@errors << "Parse error: #{e.message}"
|
|
55
|
+
nil
|
|
56
|
+
rescue StandardError => e
|
|
57
|
+
@errors << "Error: #{e.message}"
|
|
58
|
+
@errors << e.backtrace.first(5).join("\n") if @options[:verbose]
|
|
59
|
+
nil
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def compile_file(input_path, output_path = nil)
|
|
63
|
+
output_path ||= input_path.sub(/\.[^.]+$/, '.c')
|
|
64
|
+
|
|
65
|
+
source = File.read(input_path, encoding: 'UTF-8')
|
|
66
|
+
c_code = compile(source)
|
|
67
|
+
|
|
68
|
+
return false unless c_code
|
|
69
|
+
|
|
70
|
+
File.write(output_path, c_code, encoding: 'UTF-8')
|
|
71
|
+
log "Output written to: #{output_path}"
|
|
72
|
+
true
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
private
|
|
76
|
+
|
|
77
|
+
def log(message)
|
|
78
|
+
puts "[Grongigo] #{message}" if @options[:verbose]
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def print_ast(node, indent = 0)
|
|
82
|
+
prefix = ' ' * indent
|
|
83
|
+
case node
|
|
84
|
+
when AST::Program
|
|
85
|
+
puts "#{prefix}Program"
|
|
86
|
+
node.declarations.each { |d| print_ast(d, indent + 1) }
|
|
87
|
+
when AST::FunctionDecl
|
|
88
|
+
puts "#{prefix}FunctionDecl: #{node.return_type} #{node.name}"
|
|
89
|
+
node.params.each { |p| print_ast(p, indent + 1) }
|
|
90
|
+
print_ast(node.body, indent + 1)
|
|
91
|
+
when AST::Parameter
|
|
92
|
+
puts "#{prefix}Parameter: #{node.type} #{node.name}"
|
|
93
|
+
when AST::VarDecl
|
|
94
|
+
puts "#{prefix}VarDecl: #{node.type} #{node.name}"
|
|
95
|
+
print_ast(node.initializer, indent + 1) if node.initializer
|
|
96
|
+
when AST::BlockStmt
|
|
97
|
+
puts "#{prefix}BlockStmt"
|
|
98
|
+
node.statements.each { |s| print_ast(s, indent + 1) }
|
|
99
|
+
when AST::IfStmt
|
|
100
|
+
puts "#{prefix}IfStmt"
|
|
101
|
+
print_ast(node.condition, indent + 1)
|
|
102
|
+
print_ast(node.then_branch, indent + 1)
|
|
103
|
+
print_ast(node.else_branch, indent + 1) if node.else_branch
|
|
104
|
+
when AST::WhileStmt
|
|
105
|
+
puts "#{prefix}WhileStmt"
|
|
106
|
+
print_ast(node.condition, indent + 1)
|
|
107
|
+
print_ast(node.body, indent + 1)
|
|
108
|
+
when AST::ForStmt
|
|
109
|
+
puts "#{prefix}ForStmt"
|
|
110
|
+
print_ast(node.init, indent + 1) if node.init
|
|
111
|
+
print_ast(node.condition, indent + 1) if node.condition
|
|
112
|
+
print_ast(node.update, indent + 1) if node.update
|
|
113
|
+
print_ast(node.body, indent + 1)
|
|
114
|
+
when AST::ReturnStmt
|
|
115
|
+
puts "#{prefix}ReturnStmt"
|
|
116
|
+
print_ast(node.value, indent + 1) if node.value
|
|
117
|
+
when AST::BreakStmt
|
|
118
|
+
puts "#{prefix}BreakStmt"
|
|
119
|
+
when AST::ContinueStmt
|
|
120
|
+
puts "#{prefix}ContinueStmt"
|
|
121
|
+
when AST::ExprStmt
|
|
122
|
+
puts "#{prefix}ExprStmt"
|
|
123
|
+
print_ast(node.expression, indent + 1)
|
|
124
|
+
when AST::BinaryExpr
|
|
125
|
+
puts "#{prefix}BinaryExpr: #{node.operator}"
|
|
126
|
+
print_ast(node.left, indent + 1)
|
|
127
|
+
print_ast(node.right, indent + 1)
|
|
128
|
+
when AST::UnaryExpr
|
|
129
|
+
puts "#{prefix}UnaryExpr: #{node.operator} (prefix=#{node.prefix})"
|
|
130
|
+
print_ast(node.operand, indent + 1)
|
|
131
|
+
when AST::AssignExpr
|
|
132
|
+
puts "#{prefix}AssignExpr"
|
|
133
|
+
print_ast(node.target, indent + 1)
|
|
134
|
+
print_ast(node.value, indent + 1)
|
|
135
|
+
when AST::CallExpr
|
|
136
|
+
puts "#{prefix}CallExpr"
|
|
137
|
+
print_ast(node.callee, indent + 1)
|
|
138
|
+
node.arguments.each { |a| print_ast(a, indent + 1) }
|
|
139
|
+
when AST::IndexExpr
|
|
140
|
+
puts "#{prefix}IndexExpr"
|
|
141
|
+
print_ast(node.array, indent + 1)
|
|
142
|
+
print_ast(node.index, indent + 1)
|
|
143
|
+
when AST::Identifier
|
|
144
|
+
puts "#{prefix}Identifier: #{node.name}"
|
|
145
|
+
when AST::NumberLiteral
|
|
146
|
+
puts "#{prefix}NumberLiteral: #{node.value}"
|
|
147
|
+
when AST::StringLiteral
|
|
148
|
+
puts "#{prefix}StringLiteral: #{node.value.inspect}"
|
|
149
|
+
when AST::CharLiteral
|
|
150
|
+
puts "#{prefix}CharLiteral: #{node.value.inspect}"
|
|
151
|
+
else
|
|
152
|
+
puts "#{prefix}Unknown: #{node.class}"
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grongigo
|
|
4
|
+
# Grongigo numerals (base-9, English-based)
|
|
5
|
+
DIGITS = {
|
|
6
|
+
'ゼゼソ' => 0, # zero
|
|
7
|
+
'パパン' => 1, # one (wan)
|
|
8
|
+
'ドググ' => 2, # two
|
|
9
|
+
'グシギ' => 3, # three
|
|
10
|
+
'ズゴゴ' => 4, # four
|
|
11
|
+
'ズガギ' => 5, # five
|
|
12
|
+
'ギブグ' => 6, # six
|
|
13
|
+
'ゲズン' => 7, # seven
|
|
14
|
+
'ゲギド' => 8, # eight
|
|
15
|
+
'バギン' => 9 # nine (10 in base-9)
|
|
16
|
+
}.freeze
|
|
17
|
+
|
|
18
|
+
# Numeric operators
|
|
19
|
+
NUMBER_OPS = {
|
|
20
|
+
'ド' => :add, # addition
|
|
21
|
+
'グ' => :multiply # multiplication
|
|
22
|
+
}.freeze
|
|
23
|
+
|
|
24
|
+
# Keywords (types)
|
|
25
|
+
TYPE_KEYWORDS = {
|
|
26
|
+
'ゲギグウ' => 'int', # integer (seisuu)
|
|
27
|
+
'ロジ' => 'char', # character (moji)
|
|
28
|
+
'ズゾウ' => 'float', # floating point (fudou)
|
|
29
|
+
'ザダダス' => 'double', # double (fukufudou -> zadadas)
|
|
30
|
+
'バサ' => 'void', # void (kara)
|
|
31
|
+
'バゴ' => 'long', # long (naga)
|
|
32
|
+
'ジジバギ' => 'short', # short (mijikai)
|
|
33
|
+
'ブゾウ' => 'unsigned' # unsigned (mufugou)
|
|
34
|
+
}.freeze
|
|
35
|
+
|
|
36
|
+
# Keywords (control structures)
|
|
37
|
+
CONTROL_KEYWORDS = {
|
|
38
|
+
'ジョウベン' => 'if', # if (jouken)
|
|
39
|
+
'ゾバ' => 'else', # else (hoka)
|
|
40
|
+
'ガギザ' => 'while', # while (aida)
|
|
41
|
+
'ブシバゲギ' => 'for', # for (kurikaeshi)
|
|
42
|
+
'ロゾス' => 'return', # return (modosu)
|
|
43
|
+
'ブゲス' => 'break', # break (nukeru)
|
|
44
|
+
'ヅヅゲス' => 'continue', # continue (tsuzukeru)
|
|
45
|
+
'ゲンダブ' => 'switch', # switch (sentaku)
|
|
46
|
+
'ダガギ' => 'case', # case (baai)
|
|
47
|
+
'ビデギ' => 'default', # default (kitei)
|
|
48
|
+
'ジョウジ' => 'printf', # printf (hyouji)
|
|
49
|
+
'ジュウソブ' => 'scanf' # scanf (juuryoku -> juusobu)
|
|
50
|
+
}.freeze
|
|
51
|
+
|
|
52
|
+
# Keywords (other)
|
|
53
|
+
OTHER_KEYWORDS = {
|
|
54
|
+
'ボウゾウ' => 'struct', # struct (kouzou)
|
|
55
|
+
'バダデギギ' => 'typedef', # typedef (katatеigi)
|
|
56
|
+
'ゴゴビガ' => 'sizeof', # sizeof (ookisa)
|
|
57
|
+
'ル' => 'NULL', # NULL (mu)
|
|
58
|
+
'ギン' => '1', # true (shin)
|
|
59
|
+
'ギ' => '0', # false (gi) - need to distinguish from variable names
|
|
60
|
+
'ゴロ' => 'main', # main (omo)
|
|
61
|
+
'パザ' => '', # function definition marker (waza)
|
|
62
|
+
'ザジレ' => '{', # block start (hajime)
|
|
63
|
+
'ゴパシ' => '}' # block end (owari)
|
|
64
|
+
}.freeze
|
|
65
|
+
|
|
66
|
+
# Operators
|
|
67
|
+
OPERATORS = {
|
|
68
|
+
'ダグ' => '+', # add (tasu)
|
|
69
|
+
'ジブ' => '-', # subtract (hiku)
|
|
70
|
+
'バゲス' => '*', # multiply (kakeru)
|
|
71
|
+
'パス' => '/', # divide (waru)
|
|
72
|
+
'ガラシ' => '%', # modulo (amari)
|
|
73
|
+
'ギセス' => '=', # assign (ireru)
|
|
74
|
+
'ジドギギ' => '==', # equal (hitoshii)
|
|
75
|
+
'ジドギグバギ' => '!=', # not equal
|
|
76
|
+
'ギョウバシ' => '<', # less than (shounari)
|
|
77
|
+
'ザギバシ' => '>', # greater than (dainari)
|
|
78
|
+
'ギバ' => '<=', # less or equal (ika)
|
|
79
|
+
'ギジョウ' => '>=', # greater or equal (ijou)
|
|
80
|
+
'バヅ' => '&&', # and (katsu)
|
|
81
|
+
'ラダパ' => '||', # or (mataha)
|
|
82
|
+
'ジデギ' => '!', # not (hitei)
|
|
83
|
+
'ガンド' => '&', # bitwise and (ando)
|
|
84
|
+
'ゴゴ' => '|', # bitwise or (oa -> goga)
|
|
85
|
+
'ダグダグ' => '++', # increment (tasu tasu)
|
|
86
|
+
'ジブジブ' => '--' # decrement (hiku hiku)
|
|
87
|
+
}.freeze
|
|
88
|
+
|
|
89
|
+
# Merge all keywords
|
|
90
|
+
ALL_KEYWORDS = TYPE_KEYWORDS
|
|
91
|
+
.merge(CONTROL_KEYWORDS)
|
|
92
|
+
.merge(OTHER_KEYWORDS)
|
|
93
|
+
.freeze
|
|
94
|
+
|
|
95
|
+
# Proper nouns not converted in Grongigo
|
|
96
|
+
PROPER_NOUNS = %w[
|
|
97
|
+
クウガ
|
|
98
|
+
リント
|
|
99
|
+
ゲゲル
|
|
100
|
+
グロンギ
|
|
101
|
+
グセパ
|
|
102
|
+
バグンダダ
|
|
103
|
+
ゲリザギバスゲゲル
|
|
104
|
+
ザギバスゲゲル
|
|
105
|
+
].freeze
|
|
106
|
+
|
|
107
|
+
# Japanese to Grongigo conversion table (for reference)
|
|
108
|
+
JAPANESE_TO_GRONGIGO = {
|
|
109
|
+
# Basic rows
|
|
110
|
+
'あ' => 'ガ', 'い' => 'ギ', 'う' => 'グ', 'え' => 'ゲ', 'お' => 'ゴ',
|
|
111
|
+
'か' => 'バ', 'き' => 'ビ', 'く' => 'ブ', 'け' => 'ベ', 'こ' => 'ボ',
|
|
112
|
+
'さ' => 'ガ', 'し' => 'ギ', 'す' => 'グ', 'せ' => 'ゲ', 'そ' => 'ゴ',
|
|
113
|
+
'た' => 'ダ', 'ち' => 'ヂ', 'つ' => 'ヅ', 'て' => 'デ', 'と' => 'ド',
|
|
114
|
+
'な' => 'バ', 'に' => 'ビ', 'ぬ' => 'ブ', 'ね' => 'ベ', 'の' => 'ボ',
|
|
115
|
+
'は' => 'ザ', 'ひ' => 'ジ', 'ふ' => 'ズ', 'へ' => 'ゼ', 'ほ' => 'ゾ',
|
|
116
|
+
'ま' => 'ラ', 'み' => 'リ', 'む' => 'ル', 'め' => 'レ', 'も' => 'ロ',
|
|
117
|
+
'や' => 'ジャ', 'ゆ' => 'ジュ', 'よ' => 'ジョ',
|
|
118
|
+
'ら' => 'サ', 'り' => 'シ', 'る' => 'ス', 'れ' => 'セ', 'ろ' => 'ソ',
|
|
119
|
+
'わ' => 'パ',
|
|
120
|
+
# Dakuon (voiced)
|
|
121
|
+
'が' => 'ガ', 'ぎ' => 'ギ', 'ぐ' => 'グ', 'げ' => 'ゲ', 'ご' => 'ゴ',
|
|
122
|
+
'ざ' => 'ザ', 'じ' => 'ジ', 'ず' => 'ズ', 'ぜ' => 'ゼ', 'ぞ' => 'ゾ',
|
|
123
|
+
'だ' => 'ザ', 'ぢ' => 'ジ', 'づ' => 'ズ', 'で' => 'ゼ', 'ど' => 'ゾ',
|
|
124
|
+
'ば' => 'ダ', 'び' => 'ヂ', 'ぶ' => 'ヅ', 'べ' => 'デ', 'ぼ' => 'ド',
|
|
125
|
+
'ぱ' => 'マ', 'ぴ' => 'ミ', 'ぷ' => 'ム', 'ぺ' => 'メ', 'ぽ' => 'モ'
|
|
126
|
+
# Special particles (but not supported)
|
|
127
|
+
# 'が' => 'グ', 'の' => 'ン', 'は' => 'パ', 'を' => 'ゾ' (when used as particles)
|
|
128
|
+
}.freeze
|
|
129
|
+
end
|