给定两个散列,我的脚本生成两个(格式不太好) C#源文件,其中包含一些类,它们代表几个AST节点、编程语言需要和每个类的访问者模式的实现。虽然我非常关心Raku代码的格式化,但是C#输出的格式化并不特别重要--我让Rider帮我清理它。
除了标准库之外,我的程序还有一个依赖项:Map::Ordered模块的0.0.6版(可使用zef install --/test "Map::Ordered:ver<0.0.6>:auth"安装)。
它还假设您的终端支持ANSI颜色(并且您希望看到它们)。默认情况下,脚本相对于当前工作目录将文件写入src目录,但是可以使用-o/--out-dir选项指定不同的目录。
当您运行它时,它看起来如下:

(我还将输出文件上传到GitHub Gist,你想看他们吗?)
#!/usr/bin/env raku
use Map::Ordered:ver<0.0.6>:auth;
unit sub MAIN(Str :o(:$out-dir) = 'src');
my %exprs is Map::Ordered =
Binary => [left => 'Expr', operator => 'Token', right => 'Expr'],
Grouping => [expression => 'Expr'],
Literal => [value => 'object?'],
Unary => [operator => 'Token', right => 'Expr'];
my %stmts is Map::Ordered =
ExpressionStatement => [expression => 'Expr'],
Print => [expression => 'Expr'];
generate :base-class('Expr'), :classes(%exprs), :$out-dir;
generate :base-class('Stmt'), :classes(%stmts), :$out-dir;
sub generate(:$base-class!, :%classes!, :$out-dir!) {
my $source = '';
$source ~= qq:to/END/;
namespace Lox;
internal abstract class $base-class \{
END
for %classes.kv -> $class-name, @fields {
my @types = @fields.map: *.value;
my @names = @fields.map: *.key;
my @names-and-types = flat @names Z @types;
my $fields = format(-> $type, $name { "internal $type {$name.tc} \{ get; \}" }, @names-and-types);
my $parameters = format(-> $type, $name { "$type {rename-reserved-word($name)}" }, @names-and-types, ', ');
my $initializers = format(-> $name { "{$name.tc} = {rename-reserved-word($name)};" }, @names);
$source ~= qq:to/END/;
internal class $class-name : $base-class \{
$fields
internal {$class-name}($parameters) \{
$initializers
}
internal override T Accept(IVisitor visitor) => visitor.Visit(this);
}
END
}
$source ~= qq:to/END/;
internal interface IVisitor \{
{format({ "public T Visit($^type expr);" }, %classes.keys)}
}
internal abstract T Accept(IVisitor visitor);
}
END
my $path = IO::Spec::Unix.catpath($, $out-dir, "$base-class.cs");
spurt $path, $source;
say "\e[1;32m\c[CHECK MARK]\e[0m Wrote \e[36m{$base-class}\e[0m classes to \e[1;4m$path\e[0m";
}
sub rename-reserved-word($identifier) { $identifier eq 'operator' ?? '@operator' !! $identifier }
multi sub format(&fn where *.signature.params == 1, @xs, $sep = "\n") { @xs.map(&fn).join($sep) }
multi sub format(&fn where *.signature.params == 2, @xs, $sep = "\n") { @xs.map({ fn($^b, $^a) }).join($sep) }我唯一不确定的是这句话:
my $path = IO::Spec::Unix.catpath($, $out-dir, "$base-class.cs");在脚本的中间使用一个特定于平台的函数感觉很奇怪,因为它与平台无关,但是我在标准库中找不到一个在所有平台上做正确事情的函数。在一篇评论中,我想要讨论这个问题,以及一些常见的问题。
发布于 2022-05-03 17:35:46
IO::Pathmy $path = IO::Spec::Unix.catpath($,$out-dir,"$base-class.cs");
您需要使用IO::Path类型。您可以做$out-dir.IO.add: "$base-class.cs",但我建议您在MAINs签名中执行IO部分。而且,您的代码不检查$out-dir是否存在。所以我要做这些改变:
unit sub MAIN(IO::Path(Str) :o(:$out-dir) = 'src');$out-dir.mkdir: 0o755 unless $out-dir.d;
my $path = $out-dir.add: "$base-class.cs";format函数没有必要在您的签名中使用where子句,您可以指定&fn的签名。还可以删除sub和&fn参数:
multi format(&fn:($), @xs, $sep = "\n") { @xs.map(&fn).join($sep) }
multi format(&fn:($, $), @xs, $sep = "\n") { @xs.map(&fn).join($sep) }如果这样做,那么就不需要使用multi:
sub format(&fn:($, $?), @xs, $sep = "\n") { @xs.map(&fn).join($sep) }我会提到一些可能是个人喜好的东西,但如果你还不知道,也许会有价值。
您可以在引文构造中启用/禁用东西,因此如果不使用闭包,就可以禁用闭包,这样就不必转义大括号了:
$source ~= qq:!c:to/END/;
namespace Lox;
internal abstract class $base-class {
END您可以在其中使用单引号和临时使用插值:
$source ~= q:to/END/;
internal interface IVisitor {
\qq「{format({ "public T Visit($^type expr);" }, %classes.keys)}」
}
internal abstract T Accept(IVisitor visitor);
}
END您可以在字符串中调用方法:
qq:!c「internal $type $name.tc() { get; }」职能相同:
"$type &rename-reserved-word($name)"更新:不需要分别声明/初始化$source:
my $source = qq:to/END/;
namespace Lox;
internal abstract class $base-class \{
END使用您的格式函数,您将进行额外的迭代,这可能是一个设计决策,但您可以在一个循环中完成所有操作。
for %classes.kv -> $class-name, @names-and-types {
my (@fields, @parameters, @initializers);
for @names-and-types -> (:key($name), :value($type)) {
@fields.append: qq:!c「internal $type $name.tc() { get; }」;
@parameters.append: "$type &rename-reserved-word($name)";
@initializers.append: "$name.tc() = &rename-reserved-word($name);";
}
$source ~= qq:to/END/;
internal class $class-name : $base-class \{
@fields.join("\n")
internal {$class-name}(@parameters.join(', ')) \{
@initializers.join("\n")
}
internal override T Accept(IVisitor visitor) => visitor.Visit(this);
}
END
}您可以通过使用数组/列表来消除Map::Ordered的依赖性,方法是对内部列表使用数组/列表,并将基类放在一个Map中:
my %base-class is Map =
Expr => [Binary => [left => 'Expr', operator => 'Token', right => 'Expr'],
Grouping => [expression => 'Expr'],
Literal => [value => 'object?'],
Unary => [operator => 'Token', right => 'Expr']],
Stmt => [ExpressionStatement => [expression => 'Expr'],
Print => [expression => 'Expr']];
for %base-class.kv -> $base-class, @classes {
generate :$base-class, :@classes, :$out-dir;
}然后还可以消除format对IVisitor的调用,并删除format函数:
my @visits;
for @classes -> (:key($class-name), :value(@names-and-types)) {
my (@fields, @parameters, @initializers);
for @names-and-types -> (:key($name), :value($type)) {
@fields.append: qq:!c「internal $type $name.tc() { get; }」;
@parameters.append: "$type &rename-reserved-word($name)";
@initializers.append: "$name.tc() = &rename-reserved-word($name);";
}
$source ~= qq:to/END/;
internal class $class-name : $base-class \{
@fields.join("\n")
internal {$class-name}(@parameters.join(', ')) \{
@initializers.join("\n")
}
internal override T Accept(IVisitor visitor) => visitor.Visit(this);
}
END
@visits.append: "public T Visit($class-name expr);"
}
$source ~= qq:to/END/;
internal interface IVisitor \{
@visits.join("\n")
}
internal abstract T Accept(IVisitor visitor);
}
ENDhttps://codereview.stackexchange.com/questions/276202
复制相似问题