dmd库示例用法
初化项目
mkdir myproject
cd myproject
dub init
dub add dmd
dub run
第1示例
// lexer
unittest
{
import dmd.lexer;
import dmd.tokens;
import dmd.globals;
import dmd.errors;
immutable expected = [
TOK.void_,
TOK.identifier,
TOK.leftParentheses,
TOK.rightParentheses,
TOK.leftCurly,
TOK.rightCurly
];
immutable sourceCode = "void test() {} // foobar";
scope diagnosticReporter = new StderrDiagnosticReporter(global.params.useDeprecated);
scope lexer = new Lexer("test", sourceCode.ptr, 0, sourceCode.length, 0, 0, diagnosticReporter);
lexer.nextToken();
TOK[] result;
do
{
result ~= lexer.token.value;
} while (lexer.nextToken() != TOK.endOfFile);
assert(result == expected);
}
// parser
unittest
{
import dmd.astbase;
import dmd.parse;
import dmd.globals;
import dmd.errors;
scope diagnosticReporter = new StderrDiagnosticReporter(global.params.useDeprecated);
scope parser = new Parser!ASTBase(null, null, false, diagnosticReporter);
assert(parser !is null);
}
开始代码
import std.stdio;
import std.algorithm;
import std.file : readText;
import dmd.frontend;
import dmd.globals;
import dmd.dmodule;
import dmd.dsymbol;
import dmd.identifier;
import dmd.declaration;
import dmd.dstruct;
import dmd.aggregate;
import dmd.attrib; // CPP名间声明
import dmd.dversion;
//应用入口
void main(string[] args)
{
//初化编译器内部,这里加入版本标识
initDMD();
//关闭编译器实例,之后可再次调用`initDMD()`来取新的
//使用`域(exit)`会在离开域(`main`函数时)时自动执行
scope(exit) einitializeDMD();
//跑`addImport(path)`来注册默认搜索路径,可用该函数来添加自己的路径
findImportPaths.each!addImport();
//现在解析代码,然后按需可跑`语义分析`或修改`AST`或添加/删除声明
const sourcePath = args[1];
const sourceText = readText(sourcePath);
auto t = parseModule(sourcePath, sourceText);
//(可选)检查是否有警告/错误,如你是否有版本(无)代码
//添加"none"作为版本产生错误
assert(!t.diagnostics.hasErrors);
assert(!t.diagnostics.hasWarnings);
// 为了方便,定义简写
Module m = t.module_;
//执行语义趟,如果编写了某种复杂代码分析器,则想在语义分析之后跑
//m.fullSemantic();
// >>> RECIPES AND TIPS CODE GOES HERE <<<
}
关于语义趟
跑语义趟扩展了一些语言构造,并注入了模板实例化,如,替换UFCS
调用为普通调用:42.writeln
变成了writeln(42)
.此外,它还插入隐式导入
,如'导入 对象'
记住,你可能不想用基于DMD
的工具来弄乱
用户代码,所以格式化代码
/风格
工具都可能在没有语义趟
就跑.
在此代码
上跑语义趟,然后prettyPrint
它,产生超过1k
行的代码输出.
import std.stdio;
void main()
{
string s = "Hello World";
writeln(s);
}
语法树到串
最重要特性是按编译表示文件
格式化代码,帮助调试,观察修改
后的结果代码
,或写回磁盘.
注意事项:
1,不保留
格式
2,语义趟
前后输出不同
// 再次按文本打印AST
string source = prettyPrint(m);
writeln(source);
按名(标识)搜索符号
最重要操作
之一是按名查找
符号,Module
类(及大多数Dsymbol
子类)有接受位置
和标识
及可选的操作标志
的search()
方法.
// 试按名称查找标识
auto id = Identifier.idPool("MyClass");
Dsymbol myClass = m.search(Loc.initial, id);
转换基类为子类
搜索
示例中,已看到搜索
方法返回公共基类
,本例为Dsymbol
.普通
转换不管用
,因为设计决策
注重性能,而且它是从C++
代码库移植
的.相反,每个基类(Dsymbol
,Statement
,Expression
)提供了此类转换
的方便方法.
// 继续搜索示例,检查myClass是否确实是类声明类型
assert(myClass.isClassDeclaration);
// 确实转换了类型,而不仅仅是`yes`或`no`
if (ClassDeclaration decl = myClass.isClassDeclaration)
{
writefln("class '%s', derived from '%s'", decl, decl.baseClass);
}
// 打印它
writeln(myClass);
搜索导入
上个方法有忽略导入
默认标志,因此即使模块确实import std.stdio
,它也会返回null
,用如'不忽略'
标志,告诉搜索()
查找导入的模块
.
//注意,使用`std.stdio.writeln`限定名,无法工作
auto writelnId = Identifier.idPool("writeln");
// 用IgnoreNone标志,来自下而上查找声明
Dsymbol writelnSym = m.search(Loc.initial, writelnId, IgnoreNone);
assert(writelnSym.isTemplateDeclaration);
迭代模块成员
有时只想遍历
循环中的所有成员
,为此可用'模块'
类的'成员'
属性.
// 打印所有成员
foreach (s; *m.members)
{
write(s.toString());
}
创建新声明
有时候,想要添加生成的声明
,也许实现了代码生成器
来连接一部分
,并且想要自动化
该过程,可简单地创建
普通类实例,并把它添加到模块
(或另一个域声明
)成员中来实现.
假设有个叫测试.d
的模块
,要在里面添加名为MyStruct
的新构:
import dmd.arraytypes; // D符号
// 定义诊断消息中使用的位置
//注意:`DMD`内部,为了生成项从`代码`覆盖率报告中排除,使用`"Loc.initial"`.
auto newLoc = Loc("test.d",0,0);
// 创建'MyStruct'构声明
auto myStructDecl = new StructDeclaration(newLoc, Identifier.idPool("MyStruct"), false);
//加空成员列表,否则按前向声明打印
myStructDecl.members = new Dsymbols();
// 加到模块
m.members.push(myStructDecl);
// 看看
printPretty(m);
取C++
名字空间列表
这有点复杂,从代码开始,想用名字空间
转储extern(C++)
类的列表.
// foo::bar中示例类
extern(C++, "foo", "bar")
class Foo
{
// ...
}
如果已跑
语义趟,则可从通过访问Dsymbol.cppnamespace
从大多数D符号
取名字空间
,搜索Foo类
也会工作.
import dmd.attrib; // CPPNamespaceDeclaration
Dsymbol fooSym = m.search(Loc.initial, Identifier.idPool("Foo"));
CPPNamespaceDeclaration nsDecl = fooSym.cppnamespace;
if (nsDecl)
{
// 递归向上打印名字空间
void printNs (CPPNamespaceDeclaration ns) {
if (ns.cppnamespace)
printNs(ns.cppnamespace);
if (ns.cppnamespace)
write("::");
if (auto strExp = n.exp.isStringExp)
write(cast(char[]) strExp.peekData());
}
printNs(nsDecl); // 打印"foo::bar"
writeln(); // 新行
}
可惜,如果没有语义趟
,这不管用
,因此必须另外处理它.搜索
会不管用,因此必须查找CPPNamespaceDeclaration
,其cppnamespace
也将为null
,因此必须改为查找
其成员声明
.
import std.array;
import dmd.attrib; // CPP名间声明
// 查找m模块中的所有名字空间声明
auto nsdecls = (*m.members)[]
.filter!(s => s.isCPPNamespaceDeclaration)
.array;
// 示例,假设只有一个名字空间声明
if (CPPNamespaceDeclaration ns = (nsdecls[0]).isCPPNamespaceDeclaration)
{
static bool hasNestedNsDecl(CPPNamespaceDeclaration n)
{
return n.cppnamespace || (n.decl && (*n.decl)[0].isCPPNamespaceDeclaration);
}
// 同上,但无语义趟
void printNs (CPPNamespaceDeclaration n) {
if (n.cppnamespace)
printNs(n.cppnamespace);
else if (hasNestedNsDecl(n))
printNs((*n.decl)[0].isCPPNamespaceDeclaration);
if (hasNestedNsDecl(n))
write("::");
if (auto strExp = n.exp.isStringExp)
write(cast(char[]) strExp.peekData());
}
printNs(ns); // 打印"foo::bar"
writeln();
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现