sin*_*ity 2 io haskell parsec megaparsec
我正在为 DSL 编写编译器。将源文件读入字符串后,剩下的所有步骤(解析、类型检查和代码生成)都是纯代码,将代码从一种表示形式转换为另一种表示形式。一切都很好,直到源文件中有依赖项(想想 中的#include预处理器C)。解析器需要读取依赖文件并递归解析它们。这使它不再纯洁。我必须将其从返回更改AST为IO AST. 此外,所有后续步骤(类型检查和代码生成)也必须返回 IO 类型,这需要进行重大更改。在这种情况下处理读取依赖文件的好方法是什么?
ps我可以使用unsafePerformIO,但这似乎是一个可能导致技术债务的hacky解决方案。
一个好的解决方案是解析成一个包含依赖信息的 AST,然后在解析器之外单独解析依赖。例如,假设您的格式可能是#include一行或内容行:
data WithIncludes = WithIncludes [ContentOrInclude]
data ContentOrInclude
= Content String
| Include FilePath
Run Code Online (Sandbox Code Playgroud)
和一个解析器,parse :: String -> WithIncludes以便这些文件:
file1:
before
#include "file2"
after
Run Code Online (Sandbox Code Playgroud)
file2:
between
Run Code Online (Sandbox Code Playgroud)
解析这些表示:
file1 = WithIncludes
[ Content "before"
, Include "file2"
, Content "after"
]
file2 = WithIncludes
[ Content "between"
]
Run Code Online (Sandbox Code Playgroud)
您可以添加另一种表示已解决导入的扁平文件的类型:
data WithoutIncludes = WithoutIncludes [String]
Run Code Online (Sandbox Code Playgroud)
与解析分开,加载和递归展平包括:
flatten :: WithIncludes -> IO WithoutIncludes
flatten (WithIncludes ls) = WithoutIncludes . concat <$> traverse flatten' ls
where
flatten' :: ContentOrInclude -> IO [String]
flatten' (Content content) = pure [content]
flatten' (Include path) = do
contents <- readFile path
let parsed = parse contents
flatten parsed
Run Code Online (Sandbox Code Playgroud)
那么结果是:
flatten file1 == WithoutIncludes
[ "before"
, "between"
, "after"
]
Run Code Online (Sandbox Code Playgroud)
解析仍然是纯粹的,你只是IO在它周围有一个包装器来驱动加载哪些文件。您甚至可以重用此处的逻辑来加载单个文件:
load :: FilePath -> IO WithoutIncludes
load path = flatten $ WithIncludes [Include path]
Run Code Online (Sandbox Code Playgroud)
在这里添加逻辑来检查导入周期也是一个好主意,例如通过添加一个累加器来flatten包含Set规范化的FilePaths,并检查每个Include你还没有看到相同的FilePath。
对于更复杂的 AST,您可能希望在未解析类型和已解析类型之间共享大部分结构。在这种情况下,您可以通过是否已解析来参数化类型,并将未解析和已解析的类型作为具有不同参数的基础 AST 类型的别名,例如:
data File i = File [ContentOrInclude i]
data ContentOrInclude i
= Content String
| Include i
type WithIncludes = File FilePath
type WithoutIncludes = File [String]
Run Code Online (Sandbox Code Playgroud)