You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Megaparsec解析ls输出:如何在$标记前停止且不消耗标记?

解决Megaparsec解析Advent of Code 2022 Day7输入的问题

输入示例

$ cd /
$ ls
dir a
123 foo.txt
dir d
$ cd a
$ ls
dir e
4567 f
78901 h.zip
$ cd e

期望解析数据结构

data DirContent = File (Integer, String) | Directory String deriving stock (Show)

data Input
  = Cd String  -- 原代码中DirectoryName应为String
  | CdRoot     -- cd /
  | CdPop      -- cd ..
  | Ls [DirContent]
  deriving stock (Show)

当前解析器代码

import Text.Megaparsec
import Text.Megaparsec.Char

parseFull :: Parsec Void Text [Input]
parseFull = inputParser `sepBy` newline

inputParser :: Parsec Void Text Input
inputParser =
  CdRoot <$ string "$ cd /"
    <|> CdPop <$ string "$ cd .."
    <|> Cd <$> (string "$ cd " *> many alphaNumChar)
    <|> Ls <$> ((string "$ ls" <* newline) *> dirContentParser `sepBy` newline)

dirContentParser :: Parsec Void Text DirContent
dirContentParser =
  Directory <$> (string "dir " *> (pure <$> alphaNumChar))
    <|> curry File <$> decimal <* char ' ' <*> many (alphaNumChar <|> char '.')

遇到的问题

当前解析器仅在输入包含单个ls命令且位于末尾时有效,否则会抛出错误:

input> IN: "$ cd /<newline>$ ls<newline>dir d<newli <…>
input> MATCH (CERR): "$ cd /<newline>$ ls<newline>dir d<newli <…>
input> ERROR:
input> offset=33:
input> unexpected "$ cd"
input> expecting "dir " or integer

核心问题:ls分支的解析器不会在遇到下一个以$开头的命令时停止,且无法做到不消耗$标记的情况下终止解析。

解决方案

修改后的完整代码

import Text.Megaparsec
import Text.Megaparsec.Char
import Control.Applicative (optional)

data DirContent = File (Integer, String) | Directory String deriving stock (Show)

data Input
  = Cd String
  | CdRoot
  | CdPop
  | Ls [DirContent]
  deriving stock (Show)

-- 前瞻检测是否为命令行开头(不消耗输入)
isCommandLine :: Parsec Void Text ()
isCommandLine = void (lookAhead (string "$ "))

dirContentParser :: Parsec Void Text DirContent
dirContentParser =
  -- 修复目录名解析:支持多字符目录名
  Directory <$> (string "dir " *> many alphaNumChar)
    <|> curry File <$> decimal <* char ' ' <*> many (alphaNumChar <|> char '.')

inputParser :: Parsec Void Text Input
inputParser =
  CdRoot <$ string "$ cd /" <* optional newline
    <|> CdPop <$ string "$ cd .." <* optional newline
    <|> Cd <$> (string "$ cd " *> many alphaNumChar) <* optional newline
    <|> do
        string "$ ls" <* newline
        -- 收集内容直到文件结束,或遇到下一个命令行
        contents <- manyTill dirContentParser (eof <|> (void newline *> isCommandLine))
        pure $ Ls contents

parseFull :: Parsec Void Text [Input]
parseFull = many inputParser <* eof

关键修改说明

  1. 修复目录名解析:将原代码中pure <$> alphaNumChar改为many alphaNumChar,支持解析多字符目录名。
  2. 添加前瞻检测解析器:isCommandLine用lookAhead检测下一个输入是否为$ ,不消耗任何输入,确保遇到命令行时停止ls内容解析。
  3. 重写ls分支逻辑:使用manyTill收集目录内容,终止条件为文件结束或换行后检测到命令行开头,避免将下一个命令行误判为ls结果。
  4. 处理命令行换行:给所有cd相关解析器添加<* optional newline,确保命令行后的换行被正确消耗,避免残留换行干扰后续解析。
  5. 调整整体解析逻辑:将parseFull改为many inputParser <* eof,让每个命令解析器自行处理换行和内容,更适配多命令场景。

修改后的解析器可以正确处理示例输入,生成符合预期的[Input]列表。

内容的提问来源于stack exchange,提问作者l7r7

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 17:50:23