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.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/MIT +21 -0
  3. data/README.md +266 -0
  4. data/Rakefile +8 -0
  5. data/bin/grongigo +157 -0
  6. data/examples/calc.grg +17 -0
  7. data/examples/factorial.grg +21 -0
  8. data/examples/fizzbuzz.grg +86 -0
  9. data/examples/hello.grg +5 -0
  10. data/grongigo.gemspec +30 -0
  11. data/lib/grongigo/ast/assign_expr.rb +18 -0
  12. data/lib/grongigo/ast/binary_expr.rb +19 -0
  13. data/lib/grongigo/ast/block_stmt.rb +17 -0
  14. data/lib/grongigo/ast/break_stmt.rb +14 -0
  15. data/lib/grongigo/ast/call_expr.rb +18 -0
  16. data/lib/grongigo/ast/case_clause.rb +18 -0
  17. data/lib/grongigo/ast/char_literal.rb +17 -0
  18. data/lib/grongigo/ast/continue_stmt.rb +14 -0
  19. data/lib/grongigo/ast/expr_stmt.rb +17 -0
  20. data/lib/grongigo/ast/for_stmt.rb +20 -0
  21. data/lib/grongigo/ast/function_decl.rb +20 -0
  22. data/lib/grongigo/ast/identifier.rb +17 -0
  23. data/lib/grongigo/ast/if_stmt.rb +19 -0
  24. data/lib/grongigo/ast/index_expr.rb +18 -0
  25. data/lib/grongigo/ast/node.rb +15 -0
  26. data/lib/grongigo/ast/number_literal.rb +17 -0
  27. data/lib/grongigo/ast/parameter.rb +18 -0
  28. data/lib/grongigo/ast/program.rb +17 -0
  29. data/lib/grongigo/ast/return_stmt.rb +17 -0
  30. data/lib/grongigo/ast/string_literal.rb +17 -0
  31. data/lib/grongigo/ast/switch_stmt.rb +19 -0
  32. data/lib/grongigo/ast/unary_expr.rb +19 -0
  33. data/lib/grongigo/ast/var_decl.rb +19 -0
  34. data/lib/grongigo/ast/while_stmt.rb +18 -0
  35. data/lib/grongigo/ast.rb +30 -0
  36. data/lib/grongigo/codegen.rb +357 -0
  37. data/lib/grongigo/compiler.rb +156 -0
  38. data/lib/grongigo/constants.rb +129 -0
  39. data/lib/grongigo/jp2grg.rb +117 -0
  40. data/lib/grongigo/lexer.rb +349 -0
  41. data/lib/grongigo/parse_error.rb +13 -0
  42. data/lib/grongigo/parser.rb +572 -0
  43. data/lib/grongigo/token.rb +23 -0
  44. data/lib/grongigo.rb +12 -0
  45. 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