#!/usr/bin/env raku # --------------------------------------------------------------- # File Name : Generator.rakumod # File Authors : Aoran Zeng # Contributors : Nul None # Created On : <2025-07-12> # Last Modified : <2025-07-13> # # Generates C code from raw string # --------------------------------------------------------------- unit module Generator; use Parser; my class CStringConverter { method convert-char($char, $mode) { given $mode { when 'oct' { my $bytes = $char.encode('UTF-8'); return $bytes.map({ "\\" ~ sprintf("%03o", $_) }).join(''); } when 'hex' { my $bytes = $char.encode('UTF-8'); return $bytes.map({ "\\x" ~ sprintf("%02x", $_) }).join(''); } when 'escape' { given $char { when '"' { return '\\"'; } when "'" { return "\\'"; } when '\\' { return '\\\\'; } when "\n" { return '\\n'; } when "\t" { return '\\t'; } when "\r" { return '\\r'; } when "\0" { return '\\0'; } default { return $char; } } } default { die "Unknown translation mode: $mode"; } } } method convert-string($content, $mode) { my $result = ""; for $content.comb -> $char { $result ~= self.convert-char($char, $mode); } return $result; } } my class CVariableNameGenerator { method generate($global-config, $section-config, $title) { # 检查 name-literally my $name-literally = $section-config.get('name-literally'); if $name-literally.is-bool() && $name-literally.as-bool() { return $section-config.get('name', $title.lc).as-string(); } my $prefix = $global-config.get('prefix', '_rawstr4c').as-string(); my $language = $section-config.get('language').as-string(); my $postfix = self.resolve-postfix($global-config, $language); my $keep-prefix = $section-config.get('keep-prefix', 'true').as-bool(); my $keep-postfix = $section-config.get('keep-postfix', 'true').as-bool(); my $name = $section-config.get('name', $title.lc).as-string(); $name = $name.subst(/\s+/, '_', :g); # 组装变量名 my $var-name = ""; $var-name ~= $prefix if $keep-prefix && $prefix; $var-name ~= "_" if $var-name && $name; $var-name ~= $name if $name; $var-name ~= "_" if $var-name && $postfix && $keep-postfix; $var-name ~= $postfix if $postfix && $keep-postfix; return $var-name || "unnamed_var"; } method resolve-postfix($global-config, $language) { my $postfix = $global-config.get('postfix'); if $postfix.is-mode() && $postfix.as-mode() eq 'use-language' { return $language ?? 'in_' ~ $language !! ''; } # 如果不是模式,那就是用户给了一个具体的字符串 return $postfix.as-string(); } } #| 生成 .h 文件或/和 .c 文件,或存储到 @variables 中 my class CVariableGenerator { has @.variables; has $.c-header-filename; method new() { my $c-header-filename = "rawstr4c.h"; self.bless(:$c-header-filename, :variables([])); } method add-variable($name, $content, $type) { @.variables.push: { name => $name, content => $content, type => $type }; } method generate-c-header-file($output-mode = 'global-variable') { my $header = qq:to/EOF/; #pragma once /** * Generated by rawstr4c * * Date: {DateTime.now.Str} */ EOF for @.variables -> $var { given $var { when 'global-variable' { if $output-mode eq 'global-variable-only-header' { $header ~= "char {$var}[] = \"{$var}\";\n"; } else { $header ~= "extern char {$var}[];\n"; } } when 'macro' { $header ~= "#define {$var.uc} \"{$var}\"\n"; } } } return $header; } method generate-c-source-file() { my $source = qq:to/EOF/; #include "{$.c-header-filename}" /** * Generated by rawstr4c * * Date: {DateTime.now.Str} */ EOF for @.variables -> $var { if $var eq 'global-variable' { $source ~= "char {$var}[] = \"{$var}\";\n"; } } return $source; } method save-files($output-mode) { $.c-header-filename.IO.spurt(self.generate-c-header-file($output-mode)); say "Generated C header file: $.c-header-filename"; if $output-mode eq 'global-variable' { my $has-globals = @.variables.grep({ $_ eq 'global-variable' }).elems > 0; if $has-globals { my $c-source-filename = $.c-header-filename.subst(/'.h'$/, '.c'); $c-source-filename.IO.spurt(self.generate-c-source-file()); say "Generated C source file: $c-source-filename"; } } } } class Generator { has $.cstring-converter; has $.varname-generator; has $.variable-generator; method new() { self.bless( :cstring-converter(CStringConverter.new), :varname-generator(CVariableNameGenerator.new), :variable-generator(CVariableGenerator.new) ); } method get-config-value($global-config, $section-config, $key, $default = '') { return $section-config.exist($key) ?? $section-config.get($key) !! $global-config.get($key, $default); } method generate-for-section($global-config, $section) { my $section-config = $section; my $title = $section; my $code = $section<raw-string>; my $debug-parser = $global-config.get('debug', False).as-bool(); return unless $code; say "=== Section: $title ==="; my $translate-mode = self.get-config-value( $global-config, $section-config, 'translate', ':escape' ).as-mode(); my $var-name = $.varname-generator.generate($global-config, $section-config, $title); my $output-mode = $global-config.get('output', ':terminal').as-mode(); if $debug-parser { say "Variable name: $var-name"; say "Translation mode: $translate-mode"; say "Output mode: $output-mode"; my $language = $section-config.get('language', 'None').as-string(); say "Language: $language"; say ''; } my $c-string = $.cstring-converter.convert-string($code, $translate-mode); given $output-mode { when 'terminal' { say 'char ' ~ $var-name ~ '[] = "' ~ $c-string ~ '";'; } when 'global-variable' | 'global-variable-only-header' { $.variable-generator.add-variable($var-name, $c-string, 'global-variable'); } when 'macro' { $.variable-generator.add-variable($var-name, $c-string, 'macro'); } default { die "Illegal output mode: $output-mode"; } } say "\n"; } method generate($parser) { my $global-config = $parser.global-config; # 这个 generate-for-section() 要么把变量输出到终端,要么累计到 @variabels 中 for $parser.sections -> $section { self.generate-for-section($global-config, $section); } my $output-mode = $global-config.get('output', ':terminal').as-mode(); # 最后把累计到 @variables 的内容输出到文件 if $output-mode ne 'terminal' { $.variable-generator.save-files($output-mode); } } }