当前位置:

首页 > 编程开发 > 在.NET中使用ANTLR4构建语法分析器的方法

在.NET中使用ANTLR4构建语法分析器的方法

前言 在 .NET 生态中,ANTLR4 是一个相当好用的语法分析器生成工具,能帮你快速搭建编译器、解释器这类东西。这篇内容会从环境搭建、语法规则编写、生成分析器,到实际调用,完整走一遍基本流程。受篇幅限制,ANTLR4 的语法规则不会深入展开,更详细的说明可以去看官方文档或者相关资料。 例子都比较

前言

在 .NET 生态中,ANTLR4 是一个相当好用的语法分析器生成工具,能帮你快速搭建编译器、解释器这类东西。这篇内容会从环境搭建、语法规则编写、生成分析器,到实际调用,完整走一遍基本流程。受篇幅限制,ANTLR4 的语法规则不会深入展开,更详细的说明可以去看官方文档或者相关资料。

例子都比较简单,也没有做严格测试,目的只是演示操作路径。实际开发中,有个不错的习惯——先去官方的 grammars-v4 仓库看看是不是已经有现成的 grammar 文件可以用:https://github.com/antlr/grammars-v4

代码示例已经传到了 GitHub:https://github.com/eventhorizon-cli/Antlr4Demo

ANTLR4 简介

ANTLR(全称 Another Tool for Language Recognition)是一个老牌的语法分析器生成器,属于编译技术里的前端工具。用它写编译器、解释器、翻译器都很顺手。

当前版本 ANTLR4 已经支持生成 Ja va、C#、Python、Ja vaScript 等多种语言的语法分析器。它的语法规则定义方式有点类似正则表达式,描述复杂语法结构时非常灵活。

它的工作流程大致分四步:

  • 编写语法规则:通常用 .g4 文件来定义
  • 生成语法分析器:用 ANTLR4 工具输出目标语言的代码
  • 使用分析器进行语法分析:调用生成的代码,得到抽象语法树(AST)
  • 访问 AST:通过 Visitor 模式或 Listener 模式做后续处理,比如解释执行或编译转换

语法分析基本概念

语法分析可以分成两个阶段:词法分析和语法分析。

  • 词法分析:把字符序列拆成 token,比如把 1 + 2 拆成 1、+、2 三个 token
  • 语法分析:根据语法规则把 token 组织成树形结构——抽象语法树(AST),树上的每个节点代表一个语法单元,比如表达式、语句等

举个例子,表达式 1 + 2 * 3 的 AST 长这样:

  +
 / \
1   *
   / \
  2   3

如何使用 ANTLR4

1. 安装 Antlr4.Runtime.Standard 包

下面用加减乘除四则运算来演示完整流程。先新建一个 C# 项目,然后添加包:

dotnet add package Antlr4.Runtime.Standard

2. 编写 ANTLR4 的语法规则文件

新建一个 .g4 文件,比如 Arithmetic.g4,内容如下:

grammar Arithmetic; // grammar name 需要和文件名一致

// 语法规则
// op=('*'|'/') 表示 op 将 ‘*' 或者 ‘/' 标记为一个操作符号
// # MulDiv 将这个规则命名为 MulDiv,访问 AST 时会用到
expr:
   expr op=('*'|'/') expr   # MulDiv
    |   expr op=('+'|'-') expr   # AddSub
    |   INT                      # Int
    |   '(' expr ')'             # Parens
    ;

// 词法规则
INT :   [0-9]+ ;
WS  :   [ \t\r\n]+ -> skip ; // 表示忽略空格

在.NET中使用ANTLR4构建语法分析器的方法

.g4 文件由两部分组成:词法规则(通常大写开头)和语法规则(通常小写开头)。词法规则定义 token,比如数字、运算符;语法规则定义语法结构,比如表达式、语句。

上面的例子定义了一个简单的四则运算语法,支持加减乘除和括号。整数 token 用 INT 表示(一个或多个数字)。expr 规则中用 | 分隔可选分支,乘除法写在加减法前面,这样乘除法优先级更高。每个分支可以用 # 命名,比如 # MulDiv,在 Visitor 访问时会用到这个名字。规则支持递归,比如乘法规则里又引用了 expr。

这个例子比较简单,所以词法和语法写在一个文件里。复杂项目可以把它们分开。

安装 ANTLR4 的插件(Rider 或 VS Code)后,可以检查语法规则是否正确。在 Rider 中选中 expr 规则,右键选择 Test Rule expr,输入测试表达式就能看到树形结构的结果。

在.NET中使用ANTLR4构建语法分析器的方法

在.NET中使用ANTLR4构建语法分析器的方法

3. 生成语法分析器

ANTLR4 本身基于 Ja va,所以需要安装 Ja va 运行环境才能使用它的工具。有两种方式生成分析器,推荐使用 Antlr4BuildTasks 自动生成。

方式一:直接使用 ANTLR4 官方工具

从官网下载 jar 包(本文写作时最新版 4.13.2):

https://www.antlr.org/download/antlr-4.13.2-complete.jar

下载后放到 .g4 文件所在目录,然后运行:

ja va -jar antlr-4.13.2-complete.jar -Dlanguage=CSharp Arithmetic.g4

-Dlanguage=CSharp 表示生成 C# 语言的代码。执行后会生成 ArithmeticLexer.cs、ArithmeticParser.cs 等文件,然后就可以在项目里使用了。

在.NET中使用ANTLR4构建语法分析器的方法

在.NET中使用ANTLR4构建语法分析器的方法

方式二:借助 Antlr4BuildTasks 自动生成

手动方式比较繁琐,推荐使用 Antlr4BuildTasks 这个 MSBuild 任务,它可以自动下载 ANTLR4 工具、生成分析器代码,并添加到项目中。它也会尝试下载 Ja va 运行环境,如果构建出错,可以手动安装全局 Ja va。

项目文件(.csproj)配置如下:


    
        Exe
        net9.0
        enable
        enable
    

    
        
        
    

    
        
    

表示项目里所有 .g4 文件都会参与编译。构建之后,生成的代码会放在 obj 文件夹中,每次构建都会重新生成,不需要手动添加。

在.NET中使用ANTLR4构建语法分析器的方法

4. 编写代码来使用语法分析器

访问 AST 的方式有两种:Visitor 和 Listener。ANTLR4 会为每个 .g4 文件生成对应的 BaseVisitor 和 BaseListener,咱们只需要继承并重写相关方法即可。

使用 Visitor 实现

继续用四则运算的例子。ANTLR4 为我们生成了一个 ArithmeticBaseVisitor 泛型基类,对应 .g4 中命名的规则(MulDiv、AddSub、Int、Parens),基类中有 VisitMulDiv、VisitAddSub 等方法。我们写一个自定义 Visitor 重写它们:

public class ArithmeticVisitor : ArithmeticBaseVisitor
{
    // 解析乘除法
    public override int VisitMulDiv(ArithmeticParser.MulDivContext context)
    {
        int left = Visit(context.expr(0));
        int right = Visit(context.expr(1));
        return context.op.Text switch
        {
            "*" => left * right,
            "/" => left / right,
            _ => throw new NotSupportedException($"Operator {context.op.Text} is not supported.")
        };
    }

    // 解析加减法
    public override int VisitAddSub(ArithmeticParser.AddSubContext context)
    {
        int left = Visit(context.expr(0));
        int right = Visit(context.expr(1));
        return context.op.Text switch
        {
            "+" => left + right,
            "-" => left - right,
            _ => throw new NotSupportedException($"Operator {context.op.Text} is not supported.")
        };
    }

    // 去掉括号,访问括号内的表达式
    public override int VisitParens(ArithmeticParser.ParensContext context) => Visit(context.expr());

    // 解析整数
    public override int VisitInt(ArithmeticParser.IntContext context) => int.Parse(context.INT().GetText());
}

然后写一个 Evaluate 方法,把字符串表达式传进去:

Console.WriteLine(Evaluate("1 + 2 * 3")); // 7
Console.WriteLine(Evaluate("(1 + 2) * 3")); // 9

int Evaluate(string expression)
{
    var lexer = new ArithmeticLexer(new AntlrInputStream(expression));
    var tokens = new CommonTokenStream(lexer);
    var parser = new ArithmeticParser(tokens);
    var visitor = new ArithmeticVisitor();
    return visitor.Visit(parser.expr());
}

使用 Listener 实现

Listener 模式通过 EnterXXX 和 ExitXXX 方法触发,我们可以在 ExitXXX 里把中间结果压入栈,最后弹出结果。

public class ArithmeticListener : ArithmeticBaseListener
{
    private readonly Stack _stack = new();
    public int Result => _stack.Pop();

    public override void ExitMulDiv(ArithmeticParser.MulDivContext context)
    {
        int right = _stack.Pop();
        int left = _stack.Pop();
        int result = context.op.Text switch
        {
            "*" => left * right,
            "/" => left / right,
            _ => throw new NotSupportedException($"Operator {context.op.Text} is not supported.")
        };
        _stack.Push(result);
    }

    public override void ExitAddSub(ArithmeticParser.AddSubContext context)
    {
        int right = _stack.Pop();
        int left = _stack.Pop();
        int result = context.op.Text switch
        {
            "+" => left + right,
            "-" => left - right,
            _ => throw new NotSupportedException($"Operator {context.op.Text} is not supported.")
        };
        _stack.Push(result);
    }

    public override void ExitInt(ArithmeticParser.IntContext context)
    {
        int value = int.Parse(context.INT().GetText());
        _stack.Push(value);
    }
}

使用方式类似:

Console.WriteLine(Evaluate("1 + 2 * 3")); // 7
Console.WriteLine(Evaluate("(1 + 2) * 3")); // 9

int Evaluate(string expression)
{
    var lexer = new ArithmeticLexer(new AntlrInputStream(expression));
    var tokens = new CommonTokenStream(lexer);
    var parser = new ArithmeticParser(tokens);
    var listener = new ArithmeticListener();
    parser.AddParseListener(listener);
    parser.expr();
    return listener.Result;
}

构建自定义 AST 以解决复杂问题

上面的例子直接在遍历时计算了值,对于简单场景够用了。但如果处理逻辑比较复杂,更好的做法是把 ANTLR4 生成的原始 AST 转换成一个自定义的 AST 结构,把解析逻辑和执行逻辑拆开,代码会更清晰,扩展起来也更方便。

下面看一个更接近实际的需求:指定一个文件夹,用 SQL 语句查询文件夹下的 CSV 文件,支持过滤、排序等。为了简化,只支持 SELECT、FROM、WHERE、ORDER BY,数据类型全当字符串处理,过滤支持 =、!=、LIKE,条件只能用 AND 连接,排序支持 ASC 和 DESC。

这次把词法和语法规则分开成两个文件:SqlLexer.g4 和 SqlParser.g4。

SqlLexer.g4

lexer grammar SqlLexer;

options { caseInsensitive = true; }

SELECT : 'SELECT' ;
FROM   : 'FROM' ;
WHERE  : 'WHERE' ;
ORDER  : 'ORDER' ;
BY     : 'BY'  ;
ASC    : 'ASC' ;
DESC   : 'DESC' ;
AND    : 'AND' ;
OR     : 'OR' ;
COMMA  : ',' ;
STAR   : '*'  ;

EQ     : '=' ;
NEQ    : '!=' ;
LIKE   : 'LIKE' ;

STRING_LITERAL : '\'' ( ~('\'' | '\\') | '\\' . )* '\'' ;
IDENTIFIER     : [a-z_][a-z0-9_]* ;
WS             : [ \t\r\n]+ -> skip ;

SqlParser.g4

parser grammar SqlParser;

options { tokenVocab=SqlLexer; }

query : SELECT selectList FROM tableName (WHERE whereClause)? (ORDER BY orderByClause)? ;

selectList : columnName (COMMA columnName)* | STAR ;

columnName: IDENTIFIER ;
tableName: IDENTIFIER ;

whereClause : whereCondition (AND whereCondition)* ;

whereCondition
    : columnName op=(EQ | NEQ) STRING_LITERAL
    | columnName op=LIKE STRING_LITERAL
    ;

orderByClause : orderByCondition (COMMA orderByCondition)* ;

orderByCondition : columnName (ASC | DESC)? ;

写完后可以在 Rider 里用插件测试 query 规则,验证语法正确性。

在.NET中使用ANTLR4构建语法分析器的方法

接下来定义一组类型来表示 SQL 的自定义 AST:

public abstract class Expression;

public class QueryExpression : Expression
{
    public required string TableName { get; init; }
    public required bool SelectAll { get; init; }
    public required IEnumerable SelectList { get; init; }
    public required IEnumerable WhereConditions { get; init; }
    public required IEnumerable OrderByConditions { get; init; }
}

public class WhereCondition : Expression
{
    public required string ColumnName { get; init; }
    public WhereConditionOperator Operator { get; init; }
    public required string Value { get; init; }
}

public enum WhereConditionOperator
{
    Equal, NotEqual, StartsWith, EndsWith, Contains
}

public class OrderByCondition : Expression
{
    public required string ColumnName { get; init; }
    public bool IsDescending { get; init; }
}

写一个 SqlAstBuilder 类(继承 SqlParserBaseVisitor)把原始 AST 转换成自定义结构:

public class SqlAstBuilder : SqlParserBaseVisitor
{
    public override QueryExpression VisitQuery(SqlParser.QueryContext context)
    {
        var selectList = context.selectList();
        bool selectAll = selectList?.STAR() != null;
        var columns = selectList?.columnName()
            .Select(c => c.GetText())
            .ToList() ?? [];

        var tableName = context.tableName().GetText();

        var whereConditions = context.whereClause()
            ?.whereCondition()
            .Select(c =>
            {
                var stringValue = c.STRING_LITERAL().GetText().Trim('\'');
                var opText = c.op.Text.ToUpperInvariant();
                var op = WhereConditionOperator.Equal;
                if (opText == "=") op = WhereConditionOperator.Equal;
                else if (opText == "!=") op = WhereConditionOperator.NotEqual;
                else if (opText == "LIKE")
                {
                    if (stringValue.StartsWith("%") && stringValue.EndsWith("%"))
                    {
                        op = WhereConditionOperator.Contains;
                        stringValue = stringValue.Substring(1, stringValue.Length - 2);
                    }
                    else if (stringValue.StartsWith("%"))
                    {
                        op = WhereConditionOperator.EndsWith;
                        stringValue = stringValue.Substring(1);
                    }
                    else if (stringValue.EndsWith("%"))
                    {
                        op = WhereConditionOperator.StartsWith;
                        stringValue = stringValue.Substring(0, stringValue.Length - 1);
                    }
                }
                else throw new NotSupportedException($"Operator {c.op.Text} is not supported.");

                return new WhereCondition
                {
                    ColumnName = c.columnName().GetText(),
                    Operator = op,
                    Value = stringValue
                };
            })
            .ToList() ?? [];

        var orderByConditions = context.orderByClause()
            ?.orderByCondition()
            .Select(c => new OrderByCondition
            {
                ColumnName = c.columnName().GetText(),
                IsDescending = c.DESC() != null
            })
            .ToList() ?? [];

        return new QueryExpression
        {
            SelectAll = selectAll,
            SelectList = columns,
            TableName = tableName,
            WhereConditions = whereConditions,
            OrderByConditions = orderByConditions
        };
    }
}

最后实现 SqlToCsvEngine 类,负责解析 SQL、读取 CSV、过滤排序并返回结果:

public class SqlToCsvEngine(DirectoryInfo csvDirectory)
{
    public IEnumerable> ExecuteQuery(string query)
    {
        var lexer = new SqlLexer(new AntlrInputStream(query));
        var tokens = new CommonTokenStream(lexer);
        var parser = new SqlParser(tokens);
        var astBuilder = new SqlAstBuilder();
        var expression = astBuilder.Visit(parser.query());

        var csvData = ReadCsv(expression.TableName);

        // 过滤
        var filteredData = csvData.Where(row =>
        {
            bool isMatch = true;
            foreach (var condition in expression.WhereConditions)
            {
                if (row.TryGetValue(condition.ColumnName, out var value))
                {
                    isMatch = condition.Operator switch
                    {
                        WhereConditionOperator.Equal => value == condition.Value,
                        WhereConditionOperator.NotEqual => value != condition.Value,
                        WhereConditionOperator.StartsWith => value.StartsWith(condition.Value),
                        WhereConditionOperator.EndsWith => value.EndsWith(condition.Value),
                        WhereConditionOperator.Contains => value.Contains(condition.Value),
                        _ => throw new ArgumentOutOfRangeException()
                    };
                }
                else throw new InvalidOperationException($"Column {condition.ColumnName} does not exist in CSV file.");
            }
            return isMatch;
        });

        // 排序
        foreach (var orderByCondition in expression.OrderByConditions)
        {
            Func>, Func, string>,
                IEnumerable>> orderByFunc;
            if (filteredData is IOrderedEnumerable> orderedData)
            {
                orderByFunc = orderByCondition.IsDescending
                    ? (_, keySelector) => orderedData.ThenByDescending(keySelector)
                    : (_, keySelector) => orderedData.ThenBy(keySelector);
            }
            else
            {
                orderByFunc = orderByCondition.IsDescending
                    ? Enumerable.OrderByDescending
                    : Enumerable.OrderBy;
            }
            filteredData = orderByFunc(filteredData, row =>
            {
                if (row.TryGetValue(orderByCondition.ColumnName, out var value)) return value;
                throw new InvalidOperationException($"Order by column {orderByCondition.ColumnName} does not exist in CSV file.");
            });
        }

        // 投影
        if (expression.SelectAll) return filteredData;

        var selectedData = filteredData.Select(row =>
        {
            var selectedRow = new Dictionary();
            foreach (var columnName in expression.SelectList)
            {
                if (row.TryGetValue(columnName, out var value)) selectedRow[columnName] = value;
                else throw new InvalidOperationException($"Column {columnName} does not exist in CSV file.");
            }
            return selectedRow;
        });
        return selectedData;
    }

    private IEnumerable> ReadCsv(string tableName)
    {
        var csvFile = new FileInfo(Path.Combine(csvDirectory.FullName, $"{tableName}.csv"));
        if (!csvFile.Exists) throw new FileNotFoundException($"CSV file {csvFile.FullName} does not exist.");
        using var reader = new StreamReader(csvFile.FullName);
        var headerLine = reader.ReadLine();
        if (headerLine == null) throw new InvalidOperationException($"CSV file {csvFile.FullName} is empty.");
        var headers = headerLine.Split(',');
        while (!reader.EndOfStream)
        {
            var line = reader.ReadLine();
            if (line == null) continue;
            var values = line.Split(',');
            yield return headers.Zip(values).ToDictionary(x => x.First, x => x.Second);
        }
    }
}

测试用的 CSV 文件内容:

Name,City,Occupation,Company
Alice,New York,Engineer,TechCorp
Bob,Los Angeles,Designer,Creative Inc
Ben,Atlanta,Writer,Publishing House
Charlie,Chicago,Manager,Finance Group
Da vid,Houston,Teacher,School District
Eve,Miami,Student,University
Frank,Seattle,Chef,Restaurant Co
Grace,San Francisco,Doctor,HealthCare
Hannah,Boston,Lawyer,Legal Partners
Ian,Denver,Architect,BuildIt

执行查询:

var directory = new DirectoryInfo("/Users/hkh/Desktop/test");
var engine = new SqlToCsvEngine(directory);
var sql =  """
  SELECT Name, City, Occupation, Company
  FROM Employee
  WHERE City != 'Miami'
    AND Occupation LIKE '%er'
  ORDER BY Name ASC, Company DESC
  """;
var result = engine.ExecuteQuery(sql);

// 打印头部
foreach (var column in result.First().Keys)
{
    Console.Write($"{column}\t");
}
foreach (var row in result)
{
    Console.WriteLine();
    foreach (var (_, value) in row)
    {
        Console.Write($"{value}\t");
    }
}

输出结果:

Name City Occupation Company

Alice New York Engineer TechCorp

Ben Atlanta Writer Publishing House

Bob Los Angeles Designer Creative Inc

Charlie Chicago Manager Finance Group

Da vid Houston Teacher School District

Hannah Boston Lawyer Legal Partners

参考资料

https://github.com/antlr/grammars-v4

https://wizardforcel.gitbooks.io/antlr4-short-course/content/basic-concept.html

https://github.com/antlr/antlr4/blob/master/doc/csharp-target.md

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系bd@zhengruan.com
作者最新文章
编程开发
相关文章 更多
codex安装windows教程桌面版完整安装步骤指南
codex安装windows教程桌面版完整安装步骤指南

详解如何在 Windows 电脑上通过 Microsoft Store 安装 Codex 桌面应用,包括启动登录、切换 Codex 模式、关联本地代码项目及终端环境配置的界面操作指引。

github配置ssh key windows
github配置ssh key windows

本文详解如何在Windows环境中使用PowerShell生成SSH密钥对,配置ssh-agent自动加载私钥,并将公钥安全添加至GitHub账户,最后通过git remote set-url切换为SSH地址,解决每次操作需输入账号密码的问题。

backdrop和background区别
backdrop和background区别

深入解析backdrop与background在网页开发及视觉设计中的区别。通过CSS代码示例,展示如何正确使用background设置元素背景,以及利用backdrop-filter创建弹窗遮罩模糊效果,避免概念混用。

windsurf ide download Windows版安装教程
windsurf ide download Windows版安装教程

详解 Windsurf IDE 在 Windows 系统的下载来源、安装步骤及首次启动界面。指导用户如何导入编辑器设置、打开项目文件夹及使用终端与 AI 功能,适合初次接触该工具的开发者阅读。

ServBay安装配置详细教程与操作指南
ServBay安装配置详细教程与操作指南

新手入门 ServBay 本地开发环境,详解安装包下载、Dashboard 状态监控、Packages 组件安装、Services 服务控制及 Websites 项目配置。掌握 .servbay.config 版本管理与日志排查技巧,快速搭建稳定的 PHP、Node.js 等多语言开发环境。

codekit环境配置指南从安装到环境搭建完整教程
codekit环境配置指南从安装到环境搭建完整教程

详解 CodeKit 在 macOS 下的安装步骤、项目导入方法、Sass与JavaScript编译设置及浏览器自动刷新功能,助您快速搭建高效的前端开发环境。

codex安装windows 命令行完整操作教程
codex安装windows 命令行完整操作教程

详解Windows环境下安装OpenAI Codex CLI的步骤,包括WSL环境检查、Node.js/npm配置、npm全局安装命令及首次启动验证,适合开发者快速上手。

NativeRest环境配置要求与完整操作教程
NativeRest环境配置要求与完整操作教程

学习如何配置 NativeRest REST API 客户端。涵盖 Windows/macOS/Linux 安装后的工作区创建、环境变量管理、请求编辑及响应查看步骤,帮助开发者快速完成基础环境搭建与连通性测试。

CSS设置透明度的注意事项有哪些?opacity属性详解
CSS设置透明度的注意事项有哪些?opacity属性详解

深入解析CSS中设置透明度的核心属性opacity,剖析子元素继承、事件穿透、层叠上下文等关键注意事项,并提供与rgba、hsla的实用选型对比。

flutter页面传值到后台的方法及示例代码
flutter页面传值到后台的方法及示例代码

flutter页面传值到后台的完整实现方法及示例代码,帮助读者快速掌握相关技术要点。

查看更多
精品专题 更多
装机必备
装机必备

正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

Windows
Windows

正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

PDF教程
PDF教程

正软商城PDF教程频道提供PDF编辑、转换、合并、拆分、压缩及格式处理方法,同时介绍常用PDF软件和工具的使用技巧。

Mac软件 更多
WPS Office macOS版
WPS Office macOS版
macOS

WPS Office是一款面向macOS用户的综合办公套件,将文字处理、电子表格、演示文稿、PDF工具、模板与云协作整合在同一应用中。它兼容常见Microsoft Office格式,支持Intel及Apple芯片Mac,基础功能可免费使用,

Shapr3D macOS版
Shapr3D macOS版
Mac

Shapr3D是一款面向工业设计、机械工程、建筑概念和三维打印工作流的CAD软件。Mac版采用Parasolid建模内核,支持草图约束、实体建模、工程图、可视化渲染及常见CAD格式交换,并可通过账户在多台设备之间同步项目。

REAPER macOS版
REAPER macOS版
Mac

REAPER是Cockos开发的数字音频工作站,提供多轨音频与MIDI录制、剪辑、处理、混音和母带制作工具。Mac版兼容Intel与Apple芯片,支持AU、VST、VST3、CLAP等插件格式,并提供高度可定制的工作流程。

WINDOWS 更多
Capture One Pro Windows版
Capture One Pro Windows版
Windows

Capture One Pro 是面向摄影师和影像团队的专业桌面照片处理工具,提供相机定制色彩配置、RAW转换、图层与蒙版、联机拍摄、目录及会话管理等功能。它适合棚拍、人像、商业产品、婚礼和风光摄影,

Adobe Acrobat Pro Windows版
Adobe Acrobat Pro Windows版
Windows

Adobe Acrobat Pro 是面向专业 PDF 工作的综合解决方案,可在 Windows 中创建、编辑、转换、比较、整理和保护文档,并支持表单、电子签名、扫描件文字识别及敏感信息永久遮盖。订阅还包含网页、移动服务和云端协作能力,

Adobe After Effects Windows版
Adobe After Effects Windows版
Windows

Adobe After Effects 是面向动态图形、动画与影视视觉特效制作的专业工具,可完成关键帧动画、图层合成、遮罩跟踪、抠像、文字动画和二维三维元素整合。Windows 版通过 Adobe Creative Cloud 安装及更新,