from mazegen.utils import IVec2
from .parser_combinator import (
+ ParseError,
ParseResult,
Parser,
alt,
ascii_digit,
cut,
delimited,
+ eof_parser,
fold,
+ lookahead_parser,
many,
many_count,
none_of,
+ nonempty_parser,
null_parser,
one_of,
pair,
def parse_bool(s: str) -> ParseResult[bool]:
- return alt(value(True, tag("True")), value(False, tag("False")))(s)
+ return alt(
+ value(True, tag("True")),
+ value(False, tag("False")),
+ )(s)
def parse_int(s: str) -> ParseResult[int]:
return recognize(seq(tag("#"), many_count(none_of("\n"))))(s)
-def parse_empty_line(s: str) -> ParseResult[None]:
- return (None, s) if s.startswith("\n") else None
+# def parse_empty_line(s: str) -> ParseResult[None]:
+# return (None, s) if s.startswith("\n") else ParseError("temp", "temp")
def spaced[T](parser: Parser[T]) -> Parser[T]:
)
noncolor_str = fold(
alt(
- none_of('\n{\\"'),
+ none_of('\n{}\\"'),
preceeded(
tag("\\"),
cut(
alt(
value("{", tag("{")),
+ value("}", tag("}")),
value("\\", tag("\\")),
value('"', tag('"')),
)
def default(self) -> U:
acc = []
for s in default_strs:
- res = self.parse(s)
- if res is None or res[1] != "":
+ res = parser_complete(self.parse)(s)
+ if isinstance(res, ParseError):
raise ConfigException(
"Failed to construct defaulted field " + self.name()
)
)
for name, field in fields.items()
),
- parser_map(lambda _: None, parse_empty_line),
+ parser_map(lambda _: None, lookahead_parser(null_parser, tag("\n"))),
)
fields_raw: dict[str, type[ConfigField[Any]]],
) -> Parser[dict[str, Any]]:
fields = {key: cls(key) for key, cls in fields_raw.items()}
- parse_line = terminated(line_parser(fields), cut(tag("\n")))
+ parse_line = nonempty_parser(
+ cut(terminated(line_parser(fields), alt(tag("\n"), eof_parser())))
+ )
def inner(s: str) -> ParseResult[dict[str, Any]]:
def fold_fn(
fields_map,
parser_map(
default_lists,
- fold(
- parse_line,
- fold_fn,
- lambda: {name: [] for name in fields.keys()},
+ parser_complete(
+ fold(
+ parse_line,
+ fold_fn,
+ lambda: {name: [] for name in fields.keys()},
+ )
),
),
)(s)
}
)
)(s)
- if fields is None:
- raise ConfigException("Failed to parse config")
+ if isinstance(fields, ParseError):
+ raise fields
res = Config()
for key, val in fields[0].items():
res.__dict__[key.lower()] = val
from collections.abc import Callable
-from typing import Any, cast
+from typing import Any
+import textwrap
+from mazegen.utils.ivec2 import IVec2
-type ParseResult[T] = tuple[T, str] | None
+
+type ParseResult[T] = tuple[T, str] | ParseError
type Parser[T] = Callable[[str], ParseResult[T]]
class ParseError(Exception):
- def __init__(self, msg: str, at: str) -> None:
+ def __init__(
+ self, msg: str, at: str, caused_by: list["ParseError"] | None = None
+ ) -> None:
self.msg: str = msg
self.at: str = at
- super().__init__(f"{msg}\n\nat: {at[:40]}")
-
-
-def option_map[T, R](f: Callable[[T], R], val: T | None) -> R | None:
- return f(val) if val is not None else None
+ self.caused_by: list[ParseError] | None = caused_by
+ super().__init__(
+ f"{msg}\nat: {at[:40]}\n" + f"subcauses:\n{self.caused_by}"
+ )
+
+ def get_text_pos(self, input_str: str) -> IVec2:
+ pred_len = len(input_str) - len(self.at)
+ row = input_str.count("\n", 0, pred_len) + 1
+ column = pred_len - max(input_str.rfind("\n", 0, pred_len), 0)
+ return IVec2(column, row)
+
+ def get_line(self, input_str: str) -> str:
+ pred_len = len(input_str) - len(self.at)
+ line_start = input_str.rfind("\n", 0, pred_len) + 1
+ line_end = max(input_str.find("\n", pred_len), 0)
+ return input_str[line_start:line_end]
+
+ def pretty_format(self, input_str: str, filename: str) -> str:
+ # Style taken from the excellent rustc error messages
+ pos = self.get_text_pos(input_str)
+ col = pos.x
+ row = pos.y
+ num_str = f"{row} "
+ space_pad_str = " " * len(num_str)
+ pad_str = space_pad_str + "|"
+ suberrors = (
+ []
+ if self.caused_by is None
+ else [
+ textwrap.indent(
+ e.pretty_format(input_str, filename), pad_str + " "
+ )
+ + f"{pad_str}\n"
+ for e in self.caused_by
+ ]
+ )
+ return (
+ f" --> {filename}:{row}:{col}\n"
+ + f"{pad_str}\n"
+ + f"{num_str}| {self.get_line(input_str)}\n"
+ + f"{pad_str}{" " * col}^ {self.msg}\n"
+ + f"{pad_str}\n"
+ + f"{pad_str}\n".join(suberrors)
+ )
+
+
+def error_map[T, R](
+ f: Callable[[T], R], val: T | ParseError
+) -> R | ParseError:
+ return f(val) if not isinstance(val, ParseError) else val
def parser_map[T, M](m: Callable[[T], M], p: Parser[T]) -> Parser[M]:
- return lambda s: option_map(lambda res: (m(res[0]), res[1]), p(s))
+ return lambda s: error_map(lambda res: (m(res[0]), res[1]), p(s))
-def parser_flatten[T](p: Parser[T | None]) -> Parser[T]:
- return lambda s: option_map(
- lambda res: cast(tuple[T, str], res) if res[0] is not None else None,
- p(s),
+def parser_map_err[T](
+ m: Callable[[ParseError], ParseError], p: Parser[T]
+) -> Parser[T]:
+ return lambda s: (
+ res
+ if not isinstance(
+ res := p(s),
+ ParseError,
+ )
+ else m(res)
)
return alt(p, value(default, null_parser))
-def parser_complete[T](p: Parser[T]) -> Parser[T]:
+def parser_complete[T](
+ p: Parser[T],
+ msg: str = "Complete parser error: leftover characters",
+) -> Parser[T]:
def inner(res: tuple[T, str]) -> ParseResult[T]:
if len(res[1]) != 0:
- raise ParseError(
- "Complete parser still had leftover characters to process",
- res[1],
- )
+ raise ParseError(msg, res[1])
return res
- return lambda s: option_map(inner, p(s))
+ return lambda s: error_map(inner, p(s))
def recognize[T](p: Parser[T]) -> Parser[str]:
- return lambda s: option_map(
+ return lambda s: error_map(
lambda rem: (s[: len(s) - len(rem[1])], rem[1]),
p(s),
)
def cut[T](p: Parser[T]) -> Parser[T]:
def inner(s: str) -> ParseResult[T]:
res: ParseResult[T] = p(s)
- if res is None:
- raise ParseError("Cut error: parser did not complete", s)
+ if isinstance(res, ParseError):
+ raise res
return res
return inner
-def tag(tag: str) -> Parser[str]:
+def tag(tag: str, msg: str | None = None) -> Parser[str]:
+ if msg is None:
+ msg = f"Expected tag {repr(tag)}"
return lambda s: (
(s[: len(tag)], s[len(tag) :]) # noqa E203
if s.startswith(tag)
- else None
+ else ParseError(msg, s)
)
-def char(s: str) -> ParseResult[str]:
- return (s[0], s[1:]) if len(s) > 0 else None
+def char(msg: str | None = None) -> Parser[str]:
+ if msg is None:
+ msg = f"Expected char {repr(tag)}"
+ return lambda s: (s[0], s[1:]) if len(s) > 0 else ParseError(msg, s)
def null_parser(s: str) -> ParseResult[str]:
return ("", s)
+def lookahead_parser[T, U](p1: Parser[T], p2: Parser[U]) -> Parser[T]:
+ def inner(s: str) -> ParseResult[T]:
+ res = p1(s)
+ if isinstance(res, ParseError):
+ return res
+ res2 = p2(res[1])
+ if isinstance(res2, ParseError):
+ return res2
+ return res
+
+ return inner
+
+
+def eof_parser(msg: str = "Expected end of file") -> Parser[str]:
+ return lambda s: ("", "") if len(s) == 0 else ParseError(msg, s)
+
+
+def nonempty_parser[T](p: Parser[T], msg: str = "Expected non end of file"):
+ return lambda s: p(s) if len(s) > 0 else ParseError(msg, s)
+
+
def value[T, V](val: V, p: Parser[T]) -> Parser[V]:
return parser_map(lambda _: val, p)
-def alt[T](*choices: Parser[T]) -> Parser[T]:
- return lambda s: next(
- filter(
- lambda e: e is not None,
- map(lambda p: p(s), choices),
- ),
- None,
- )
+def alt[T](
+ *choices: Parser[T], msg: str = "None of the following was met:"
+) -> Parser[T]:
+ def inner(s: str) -> ParseResult[T]:
+ acc: list[ParseError] = []
+ for e in map(lambda p: p(s), choices):
+ if not isinstance(e, ParseError):
+ return e
+ acc.append(e)
+ return ParseError(msg, s, acc)
+
+ return inner
def fold[T, R](
min_n: int = 0,
max_n: int | None = None,
sep: Parser[Any] = null_parser,
+ msg: Callable[[int], str] | None = None,
) -> Parser[R]:
+ """
+ Repeatedly call the p parser, folding the results using f, with an acc
+ created through acc_init
+ Returns error if and only if min_n iterations are not reached
+ """
+
+ if msg is None:
+ msg = (
+ lambda count: f"Expected at least {min_n} elements, got {count}, after error:"
+ )
+
# no clean way to do this with lambdas i could figure out :<
def inner(s: str) -> ParseResult[R]:
+ curr_s = s
acc = acc_init()
count: int = 0
curr_p: Parser[T] = p
+ err: list[ParseError] | None = None
while max_n is None or count < max_n:
- next: ParseResult[T] = curr_p(s)
- if next is None:
+ nxt: ParseResult[T] = curr_p(curr_s)
+ if isinstance(nxt, ParseError):
+ err = [nxt]
break
if count == 0:
curr_p = preceeded(sep, p)
count += 1
- acc = f(acc, next[0])
- s = next[1]
- return (acc, s) if count >= min_n else None
+ acc = f(acc, nxt[0])
+ curr_s = nxt[1]
+ return (
+ (acc, curr_s) if count >= min_n else ParseError(msg(count), s, err)
+ )
return inner
min_n: int = 0,
max_n: int | None = None,
sep: Parser[Any] = null_parser,
+ msg: Callable[[int], str] | None = None,
) -> Parser[list[T]]:
return fold(
parser_map(lambda e: [e], p),
min_n,
max_n,
sep,
+ msg,
)
min_n: int = 0,
max_n: int | None = None,
sep: Parser[Any] = null_parser,
+ msg: Callable[[int], str] | None = None,
) -> Parser[int]:
- return fold(value(1, p), int.__add__, lambda: 0, min_n, max_n, sep)
+ return fold(value(1, p), int.__add__, lambda: 0, min_n, max_n, sep, msg)
def seq[T](*parsers: Parser[T]) -> Parser[str]:
def inner(s: str) -> ParseResult[None]:
for parser in parsers:
res = parser(s)
- if res is None:
- return None
+ if isinstance(res, ParseError):
+ return res
s = res[1]
return (None, s)
def pair[T, U](p1: Parser[T], p2: Parser[U]) -> Parser[tuple[T, U]]:
- return lambda s: option_map(
+ return lambda s: error_map(
lambda res1: parser_map(lambda res2: (res1[0], res2), p2)(res1[1]),
p1(s),
)
def one_of(chars: str) -> Parser[str]:
- return alt(*map(tag, chars))
+ return parser_map_err(
+ lambda s: ParseError(f"Expected one char of {repr(chars)}", s.at),
+ alt(
+ *map(tag, chars),
+ ),
+ )
def none_of(chars: str) -> Parser[str]:
- return lambda s: char(s) if one_of(chars)(s) is None else None
+ return lambda s: (
+ char()(s)
+ if isinstance(one_of(chars)(s), ParseError)
+ else ParseError(f"Expected any character except {repr(chars)}", s)
+ )
def ascii_hexdigit(s: str) -> ParseResult[str]:
--- /dev/null
+97D553D551517915557D51393B93D1557D3B9395797D15793BB9797BD17979579139513BD5551513D13BD3B97955513BD17B
+C3B9383D103852EBBD1156EAE82E90153942A8697C3D297A82807C14103C10556EE87E8457B907EE928412AA94117AE85456
+D42AAAC12EEC7E94416C51787AC3EE83C47C6A92916D2C16A86ABD6BEAC3AE97951439039782E9552EC7AAC46D6ABAD07BD3
+D3C6C07A857D516BD079543814387BEC7957D4286E93ABAD6E900794387C07ABAD292EE807AC507D43B9283D55146EBC5012
+B87D107A87BBD47C16D43BEEA9287C113C55396AD16E8407BBEEAD2B86D3C3AC696EAD16AD6D3AD17C6AEE851505510396AA
+C053AC7A83C03D39457D46D12AE817EEA9792E94145787878691412AC3905469101787ABC797C056917857A907C53EAC692E
+BE9287D6AEBEAB8055513D3EE83EEB93869287C3C7D52D2BC56C3EC6BAEC17D6EE87C7C6D1439293A87EB96EE9392BC53EAB
+C56A83BD410786A817D687AD3AED52AEABEE811053B92D687D5543D16817C3B97B8117953A946AAAE8516E97BAEE82B943C2
+D5542EC396A943EEC3952B81041112AB86952AAED02A87D2BD5512D03E87D2AA946E87E96AA978685696D50540116EC2BEBE
+9553A93C47C0387D12AD286EAD2EAC446B87EAC13AEE87D0417BAC52ED453EC40797ABD2BAEABEBC53C7BD297AEEB9528783
+E91286C7B956E853EAAD44796BC787D552E952BAEC796D56903C6952BD396D15012D00384412A92D141787E87A9784782BAE
+BEE86BB9441792D6BAA97BB852B9057916D6D0685578797D6EABBED0294413ABAEE92A843D2AAA87E96D43D43869457AE803
+817ED2AC13ED6AD16EE852C6D686C13AED5156947D5690395506C156AC57EAAAC3BEAAEBC7E842C3B839507D40787956BAEE
+AEBD50452E95501017BAD4117D0396C2D1507D457BD3EAEC3B853ABB83BBBE82D6AD2A96B9503C3EAAC07A97BC7ED2D50417
+C101787BE96D16EA83C2BD6C3D2AAD3ABAD2D53BB87AD2952847AE82AEA8296ED507EC2BAC3EAD6BC6D43C6D2955381547EB
+BEAE9696BC7BED3AEABA815387AEAD40069697AC407AD047EC7BED6C292EC051556BBD6C43C7A95693D3ED53C07946C17BBA
+C5412BABAB92BB829446AED02D2BC3BEC3A943C1147C16D391143BD546C1783C793847D5169782956EB857907EBC17B85286
+93BA86C046A8282EA91543D6C52A906BD6A83C16A9552956EEA9287BBD5016C156E83D3D07ABEAABD384556E95456BC47C43
+A846AB96BBEEC6C7AEED5079152C2ED0512AE943EC7942B9796A803AABBEE91457D285052D2814047AAD393B87BB969557D6
+E839682BC41553BB83B9783EEBAD6D387EC2BAD2913EBC28147AEAE842C3D2C15392C7C3E942C783944386AC07C02D055157
+D2AABAEC156BBA846AC2BEABBC6BBD0293D46ED42EC5012EC7D6D296D416BABAD2AE95787AD6BBE86952EBC12D52A943BC17
+92AC443D6D3C2C2D5696878403D4292EE83D11512D396EE97D111447BD05282C3C43ABD2BE956C547EBED2BEAD3E847EC543
+AAAD3B815547AD29150387AD2C516E87D6C56EBEABC0553ABD6EAD554387C2ED6938687C2BC5539515697C03A9692BD3957A
+AE856AAC391143AEC3EC052D07BAD38397B95383C6D453C443BB8791546D3ABD56C2BC5506D516EBABD2916C2ABE82B86B96
+E9297C6D6EAC786B9057EBED416816AEC52ABEEABBBD143D500287AAD51142A93D16A95169396D1444102E93A807EA86D443
+BEAAB93BD5057E92EC5794113E96EBEBD16C43D06C03ED6956EEED28792C3C02C3C782947AC6D5297D6EE9686EABB82BD3BA
+C38686C2D3857BEC513BABAE87817ABA92BB9450396C53BC153B97AC7EC3ED2C3E97EAC7D439556A97B956D47D42C6A83C2E
+BC6B87D6BAC390393AE842E96D68542AEA84057E8439784787846D6917D47BC7ED417E93BBC2BD52C52A9157B956BD2A812B
+ABBC03B92856AEC6EAD2BEBC5152BBEAD46D6BD3EBC692D3C3C79156C57D50151152D56A86942D50152EEC17843D07AAAA82
+C043AEC2C4510555569447ABBC784052D17BBC1454512EB87E952ED393BD3EEBAC3A9392C56907BAABE9396D6941416AEEEE
+92B8695693BA851513C5138069507AD6B854292D393A8506950787D02AC52D502BAC6AEA957AABC2C07A8693D4783C16D13B
+AAC03AD52E86E96BE87BA86EBED2D457C6D546C3AAEEED4147E96B96AC3D4396AAC5787AABD6AC52BC16C56853BEC3ED52C6
+E83AC2D14787945438382E95413C7BD17D5117BEC451557813BEB847C3ABBC290697B87C06BB853C0785117C3C453AB95057
+96E83ED297EB8157EEAAC383D6E97C543916C7AB953A97D2EC03A8793AEC056EAD2BAC512D2C2907E943AAD52D17C046BA97
+C396A9504396E87BD56ABC6ED156D55546855106ED046BD417AEA83AAEB96BB94386AD3AAD07AE83B87AEA97C5693C7D42AB
+D2EBEED07EA9147A97BE8553D053BD53BBC7D687B94392D52BC782C6ED2C7AC2D403AD46A907E96844387A85793AC153906A
+BE92D3BA956A817C45292BD296946D5006D153ABA83AA8514457EC151145787A93EA83B96EC1107857E87C2D12C07E942856
+C3AE94442B96EC3D5386EABEE96BD156EBBED406AEAC6EBABD3BD16BE8539696E87EAC42953AA87C393AD3EBEED2D5696C57
+D403ED3906A917C3D2A97C43907C3C3D14057D0787C13B846B82B87C52BE812D107907BE83C6AED56E86BC5293D2B97E93D3
+D16C3946AD6E85105046B9146855292B83ED17856BBAEC2D542C2C57D4456AABAEBAC383AAD5417917A9293C28786C392E96
+BA97AABB87BBAD6C387D46AD3C57EAEAAC3BC3C396843BC3BBAD6B953BBBD2E86BAE942EEE955292E906EEC3AC3ED16A8383
+AAC3EA82ED4003BBAAB97D2D45179052AD403ABAABE96C52C0691047E84416B85683C3C79541386AD2853BD043C13C3AEC2A
+829692AEB956EEC2AE869103BB856E942BD2C440687C3BBAB83AAAD55297AD44552C3817E956EABC7EC7C412BC7EC1047D2E
+EC03AE85469517BC41016AA86A83BD2D42BC79147AB9446AC686AA93D469697D392B86E97C7D3A87D3BBD12C47939687D147
+BD2A83AD17AD6B87D6A87EE87AAEA903D02D16ABBA86913C3907AC2EB97C147D02C6ABB81553842BBAAC16853D286D6B9017
+87AC2E8543C516AB952A97BE92ED06EED2EBAD4042856AABAEEBC7878057ED13E87BC442853EEBC286C503A947E813D06EAB
+852D43AD3C79452C6D2C0543AEB92D1552BAC7D6D403D6E80556D3C3E815552E947C5152C385169685396AA87B96E83ED3C6
+87ED144387BABBE917C7C3D6A92AAD2D546C3BBD13EC7BFE83FFFC3C52ED3B87A97BD03E96EBED43E9047AAC12C7D6C5383B
+8795453AC542C292EB9554156AEAEBC53BBBAAAD2ABD16FD6857FD2D503BC045681692C7C11055507EC53EED2853BD5146C2
+87EBBBEAD13C16EABC017D6BD2BC16BBEA82A8696EAD07FFFEFFFB853EE81057BEC56E9556EE957C3B916BD12ABE8794393A
+C3BA86BA92ED4156852EBB90106907AC386EA8547947C553FBFD50038516EC51295157A951796BD3EAEE9692AC6B8787EEAA
+BE8445442C3D387BAD43806EEC16852D06BBA8179017D152FAFFFEAAE94517BEEABC7BC6945296D052D52D6EC512AB87D16E
+816913D3E92BC052ABBEEA95516BABC7850282C52E8392D2D43D3BAE92D505297AAB943D2B902D5692D52BD1392C46C3D07B
+AED2EC5456C6BABE84453EA9547A82D387EEEA91292EAABEBB854407E817E96C502803EBE86AA93D6C3BC412AEEBB956B816
+A93EBBD15517A841693BC7EED396EAD42D397EAAAAEBEC052AE9796916ED52D3D2EAEEB83ED2AEC15546956EC7BC2EBBAAAB
+AEA902D295056AD412EC395552A952BBABC4516EAC16BD43C6B83ED2C1797EB83A96916AC156A9107D3D05397D4501444286
+A902E856C7C3D2D3E817AC7D12C43C6844557817AD4503D4396EABBABC14116AEA852ED6BC3D6AEA9507A9447953AA957AEB
+EAEAD2D51516BEB87E83C3956C11455453BD3AED2D556ED14451440007ABEC507C07815569293ED047C7EAD53C786C03BC7A
+BEB87C3BED43C142952E904557EED17D38412C3D0793B97ED3BAD3EEC3EAD15697ABEE953EEEA93AD3D3D2B947903D284556
+C502D3E87D3C12BC2D2D2C53B9579697C692C50387AC047D104452BD169692BB81407D07AD3D02AC16B8384017EEC3EC3B97
+93AEBABC3D052A8783C3AD50447BAB87D56C7D2AC7C7AB93AC17D28787856A846ED45783AB856AE92946807AED3D56B92C03
+AAAD42812D47EA85683C6D1051786843BD153BAC155506E86947D6AD47EBD2857957BD2EEC47907E8053EC3C512D3906E96A
+AC29386A83D116E952C7BBAED4507C3A8107AC03C17BA93AD4179547BD16906D3C53C12BD17968552E9457AD3A816AED147A
+ABEAC07EAC16ED12D2B96C6BD55057E82E83C3EAD07C42C291056BD3ABC56ED1055052847A907E93ED439387AAAC501543D2
+EA96D2B94385796EBE8053BABBD0797AEBAC7E96D079147AAAC578382AD3BBBEC796BAC7D2A857EA97D06EED46857E817C7E
+B847D06AD2ED1051692ED2842ABEB83ED2C17945787E83BEAED13AC2EC5028417B83AC7BBC6ED11047D453BD3BC1796EBBD3
+C457D43ED07D6C787A83BC2BC287C6C15296BE9792D3EC6D2BD2EEBC3B96AAD4146C43BAABD112EABBB91443C2BEB87BAA96
+D5555387BC5111783AEAC50290457BD03C05696D6AD453BD44169547AAC7AEBD03D53C446C52AED2A806AD5016A968542A87
+D17D5047A952AABAC43EB96AEAD5387EE94538553AD3D043BB8507B92AD52903AC3D07D3D3D6C1386AA943D6EBC012D16807
+945796B902BEEC6C13ED047C7ED146B97C3D4057EE9692BC406D696E805386EC6BC52D38145552EC12EABAD5543EE8787A87
+85152D46AE8111516E978553D55293C43BE91695152BAC6B94553AD56C7C2B9154138786A93BD457EA92AC3939697C7C56C7
+C7E92BD3ABEEEABA9147C7D697D42A9382BA8547EBAC07BC03D54451555142AC3BEE87C786AA97953AEE8782C6B85797D3BB
+9556EA9682B952C46E93B93D03B92C6EEC46A91152ED2BA9683B95547D12942D0413EBD147AC07E9405147EEBBC05787BA86
+ABBBD2852AC69297BD2EAEC5686E8515395382AC7A97EC0692C6E97D13AEAD6D2BEC547ED52D45547C38151780507D45286B
+EAC296ABAC796C0569416D397AD52D47AED42EEBD0453943EABB96916EC7E97940797B97D3E97955156A87EBA83ED157C43A
+969287EC07D4792B94787D0452BD4557C55169507AD3807ED68047AABBBB92B87856D0479478107907BAC5546EC57C113BAA
+852C017945513EC447941547BC07D3955150543ED456AAD1556AD16C4286AEC07C1794152D3EAA96E90017955579796EC402
+AD6D6AD6BBD6857955296D152D693847B87E97C7953BE852B97E943B92AB87943D016D43EBAD6AABD2EA87857B92D2D1796E
+857BD69786BBABBC7BEC5547ABD28417807D453BAD2AB878407947E86AC00387ED2E97BE96C552C2BC3EC143D4687ED05297
+A97ABBC3A9682C017C3979796A96E947EC3B952C292C2C787EBED392BC7EEA87B9412B8781157AD287AD143C79781152D687
+AC16C452C2BAC7EC3D285692D42D16D53D0407EBAEEBC552950794280557D0456C3E842D2EE95692C503AD0556D2EC7A97AB
+C105397854069179696ABBEAB94787B96BAD2D386BBAD156A92D2D6EAD3D1015392BAD2D01547BAA97EEA96D51507BBA8106
+D6E906D43BED2AB8543C40386AD5694478292D2814685453AAC7C57B87ABEAED2EE843A92ED13C6C457D6C51507C546C6AC3
+913E8397EC3BEC0695057E86D6979457BAEEC7AAC15297D2EABD53BAC14412BBA9503EC6A952C3B95797BD383ED5179112BE
+AAC7EC07BBC0396D2D43B96D138547956A97BD283E96C552D28552AAD2952EC42ED2C397C2D6D046BBAB8506C15107AEA847
+EC5115696C56C457C7B82817E807BD2BD6C16D06ED413BD6BC43968296E94797ED56D02BBE97BC53C406C78796D6C7C7AC7B
+9556AD507D17D17BD38686C7BAA96D0057D057E9153EC013C3D6816AAD507BC179797AC047C1413817C117ED43951397C112
+E957C3BAB90392943AED2BD3AEC2952AD516BD1287C796E856BD6AD407D05696907C547C517EBE868796817BBC07E86D12AE
+D07D54042AE82C47843B8694053AABAC7BC3C3AAAD13EBBED3A97857E952B92BE87D17BD3AB905692903AC3C43ED14392E83
+BC1557C7AAD2C3D3EBC2812BC7AC2AE91696D06EA96A92ABD0001413D47AAEC07AB9696D446AABD286EAED2BD43D692E83AA
+C383D53D06BC7C543C386EA87D2942D2C507BC17AABC6AC416EAED2ED1546D14546C16D13956843E857C17AC116952E92EAE
+B82AD145016D5553C3AAD5003D02BA969569696BEEC112BD69103BAD54395141557BABD6C6BD43EBED152D6BEABE96D6ABC3
+C2EAD453EC1557D43E807D6A83EAC28787D2BABC517AEC6D3AEA82EBBD2C3E94555406D3D38114501387C17C382D47BBC2BE
+D2D07D54794515556BE87BD2EC7C7843AB906803B8505557C47AE8382D43AD6955156D14142AC7D6AAEBD07D06E97B82D003
+D2B85393BED50139507C52903957BC7E842ED6AA847C791539383AAAEBD06D103D453D696D6C13B96ED296D505543AEABEEA
+96AAD6AAABBD6E86BE97BEAAC41507B96B857BEC2D397EAD06EEAAE85416BD6EED13A93C7D13A86C3BD2ED3D0557C05287BA
+A90017E8680555054147AD6E97C3C786D06BD07BAD2C152D0793EED6B96BC3D3D52EAAE953E82EBBC07C17A947B9503807AE
+AEAEABBA92C53BED14396917AB943B8396D41456C1452BA9052C3B9542BAD03C394500387C52ABA8147945403946BAEA8143
+AD2900442C3B8053ED407AEB86C3A86EED3D4397D057EAAAC3E9440792AC3AC56A956AE853D2EA86AB9413D2EC152C7EEABA
+C7EEEC7D6D46EED4557C547C4556EC55554554457C57D46ED47C7D6D6C4544557EC57ED6D6D6D447C447EED6D56D457D56C6
+
+1,1
+24,24
+SESENNESSENNESEESSSSSEESSENEESSSSSEEESESSEENNNNEESEESESSWNWWWSSESSESSEESSS