# --------------------------------------------------------------- # SPDX-License-Identifier: GPL-3.0-or-later # --------------------------------------------------------------- # File Name : Generator.rakumod # File Authors : Aoran Zeng # Contributors : Nul None # Created On : <2025-07-12> # Last Modified : <2025-07-16> # # Generates C code from rawstr4c configuration # --------------------------------------------------------------- unit module Generator; use Parser; use Config; use Version; 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 "\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($section) { my $config = Config::SectionConfig.new($section); my $prefix = $config.prefix.string-value; my $language = $config.language.string-value; my $postfix; my $config-postfix = $config.postfix; if $config-postfix.is-mode() && $config-postfix.mode-value() eq 'use-language' { $postfix = $language ?? 'in_' ~ $language !! ''; } else { # 如果不是模式,那就是用户给了一个具体的字符串 $postfix = $config-postfix.string-value(); } my $keep-prefix = $config.keep-prefix.bool-value; my $keep-postfix = $config.keep-postfix.bool-value; my $name = $config.name.string-value; my $namespace = $config.namespace.string-value; my $name-literally = $config.name-literally.bool-value; # 替换非法字符 $name = $name.subst(/<-[a..z A..Z 0..9 _]>/, '_', :g); # 合并连续的下划线 $name = $name.subst(/_+/, '_', :g); # 移除结尾的下划线 $name = $name.subst(/_+$/, ''); my $varname = ""; if $name-literally { # 如果是字面量,直接使用原名 $varname = $name; } else { # 否则,按照规则组装变量名 $varname ~= $prefix if $keep-prefix && $prefix; $varname ~= "_" if $varname && $namespace; $varname ~= $namespace if $namespace; $varname ~= "_" if $varname && $name; $varname ~= $name if $name; $varname ~= "_" if $varname && $postfix && $keep-postfix; $varname ~= $postfix if $postfix && $keep-postfix; } return $varname || "unnamed_var"; } } #| 生成 .h 文件或/和 .c 文件,或存储到 @variables 中 my class CVariableGenerator { has Hash @.variables; has Str $.c-header-filename; method new() { my $c-header-filename = "rawstr4c.h"; self.bless(:$c-header-filename, :variables([])); } #| C变量名,C变量值, 生成类型 method add-variable($name, $value, $kind) { @.variables.push: { name => $name, value => $value, kind => $kind }; } #| 生成 C 头文件的内容 method generate-c-header-file() { my $header = qq:to/EOF/; #pragma once /** * Generated by rawstr4c v{Version::VERSION}-{Version::RELEASE_DATE} * * Date: {DateTime.now.Str} */ EOF for @.variables -> $var { given $var { when 'global-variable-only-header' { $header ~= "char {$var}[] = \"{$var}\";\n\n"; } when 'global-variable' { $header ~= "extern char {$var}[];\n"; } when 'macro' { $header ~= "#define {$var.uc} \"{$var}\"\n\n"; } default { die "Unknown variable kind: {$var}"; } } } return $header; } #| 生成 C 源文件的内容 method generate-c-source-file() { my $source = qq:to/EOF/; /** * Generated by rawstr4c v{Version::VERSION}-{Version::RELEASE_DATE} * * Date: {DateTime.now.Str} */ #include "{$.c-header-filename}" EOF for @.variables -> $var { if $var eq 'global-variable' { $source ~= "char {$var}[] = \"{$var}\";\n"; } } return $source; } method save-files($dest-dir) { my $c-header-file = $dest-dir.IO.child($.c-header-filename).Str; $c-header-file.IO.spurt(self.generate-c-header-file()); say "Generated C header file: $c-header-file"; # 检查是否有 "头、源并存的变量",如果有就使用并存的头文件和源文件模式 my $need-gen-c-source-file = @.variables.grep({ $_ eq 'global-variable' }).elems > 0; if $need-gen-c-source-file { my $c-source-filename = $.c-header-filename.subst(/'.h'$/, '.c'); my $c-source-file = $dest-dir.IO.child($c-source-filename).Str; $c-source-file.IO.spurt(self.generate-c-source-file()); say "Generated C source file: $c-source-file"; } } } class Generator { has Parser::Parser $.parser; has CStringConverter $.string-converter; has CVariableNameGenerator $.varname-generator; has CVariableGenerator $.variable-generator; method new($parser) { self.bless( :$parser, :string-converter(CStringConverter.new), :varname-generator(CVariableNameGenerator.new), :variable-generator(CVariableGenerator.new) ); } method generate-for-section($section) { my $configblock = $section.configblock; my $title = $section.title; my $rawstr = $section.codeblock; my $config = Config::SectionConfig.new($section); my $debug-config = $config.debug.bool-value; return unless $rawstr; my $translate-mode = $config.translate-mode.mode-value; my $output-mode = $config.output-mode.mode-value; my $language = $config.language.string-value; my $prefix = $config.prefix.string-value; my $varname = $.varname-generator.generate($section); if $debug-config { say "--- Section: $title ---"; say "Output mode = $output-mode"; say "Translation mode = $translate-mode"; say "Language = $language"; say "Prefix = $prefix"; say "Variable name = $varname"; say ''; } my $c-string = $.string-converter.convert-string($rawstr, $translate-mode); given $output-mode { when 'terminal' { say 'char ' ~ $varname ~ '[] = "' ~ $c-string ~ '";'; say ""; } when 'global-variable' { $.variable-generator.add-variable($varname, $c-string, 'global-variable'); } when 'global-variable-only-header' { $.variable-generator.add-variable($varname, $c-string, 'global-variable-only-header'); } when 'macro' { $.variable-generator.add-variable($varname, $c-string, 'macro'); } default { die "Illegal output mode: $output-mode"; } } } method generate() { my $root-section = $.parser.root-section; # 获取所有需要处理的 sections:包括 root section 和所有子 sections my @all-sections = ($root-section, |$root-section.get-all-descendants()); # 这个 generate-for-section() 要么把变量输出到终端,要么累计到 @variables 中 for @all-sections -> $section { self.generate-for-section($section); } # 如果有任何变量被添加 (没有被输出到终端),就保存文件 if $.variable-generator.variables.elems > 0 { my $dest-dir = $.parser.input-file.IO.dirname.Str; $.variable-generator.save-files($dest-dir); } } }