This package is a simple parser for puzzle inputs such as the Advent of Code input files.
The grammar consists of four basic constructs:
Literals are matched exactly. Characters with special meaning, i.e. [
, ]
, {
, }
, <
, >
, and |
must be escaped with a backtick (`
).
foo`|bar
matchesfoo|bar
.
Value elements (i.e., elements that should be parsed into a value) are denoted by curly brackets ({
and }
), with the type given after a colon (:
):
{foo:i}
matches42
and parses into{'foo': 42}
.
The name of the value element is optional. If no name is given, the value is stored with its index as its key. If none of the value elements in the container element are named, the container element is parsed into a list. Otherwise, it is parsed into a dict.
{i}{i}
matches42 43
and parses into[42, 43]
{i}{bar:i}
matches42 43
and parses into{0: 42, 'bar': 43}
.
Currently, the following types are defined:
i
: integerw
: word (i.e., an alphanumeric string without whitespace)n
: name (i.e., a word not starting with a number)il
: list of integerswl
: list of wordsnl
: list of names
Container elements (i.e., elements that should be parsed into a list or a dict) are denoted by square brackets ([
and ]
), with the 'join string' given after a pipe (|
):
[{i}|,]
matches42,43
and parses into[42, 43]
.
The join string is optional. If no join string is given, it will default to (an arbitrary amount of) spaces.
[{i}]
matches42 43
and parses into[42, 43]
.
If a value element inside the container has the name key
, the container is parsed into a dict, with the value of the key
element as the key.
[{key:w} = {val:i}|\n]
matchesfoo = 42 bar = 43and parses into
{'foo': {'key': 'foo', 'val': 42}, 'bar': {'key': 'bar', 'val': 43}}
If a container has two child elements, one 'key' element and one unnamed element, the unnamed element gets returned directly as the value:
[{key:w} = {i}|\n]
matchesfoo = 42 bar = 43and parses into
{'foo': 42, 'bar': 43}
Choice elements (i.e., elements that should be parsed into one of multiple options) are denoted by angle brackets (<
and >
), with the choices separated by a pipe (|
).
To distinguish the choices, they need to be named, with the name given before a colon (:
).
<num:{i}|word:{w}|nop:nop>
matches42
and parses intodict(num=42, word=None)
, and matchesfoo
and parses intodict(word='foo', num=None)
.
If all choices contain an element with the same name, that element is lifted to the result of the choice element as well.
<inc:{n:w}++|dec:{n:w}-->
matchesa++
and parses intodict(n='a', inc={'n': 'a'}, dec=None)
, and matchesa--
and parses intodict(n='a', dec={'n': 'a'}, inc=None)
.
The returned dictionaries are actually NamespaceDict
s, which subclass dictionaries to also allow access to the values as attributes.
This means that parsed['foo']
is equivalent to parsed.foo
.
from aocparser import parse
example = """\
Card 1: 41 48 83 86 17 | 83 86 6 31 17 9 48 53
Card 2: 13 32 20 16 61 | 61 30 68 82 17 32 24 19"""
spec = "[Card {key:i}: {p1:il} `| {p2:il}|\n]"
assert parse(spec, example) == {
1: {"key": 1,
"p1": [41, 48, 83, 86, 17],
"p2": [83, 86, 6, 31, 17, 9, 48, 53]},
2: {
"key": 2,
"p1": [13, 32, 20, 16, 61],
"p2": [61, 30, 68, 82, 17, 32, 24, 19],
},
}
from aocparser import parse
example = """\
seeds: 79 14 55 13
seed-to-soil map:
50 98 2
52 50 48"""
spec = """\
seeds: {seeds:il}
{maps:[{key:w}-to-{to:w} map:
{vals:[{il}|\n]}|\n\n]}"""
assert parse(spec, example) == {
"maps": {
"seed": {"key": "seed", "to": "soil", "vals": [[50, 98, 2], [52, 50, 48]]}
},
"seeds": [79, 14, 55, 13],
}
from aocparser import parse
example = """\
RL
AAA = (BBB, CCC)
BBB = (DDD, EEE)"""
spec = """\
{w}
[{key:w} = ({L:w}, {R:w})|\n]"""
assert parse(spec, example) == [
"RL",
{
"AAA": {"L": "BBB", "R": "CCC", "key": "AAA"},
"BBB": {"L": "DDD", "R": "EEE", "key": "BBB"},
},
]
from aocparser import parse
example = """\
mask = 00
mem[1] = 1
mask = 01
mem[2] = 2"""
spec = "[<mask:mask = {w}|mem:mem`[{i}`] = {i}>|\n]"
assert parsed == [
{"mask": "00", "mem": None},
{"mask": None, "mem": {"addr": 1, "val": 1}},
{"mask": "01", "mem": None},
{"mask": None, "mem": {"addr": 2, "val": 2}},
]
assert parsed[0].mask == "00"
assert parsed[1].mem.addr == 1
from aocparser import parse
example = "a=1,b=2,c-"
spec = "[<set:{n:w}={v:i}|minus:{n:w}->|,]"
parsed = parse(spec, example)
assert parsed == [
{"minus": None, "n": "a", "set": {"n": "a", "v": 1}},
{"minus": None, "n": "b", "set": {"n": "b", "v": 2}},
{"minus": {"n": "c"}, "n": "c", "set": None},
]
assert parsed[0].n == "a"
assert parsed[2].n == "c"