mirror of
https://github.com/RubyMetric/chsrc
synced 2025-07-25 17:59:32 +08:00
Make rawstr4c
a separate project
This commit is contained in:
parent
38bafe6ed1
commit
ff82bc632b
12
.gitignore
vendored
12
.gitignore
vendored
@ -82,15 +82,3 @@ chsrc-dbgsym_*.ddeb
|
|||||||
chsrc_*.build
|
chsrc_*.build
|
||||||
chsrc_*.buildinfo
|
chsrc_*.buildinfo
|
||||||
chsrc_*.changes
|
chsrc_*.changes
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
##############################
|
|
||||||
# rawstr4c
|
|
||||||
##############################
|
|
||||||
.precomp
|
|
||||||
|
|
||||||
*.tar.gz
|
|
||||||
|
|
||||||
tool/rawstr4c/test/fixture/rawstr4c.c
|
|
||||||
tool/rawstr4c/test/fixture/rawstr4c.h
|
|
||||||
|
7
tool/rawstr4c/.gitignore
vendored
7
tool/rawstr4c/.gitignore
vendored
@ -1,7 +0,0 @@
|
|||||||
# 2025-07-21
|
|
||||||
#
|
|
||||||
# 这个放在内部的 .gitignore 文件也是能起作用的
|
|
||||||
#
|
|
||||||
# 但是 Raku 很容易不小心生成 .precomp 目录
|
|
||||||
#
|
|
||||||
# 所以我们还是把所有 ignore 配置放在项目根目录下吧
|
|
@ -1,201 +0,0 @@
|
|||||||
The Artistic License 2.0
|
|
||||||
|
|
||||||
Copyright (c) 2000-2006, The Perl Foundation.
|
|
||||||
|
|
||||||
Everyone is permitted to copy and distribute verbatim copies
|
|
||||||
of this license document, but changing it is not allowed.
|
|
||||||
|
|
||||||
Preamble
|
|
||||||
|
|
||||||
This license establishes the terms under which a given free software
|
|
||||||
Package may be copied, modified, distributed, and/or redistributed.
|
|
||||||
The intent is that the Copyright Holder maintains some artistic
|
|
||||||
control over the development of that Package while still keeping the
|
|
||||||
Package available as open source and free software.
|
|
||||||
|
|
||||||
You are always permitted to make arrangements wholly outside of this
|
|
||||||
license directly with the Copyright Holder of a given Package. If the
|
|
||||||
terms of this license do not permit the full use that you propose to
|
|
||||||
make of the Package, you should contact the Copyright Holder and seek
|
|
||||||
a different licensing arrangement.
|
|
||||||
|
|
||||||
Definitions
|
|
||||||
|
|
||||||
"Copyright Holder" means the individual(s) or organization(s)
|
|
||||||
named in the copyright notice for the entire Package.
|
|
||||||
|
|
||||||
"Contributor" means any party that has contributed code or other
|
|
||||||
material to the Package, in accordance with the Copyright Holder's
|
|
||||||
procedures.
|
|
||||||
|
|
||||||
"You" and "your" means any person who would like to copy,
|
|
||||||
distribute, or modify the Package.
|
|
||||||
|
|
||||||
"Package" means the collection of files distributed by the
|
|
||||||
Copyright Holder, and derivatives of that collection and/or of
|
|
||||||
those files. A given Package may consist of either the Standard
|
|
||||||
Version, or a Modified Version.
|
|
||||||
|
|
||||||
"Distribute" means providing a copy of the Package or making it
|
|
||||||
accessible to anyone else, or in the case of a company or
|
|
||||||
organization, to others outside of your company or organization.
|
|
||||||
|
|
||||||
"Distributor Fee" means any fee that you charge for Distributing
|
|
||||||
this Package or providing support for this Package to another
|
|
||||||
party. It does not mean licensing fees.
|
|
||||||
|
|
||||||
"Standard Version" refers to the Package if it has not been
|
|
||||||
modified, or has been modified only in ways explicitly requested
|
|
||||||
by the Copyright Holder.
|
|
||||||
|
|
||||||
"Modified Version" means the Package, if it has been changed, and
|
|
||||||
such changes were not explicitly requested by the Copyright
|
|
||||||
Holder.
|
|
||||||
|
|
||||||
"Original License" means this Artistic License as Distributed with
|
|
||||||
the Standard Version of the Package, in its current version or as
|
|
||||||
it may be modified by The Perl Foundation in the future.
|
|
||||||
|
|
||||||
"Source" form means the source code, documentation source, and
|
|
||||||
configuration files for the Package.
|
|
||||||
|
|
||||||
"Compiled" form means the compiled bytecode, object code, binary,
|
|
||||||
or any other form resulting from mechanical transformation or
|
|
||||||
translation of the Source form.
|
|
||||||
|
|
||||||
|
|
||||||
Permission for Use and Modification Without Distribution
|
|
||||||
|
|
||||||
(1) You are permitted to use the Standard Version and create and use
|
|
||||||
Modified Versions for any purpose without restriction, provided that
|
|
||||||
you do not Distribute the Modified Version.
|
|
||||||
|
|
||||||
|
|
||||||
Permissions for Redistribution of the Standard Version
|
|
||||||
|
|
||||||
(2) You may Distribute verbatim copies of the Source form of the
|
|
||||||
Standard Version of this Package in any medium without restriction,
|
|
||||||
either gratis or for a Distributor Fee, provided that you duplicate
|
|
||||||
all of the original copyright notices and associated disclaimers. At
|
|
||||||
your discretion, such verbatim copies may or may not include a
|
|
||||||
Compiled form of the Package.
|
|
||||||
|
|
||||||
(3) You may apply any bug fixes, portability changes, and other
|
|
||||||
modifications made available from the Copyright Holder. The resulting
|
|
||||||
Package will still be considered the Standard Version, and as such
|
|
||||||
will be subject to the Original License.
|
|
||||||
|
|
||||||
|
|
||||||
Distribution of Modified Versions of the Package as Source
|
|
||||||
|
|
||||||
(4) You may Distribute your Modified Version as Source (either gratis
|
|
||||||
or for a Distributor Fee, and with or without a Compiled form of the
|
|
||||||
Modified Version) provided that you clearly document how it differs
|
|
||||||
from the Standard Version, including, but not limited to, documenting
|
|
||||||
any non-standard features, executables, or modules, and provided that
|
|
||||||
you do at least ONE of the following:
|
|
||||||
|
|
||||||
(a) make the Modified Version available to the Copyright Holder
|
|
||||||
of the Standard Version, under the Original License, so that the
|
|
||||||
Copyright Holder may include your modifications in the Standard
|
|
||||||
Version.
|
|
||||||
|
|
||||||
(b) ensure that installation of your Modified Version does not
|
|
||||||
prevent the user installing or running the Standard Version. In
|
|
||||||
addition, the Modified Version must bear a name that is different
|
|
||||||
from the name of the Standard Version.
|
|
||||||
|
|
||||||
(c) allow anyone who receives a copy of the Modified Version to
|
|
||||||
make the Source form of the Modified Version available to others
|
|
||||||
under
|
|
||||||
|
|
||||||
(i) the Original License or
|
|
||||||
|
|
||||||
(ii) a license that permits the licensee to freely copy,
|
|
||||||
modify and redistribute the Modified Version using the same
|
|
||||||
licensing terms that apply to the copy that the licensee
|
|
||||||
received, and requires that the Source form of the Modified
|
|
||||||
Version, and of any works derived from it, be made freely
|
|
||||||
available in that license fees are prohibited but Distributor
|
|
||||||
Fees are allowed.
|
|
||||||
|
|
||||||
|
|
||||||
Distribution of Compiled Forms of the Standard Version
|
|
||||||
or Modified Versions without the Source
|
|
||||||
|
|
||||||
(5) You may Distribute Compiled forms of the Standard Version without
|
|
||||||
the Source, provided that you include complete instructions on how to
|
|
||||||
get the Source of the Standard Version. Such instructions must be
|
|
||||||
valid at the time of your distribution. If these instructions, at any
|
|
||||||
time while you are carrying out such distribution, become invalid, you
|
|
||||||
must provide new instructions on demand or cease further distribution.
|
|
||||||
If you provide valid instructions or cease distribution within thirty
|
|
||||||
days after you become aware that the instructions are invalid, then
|
|
||||||
you do not forfeit any of your rights under this license.
|
|
||||||
|
|
||||||
(6) You may Distribute a Modified Version in Compiled form without
|
|
||||||
the Source, provided that you comply with Section 4 with respect to
|
|
||||||
the Source of the Modified Version.
|
|
||||||
|
|
||||||
|
|
||||||
Aggregating or Linking the Package
|
|
||||||
|
|
||||||
(7) You may aggregate the Package (either the Standard Version or
|
|
||||||
Modified Version) with other packages and Distribute the resulting
|
|
||||||
aggregation provided that you do not charge a licensing fee for the
|
|
||||||
Package. Distributor Fees are permitted, and licensing fees for other
|
|
||||||
components in the aggregation are permitted. The terms of this license
|
|
||||||
apply to the use and Distribution of the Standard or Modified Versions
|
|
||||||
as included in the aggregation.
|
|
||||||
|
|
||||||
(8) You are permitted to link Modified and Standard Versions with
|
|
||||||
other works, to embed the Package in a larger work of your own, or to
|
|
||||||
build stand-alone binary or bytecode versions of applications that
|
|
||||||
include the Package, and Distribute the result without restriction,
|
|
||||||
provided the result does not expose a direct interface to the Package.
|
|
||||||
|
|
||||||
|
|
||||||
Items That are Not Considered Part of a Modified Version
|
|
||||||
|
|
||||||
(9) Works (including, but not limited to, modules and scripts) that
|
|
||||||
merely extend or make use of the Package, do not, by themselves, cause
|
|
||||||
the Package to be a Modified Version. In addition, such works are not
|
|
||||||
considered parts of the Package itself, and are not subject to the
|
|
||||||
terms of this license.
|
|
||||||
|
|
||||||
|
|
||||||
General Provisions
|
|
||||||
|
|
||||||
(10) Any use, modification, and distribution of the Standard or
|
|
||||||
Modified Versions is governed by this Artistic License. By using,
|
|
||||||
modifying or distributing the Package, you accept this license. Do not
|
|
||||||
use, modify, or distribute the Package, if you do not accept this
|
|
||||||
license.
|
|
||||||
|
|
||||||
(11) If your Modified Version has been derived from a Modified
|
|
||||||
Version made by someone other than you, you are nevertheless required
|
|
||||||
to ensure that your Modified Version complies with the requirements of
|
|
||||||
this license.
|
|
||||||
|
|
||||||
(12) This license does not grant you the right to use any trademark,
|
|
||||||
service mark, tradename, or logo of the Copyright Holder.
|
|
||||||
|
|
||||||
(13) This license includes the non-exclusive, worldwide,
|
|
||||||
free-of-charge patent license to make, have made, use, offer to sell,
|
|
||||||
sell, import and otherwise transfer the Package with respect to any
|
|
||||||
patent claims licensable by the Copyright Holder that are necessarily
|
|
||||||
infringed by the Package. If you institute patent litigation
|
|
||||||
(including a cross-claim or counterclaim) against any party alleging
|
|
||||||
that the Package constitutes direct or contributory patent
|
|
||||||
infringement, then this Artistic License to you shall terminate on the
|
|
||||||
date that such litigation is filed.
|
|
||||||
|
|
||||||
(14) Disclaimer of Warranty:
|
|
||||||
THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS
|
|
||||||
IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED
|
|
||||||
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR
|
|
||||||
NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL
|
|
||||||
LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL
|
|
||||||
BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
|
||||||
DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, EVEN IF
|
|
||||||
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -1,28 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "rawstr4c",
|
|
||||||
"version": "0.2.1",
|
|
||||||
"description": "Raw strings for the C programming language",
|
|
||||||
|
|
||||||
"authors": ["Aoran Zeng"],
|
|
||||||
"auth": "zef:ccmywish",
|
|
||||||
"license": "Artistic-2.0",
|
|
||||||
|
|
||||||
"tags": ["c"],
|
|
||||||
"support": {
|
|
||||||
"homepage": "https://github.com/RubyMetric/chsrc/blob/dev/tool/rawstr4c",
|
|
||||||
"source": "https://github.com/RubyMetric/chsrc.git",
|
|
||||||
"bugtracker": "https://github.com/RubyMetric/chsrc/issues"
|
|
||||||
},
|
|
||||||
|
|
||||||
"raku": "6.*",
|
|
||||||
"resources": [],
|
|
||||||
"depends": [],
|
|
||||||
"build-depends": [],
|
|
||||||
"test-depends": [],
|
|
||||||
"provides": {
|
|
||||||
"Rawstr4c::Parser": "lib/Rawstr4c/Parser.rakumod",
|
|
||||||
"Rawstr4c::Config": "lib/Rawstr4c/Config.rakumod",
|
|
||||||
"Rawstr4c::Generator": "lib/Rawstr4c/Generator.rakumod",
|
|
||||||
"Rawstr4c::Version": "lib/Rawstr4c/Version.rakumod"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,104 +0,0 @@
|
|||||||
<!-- -----------------------------------------------------------
|
|
||||||
! SPDX-License-Identifier: GFDL-1.3-or-later
|
|
||||||
! -------------------------------------------------------------
|
|
||||||
! Doc Type : Markdown
|
|
||||||
! Doc Name : (rawstr4c introduction).md
|
|
||||||
! Doc Authors : Aoran Zeng <ccmywish@qq.com>
|
|
||||||
! Contributors : Nul None <nul@none.org>
|
|
||||||
! |
|
|
||||||
! Created On : <2025-07-12>
|
|
||||||
! Last Modified : <2025-07-21>
|
|
||||||
! ---------------------------------------------------------- -->
|
|
||||||
|
|
||||||
# rawstr4c
|
|
||||||
|
|
||||||
Use this tool when you need to write complex C language strings.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ rawstr4c --help
|
|
||||||
```
|
|
||||||
|
|
||||||
<br>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Convention
|
|
||||||
|
|
||||||
A configuration file should use this order:
|
|
||||||
|
|
||||||
1. section title
|
|
||||||
2. description of the variable
|
|
||||||
3. configuration block (configblock)
|
|
||||||
4. configuration block (configblock) comments
|
|
||||||
5. code block (codeblock) (raw string)
|
|
||||||
6. comments for the content of the code block (codeblock)
|
|
||||||
|
|
||||||
<br>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Configuration Syntax
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
|
|
||||||
- config-item1 = `:mode`
|
|
||||||
|
|
||||||
- config-item2 = `true|false|yes|no`
|
|
||||||
|
|
||||||
- config-item3 = `string value`
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
Configuration items always start with `-`, followed by the configuration item name and an `=`, and the right-hand value must be wrapped with ``` `` ```.
|
|
||||||
|
|
||||||
Note: if the value is not arbitrarily given by the user, it should be set as a mode type, using `:` as a prefix.
|
|
||||||
|
|
||||||
<br>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Configuration Items
|
|
||||||
|
|
||||||
Note: unless otherwise specified, the first item is the default value
|
|
||||||
|
|
||||||
- output =
|
|
||||||
|
|
||||||
- `:terminal` = output to terminal
|
|
||||||
- `:macro` = output as a `.h` file, defined as macro
|
|
||||||
- `:global-variable` = output a `.h` file and corresponding `.c` file, defined as global variable
|
|
||||||
- `:global-variable-only-header` = output only as a `.h` file, defined as global variable
|
|
||||||
|
|
||||||
- ~~output-file~~ = (not yet implemented)
|
|
||||||
|
|
||||||
Custom generated header filename, default value is `rawstr4c.h`
|
|
||||||
|
|
||||||
- translate =
|
|
||||||
|
|
||||||
- `:escape` = escape only
|
|
||||||
- `:oct` = octal
|
|
||||||
- `:hex` = hexadecimal
|
|
||||||
|
|
||||||
- postfix =
|
|
||||||
|
|
||||||
- `:use-language` = use the language of the codeblock
|
|
||||||
- `your string` = use a custom string as suffix
|
|
||||||
|
|
||||||
- name =
|
|
||||||
|
|
||||||
Generated variable name, will include prefix and suffix by default. If this configuration item is not given, the section title will be used
|
|
||||||
|
|
||||||
- name-literally = `false` | `true`
|
|
||||||
|
|
||||||
Ignore other configuration items and directly use `name` as the variable name
|
|
||||||
|
|
||||||
- namespace =
|
|
||||||
|
|
||||||
Will serve as a prefix after `prefix` and before variable name `name`, affecting the next level section
|
|
||||||
|
|
||||||
- keep-prefix = `true` | `false`
|
|
||||||
|
|
||||||
Whether the variable name uses prefix
|
|
||||||
|
|
||||||
- keep-postfix = `true` | `false`
|
|
||||||
|
|
||||||
Whether the variable name uses postfix
|
|
@ -1,104 +0,0 @@
|
|||||||
#!/usr/bin/env raku
|
|
||||||
# ---------------------------------------------------------------
|
|
||||||
# Copyright © 2025-2025 Aoran Zeng
|
|
||||||
# SPDX-License-Identifier: Artistic-2.0
|
|
||||||
# ---------------------------------------------------------------
|
|
||||||
# File Name : rawstr4c.raku
|
|
||||||
# File Authors : Aoran Zeng <ccmywish@qq.com>
|
|
||||||
# Contributors : Nul None <nul@none.org>
|
|
||||||
# Created On : <2025-07-12>
|
|
||||||
# Last Modified : <2025-07-16>
|
|
||||||
#
|
|
||||||
# rawstr4c:
|
|
||||||
#
|
|
||||||
# Raw strings for the C programming language
|
|
||||||
# ---------------------------------------------------------------
|
|
||||||
|
|
||||||
use Rawstr4c::Parser;
|
|
||||||
use Rawstr4c::Generator;
|
|
||||||
use Rawstr4c::Version;
|
|
||||||
|
|
||||||
sub USAGE() {
|
|
||||||
print qq:to/END/;
|
|
||||||
rawstr4c: Raw Strings for C (Artistic-2.0) v{Rawstr4c::Version::VERSION}-{Rawstr4c::Version::RELEASE_DATE}
|
|
||||||
|
|
||||||
Usage: rawstr4c [options] <FILE.md|DIR>
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
FILE.md Process a specific markdown file
|
|
||||||
DIR Process rawstr4c.md file in the given directory
|
|
||||||
|
|
||||||
Options:
|
|
||||||
-d|--debug Show debug information during processing
|
|
||||||
Value can be [generator|parser]. Default to generator.
|
|
||||||
|
|
||||||
-v|--version Show version information
|
|
||||||
|
|
||||||
-h|--help Show this help message
|
|
||||||
|
|
||||||
END
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
sub MAIN(
|
|
||||||
Str $input-path?,
|
|
||||||
# 如果是 Str 类型,则 --debug 缺少命令行参数
|
|
||||||
# 如果是 Any 类型,则可以直接使用 --debug,值为 True
|
|
||||||
Any :$debug,
|
|
||||||
Any :$version,
|
|
||||||
:$d, :$v, :$h
|
|
||||||
)
|
|
||||||
{
|
|
||||||
if ($version || $v) {
|
|
||||||
print Rawstr4c::Version::VERSION_CONTENT_FOR_-version;
|
|
||||||
exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($h) {
|
|
||||||
USAGE;
|
|
||||||
exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$input-path) {
|
|
||||||
USAGE;
|
|
||||||
exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
my $markdown-file;
|
|
||||||
|
|
||||||
if $input-path.IO.d {
|
|
||||||
$markdown-file = $input-path.IO.add("rawstr4c.md");
|
|
||||||
unless $markdown-file.e {
|
|
||||||
# 也可以 warn
|
|
||||||
note "Error: No 'rawstr4c.md' file found in directory '$input-path'";
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
elsif $input-path.IO.f {
|
|
||||||
$markdown-file = $input-path.IO;
|
|
||||||
} else {
|
|
||||||
note "Error: '$input-path' is neither a file nor a directory";
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
my $parser = Rawstr4c::Parser::Parser.new($markdown-file.Str);
|
|
||||||
$parser.parse;
|
|
||||||
|
|
||||||
my $generator = Rawstr4c::Generator::Generator.new($parser);
|
|
||||||
|
|
||||||
if ($debug.defined) {
|
|
||||||
given $debug {
|
|
||||||
when 'parser' {$parser.debug;}
|
|
||||||
default {$generator.debug;}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($d.defined) {
|
|
||||||
given $d {
|
|
||||||
when 'parser' {$parser.debug;}
|
|
||||||
default {$generator.debug;}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$generator.generate;
|
|
||||||
}
|
|
@ -1,116 +0,0 @@
|
|||||||
<!-- -----------------------------------------------------------
|
|
||||||
! SPDX-License-Identifier: GFDL-1.3-or-later
|
|
||||||
! -------------------------------------------------------------
|
|
||||||
! Doc Type : Markdown
|
|
||||||
! Doc Name : 01-Develop.md
|
|
||||||
! Doc Authors : Aoran Zeng <ccmywish@qq.com>
|
|
||||||
! Contributors : Nul None <nul@none.org>
|
|
||||||
! |
|
|
||||||
! Created On : <2025-07-21>
|
|
||||||
! Last Modified : <2025-07-21>
|
|
||||||
! ---------------------------------------------------------- -->
|
|
||||||
|
|
||||||
# Develop `rawstr4c`
|
|
||||||
|
|
||||||
## Dependencies and Dev environment
|
|
||||||
|
|
||||||
Please install these first:
|
|
||||||
|
|
||||||
1. [rakudo]
|
|
||||||
2. [just]
|
|
||||||
|
|
||||||
<br>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Get code
|
|
||||||
|
|
||||||
**Please make sure to use the dev branch for development**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git clone https://gitee.com/RubyMetric/chsrc.git -b dev
|
|
||||||
```
|
|
||||||
|
|
||||||
<br>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Run
|
|
||||||
|
|
||||||
**For convenience, when developing, we only use `just` to invoke it.**
|
|
||||||
|
|
||||||
> [!IMPORTANT]
|
|
||||||
> When developing `rawstr4c` and maintaining `chsrc`,
|
|
||||||
> we must always give a path relative to the root directory of the whole `chsrc` project!
|
|
||||||
> This is because `just` will switch back to the project root directory by itself.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Now we've already cd into the current dir
|
|
||||||
cd src/recipe
|
|
||||||
# Still have to use path relative to root!!!
|
|
||||||
just rawstr4c ./src/recipe/ware
|
|
||||||
```
|
|
||||||
|
|
||||||
```bash
|
|
||||||
just rawstr4c
|
|
||||||
```
|
|
||||||
|
|
||||||
We can install the distribution, by this way, we don't need `just`.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
zef install .
|
|
||||||
|
|
||||||
rawstr4c --help
|
|
||||||
```
|
|
||||||
|
|
||||||
And therefore no such limitations mentioned above!
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd src/recipe
|
|
||||||
# No need to to use path relative to root now!!!
|
|
||||||
rawstr4c .
|
|
||||||
```
|
|
||||||
|
|
||||||
```bash
|
|
||||||
zef uninstall rawstr4c
|
|
||||||
```
|
|
||||||
|
|
||||||
<br>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Debug
|
|
||||||
|
|
||||||
```bash
|
|
||||||
just rawstr4c --debug
|
|
||||||
# Note: there must be an = between option value and option
|
|
||||||
just rawstr4c --debug=parser
|
|
||||||
just rawstr4c --debug=generator
|
|
||||||
```
|
|
||||||
|
|
||||||
<br>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Test
|
|
||||||
|
|
||||||
Run test scripts
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd test
|
|
||||||
|
|
||||||
raku ./xx-file.rakutest
|
|
||||||
```
|
|
||||||
|
|
||||||
Test executable file
|
|
||||||
|
|
||||||
```bash
|
|
||||||
just rawstr4c
|
|
||||||
```
|
|
||||||
|
|
||||||
<br>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[rakudo]: https://rakudo.org/
|
|
||||||
[just]: https://github.com/casey/just
|
|
@ -1,174 +0,0 @@
|
|||||||
# ---------------------------------------------------------------
|
|
||||||
# SPDX-License-Identifier: Artistic-2.0
|
|
||||||
# ---------------------------------------------------------------
|
|
||||||
# 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 Rawstr4c::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 Rawstr4c::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 Rawstr4c::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 Rawstr4c::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 Rawstr4c::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 Rawstr4c::Parser::ConfigItem's-Value.new($current-namespace);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#| RS4C-Bool
|
|
||||||
method debug() {
|
|
||||||
return self.get-inherited-config('debug', 'false');
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,291 +0,0 @@
|
|||||||
# ---------------------------------------------------------------
|
|
||||||
# SPDX-License-Identifier: Artistic-2.0
|
|
||||||
# ---------------------------------------------------------------
|
|
||||||
# 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 = Rawstr4c::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 = Rawstr4c::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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,356 +0,0 @@
|
|||||||
# ---------------------------------------------------------------
|
|
||||||
# SPDX-License-Identifier: Artistic-2.0
|
|
||||||
# ---------------------------------------------------------------
|
|
||||||
# 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));
|
|
||||||
}
|
|
||||||
|
|
||||||
# 获取根section(level 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();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,27 +0,0 @@
|
|||||||
# ---------------------------------------------------------------
|
|
||||||
# SPDX-License-Identifier: Artistic-2.0
|
|
||||||
# ---------------------------------------------------------------
|
|
||||||
# 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";
|
|
||||||
|
|
||||||