Move to sub lib

This commit is contained in:
Aoran Zeng
2025-07-21 01:28:29 +08:00
parent 47129d36f7
commit 3eb948f1cf
5 changed files with 18 additions and 18 deletions

View File

@@ -0,0 +1,174 @@
# ---------------------------------------------------------------
# SPDX-License-Identifier: GPL-3.0-or-later
# ---------------------------------------------------------------
# File Name : Config.rakumod
# File Authors : Aoran Zeng <ccmywish@qq.com>
# Contributors : Nul None <nul@none.org>
# Created On : <2025-07-16>
# Last Modified : <2025-07-16>
#
# Represent a section's working configuration
# ---------------------------------------------------------------
use Rawstr4c::Parser;
unit module Rawstr4c::Config;
#| section ()
class SectionConfig {
has Rawstr4c::Parser::Section $.section;
method new($section) {
self.bless(:$section);
}
#| section
#|
#| @param $default 使!
#|
method get-inherited-config($key, Str $default?) {
my $current = $.section;
while $current {
if $current.configblock.exist($key) {
return $current.configblock.get($key);
}
$current = $current.parent;
}
# 如果都没找到,生成一个新值
# 当 $default 为空时,生成的是 RS4C-Nil
return Parser::ConfigItem's-Value.new($default);
}
#| section
#|
#| @param $default 使!
#|
method get-direct-config($key, Str $default?) {
if $.section.configblock.exist($key) {
return $.section.configblock.get($key);
}
if ! $default.defined {
# say "DEBUG: Key <$key> is undefined";
}
return Parser::ConfigItem's-Value.new($default);
}
# ============================================================
# 返回当前 section 的 各种配置
# 注意,这些函数全部都返回 ConfigValue's-Value 实例
#| RS4C-Mode
method translate-mode() {
return self.get-inherited-config('translate', ':escape');
}
#| RS4C-Mode
method output-mode() {
return self.get-inherited-config('output', ':terminal');
}
#| RS4C-String
method prefix() {
return self.get-inherited-config('prefix', '_rawstr4c');
}
#| RS4C-String
method postfix() {
# RS4C-Mode 或 RS4C-String
my $config-postfix = self.get-inherited-config('postfix', ':use-language');
my $language = self.language.string-value;
my $postfix;
if $config-postfix.is-mode() && $config-postfix.mode-value() eq 'use-language' {
$postfix = 'in_' ~ $language;
} else {
# 如果不是模式,那就是用户给了一个具体的字符串
$postfix = $config-postfix.string-value();
}
return Parser::ConfigItem's-Value.new($postfix);
}
#| RS4C-Bool
method keep-prefix() {
return self.get-inherited-config('keep-prefix', 'true');
}
#| RS4C-Bool
method keep-postfix() {
return self.get-inherited-config('keep-postfix', 'true');
}
#| RS4C-String
method language() {
# RS4C-String 或 RS4C-Nil
my $config-language = self.get-direct-config('language');
my Str $lang;
if $config-language.is-nil {
# codeblock 没有写语言,就给 Unknown
$lang = 'Unknown';
} else {
$lang = $config-language.string-value;
}
return Parser::ConfigItem's-Value.new($lang);
}
#| RS4C-String
method name() {
# RS4C-String 或 RS4C-Nil
my $config-name = self.get-direct-config('name');
my $name;
if $config-name.is-nil {
$name = $.section.title.lc
} else {
$name = $config-name.string-value;
}
return Parser::ConfigItem's-Value.new($name);
}
#| RS4C-Bool
method name-literally() {
return self.get-direct-config('name-literally', 'false');
}
# RS4C-String
method namespace() {
my $config-namespace = self.get-direct-config('namespace', '');
my $current-namespace = $config-namespace.string-value;
# 嵌套增加
my $parent = $.section.parent;
while $parent {
if $parent.configblock.exist('namespace') {
$current-namespace = $parent.configblock.get('namespace').string-value ~ $current-namespace;
} else {
# 空字符串
$current-namespace = '' ~ $current-namespace;
}
$parent = $parent.parent;
}
return Parser::ConfigItem's-Value.new($current-namespace);
}
#| RS4C-Bool
method debug() {
return self.get-inherited-config('debug', 'false');
}
}

View 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([]));
}
#| 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 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);
}
}
}

View File

@@ -0,0 +1,356 @@
# ---------------------------------------------------------------
# SPDX-License-Identifier: GPL-3.0-or-later
# ---------------------------------------------------------------
# File Name : Parser.rakumod
# File Authors : Aoran Zeng <ccmywish@qq.com>
# Contributors : Nul None <nul@none.org>
# Created On : <2025-07-12>
# Last Modified : <2025-07-16>
#
# rawstr4c.md parsing
# ---------------------------------------------------------------
unit module Rawstr4c::Parser;
#| Bool Boolean
my enum ConfigItem's-ValueType < RS4C-Nil RS4C-String RS4C-Mode RS4C-Bool>;
#|
class ConfigItem's-Value {
has ConfigItem's-ValueType $.type;
has Str $.raw-value;
has Any $.parsed-value;
#| $raw-text undefined
method new(Str $input-text) {
my $type;
my $parsed-value;
my $raw-value = $input-text;
# 明确区分空字符串和无值情况
# 这种情况不可能是用户写的(并没有nil这个字面量)
if ! $input-text.defined {
$type = RS4C-Nil;
$parsed-value = Nil;
$raw-value = "<internal-rs4c-nil>"; # 一个完全用不到的值,但是由于 $.raw-value 类型是字符串,所以必须随便给一个值
}
else {
# wrtd: 不要试图在这里利用 given when 统一处理未定义的值,因为会一直报错
given $input-text {
when /^ ':' (.+) $/ {
# 模式值 :mode
$type = RS4C-Mode;
$parsed-value = ~$0;
}
when /^ ('true'|'false'|'yes'|'no') $/ {
# 特殊字面量 - true/false/yes/no 都是 literal
$type = RS4C-Bool;
$parsed-value = ~$0 ~~ /^('true'|'yes')$/ ?? True !! False;
}
# 输入为空时被当做是字符串类型
default {
# 普通字符串
$type = RS4C-String;
$parsed-value = $input-text;
}
}
}
self.bless(:$type, :$raw-value, :$parsed-value);
}
# 获得适合调用者接受的值
method value() {
given $.type {
when RS4C-Nil | RS4C-String | RS4C-Bool | RS4C-Mode { return $.parsed-value; }
default { die "Unknown config value type: {$.type}"; }
}
}
# 这些函数防止开发者写错类型
method nil-value() {
return self.value if $.type == RS4C-Nil;
die "The config value type should be RS4C-Nil, but it is: {$.type}";
}
method string-value() {
return self.value if $.type == RS4C-String;
die "The config value type should be RS4C-String, but it is: {$.type}";
}
method bool-value() {
return self.value if $.type == RS4C-Bool;
die "The config value type should be RS4C-Bool, but it is: {$.type}";
}
method mode-value() {
return self.value if $.type == RS4C-Mode;
die "The config value type should be RS4C-Mode, but it is: {$.type}";
}
# 类型检查方法
method is-nil() { return $.type == RS4C-Nil; }
method is-mode() { return $.type == RS4C-Mode; }
method is-bool() { return $.type == RS4C-Bool; }
method is-string() { return $.type == RS4C-String; }
}
#| config items
#| get ConfigItem's-Value
my class ConfigBlock {
has %!items;
# 如果非要在程序内部中调用,而不是直接从 Markdown 文件中读取出来
# 一定要记得 $raw-value 用的是 rawstr4c 的语法!也就是说,这里一定是一个字符串
method set($k, $raw-value) {
%!items{$k} = ConfigItem's-Value.new($raw-value);
}
method get($k) {
return %!items{$k};
}
method exist($k) {
return %!items{$k}:exists;
}
# 配置项名称
# @danger: 把这个函数命名为 items会让我的机器蓝屏.....
method keys() {
return %!items.keys;
}
}
#| section
class Section {
has Str $.title;
has Int $.level;
has ConfigBlock $.configblock;
has Str $.codeblock is rw;
has Section $.parent is rw;
has Section @.children;
method new($title, $level) {
my $configblock = ConfigBlock.new();
# parent 和 codeblock 刻意不初始化
self.bless(:$title, :$level, :$configblock, :children([]));
}
method add-child($child-section) {
$child-section.parent = self;
@.children.push: $child-section;
}
method has-children() {
return @.children.elems > 0;
}
# 递归获取所有后代section深度优先遍历
method get-all-descendants() {
my @descendants;
for @.children -> $child {
@descendants.push: $child;
@descendants.append: $child.get-all-descendants();
}
return @descendants;
}
# 获取section的路径从根到当前节点
method get-hierarchical-path() {
my @path;
my $current = self;
while $current {
@path.unshift: $current.title;
$current = $current.parent;
}
return @path.join(" > ");
}
}
#|(
section:
- level 0: root section
- level 1: #
- level 2: ##
- ...
)
class Parser {
has Str $.input-file is rw;
#| sections
has Section @!sections;
#| $markdown-file markdown
method new($markdown-file) {
self.bless(:input-file($markdown-file));
}
# 获取根sectionlevel 0
method root-section() {
return @!sections.first({ $_.level == 0 });
}
# 配置项所在行 -> 解析为配置项
method parse-config-item-line($line, $section-config) {
# 语法: - key = `value`
if $line ~~ /^ '-' \s* (<[a..z\-]>+) \s* '=' \s* '`' (.+?) '`' / {
my $key = ~$0;
my $value = ~$1;
$section-config.set($key, $value);
return True;
}
return False;
}
method parse() {
my $content = $.input-file.IO.slurp;
my @lines = $content.lines;
my $current-section;
my $in-codeblock = False;
# 在代码块中的 raw string
my $rawstr = "";
# 无论有没有具体的 root 信息 (比如所处理的文件第一行就是标题)
# 都创建一个 root section (level 0)
$current-section = Section.new("", 0);
@!sections.push: $current-section;
# 开始遍历
my $line-count = 0;
for @lines -> $line {
$line-count++;
# Step1: 处理标题,这里最重要,因为是判断上一个 section 结束的标志
if !$in-codeblock && $line ~~ /^ '#' ('#'*) \s* (.+) / {
my $level = 1 + $0.chars;
my $title = ~$1;
# 保存当前section的codeblock
$current-section.codeblock = $rawstr;
# 准备创建一个新的 section
$rawstr = "";
my $new-section = Section.new($title, $level);
@!sections.push: $new-section;
# 找到合适的父节点
my $parent = self.find-parent-section($level);
if $parent {
$parent.add-child($new-section);
}
$current-section = $new-section;
next;
}
# Step2: 处理配置项 (如果该行不是配置项则下一行)
if self.parse-config-item-line($line, $current-section.configblock) {
next;
}
# Step3: 开始处理 codeblock
if $line ~~ /^ '```' (.*)? / {
if $in-codeblock {
$in-codeblock = False;
} else {
$in-codeblock = True;
my $lang = ~($0 // '');
if $lang && $current-section && !$current-section.configblock.exist('language') {
$current-section.configblock.set('language', $lang);
}
}
next;
}
# 代码块里的内容统统进来
if $in-codeblock {
$rawstr ~= $line ~ "\n";
}
}
# 遍历结束保存最后一个section的codeblock
if $rawstr && $current-section {
$current-section.codeblock = $rawstr;
}
}
method find-parent-section($new-level) {
# 从@!sections尾部向前找找到第一个level小于new-level的section作为父节点
for @!sections.reverse -> $section {
if $section.level < $new-level {
return $section;
}
}
return Nil; # 没有找到父节点,说明是 root section
}
# 调试方法扁平打印所有sections
method debug-print-sections-flatly() {
say "====== Sections ======";
for @!sections.kv -> $i, $section {
my $title = $section.title || "(Root)";
my $has-config = $section.configblock.keys ?? "[Has Config]" !! "[NO Config]";
my $has-code = $section.codeblock ?? "[Has Code]" !! "[NO Code]";
say " [$i] Level {$section.level}: $title - $has-config, $has-code";
}
}
# 调试方法层级打印sections
method debug-print-sections-hierarchyly() {
say "====== Hierarchy ======";
my $indent = 0;
# 嵌套的格式化函数
my sub format-section($section, $level) {
my $prefix = ' ' x $level;
my $title = $section.title // '(Root)';
my $base-info = "{$prefix}- {$title} (level {$section.level})";
my $config-info = "";
if $section.configblock.keys {
my @config-items;
for $section.configblock.keys -> $key {
my $value = $section.configblock.get($key);
@config-items.push: "$key = {$value.raw-value}";
}
$config-info = "\n" ~ "{$prefix} [" ~ @config-items.join(", ") ~ "]";
}
return $base-info ~ $config-info;
}
# 嵌套的递归打印函数
my sub print-section-tree($section, $level) {
say format-section($section, $level);
if $section.has-children {
for $section.children -> $child {
print-section-tree($child, $level + 1);
}
}
}
my $root = self.root-section();
print-section-tree($root, $indent);
}
# 调试方法:完整的调试信息打印
method debug() {
self.debug-print-sections-flatly();
self.debug-print-sections-hierarchyly();
}
}

View File

@@ -0,0 +1,27 @@
# ---------------------------------------------------------------
# SPDX-License-Identifier: GPL-3.0-or-later
# ---------------------------------------------------------------
# File Name : Version.rakumod
# File Authors : Aoran Zeng <ccmywish@qq.com>
# Contributors : Nul None <nul@none.org>
# Created On : <2025-07-14>
# Last Modified : <2025-07-16>
# ---------------------------------------------------------------
unit module Rawstr4c::Version;
constant VERSION = "0.2.1.0";
constant RELEASE_DATE = "2025/07/16";
constant Maintain_URL = "https://github.com/RubyMetric/chsrc/blob/dev/tool/rawstr4c";
constant Maintain_URL2 = "https://gitee.com/RubyMetric/chsrc/blob/dev/tool/rawstr4c";
constant VERSION_CONTENT_FOR_-version = qq:to/EOF/
rawstr4c {VERSION}
Copyright (C) 2025 Aoran Zeng
License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Written by Aoran Zeng.
EOF