name = "pretty-show"
description = "Parser/printer for derived `Show` output (yav/pretty-show). Bug fixes mined from upstream history; modern HEAD is the base, each patch reverse-applies a fix to install the original bug."
language = "haskell"
strategies = ["quickcheck", "hedgehog", "falsify", "smallcheck"]
crate = "pretty-show"
base_commit = "1ac2eb4be001475817d918ea0ee0c8300143693b"

# ---------------------------------------------------------------------------
# Variant: relax_parser_atoms_879d764d_1
# ---------------------------------------------------------------------------
[[tasks]]
mutations = ["relax_parser_atoms_879d764d_1"]

[tasks.source]
repo = "https://github.com/yav/pretty-show"
commits = ["879d764dff30595c0fcdedd07fa7f4bbfbc3d389"]
commit_subjects = ["Relax the parser a bit to support lists of atoms."]
origin = "internal"
summary = "The buggy app_value rule required a leading constructor (`con list1(avalue)` or a single `avalue`), so a sequence of atoms with no leading constructor (e.g. `1 2 3`, useful for `Show` instances like `StdGen`) failed to parse. The fix replaces the rule with `list1(avalue) { mkValue $1 }` and introduces an `mkValue` helper that wraps bare atom sequences in `Con \"\" [...]`."

[tasks.injection]
kind = "patch"
files = ["Text/Show/Parser.y"]
locations = [{ file = "Text/Show/Parser.y", line = 62, symbol = "app_value" }]
patch = "patches/relax_parser_atoms_879d764d_1.patch"

[tasks.bug]
short_name = "app_value_rejects_atom_lists"
invariant = "parseValue applied to a whitespace-separated sequence of two or more decimal integer literals (e.g. `\"1 2 3\"`) returns `Just _`, never `Nothing`."
how_triggered = "Reverse-applying the patch restores the pre-fix `app_value : con list1(avalue) { Con $1 $2 } | avalue { $1 } | '-' avalue { Neg $2 }` rule. Calling `parseValue \"1 2 3\"` then returns `Nothing` instead of `Just (Con \"\" [Integer \"1\", Integer \"2\", Integer \"3\"])`."

[[tasks.tasks]]
property = "AtomSequenceParses"
witnesses = [
  { test_fn = "witness_atom_sequence_parses_case_1_2_3", note = "parseValue \"1 2 3\" must be Just _" },
  { test_fn = "witness_atom_sequence_parses_case_42_99", note = "parseValue \"42 99\" must be Just _" },
]

# ---------------------------------------------------------------------------
# Variant: ratio_round_trip_a0e39e1c_1
# ---------------------------------------------------------------------------
[[tasks]]
mutations = ["ratio_round_trip_a0e39e1c_1"]

[tasks.source]
repo = "https://github.com/yav/pretty-show"
commits = ["a0e39e1c26bd7f82156d4db0831a8540a72c871c"]
commit_subjects = ["Pretty-print Ratios so they can be Read back"]
origin = "internal"
summary = "valToDoc rendered a `Ratio x y` as `ppCon \"(%)\" [x,y]`, producing output like `(%) 1 2`. That string parses back as `Con \"(%)\" [Integer \"1\", Integer \"2\"]`, not as a Ratio, so the Show round-trip via parseValue is broken. The fix renders Ratio as `x % y` directly."

[tasks.injection]
kind = "patch"
files = ["Text/Show/Pretty.hs"]
locations = [{ file = "Text/Show/Pretty.hs", line = 177, symbol = "valToDoc" }]
patch = "patches/ratio_round_trip_a0e39e1c_1.patch"

[tasks.bug]
short_name = "ratio_pretty_loses_ratio_constructor"
invariant = "Pretty-printing a `Ratio (Integer n) (Integer d)` and re-parsing the result yields `Just (Ratio _ _)` (the same shape, not a regular constructor application)."
how_triggered = "Reverse-applying the patch restores `Ratio x y -> ppCon \"(%)\" [x,y]`. Calling `parseValue (valToStr (Ratio 1 2))` then returns `Just (Con \"(%)\" [Integer \"1\", Integer \"2\"])` instead of `Just (Ratio (Integer \"1\") (Integer \"2\"))`."

[[tasks.tasks]]
property = "RatioRoundTrip"
witnesses = [
  { test_fn = "witness_ratio_round_trip_case_1_over_2", note = "Ratio 1/2 must round-trip as Ratio _ _" },
  { test_fn = "witness_ratio_round_trip_case_7_over_3", note = "Ratio 7/3 must round-trip as Ratio _ _" },
]

# ---------------------------------------------------------------------------
# Variant: unary_minus_precedence_aefc6dfc_1
# ---------------------------------------------------------------------------
[[tasks]]
mutations = ["unary_minus_precedence_aefc6dfc_1"]

[tasks.source]
repo = "https://github.com/yav/pretty-show"
commits = ["aefc6dfc6ec75607471af90df8b8ee4bc50dfe80"]
commit_subjects = ["Make unary minus have high precedence."]
origin = "internal"
summary = "The buggy grammar puts `'-' avalue { Neg $2 }` at the `value` level, so a unary minus cannot appear in any context that requires `app_value` (notably the right-hand side of a Ratio: `1 % -2`). The fix moves the rule down so it lives at the `app_value` level (after later restructurings)."

[tasks.injection]
kind = "patch"
files = ["Text/Show/Parser.y"]
locations = [
  { file = "Text/Show/Parser.y", line = 54, symbol = "value" },
  { file = "Text/Show/Parser.y", line = 62, symbol = "app_value" },
]
patch = "patches/unary_minus_precedence_aefc6dfc_1.patch"

[tasks.bug]
short_name = "unary_minus_low_precedence"
invariant = "parseValue applied to a Ratio expression with a negated denominator (e.g. `\"1 % -2\"`) returns `Just (Ratio _ (Neg _))`."
how_triggered = "Reverse-applying the patch moves `'-' avalue` from app_value back to value. The right-hand side of `value '%' app_value` then cannot start with `-`, so `parseValue \"1 % -2\"` returns `Nothing`."

[[tasks.tasks]]
property = "NegInRatioParses"
witnesses = [
  { test_fn = "witness_neg_in_ratio_parses_case_1_neg2", note = "parseValue \"1 % -2\" must be Just (Ratio _ (Neg _))" },
  { test_fn = "witness_neg_in_ratio_parses_case_5_neg9", note = "parseValue \"5 % -9\" must be Just (Ratio _ (Neg _))" },
]

# ---------------------------------------------------------------------------
# Variant: reserved_op_infix_122936f1_1
# ---------------------------------------------------------------------------
[[tasks]]
mutations = ["reserved_op_infix_122936f1_1"]

[tasks.source]
repo = "https://github.com/yav/pretty-show"
commits = ["122936f1e67774ca433c6d884ce78053376c154c"]
commit_subjects = ["Treat reserved operators as infix constructors."]
origin = "internal"
summary = "The buggy `infixcon` rule does not include `RESOP` (Haskell reserved operators such as `..`, `::`, `=>`). Show output that uses one of these as an infix operator (e.g. `Foo .. Bar`) fails to parse. The fix adds `RESOP { $1 }` as an alternative."

[tasks.injection]
kind = "patch"
files = ["Text/Show/Parser.y"]
locations = [{ file = "Text/Show/Parser.y", line = 97, symbol = "infixcon" }]
patch = "patches/reserved_op_infix_122936f1_1.patch"

[tasks.bug]
short_name = "infixcon_drops_reserved_op"
invariant = "parseValue applied to `\"<ConA> .. <ConB>\"` returns `Just (InfixCons (Con A []) [(\"..\", Con B [])])`."
how_triggered = "Reverse-applying the patch removes the `RESOP { $1 }` alternative from infixcon. `parseValue \"Foo .. Bar\"` then returns `Nothing` because `..` lexes as a Reservedop and no infixcon production matches."

[[tasks.tasks]]
property = "ReservedOpInfixParses"
witnesses = [
  { test_fn = "witness_reserved_op_infix_parses_case_FooBar", note = "parseValue \"Foo .. Bar\" must be Just (InfixCons _ [(\"..\", _)])" },
  { test_fn = "witness_reserved_op_infix_parses_case_AaBb",   note = "parseValue \"Aa .. Bb\" must be Just (InfixCons _ [(\"..\", _)])" },
]

# ---------------------------------------------------------------------------
# Variant: varsym_infix_4a810ee5_1
# ---------------------------------------------------------------------------
[[tasks]]
mutations = ["varsym_infix_4a810ee5_1"]

[tasks.source]
repo = "https://github.com/yav/pretty-show"
commits = ["4a810ee5ec8c55c026c353d28ae94c11071e80bb"]
commit_subjects = ["Allow varsyms to be used as infix constructors."]
origin = "internal"
summary = "The buggy `infixcon` rule does not include `VARSYM` or `QVARSYM`. Show output that uses a variable symbol as an infix operator (e.g. `Foo + Bar`, `Foo <> Bar`) fails to parse. The fix adds `VARSYM`/`QVARSYM` as alternatives."

[tasks.injection]
kind = "patch"
files = ["Text/Show/Parser.y"]
locations = [{ file = "Text/Show/Parser.y", line = 97, symbol = "infixcon" }]
patch = "patches/varsym_infix_4a810ee5_1.patch"

[tasks.bug]
short_name = "infixcon_drops_varsym"
invariant = "parseValue applied to `\"<ConA> + <ConB>\"` returns `Just (InfixCons (Con A []) [(\"+\", Con B [])])`."
how_triggered = "Reverse-applying the patch removes the `VARSYM { $1 }` and `QVARSYM { $1 }` alternatives. `parseValue \"Foo + Bar\"` then returns `Nothing` because `+` lexes as a Varsym and no infixcon production matches."

[[tasks.tasks]]
property = "VarsymInfixParses"
witnesses = [
  { test_fn = "witness_varsym_infix_parses_case_FooBar",    note = "parseValue \"Foo + Bar\" must be Just (InfixCons _ [(\"+\", _)])" },
  { test_fn = "witness_varsym_infix_parses_case_AlphaBeta", note = "parseValue \"Alpha + Beta\" must be Just (InfixCons _ [(\"+\", _)])" },
]

# ---------------------------------------------------------------------------
# Variant: reserved_id_con_cfb8b434_1
# ---------------------------------------------------------------------------
[[tasks]]
mutations = ["reserved_id_con_cfb8b434_1"]

[tasks.source]
repo = "https://github.com/yav/pretty-show"
commits = ["cfb8b4344084fe6b427b5287b8a24f98361a3e67"]
commit_subjects = ["Allow keywords as the names of constructors"]
origin = "internal"
summary = "The buggy `con` rule does not include `RESID` (Haskell reserved identifiers such as `data`, `case`, `module`). A Show instance whose constructor name happens to be a Haskell keyword fails to parse. The fix adds `RESID { $1 }` as an alternative."

[tasks.injection]
kind = "patch"
files = ["Text/Show/Parser.y"]
locations = [{ file = "Text/Show/Parser.y", line = 83, symbol = "con" }]
patch = "patches/reserved_id_con_cfb8b434_1.patch"

[tasks.bug]
short_name = "con_drops_reserved_id"
invariant = "parseValue applied to a single Haskell reserved identifier (e.g. `\"data\"`, `\"module\"`) returns `Just (Con <word> [])`."
how_triggered = "Reverse-applying the patch removes the `RESID { $1 }` alternative from `con`. `parseValue \"data\"` then returns `Nothing` because `data` lexes as a Reservedid and no `con` production accepts it."

[[tasks.tasks]]
property = "ReservedIdConParses"
witnesses = [
  { test_fn = "witness_reserved_id_con_parses_case_data",   note = "parseValue \"data\" must be Just (Con \"data\" [])" },
  { test_fn = "witness_reserved_id_con_parses_case_module", note = "parseValue \"module\" must be Just (Con \"module\" [])" },
]

# ---------------------------------------------------------------------------
# Variant: constructor_extra_args_6fb39a38_1
# ---------------------------------------------------------------------------
[[tasks]]
mutations = ["constructor_extra_args_6fb39a38_1"]

[tasks.source]
repo = "https://github.com/yav/pretty-show"
commits = ["6fb39a3839119079a05404e07e3186c4c7f75049"]
commit_subjects = ["If a constructor is applied to more values, add them as children."]
origin = "internal"
summary = "The buggy `mkValue` clause `mkValue (Con x [] : vs) = Con x vs` only handles a leading constructor with no arguments. When the leading element of an `app_value` list already has arguments (because of extra parens, e.g. `(Foo 1) 2 3`), the trailing atoms are not folded into the leading Con; they fall through to `mkFakeCon` and get wrapped in an empty-named `Con \"\"`. The fix generalises the clause to `Con x as` and concatenates the trailing values onto `as`."

[tasks.injection]
kind = "patch"
files = ["Text/Show/Parser.y"]
locations = [{ file = "Text/Show/Parser.y", line = 147, symbol = "mkValue" }]
patch = "patches/constructor_extra_args_6fb39a38_1.patch"

[tasks.bug]
short_name = "mkValue_drops_existing_args"
invariant = "parseValue on `\"(<lead> <leadArg>) <a> <b>\"` returns `Just (Con <lead> [Integer <leadArg>, Integer <a>, Integer <b>])`."
how_triggered = "Reverse-applying the patch restores `mkValue (Con x [] : vs) = Con x vs`. The non-empty case falls through to `mkFakeCon`, so `parseValue \"(Foo 1) 2 3\"` returns `Just (Con \"\" [Con \"Foo\" [Integer \"1\"], Integer \"2\", Integer \"3\"])` instead of `Just (Con \"Foo\" [Integer \"1\", Integer \"2\", Integer \"3\"])`."

[[tasks.tasks]]
property = "ConstructorExtraArgs"
witnesses = [
  { test_fn = "witness_constructor_extra_args_case_Foo123",  note = "parseValue \"(Foo 1) 2 3\" must equal Con Foo [1,2,3]" },
  { test_fn = "witness_constructor_extra_args_case_Node567", note = "parseValue \"(Node 5) 6 7\" must equal Con Node [5,6,7]" },
]

# ---------------------------------------------------------------------------
# Variant: hex_literal_join_546f1862_1
# ---------------------------------------------------------------------------
[[tasks]]
mutations = ["hex_literal_join_546f1862_1"]

[tasks.source]
repo = "https://github.com/yav/pretty-show"
commits = ["546f1862ef2d0d9211ec4637987efacc38498bba"]
commit_subjects = ["Merge together a literal followed immediately by some hex-digits into a single token."]
origin = "internal"
summary = "Without the joinTokens pass, `lexerPass0 \"4ab\"` produces `[IntLit \"4\", Varid \"ab\"]`; the parser then assembles a constructor-application Value instead of a single `Integer \"4ab\"`. The fix adds a token-stream post-processing step that merges an `IntLit` followed by all-hex-digit chars into one `IntLit`."

[tasks.injection]
kind = "patch"
files = ["Text/Show/Pretty.hs"]
locations = [{ file = "Text/Show/Pretty.hs", line = 84, symbol = "parseValue" }]
patch = "patches/hex_literal_join_546f1862_1.patch"

[tasks.bug]
short_name = "lexer_splits_hex_run"
invariant = "parseValue on a digit-prefix immediately followed by hex letters (e.g. `\"4ab\"`) returns `Just (Integer \"4ab\")` (a single Integer Value, not a constructor application)."
how_triggered = "Reverse-applying the patch removes the `foldr joinTokens [] .` pass and the joinTokens helper. `parseValue \"4ab\"` then returns `Just (Con \"\" [Integer \"4\", Con \"ab\" []])` instead of `Just (Integer \"4ab\")`."

[[tasks.tasks]]
property = "HexLiteralJoin"
witnesses = [
  { test_fn = "witness_hex_literal_join_case_4ab",   note = "parseValue \"4ab\" must be Just (Integer \"4ab\")" },
  { test_fn = "witness_hex_literal_join_case_12def", note = "parseValue \"12def\" must be Just (Integer \"12def\")" },
]
