chsrc/tool/rawstr4c/lib/Generator.rakumod
2025-07-14 04:05:09 +08:00

299 lines
8.6 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-14>
#
# Generates C code from raw string
# ---------------------------------------------------------------
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;
}
}
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();
}
# 优先从 section-config 获取配置,如果没有则从 global-config 获取
my $prefix = $section-config.exist('prefix') ??
$section-config.get('prefix').as-string() !!
$global-config.get('prefix', '_rawstr4c').as-string();
my $language = $section-config.get('language').as-string();
my $postfix = self.resolve-postfix($global-config, $section-config, $language);
my $keep-prefix = $section-config.exist('keep-prefix') ??
$section-config.get('keep-prefix').as-bool() !!
$global-config.get('keep-prefix', 'true').as-bool();
my $keep-postfix = $section-config.exist('keep-postfix') ??
$section-config.get('keep-postfix').as-bool() !!
$global-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, $section-config, $language) {
# 优先从 section-config 获取 postfix
my $postfix = $section-config.exist('postfix') ??
$section-config.get('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 v{Version::VERSION}-{Version::RELEASE_DATE}
*
* Date: {DateTime.now.Str}
*/
EOF
for @.variables -> $var {
given $var<type> {
when 'global-variable' {
if $output-mode eq 'global-variable-only-header' {
$header ~= "char {$var<name>}[] = \"{$var<content>}\";\n\n";
} else {
$header ~= "extern char {$var<name>}[];\n";
}
}
when 'macro' {
$header ~= "#define {$var<name>.uc} \"{$var<content>}\"\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<type> eq 'global-variable' {
$source ~= "char {$var<name>}[] = \"{$var<content>}\";\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({ $_<type> 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;
has $.cstring-converter;
has $.varname-generator;
has $.variable-generator;
method new($parser) {
self.bless(
:$parser,
:cstring-converter(CStringConverter.new),
:varname-generator(CVariableNameGenerator.new),
:variable-generator(CVariableGenerator.new)
);
}
method get-config-value($global-config, $section-config, $key, $default = '') {
# 优先级section-config > global-config > default
if $section-config && $section-config.exist($key) {
return $section-config.get($key);
}
elsif $global-config && $global-config.exist($key) {
return $global-config.get($key);
}
else {
return $global-config.get($key, $default);
}
}
method generate-for-section($global-config, $section) {
my $section-config = $section<config>;
my $title = $section<title>;
my $code = $section<raw-string>;
my $debug-parser = $global-config.get('debug', False).as-bool();
return unless $code;
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 "--- Section: $title ---";
say "Variable name = $var-name";
say "Translation mode = $translate-mode";
say "Output mode = $output-mode";
my $language = $section-config.get('language', 'None').as-string();
my $prefix = self.get-config-value($global-config, $section-config, 'prefix', '_rawstr4c').as-string();
say "Language = $language";
say "Prefix = $prefix (from " ~ ($section-config.exist('prefix') ?? 'section' !! 'global') ~ ")";
say '';
}
my $c-string = $.cstring-converter.convert-string($code, $translate-mode);
given $output-mode {
when 'terminal' {
say 'char ' ~ $var-name ~ '[] = "' ~ $c-string ~ '";';
say "";
}
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";
}
}
}
method generate() {
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' {
my $dest-dir = $.parser.input-file.dirname.Str;
$.variable-generator.save-files($output-mode, $dest-dir);
}
}
}