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();
    }
posted @   zjh6  阅读(32)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示