Skip to content
Snippets Groups Projects
Commit f6d59db6 authored by Hugo Hörnquist's avatar Hugo Hörnquist
Browse files

Handle bare hashes.

parent 05c0886f
No related branches found
No related tags found
No related merge requests found
......@@ -444,13 +444,38 @@ class ParserFormatter(Serializer[ParseDirective]):
@override
def _puppet_hash(self, it: PuppetHash) -> ParseDirective:
parser = ws & '{'
"""
Parse a puppet hash literal.
The reason for the enclosing braces being optional is that
they are in some contexts.
For example:
.. code-block:: puppet
f("x",
a => 1,
b => 2)
is parsed as
.. code-block:: puppet
f("x", {
a => 1,
b => 2 })
This will however not give any false positives, since our
parser is built from the source.
"""
parser = ws & optional(s('{'))
for entry in it.entries:
parser &= (ws & self.s(entry.k) &
ws & '=>' &
ws & self.s(entry.v) &
optional(ws & ','))
parser &= ws & '}'
parser &= ws & optional(s('}'))
return parser
@override
......
......@@ -29,19 +29,72 @@ error with the if block.
import pytest
from muppet.puppet.format.parser import ParserFormatter
from muppet.puppet.ast import build_ast
from muppet.puppet.ast import (
build_ast,
Puppet,
HashEntry,
PuppetHash,
PuppetNumber,
PuppetQn,
PuppetString,
PuppetInvoke,
)
from muppet.puppet.parser import puppet_parser
from muppet.parser_combinator import ParserCombinator
from pprint import pprint
from typing import Any, Optional
def parse_string(s: str, *,
ast: Optional[Puppet] = None,
matched: Optional[list[Any]] = None):
"""
Parse and validate the given puppet fragment.
At it's core this procedure simply:
- Parses the puppet fragment into a well-behaved JSON structure
(note that this step is actually two steps, see documentation)
- Builds a Python AST from it
- Prints that AST to stdout
- Builds a parser combinator from that AST
- Prints that parser combinator to stdout
(TODO add rainbow parenthesis if module is available)
- Attempt to parse the original string with the generated parser
combinator
- Prints that result.
After the generation and printing of different data, there's an
option to compare it to a known correct value. This comparison is
done inside an `assert`, meaning that it works well with the
running test.
No comparison option exist for the parser object, since it may
contain functions, and the only function comparison is if it's the
exact same object, making it useless here.
:param s:
A self contained puppet fragment.
:param ast:
AST to compare the output of `build_ast` to.
:param matched:
List of match objects (or whatever the parser combinator
returned) to compare agains.
"""
generated_ast = build_ast(puppet_parser(s))
pprint(generated_ast)
if ast:
assert generated_ast == ast
parser = ParserFormatter(s, "s").serialize(generated_ast)
def parse_string(s: str):
ast = build_ast(puppet_parser(s))
pprint(ast)
parser = ParserFormatter(s, "s").serialize(ast)
print()
pprint(parser)
match_objects = ParserCombinator(s, "s").get(parser)
print()
pprint(match_objects)
if matched:
assert matched == match_objects
def test_parse_else_if():
......@@ -121,6 +174,7 @@ def test_invoke_inside_if():
}
""")
def test_funcall_with_block():
parse_string("""
assert_type(A::B, $name) |$_expected, $actual | {
......@@ -210,3 +264,25 @@ def test_unless():
}
}
""")
def test_bare_hash():
# This will be parsed just as if there was a curly brace after
# `"String",`, with a matching end brace before the closing
# parenthesis.
s = """
f("String",
x => 1,
y => 2,
)
"""
parse_string(
s,
ast=PuppetInvoke(
PuppetQn(name='f'),
[PuppetString('String'),
PuppetHash(entries=[HashEntry(PuppetQn('x'),
PuppetNumber(1)),
HashEntry(PuppetQn('y'),
PuppetNumber(2))])]))
assert False
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment