chsrc/tool/rawstr4c/lib/Generator.rakumod
2025-07-16 20:51:08 +08:00

296 lines
7.9 KiB
Raku
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# ---------------------------------------------------------------
# SPDX-License-Identifier: GPL-3.0-or-later
# ---------------------------------------------------------------
# File Name : Generator.rakumod
# File Authors : Aoran Zeng <ccmywish@qq.com>
# Contributors : Nul None <nul@none.org>
# 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([]));
}
#| CC,
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<kind> {
when 'global-variable-only-header' {
$header ~= "char {$var<name>}[] = \"{$var<value>}\";\n\n";
}
when 'global-variable' {
$header ~= "extern char {$var<name>}[];\n";
}
when 'macro' {
$header ~= "#define {$var<name>.uc} \"{$var<value>}\"\n\n";
}
default {
die "Unknown variable kind: {$var<kind>}";
}
}
}
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<kind> eq 'global-variable' {
$source ~= "char {$var<name>}[] = \"{$var<value>}\";\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({ $_<kind> 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);
}
}
}