feat: add docs latex source files

This commit is contained in:
2024-07-03 16:28:25 +08:00
parent 580f2d505a
commit 460a1e71e2
41 changed files with 3854 additions and 0 deletions

BIN
docs/contents/assets/1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 347 KiB

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,459 @@
\documentclass[../main.tex]{subfiles}
\begin{document}
\section{总体设计}
% 说明,包括:
% 1数据结构设计
% 2总体结构设计包括
% 功能模块的划分
% 模块功能
% 模块之间的关系
% 模块之间的接口
% 3用户接口设计
\subsection{数据流图}
\begin{figure}[h]
\centering
\includegraphics[width=0.9\linewidth]{assets/数据流图.png}
\caption{数据流图}
\label{fig:data_flow_diagram}
\end{figure}
\subsection{数据结构设计}
在整个编译器设计中,数据结构的设计是至关重要的一环。它不仅需要支持编译器的各个阶段,还需要保证数据的正确传递和高效处理。在本节中将对编译器中各个模块之间共有的部分数据结构进行说明。
\subsubsection{词法记号}
\texttt{SemanticToken} 是一个抽象基类,定义了所有词法记号的共有属性和方法。具体类型的词法记号(如关键字、标识符等)都继承自这个类。
每个Token至少有四个属性记号类型 \texttt{SemanticTokenType TokenType},行号 \texttt{uint LinePos},字符位置 \texttt{uint CharacterPos},字面量 \texttt{string LiteralValue}
\begin{lstlisting}[
style=csharp
]
public abstract class SemanticToken
{
public abstract SemanticTokenType TokenType { get; }
/// <summary>
/// 记号出现的行号
/// </summary>
public required uint LinePos { get; init; }
/// <summary>
/// 记号出现的列号
/// </summary>
public required uint CharacterPos { get; init; }
/// <summary>
/// 记号的字面值
/// </summary>
public required string LiteralValue { get; init; }
}
\end{lstlisting}
实际继承词法记号基类的词法记号类有:
\begin{itemize}
\item 字符类型记号 \texttt{CharacterSemanticToken}
\item 字符串类型记号 \texttt{StringSemanticToken}
\item 分隔符类型记号 \texttt{DelimiterSemanticToken}
\item 关键词类型记号 \texttt{KeywordSemanticToken}
\item 操作符类型记号 \texttt{OperatorSemanticToken}
\item 数值类型记号 \texttt{NumberSemanticToken}
\item 标识符类型记号 \texttt{IdentifierSemanticToken}
\end{itemize}
其中分隔符类型、关键词类型、操作符类型等记号提供一个属性获得该记号代表的分隔符、关键词、操作符,这些可以穷举的类型使用枚举标识,在表\ref{table:operator_and_delimiter}和表\ref{table:keyword_and_operator}中列举了所有的分隔符、关键词和操作符。而对于字符类型记号字符串类型记号、数组类型记号在代码中分别提供了将字面值识别为C\#中的字符、字符串和数值等类型的功能方便在代码中对于这种固定值进行操作。在标识符类型中则是提供了一个返回标识符值的方法在该方法中会自动将字面值小写以此来提供Pascal代码中对于大小写不敏感的功能。
% \begin{table}[h]
% \centering
% \caption{基本类型和标识符}
% \begin{tabular}{|c|c|c|c|}
% \hline
% \textbf{描述} & \textbf{字面量记录} & \textbf{记号类型} & \textbf{详细类型} \\
% \hline
% 标识符 & 该标识符本身 & IDENTIFIER & \\
% 无符号整数 & 该整数本身(字符串表示) & NUMBER & 整数 \\
% 无符号浮点数 & 该浮点数本身(字符串表示) & & 实数 \\
% 十六进制数 & 该十六进制数本身(字符串表示) & & 十六进制 \\
% 字符常量 & 该字符常量本身(不包含两侧的单引号) & CHARACTER & \\
% \hline
% \end{tabular}
% \end{table}
\begin{longtable}{|c|c|c|c|}
\caption{运算符和分界符} \label{table:operator_and_delimiter} \\
\hline
% 跨页表的第一行
\textbf{描述} & \textbf{字面量记录} & \textbf{记号类型} & \textbf{详细类型} \\
\hline
\endhead
% 跨页表的最后一行
\hline
\multicolumn{4}{r@{}}{接下一页}
\endfoot
% 跨页表的最后一页的最后一行
\hline
\endlastfoot
关系运算符 & $\geq$ & Operator & 大于等于 \\
& $>$ & & 大于 \\
& $\leq$ & & 小于等于 \\
& $\neq$ & & 不等于 \\
& $<$ & & 小于 \\
关系运算符:相等 & $=$ & & 等于 \\
算术运算符:加法 & $+$ & &\\
算术运算符:减法 & $-$ & &\\
算术运算符:乘法 & $*$ & &\\
算术运算符:除法 & $/$ & &\\
赋值符号 & $:=$ & & 赋值 \\
范围连接符 & $..$ & Delimiter & 点点 \\
界符 & $($ & & 左括号 \\
& $)$ & & 右括号 \\
& $[$ & & 左方括号 \\
& $]$ & & 右方括号 \\
& $:$ & & 冒号 \\
& $,$ & & 逗号 \\
& $;$ & & 分号 \\
& $.$ & & 句号/点 \\
\hline
\end{longtable}
\begin{longtable}{|c|c|c|c|}
\caption{关键字和逻辑运算符} \label{table:keyword_and_operator} \\
\hline
\textbf{描述} & \textbf{字面量记录} & \textbf{记号类型} & \textbf{详细类型} \\
\hline
\endhead
% 跨页表的最后一行
\hline
\multicolumn{4}{r@{}}{接下一页}
\endfoot
% 跨页表的最后一页的最后一行
\hline
\endlastfoot
逻辑运算符:或 & or & Keyword &\\
算术运算符:取余 & mod & & 取余 \\
逻辑运算符:且 & and & &\\
逻辑运算符:非 & not & &\\
关键字 & program & & 程序 \\
关键字 & const & & 常量 \\
关键字 & var & & 变量 \\
关键字 & array & & 数组 \\
关键字 & of & & 属于 \\
关键字 & procedure & & 过程 \\
关键字 & function & & 函数 \\
关键字 & begin & & 开始 \\
关键字 & end & & 结束 \\
关键字 & if & & 如果 \\
关键字 & then & & 那么 \\
关键字 & for & & 对于 \\
关键字 & to & &\\
关键字 & do & & 执行 \\
关键字 & else & & 否则 \\
关键字 & repeat & & 重复 \\
关键字 & until & & 直到 \\
关键字 & while & &\\
关键字 & integer & & 整数 \\
关键字 & real & & 实数 \\
关键字 & char & & 字符 \\
关键字 & boolean & & 布尔 \\
\end{longtable}
\subsubsection{语法树}
语法树是编译器中用于表示源代码结构的树状数据结构。在语法分析阶段,编译器将源代码转换为语法树,以便后续阶段可以更高效地进行处理。因此,语法树中每个节点和语法中的每个符号一一对应,其中非终结符即对应书上的父节点,终结符对应了树上的叶子节点。
在终结节点上直接封装了访问对应的词法分析令牌的功能。
\begin{lstlisting}[style=csharp]
public class TerminatedSyntaxNode : SyntaxNodeBase
{
public override bool IsTerminated => true;
public required SemanticToken Token { get; init; }
// 其他代码有删节
}
\end{lstlisting}
在针对不同的非终结节点,首先在其的共同基类\texttt{NonTerminatedSyntaxNode}中封装了访问其子节点的功能,并针对该节点产生式的不同提供了不同的方式模型。
针对只有一个产生式的非终结节点,直接在该非终结节点上使用属性的方式将其有意义的子节点暴露出来,例如在\texttt{ProgramStruct}上就直接暴露放访问\texttt{ProgramHead}的属性。
\begin{lstlisting}[style=csharp]
public class ProgramStruct : NonTerminatedSyntaxNode
{
public override NonTerminatorType Type => NonTerminatorType.ProgramStruct;
/// <summary>
/// 程序头
/// </summary>
public ProgramHead Head => Children[0].Convert<ProgramHead>();
}
\end{lstlisting}
针对含有多个产生式的非终结节点,如果是有效的子节点只有一种的,则仍然使用属性的方式进行暴露,例如\texttt{ConstDeclaration},其就暴露了标识符名称和值两个属性。
\begin{lstlisting}[style=csharp]
public class ConstDeclaration : NonTerminatedSyntaxNode
{
public override NonTerminatorType Type => NonTerminatorType.ConstDeclaration;
/// <summary>
/// 是否递归的声明下一个ConstDeclaration
/// </summary>
public bool IsRecursive { get; private init; }
/// <summary>
/// 获得声明的常量
/// </summary>
public (IdentifierSemanticToken, ConstValue) ConstValue => GetConstValue();
public static ConstDeclaration Create(List<SyntaxNodeBase> children)
{
bool isRecursive;
if (children.Count == 3)
{
isRecursive = false;
}
else if (children.Count == 5)
{
isRecursive = true;
}
else
{
throw new InvalidOperationException();
}
return new ConstDeclaration { Children = children, IsRecursive = isRecursive };
}
private static IdentifierSemanticToken ConvertToIdentifierSemanticToken(SyntaxNodeBase node)
{
return (IdentifierSemanticToken)node.Convert<TerminatedSyntaxNode>().Token;
}
private (IdentifierSemanticToken, ConstValue) GetConstValue()
{
if (IsRecursive)
{
return (ConvertToIdentifierSemanticToken(Children[2]), Children[4].Convert<ConstValue>());
}
else
{
return (ConvertToIdentifierSemanticToken(Children[0]), Children[2].Convert<ConstValue>());
}
}
}
\end{lstlisting}
而对于使用的多个产生式且无法有效提取信息的非终结节点,则设计使用\textbf{事件}以提供相关信息的功能。访问者可以在需要使用对应产生式的信息时订阅对应的事件,并且语法树的实现保证对应的事件会在第一次访问和第二次访问时按照订阅的顺序进行调用。对应事件的事件参数也可提供产生式相关的信息。
\texttt{ConstValue}就是一个不错的例子,其提供了使用数值产生式和字符产生式的两个事件供订阅。
\begin{lstlisting}[style=csharp]
/// <summary>
/// 使用数值产生式事件的事件参数
/// </summary>
public class NumberConstValueEventArgs : EventArgs
{
/// <summary>
/// 是否含有负号
/// </summary>
public bool IsNegative { get; init; }
/// <summary>
/// 数值记号
/// </summary>
public required NumberSemanticToken Token { get; init; }
}
/// <summary>
/// 使用字符产生式事件的事件参数
/// </summary>
public class CharacterConstValueEventArgs : EventArgs
{
/// <summary>
/// 字符记号
/// </summary>
public required CharacterSemanticToken Token { get; init; }
}
public class ConstValue : NonTerminatedSyntaxNode
{
public override NonTerminatorType Type => NonTerminatorType.ConstValue;
/// <summary>
/// 使用数值产生式的事件
/// </summary>
public event EventHandler<NumberConstValueEventArgs>? OnNumberGenerator;
/// <summary>
/// 使用字符产生式的事件
/// </summary>
public event EventHandler<CharacterConstValueEventArgs>? OnCharacterGenerator;
}
\end{lstlisting}
\subsubsection{符号表}
符号表是在语义分析阶段使用的数据结构,用于存储变量、函数和过程的信息。符号表支持查询、插入和作用域管理操作。每个作用域都有自己的符号表,如果当前作用域中没有找到符号,则会递归查询父作用域。
符号表的设计如下:
\begin{itemize}
\item \textbf{符号表项SymbolTableItem}包含类型MegaType、名称、是否为变量、是否为函数和参数列表。
\item \textbf{类型MegaType}:包含指针类型和项类型。
\end{itemize}
符号表的物理结构采用哈希表实现,以支持高效的查询和插入操作。
\subsubsection{语法树上的旅行者}
在语法分析完成对于语法树的构建之后,我们需要在语法树的各个节点上进行一系列的操作,例如进行符号表的维护、类型检查和代码生成等任务。为了降低程序的复杂度,我们希望在程序中提供一个统一的语法树遍历和访问接口。因此,我们使用访问者设计模式设计了\texttt{SyntaxNodeVisitor}(语法节点访问者)和\texttt{SyntaxTreeTraveller}(语法树旅行者)。同时结合编译原理课程中语义分析和翻译方案相关的知识,我们设计了一种称为\textit{前后序遍历}的语法树访问模型。例如对于图\ref{fig:syntax_tree_example}中的一颗语法树,其的遍历顺序为
\begin{align}\notag
& ProgramStruct \to ProgramHead \to program \to program \to main \to main \\ \notag
&\to ProgramHead \to ; \to ; \to ProgramBody \to ConstDeclarations \to \\ \notag
&ConstDelcarations \to VarDeclarations \to VarDeclarations \to \\ \notag
&SubprogramDeclarations \to SubprogramDeclarations \to CompoundStatement \\ \notag
&\to begin \to begin \to StatementList \to Statement \to Statement \to \\ \notag
&StatementList \to end \to end \to CompoundStatement \to ProgramBody \\ \notag
&\to . \to . \to ProgramStruct \notag
\end{align}
\begin{figure}[t]
\centering
\includegraphics[width=0.9\linewidth]{assets/示例语法树图.png}
\caption{示例的语法树图}
\label{fig:syntax_tree_example}
\end{figure}
在设计对于语法树的遍历之后,我们在设计了对于语法节点的访问者,访问者针对语法树上的每一个节点都提供了两个访问接口,分别会在第一次遍历到该节点和第二次遍历到该节点时调用,称为\texttt{PreVisit}\texttt{PostVisit}。按照编译原理课程中的知识来说,\texttt{PreVisit}接口理解为对于该节点的L-属性计算,\texttt{PostVisit}接口理解为对该节点的S-属性计算。
为了使得各语义分析的工作可以方便的组合在一起运行,例如类型检查需要在代码检查之前运行,容易想到使用类型继承的方式进行抽象。例如类型检查类直接继承了语法节点访问者抽象基类\texttt{SyntaxNodeVisitor},而代码生成了则直接继承了类型检查类。需要注意的是,在重载访问语法节点的接口函数之间,需要在执行任何操作之前调用基类的对应操作。
\begin{lstlisting}[
style=csharp,
caption={示例的代码生成类代码}
]
public class CodeGeneratorVisitor(ICompilerLogger? logger = null) : TypeCheckVisitor(logger)
{
public override void PreVisit(ProgramHead programHead)
{
// 调用基类的访问方法
base.PreVisit(programHead);
// 实际的代码生成逻辑...
}
}
\end{lstlisting}
\subsection{总体结构设计}
\textit{Canon}编译器的核心库按照编译的工作流程和相关工作划分为各个模块:
\begin{itemize}
\item 源代码读取模块
\item 词法分析模块
\item 语法分析模块
\item 语义分析模块
\item 日志输出模块
\end{itemize}
鉴于项目中主要使用依赖注入的设计模块进行开发,因此各个模块都提供了对应接口。下面首先介绍各个模块之前的接口,然后将分模块介绍各个模块的功能。
\subsubsection{模块提供的接口}
\paragraph{ISourceReader} 源代码读取模块提供的接口。该接口在提供文件读取函数的同时,还提供了读取的缓冲区功能,在获得当前读取字符及行号、列号的同时,可以前进读取一个字符,后退一个字符,最后尝试读取下一个字符。
\paragraph{ILexer} 词法分析器的接口。该接口提供了从源代码中分析为一个语法分析流的功能。
\paragraph{IGrammarParser} 语法分析模块的接口。该接口提供了将一个词法分析流构建为一颗语法树的功能。
\paragraph{SyntaxNodeVisitor} 语法树节点访问抽象类。该接口提供了对于语法树上各个节点的访问方法。
\paragraph{ICompilerLogger} 编译日志输出接口。该接口提供了输出各个等级信息的能力。
\subsubsection{词法分析模块}
词法分析模块负责读入输入字符串,解析为词法记号流输出。
\subsubsection{语法分析模块}
语法分析模块主要负责从Pascal语法构建LR(1)分析表和对输入的词法记号流进行分析构建语法树的工作。
对于语法分析模块而言LR(1)分析表存在两种表现形式:(1) 内存形式直接通过Pascal-S语法分析并构建自动机进而得到的分析表(2)源代码形式鉴于每次都从Pascal-S语法进行分析并构建自动机消耗的时间和资源非常多而且语法在大多数时间都是不变的因此我们实现了将LR(1)分析表生成到C\#源代码形式的功能。
因此语法分析模块主要提供三个功能从语法构建自动机并得到LR(1)分析表将LR(1)分析表生成为C\#源代码形式;从分析表分析输入的语法分析流并构建语法树。
\subsubsection{语义分析模块}
语义分析模块负责完成类型检查和代码生成两个功能。为了完成上述的工作在语义分析模块中实现了Pascal-S语言的类型系统和对于语法树的访问和遍历逻辑。
\subsection{用户接口设计}
\subsubsection{命令行版本}
命令行版本的接口设计旨在为用户提供一个简单、直接的方式来使用编译器。用户可以通过命令行工具 \texttt{Canon Pascal Compiler} 来转换 Pascal 源代码文件到 C 代码文件。
使用方法如下:
\begin{verbatim}
Canon.Console [options]
Options:
-i, --input <input> (REQUIRED) Pascal源代码文件地址
--version 显示版本信息
-?, -h, --help 显示帮助信息
\end{verbatim}
其中 \texttt{<input>} 是必须提供的 Pascal 源文件路径。命令行版本支持以下特性:
\begin{itemize}
\item \textbf{参数解析}:通过 \texttt{System.CommandLine} 库解析命令行参数,提供灵活的命令行选项。
\item \textbf{日志记录}:使用 \texttt{CompilerLogger} 类记录编译过程中的信息,帮助用户了解编译状态。
\end{itemize}
\subsubsection{Web在线版本}
\begin{figure}[h]
\centering
\includegraphics[width=0.9\linewidth]{assets/编译器Web在线版本.png}
\caption{编译器Web在线版本}
\label{fig:compiler_web_fig}
\end{figure}
Web在线版本提供了一个图形化界面允许用户在网页上直接输入Pascal 源代码,并在线编译和查看生成的 C 代码。这为没有命令行使用经验的用户提供了便利(图\ref{fig:compiler_web_fig}。同时图形化界面提供了Pascal源代码生成的语法树示意图\ref{fig:compiler_web_fig_tree}),可供用户查看并分析语法树结构。
\begin{figure}[h]
\centering
\includegraphics[width=0.9\linewidth]{assets/编译器Web在线版本_语法树.png}
\caption{语法树渲染}
\label{fig:compiler_web_fig_tree}
\end{figure}
Web版本的特点包括
\begin{itemize}
\item \textbf{代码编辑器}:集成代码编辑器,支持语法高亮,提供更好的代码编写体验。
\item \textbf{实时编译}:用户输入代码后,可以实时编译并显示输出结果。
\item \textbf{错误提示}:编译过程中的错误会在网页上直接显示,方便用户快速定位问题。
\item \textbf{语法树渲染}:编译过程中,会根据输入的代码,渲染出对应的语法树。语法树上节点对应的记号类型。
\item \textbf{历史记录}编译器会保存成功编译的记录并提供查看历史记录的功能。使用唯一id作为历史记录标识实现了通过连接分享一个编译记录的功能\ref{fig:compiler_web_fig_history})。
\end{itemize}
\textit{注: 在实现语法树的可视化过程中,我们参考了论文\cite{goos_improving_2002}以在线性时间复杂度中绘制完整棵树。}
\begin{figure}[h]
\centering
\includegraphics[width=0.9\linewidth]{assets/编译器Web在线版本_历史记录.png}
\caption{历史记录}
\label{fig:compiler_web_fig_history}
\end{figure}
Web在线版本的实现依赖于前后端分离的架构前端使用React框架提供用户交互界面后端处理编译任务。通过 AJAX 请求与后端通信,实现代码的提交和结果的获取。
总体来说,用户接口设计考虑了不同用户群体的使用习惯和需求,提供了灵活、友好的使用方式,使得用户可以更加方便地使用。
\end{document}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,118 @@
\documentclass[../main.tex]{subfiles}
\begin{document}
\section{需求分析}
% 包括:数据流图、功能及数据说明等
% 开发环境
\subsection{开发环境}
在本次课程设计中我们没有使用编译原理课程设计的传统工具flex和bison而是决定自行手动实现词法分析和语法分析。因此我们在进行开发环境选型是就具有较高的灵活性不必拘泥于C++语言。在综合了小组人员的开发经验和各个不同语言的优劣之后,我们决定选择.NET平台的C\#语言作为我们的开发语言。使用C\#语言作为开发语言所给我们带来的好处有:
\begin{itemize}
\item C\#是一门面向对象的新式类型安全语言,具有自动垃圾回收的功能。
\item .NET平台提供了多种不同的部署方式。可以直接AOT(Ahead of time)编译到单个可执行程序亦可以使用JIT(Just in time)编译的方式使用运行时进行运行。因此在共享同样的核心库时,我们可以提供编译到单个可执行文件的编译器程序,也可以基于.NET强大的Web开发能力提供在线编译器。
\item C\#在全平台上提供了统一的开发和运行体验,适用于我们小组中需要兼容多个平台开发的需求。
\end{itemize}
此外,为了提高开发效率和代码的可维护性,我们还选用了一些辅助工具和库:
\begin{itemize}
\item \textbf{Gitea}我们通过使用自行搭建的Gitea服务器进行版本控制这样可以确保团队成员之间的代码同步和变更记录。
\item \textbf{Gitea Actions}我们依托Gitea提供的丰富持续集成、自动部署的功能编写了一系列的自动化脚本在每次提交新代码和合并到主线代码时运行单元测试和集成测试。
\end{itemize}
为了撰写开发文档和实验报告我们利用了Overleaf和飞书的在线文档协作功能。这使得文档的共享和协作变得更加高效和便捷尤其是在团队分布在不同地点时。
\subsection{功能分析}
在需求文档中提供的Pascal-S语法基础上我们希望我们的编译器支持如下的Pascal语法和功能
\begin{enumerate}
\item 支持Pascal-S语法中的常见数据类型包括整数、浮点数、字符值和布尔值。
\item 支持Pascal-S语法中的常见流程控制语句包括分支语句循环语句(While循环和For循环)
\item 支持Pascal-S语法中的流程定义和函数定义
\item 支持Pascal-S标准库中的输入输出函数(write, writeln, read)
\end{enumerate}
基于上述语法和功能我们基于Pascal-S语法设计了如下的Pascal语法。
\subsubsection{支持的Pascal语法}\label{pascal_grammar}
\begin{lstlisting}[
style=grammar,
caption={Pascal-S语法},
]
ProgramStart -> ProgramStruct
ProgramStruct -> ProgramHead ; ProgramBody .
ProgramHead -> program id (IdList) | program id
ProgramBody -> ConstDeclarations
VarDeclarations
SubprogramDeclarations
CompoundStatement
IdList -> , id IdList | : Type
ConstDeclarations -> $\epsilon$ | const ConstDeclaration ;
ConstDeclaration -> id = ConstValue | ConstDeclaration ; id = ConstValue
ConstValue -> +num | -num | num | 'letter' | true | false
VarDeclarations -> | var VarDeclaration ;
VarDeclaration -> id IdList | VarDeclaration ; id IdList
Type -> BasicType | array [ Period ] of BasicType
BasicType -> integer | real | boolean | char
Period -> digits .. digits | Period , digits .. digits
SubprogramDeclarations -> $\epsilon$ | SubprogramDeclarations Subprogram ;
Subprogram -> SubprogramHead ; SubprogramBody
SubprogramHead -> procedure id FormalParameter
| function id FormalParameter : BasicType
FormalParameter -> $\epsilon$ | () | ( ParameterList )
ParameterList -> Parameter | ParameterList ; Parameter
Parameter -> VarParameter | ValueParameter
VarParameter -> var ValueParameter
ValueParameter -> id IdList
SubprogramBody -> ConstDeclarations
VarDeclarations
CompoundStatement
CompoundStatement -> begin StatementList end
StatementList -> Statement | StatementList ; Statement
Statement -> $\epsilon$
| Variable assignOp Expression
| ProcedureCall
| CompoundStatement
| if Expression then Statement ElsePart
| for id assignOp Expression to Expression do Statement
| while Expression do Statement
Variable -> id IdVarPart
IdVarPart -> $\epsilon$ | [ ExpressionList ]
ProcedureCall -> id | id () | id ( ExpressionList )
ElsePart -> $\epsilon$ | else Statement
ExpressionList -> Expression | ExpressionList , Expression
Expression -> SimpleExpression | SimpleExpression RelationOperator SimpleExpression
SimpleExpression -> Term | SimpleExpression AddOperator Term
Term -> Factor | Term MultiplyOperator Factor
Factor -> num
| true
| false
| Variable
| ( Expression )
| id ()
| id ( ExpressionList )
| not Factor
| - Factor
| + Factor
AddOperator -> + | - | or
MultiplyOperator -> * | / | div | mod | and
RelationOperator -> = | <> | < | <= | > | >=
\end{lstlisting}
\paragraph{对语法的调整} 相较于需求中给定的Pascal-S语法我们在开发和实践的过程中对于语法做出了如下的调整和扩充。
\begin{itemize}
\item 消除文法中存在的部分左递归例如VarDeclaration。消除左递归使得我们可以使用S-属性的翻译方案进行类型检查和代码生成。
\item 将ProcedureCall中添加空括号的产生式。支持在调用无参的过程或者是函数时添加一对空括号。
\item 删除Statment中产生funcid的产生式。因为Pascal中的函数返回语句只是一个合法的赋值语句在实际上并不会终止函数的执行。因此删除该产生式并在类型检查和代码生成的阶段进行进一步的处理。
\item 添加Factor中对于加号的支持。支持在运算的过程中使用显式注明的整数$ 1 ++ 1$类型的表达式。
\item 调整对于Factor中对于ProcedureCall的定义为Id() | Id (ExpressionList)。支持调用没有参数的函数。
\item 在FormalParameter中添加一对空括号。支持在定义无参的过程和函数时添加一对空括号。
\item 增加while-do语句的支持。
\end{itemize}
\paragraph{冲突的处理} 在实现使用LR(1)分析技术的语法分析器时我们发现在需求分析中给出的Pascal-S语法中存在着一处移进-归约冲突即语法中的ElsePart非终结符在对含有多个嵌套的If语句进行处理时ElsePart既可以直接从空产生式中归约出来也继续可以继续移进。但是在语法层面上Else语句应该和最近的一个If语句相结合。因此在语法分析器中做如下处理(1) 在构建分析表出添加一个特殊判断如果是检测到ElsePart的移进-归约冲突,则不报错继续处理;(2) 在按照分析表进行分析时首先进行移进操作然后再进行归约操作这样就能保证ElsePart会优先和最近和If语句进行结合。
\end{document}

256
docs/contents/source.tex Normal file
View File

@@ -0,0 +1,256 @@
\documentclass[../main.tex]{subfiles}
\begin{document}
\section{源程序清单}
为了使得项目开发更加清晰程序中由五个C\#项目组成:
\begin{itemize}
\item Canon.Core 编译器的核心库,含有编译器的所有核心功能。
\item Canon.Tests 编译器核心库的测试库,含有项目中编写的所有单元测试。
\item Canon.Console 编译器的命令行版本程序,在核心库的基础上以命令行的方式同编译器进行交互。
\item Canon.Server 编译器的服务器版本程序以Web的方式同编译器进行交互。
\item Canon.Generator 用于生成源代码形式的LR(1)分析表的工具。
\end{itemize}
代码中的总行数如表\ref{tab:code_lines}所示。
\begin{table}[htbp]
\centering
\begin{tabular}{|l|r|r|r|r|r|}
\hline
语言 & 文件数 & 行数 & 空白行数 & 注释数 & 代码行数 \\
\hline
C\# & 132 & 13263 & 1889 & 978 & 10396 \\
Pascal & 95 & 4989 & 368 & 34 & 4587 \\
TypeScript & 8 & 521 & 52 & 7 & 462 \\
MSBuild & 6 & 195 & 23 & 2 & 170 \\
TypeScript Typings & 2 & 149 & 7 & 13 & 129 \\
HTML & 1 & 12 & 0 & 0 & 12 \\
Python & 1 & 111 & 26 & 0 & 85 \\
\hline
\end{tabular}
\caption{代码行数统计}
\label{tab:code_lines}
\end{table}
\subsection{Canon.Core项目}
\begin{verbatim}
.
├── Abstractions
│ ├── ICompilerLogger.cs
│   ├── IGrammarParser.cs
│   ├── ILexer.cs
│   ├── ISourceReader.cs
│   ├── ITransformer.cs
│   └── SyntaxNodeVisitor.cs
├── Canon.Core.csproj
├── CodeGenerators
│   └── CCodeBuilder.cs
├── Enums
│   ├── BasicType.cs
│   ├── ErrorEnums.cs
│   ├── GrammarEnums.cs
│   └── SemanticEnums.cs
├── Exceptions
│   ├── GrammarException.cs
│   ├── LexemeException.cs
│   ├── ReduceAndShiftConflictException.cs
│   └── ReduceConflictException.cs
├── GrammarParser
│   ├── Expression.cs
│   ├── GeneratedParser.g.cs
│   ├── GrammarBuilder.cs
│   ├── Grammar.cs
│   ├── LrState.cs
│   ├── PascalGrammar.cs
│   └── Terminator.cs
├── LexicalParser
│   ├── LexemeFactory.cs
│   ├── Lexer.cs
│   ├── LexRules.cs
│   └── SemanticToken.cs
├── SemanticParser
│   ├── CodeGeneratorVisitor.cs
│   ├── PascalArrayType.cs
│   ├── PascalBasicType.cs
│   ├── PascalFunctionType.cs
│   ├── PascalParameterType.cs
│   ├── PascalType.cs
│   ├── Symbol.cs
│   ├── SymbolTable.cs
│   ├── SyntaxTreeTraveller.cs
│   ├── TypeCheckVisitor.cs
│   └── TypeTable.cs
└── SyntaxNodes
├── AddOperator.cs
├── BasicType.cs
├── CompoundStatement.cs
├── ConstDeclaration.cs
├── ConstDeclarations.cs
├── ConstValue.cs
├── ElsePart.cs
├── Expression.cs
├── ExpressionList.cs
├── Factor.cs
├── FormalParameter.cs
├── IdentifierList.cs
├── IdentifierVarPart.cs
├── MultiplyOperator.cs
├── NonTerminatedSyntaxNode.cs
├── Parameter.cs
├── ParameterList.cs
├── Period.cs
├── ProcedureCall.cs
├── ProgramBody.cs
├── ProgramHead.cs
├── ProgramStruct.cs
├── RelationOperator.cs
├── SimpleExpression.cs
├── Statement.cs
├── StatementList.cs
├── SubprogramBody.cs
├── Subprogram.cs
├── SubprogramDeclarations.cs
├── SubprogramHead.cs
├── SyntaxNodeBase.cs
├── Term.cs
├── TerminatedSyntaxNode.cs
├── TypeSyntaxNode.cs
├── ValueParameter.cs
├── VarDeclaration.cs
├── VarDeclarations.cs
├── Variable.cs
└── VarParameter.cs
\end{verbatim}
\subsection{Canon.Console项目}
\begin{verbatim}
.
├── Canon.Console.csproj
├── Extensions
│   └── RootCommandExtensions.cs
├── Models
│   └── CompilerOption.cs
├── Program.cs
└── Services
├── Compiler.cs
├── CompilerLogger.cs
└── StringSourceReader.cs
\end{verbatim}
\subsection{Canon.Server项目}
\begin{verbatim}
.
├── appsettings.json
├── Canon.Server.csproj
├── client-app
│   ├── index.html
│   ├── package.json
│   ├── pnpm-lock.yaml
│   ├── public
│   │   └── pic
│   │   └── uncompiled.png
│   ├── src
│   │   ├── App.tsx
│   │   ├── main.tsx
│   │   ├── openapi.d.ts
│   │   ├── Pages
│   │   │   ├── HistoryPage.tsx
│   │   │   ├── Index.tsx
│   │   │   ├── InputField.tsx
│   │   │   ├── Loader.tsx
│   │   │   └── OutputField.tsx
│   │   └── vite-env.d.ts
│   ├── tsconfig.json
│   ├── tsconfig.node.json
│   └── vite.config.ts
├── Controllers
│   ├── CompilerController.cs
│   └── FileController.cs
├── DataTransferObjects
│   ├── CompileResponse.cs
│   └── SourceCode.cs
├── Dockerfile
├── Entities
│   └── CompileResult.cs
├── Extensions
│   └── ServiceCollectionExtensions.cs
├── Models
│   ├── Brush.cs
│   ├── CodeReader.cs
│   ├── CompilerLogger.cs
│   └── PresentableTreeNode.cs
├── Program.cs
├── Properties
│   └── launchSettings.json
├── Services
│   ├── CompileDbContext.cs
│   ├── CompilerService.cs
│   ├── DatabaseSetupService.cs
│   ├── GridFsService.cs
│   └── SyntaxTreePresentationService.cs
└── wwwroot
\end{verbatim}
\subsection{Canon.Generator项目}
\begin{verbatim}
.
├── Canon.Generator.csproj
├── Extensions
│   └── RootCommandExtension.cs
├── GrammarGenerator
│   ├── GenerateCommand.cs
│   ├── GeneratedGrammarParser.cs
│   ├── GeneratedTransformer.cs
│   └── GrammarExtensions.cs
├── Program.cs
└── SyntaxVisitorGenerator
└── SyntaxVisitorGenerator.cs
\end{verbatim}
\subsection{Canon.Tests项目}
\begin{verbatim}
.
├── Canon.Tests.csproj
├── GeneratedParserTests
│   └── GenerateParserTests.cs
├── GlobalUsings.cs
├── GrammarParserTests
│   ├── PascalGrammarFailedTests.cs
│   ├── PascalGrammarTests.cs
│   ├── SimpleGrammarTests.cs
│   ├── SimpleGrammarWithEmptyTests.cs
│   └── TerminatorTests.cs
├── LexicalParserTests
│   ├── CharacterTypeTests.cs
│   ├── DelimiterTests.cs
│   ├── ErrorSingleTests.cs
│   ├── IndentifierTypeTests.cs
│   ├── KeywordTypeTests.cs
│   ├── LexicalFileTests.cs
│   ├── NumberTests.cs
│   └── OperatorTypeTests.cs
├── SemanticTests
│   ├── ConstValueTests.cs
│   ├── PascalTypeTests.cs
│   ├── SymbolTableTests.cs
│   ├── SyntaxTreeTravellerTests.cs
│   ├── Tests.cs
│   └── TypeCheckVisitorTests.cs
└── Utils
├── CompilerHelpers.cs
├── EnumerableExtensions.cs
├── SampleSyntaxTreeVisitor.cs
├── StringSourceReader.cs
├── StringSourceReaderTests.cs
└── TestLogger.cs
\end{verbatim}
\end{document}

110
docs/contents/summary.tex Normal file
View File

@@ -0,0 +1,110 @@
\documentclass[../main.tex]{subfiles}
\begin{document}
\section{课程设计总结}
% 1) 体会/收获(每个成员完成的工作、收获等)
% 2) 设计过程中遇到或存在的主要问题及解决方案
% 3) 改进建议
\subsection{成员分工}
\begin{table}[htbp]
\centering
\begin{tabular}{|l|l|}
\hline
\textbf{姓名} & \textbf{分工} \\
\hline
任昌骏 & 组长,主持开发工作,负责编译器的总体设计 \\
\hline
张弈纶 & 负责语法分析和类型检查部分的开发,前端界面的搭建 \\
\hline
兰建国 & 类型系统、符号表和代码生成部分的开发 \\
\hline
肖可扬 & 词法令牌、词法分析器的设计与实现 \\
\hline
杜含韵 & 词法分析和语法分析单元测试的编写 \\
\hline
陈劲淞 & 撰写课程设计文档 \\
\hline
\end{tabular}
\caption{成员分工表}
\label{tab:my_label}
\end{table}
\subsection{体会与收获}
\subsubsection{张弈纶}
这次课设我主要负责语法的定义引入、语义分析中代码类型检查和前端可视化落地。上学期我仅仅是完成了课程要求的词法分析和语法分析实验对语义分析和其中的代码类型检查部分没有实际的了解。通过这次课程设计我充分理解了代码类型检查的必要性和其实际运作流程。并且我通过这次的编码了解了c\#访问者模式和事件机制的使用,对我来说是一次开阔眼界的过程,收获颇多。
编码过程中,我经历了多次代码重构和迭代更新,这让我充分认识到代码质量对编码效率的影响。同时,对于一些代码设计,也重复验证了很多次。这给我很深的体会,在今后的程序设计中要做好规划,提高代码的鲁棒性与可读性。
这次课设还让我培养了一定的文献调查能力。对于语法树的绘制,我参考了相关论文并进行了复现,这也让我锻炼了论文的调研能力。同时,这次课设也让我加深了团队合作的精神,培养了团结协作的能力。
\subsubsection{兰建国}
本次课程设计我主要负责代码生成部分在此过程中我学到了很多。首先我对代码生成的过程有了更深入的理解。在一开始我以为代码生成就是机械地将Pascal-S代码翻译到C语言代码但是在动手编码之后发现困难重重。在经过几次代码重构和迭代之后我反应过来发现是设计方面的缺陷。在开始的设计中我采取的是一种"遍历到哪,翻译到哪"的设计。但实际上应该在语法树上收集够了相应信息之后才进行代码的生成。其次,我体会到了三地址代码的便捷性。在引入三地址代码之前,很多语句的代码生成很难进行,在引入三地址代码之后,代码生成的过程也更加清晰,程序的可扩展性也大大增加。此外,我还感受到了在开发过程的不稳定性,在实际开发过程中,设计方案需要不断调整,以适应各种变化。最后,此次课设还让我感受到了团队交流的重要性,通过在发现问题时及时沟通,提高了我们的开发效率。
\subsubsection{任昌骏}
在这次的课程设计中我有幸担任组长,负责整个编译器的总体设计。站在现在的角度上看来,当时选择整个编译器的设计和实现不依赖与传统的工具而是完全手动实现是非常冒险的,并且在语言选型方面也非常的``激进''。所幸在全组同学的通力配合以及王老师和助教同学的大力支持下顺利完成了。
这次的课程设计也算是我个人能力上的一次突破。在以往的项目经历中,很少有像``设计一个编译器''这样一个在算法设计和软件设计上都非常具有挑战性的课题。也正是有了这个机会使我得以将过去几年学到的各种知识和技能融会贯通无论是按照编译课本上的描述实现词法分析和语法分析的相关算法还是考古论文实现树的可视化绘制都是对于我算法能力的考验亦或是使用访问者模式在语法树节点上扩展各种功能还是通过事件的机制抽象同一个语法树节点可能使用的多个不同的生成式都是对我软件工程能力的挑战。而且编译原理课程设计作为大学少数几门要求由5至7人协作完成的课程设计也进一步锻炼了我组织小组合作的能力。无论是复杂项目的\texttt{Git}管理还是使用\texttt{CI/CD}实现持续测试和快速部署,都是我在个人项目之中难以接触到的东西。
最后还是非常感谢老师和各位同学能给我这样一个锻炼自己和提高自己的机会,在未来我一定认真复盘这次课程设计中的得与失,进一步的提高自己的个人能力。
\subsubsection{肖可扬}
这次课设我主要负责词法分析器的编写在上学期实验的基础上此次课设的词法分析器更接近真实场景需要处理更多与Pascal语法特性相关的内容所以在前期调研方面我们首先形成了翻译表在这个过程中我感受到了明确需求的重要性。此外这是我第一次使用C\#在纯代码环境中开发软件,和小组同学学习到了许多工具的使用、软件的组织架构以及代码编写规范等内容;在重构自己的代码的过程中,我更好地理解和掌握了面向对象的相关语法以及设计模式。同时,和负责测试的同学进行交流也是宝贵的经验,我意识到了自己在编程方面的严谨性还有待提高,需要更系统地全面地考虑输入的各种情况。在编写程序过程中,我明白了团队交流的意义,在编译的不同流程之间确定接口以及对特殊问题的配合处理需要团队紧密讨论、通力协作,才能提高代码的效率。
\subsubsection{杜含韵}
本次课程设计我主要参与了单元测试的部分编写并在此过程中受益良多。首先是通过此次项目我熟悉了C\#语言特性与.NET框架纠正了先前对git的错误使用帮助拓宽了我的技术认知与技术组成。其次是对Xunit测试框架的熟练使用也使得我在本学期其他课程中操作实践。最后是作为对上学期编译原理理论课程延申而出的课程设计帮助我学会如何将理论转化为实践以及如何克服实践的具体困难。同时我还认识到了测试的编写需要更为明确的对组件任务的了解和认知需要从宏观角度上思考测试的方向和方法组内同学的实践也让我了解到测试载体的多样化。而头歌平台测试集对边界情况的探索也帮助我认识到在思考的完善性上有着诸多不足。最后我最为感激的是组内同学的通力配合和辛苦付出他们在我遇到困难时的耐心解答与帮助使得我受益匪浅。此次学习实践经验也将助力我日后的学习生活行稳致远。
\subsubsection{陈劲淞}
在本次课程设计中,我主要负责撰写项目的文档。这不仅仅包括项目的设计文档,还有整个开发过程的文档记录和最终的报告。通过这个过程,我深刻理解到了文档在软件开发过程中的重要性。良好的文档不仅可以帮助团队成员理解和维护代码,还可以为未来的开发提供参考。
首先,我学习并实践了如何使用\LaTeX 来创建专业的文档。这包括了解其基本语法、文档结构组织、图表和代码的插入等。这些技能的获得,让我在未来的学术写作和报告制作中更加得心应手。
其次团队在开发过程中采用Docker容器化技术这极大地提高了开发环境的一致性和项目的可移植性。通过Docker我们能够确保每个团队成员都在相同的环境中开发和测试减少了环境差异带来的问题。我在文档中详细记录了如何使用Docker来配置和管理我们的开发环境这对于团队成员理解整个系统的部署和运行至关重要。
同时本项目中使用Git进行版本控制和团队协作我负责记录各个分支的合并和版本发布的详细过程确保所有团队成员都能迅速地获取最新的项目状态和历史修改记录。这不仅提高了团队的工作效率也增强了项目的可追溯性。
在编译原理中,词法分析、语法分析和语义分析是构建编译器的重要步骤。在本次课程设计中,我们团队的工作涉及到了这些方面,我作为文档撰写者也深刻地参与其中并从中受益匪浅。通过撰写文档的过程,我加深了对整个项目的理解,提高了与团队成员的交流合作能力,并锻炼了自己的表达和文字组织能力。
此外我参与了单元测试和集成测试的文档撰写记录了测试策略和测试结果。通过Xunit框架进行单元测试以及使用Jenkins进行持续集成我们能够及时发现并解决开发过程中出现的问题保证软件质量。这一过程不仅加深了我对测试理论的理解也提升了我在实际项目中应用测试的能力。
最后,通过这次经验,我认识到了持续学习和自我提升的重要性。未来,我希望能继续提高我的专业技能,尤其是在技术写作和项目管理方面,以便在未来的职业生涯中更好地服务于团队和项目。
\subsection{设计中的主要问题和解决方案}
\paragraph{生成LR(1)分析表耗时较长}
在编译的过程中从原始的语法生成对应的LR(1)分析表是一个时间复杂度较大的工作。经过实际测试生成本课程设计中需要支持的基础语法对应的分析表就需要大约7秒至10秒的时间。
\textbf{解决方案}: 将生成好的LR(1)分析表以C\#源代码的形式直接输出再打包编译到程序中。在输入的Pasccal语法没有变化的情况下不用重复的生成相同的分析表。
\paragraph{语法树的访问者和类型检测访问者}
在编译过程中,管理和遍历语法树对于进行有效的类型检查和语义分析至关重要。传统的遍历方法可能导致代码重复,难以维护,且使用递归进行遍历还可能因为递归深度过深而造成占空间耗尽的运行时错误。
\textbf{解决方案}: 采用访问者设计模式Visitor Pattern来分离数据结构和操作。这使得在不修改语法树结构的情况下添加新的操作变得简单提高了代码的可维护性和扩展性。对于类型检测定义一个专门的类型检测访问者该访问者遍历语法树并对每个节点进行类型验证。
\paragraph{语法中的左递归难以进行类型检查}
在原始需求文档中给定的Pascal语法中存在的大量左递归使得我们在进行语义分析时很难设计出S-属性的翻译方案。
\textbf{解决方案}: 改写文法,消除文法中的左递归,详解\ref{pascal_grammar}节中给出的对应语法和修改说明。
\paragraph{代码生成中涉及的各种困难}
在初始设计面向C语言的代码生成时在翻译循环语句和函数调用语句时遇到了很大的困难因为初始化仍然采用一对一的翻译思想试图将Pascal中的每一个语法结构都翻译到一个对应的语法结构。
\textbf{解决方案}: 借鉴三地址代码将翻译思想设计为翻译到一种使用C语言书写的三地址代码并且大量的使用\texttt{goto}语句和标签,成功地解决了上述问题。
\subsection{改进建议}
\paragraph{提供更为详尽的报错信息} 目前语法分析的报错系统仍然十分的不人性化,仅仅输出了编译器此时希望输入什么样的词法记号。
\paragraph{进行代码优化} 因为在进行代码生成时使用率类似于三地址代码的代码生成形式,因此在进行代码生成会生成大量的冗余变量,造成程序的编译时间和运行占用的内存空间都非常大。
\end{document}

View File

@@ -0,0 +1,27 @@
\documentclass[../main.tex]{subfiles}
\begin{document}
\section{课程设计的任务和目标}
课程设计的目标是设计一个针对Pascal-S语言的编译程序使用C语言作为编译器的目标语言。
课程设计的目标是设计并实现一个编译器该编译器能够将Pascal-S语言编写的源代码转换为C语言代码。Pascal-S是Pascal语言的一个子集专门用于教学目的它包含了Pascal语言的核心特性但去除了一些复杂的构造以简化学习和编译过程。
编译器的设计将分为几个主要部分:
\begin{enumerate}
\item \textbf{词法分析器(Lexical Analyzer)}: 该部分将读取源代码并将其分解成一系列的标记tokens这些标记是编译过程中语法分析的基本单位。
\item \textbf{语法分析器(Syntax Analyzer)}: 语法分析器将使用词法分析器提供的标记来构建抽象语法树AST。AST是源代码的树状表示反映了程序的结构。
\item \textbf{语义分析器(Semantic Analyzer)}: 语义分析器将检查AST以确保源代码的逻辑是一致的例如变量的声明与使用是否匹配类型是否兼容等。
\item \textbf{中间代码生成器(Intermediate Code Generator)}: 该部分将AST转换为中间表示IRIR是一种更接近机器语言的代码形式但仍然保畴一定程度的抽象。
\item \textbf{代码优化器(Code Optimizer)}: 代码优化器将对IR进行分析和转换以提高生成的C代码的效率和性能。
\item \textbf{目标代码生成器(Target Code Generator)}: 最后目标代码生成器将把优化后的IR转换为C语言代码这是编译过程的最终产物。
\end{enumerate}
此外,编译器还将包括错误处理机制,以便在编译过程中捕捉并报告错误,帮助用户理解并修正源代码中的问题。
整个编译器的设计将遵循模块化原则每个部分都将有明确的接口和职责以便于测试和维护。我们还将使用C语言的特性如指针和结构体来高效地实现编译器的各个组成部分。
最终我们的目标是实现一个健壮的编译器它不仅能够正确地将Pascal-S代码转换为C代码而且还能够提供有用的错误信息帮助用户改进他们的源代码。
\end{document}