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

299 lines
8.7 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 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 '\\' { 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;
}
}
#|
class ConfigManager {
#| section
method get-inherited-config($section, $key, $default = '') {
my $current = $section;
while $current {
if $current.config && $current.config.exist($key) {
return $current.config.get($key);
}
$current = $current.parent;
}
# 如果都没找到,使用 section 的 config 来获取默认值
return $section.config.get($key, $default);
}
}
my class CVariableNameGenerator {
method generate($section, $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();
}
# 从层次化配置中获取值,使用 ConfigManager 类方法
my $prefix = ConfigManager.get-inherited-config($section, 'prefix', '_rawstr4c').as-string();
my $language = $section-config.get('language').as-string();
my $postfix = self.resolve-postfix($section, $language);
my $keep-prefix = ConfigManager.get-inherited-config($section, 'keep-prefix', 'true').as-bool();
my $keep-postfix = ConfigManager.get-inherited-config($section, 'keep-postfix', 'true').as-bool();
my $name = $section-config.get('name', $title.lc).as-string();
# 替换非法字符
$name = $name.subst(/<-[a..z A..Z 0..9 _]>/, '_', :g);
# 合并连续的下划线
$name = $name.subst(/_+/, '_', :g);
# 移除结尾的下划线
$name = $name.subst(/_+$/, '');
# 组装变量名
my $varname = "";
$varname ~= $prefix if $keep-prefix && $prefix;
$varname ~= "_" if $varname && $name;
$varname ~= $name if $name;
$varname ~= "_" if $varname && $postfix && $keep-postfix;
$varname ~= $postfix if $postfix && $keep-postfix;
return $varname || "unnamed_var";
}
method resolve-postfix($section, $language) {
my $postfix = ConfigManager.get-inherited-config($section, 'postfix', ':use-language');
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 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
};
}
method generate-c-header-file($output-mode = 'global-variable') {
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' {
if $output-mode eq 'global-variable-only-header' {
$header ~= "char {$var<name>}[] = \"{$var<value>}\";\n\n";
} else {
$header ~= "extern char {$var<name>}[];\n";
}
}
when 'macro' {
$header ~= "#define {$var<name>.uc} \"{$var<value>}\"\n\n";
}
}
}
return $header;
}
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($output-mode, $dest-dir) {
my $c-header-file = $dest-dir.IO.child($.c-header-filename).Str;
$c-header-file.IO.spurt(self.generate-c-header-file($output-mode));
say "Generated C header file: $c-header-file";
if $output-mode eq 'global-variable' {
my $has-globals = @.variables.grep({ $_<kind> eq 'global-variable' }).elems > 0;
if $has-globals {
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 $section-config = $section.config;
my $title = $section.title;
my $code = $section.codeblock;
my $debug-parser = ConfigManager.get-inherited-config($section, 'debug', 'false').as-bool();
return unless $code;
my $translate-mode = ConfigManager.get-inherited-config($section, 'translate', 'escape').as-mode();
my $varname = $.varname-generator.generate($section, $section-config, $title);
my $output-mode = ConfigManager.get-inherited-config($section, 'output', 'terminal').as-mode();
if $debug-parser {
say "--- Section: $title ---";
say "Variable name = $varname";
say "Translation mode = $translate-mode";
say "Output mode = $output-mode";
my $language = $section-config.get('language', 'None').as-string();
my $prefix = ConfigManager.get-inherited-config($section, 'prefix', '_rawstr4c').as-string();
say "Language = $language";
say "Prefix = $prefix (inherited from hierarchy)";
say '';
}
my $c-string = $.string-converter.convert-string($code, $translate-mode);
given $output-mode {
when 'terminal' {
say 'char ' ~ $varname ~ '[] = "' ~ $c-string ~ '";';
say "";
}
when 'global-variable' | 'global-variable-only-header' {
$.variable-generator.add-variable($varname, $c-string, 'global-variable');
}
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() 要么把变量输出到终端,要么累计到 @variabels 中
for @all-sections -> $section {
self.generate-for-section($section);
}
my $output-mode = ConfigManager.get-inherited-config($root-section, 'output', 'terminal').as-mode();
# 最后把累计到 @variables 的内容输出到文件
if $output-mode ne 'terminal' {
my $dest-dir = $.parser.input-file.IO.dirname.Str;
$.variable-generator.save-files($output-mode, $dest-dir);
}
}
}