初化项目

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();
}