mirror of
https://github.com/RubyMetric/chsrc
synced 2026-03-07 06:50:05 +08:00
Move to sub lib
This commit is contained in:
291
tool/rawstr4c/lib/Rawstr4c/Generator.rakumod
Normal file
291
tool/rawstr4c/lib/Rawstr4c/Generator.rakumod
Normal file
@@ -0,0 +1,291 @@
|
||||
# ---------------------------------------------------------------
|
||||
# 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 Rawstr4c::Generator;
|
||||
|
||||
use Rawstr4c::Parser;
|
||||
use Rawstr4c::Config;
|
||||
use Rawstr4c::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 translate 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 $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<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 Bool $!enable-debug = False; # 是否启用调试模式
|
||||
has Rawstr4c::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 debug() {
|
||||
$!enable-debug = True;
|
||||
}
|
||||
|
||||
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-in-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 $varname = $.varname-generator.generate($section);
|
||||
|
||||
if $debug-in-config || $!enable-debug {
|
||||
my $language = $config.language.string-value;
|
||||
my $prefix = $config.prefix.string-value;
|
||||
my $postfix = $config.postfix.string-value;
|
||||
|
||||
say "------ Section: $title ------";
|
||||
say "Output mode = $output-mode";
|
||||
say "Translate mode = $translate-mode";
|
||||
say "Language = $language";
|
||||
say "Prefix = $prefix";
|
||||
say "Postfix = $postfix";
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user