generate string as per grammar in pyparsing

Viewed 122

I have a grammar defined in pyparsing

OCB, CCB, SQ = map(Suppress, "{}'")
name = SQ + Word(printables, excludeChars="{},'") + SQ
_name = CaselessKeyword("name").suppress()
_interface = Keyword("Component").suppress()
interface = Group(_interface + OCB + _name + name("interface_name") + CCB)
system = OneOrMore(interface + Optional(",").suppress())("interfaces")

If I have an input string:

model = "Component { name '/comp1' }, 
         Component { name '/comp2' }"
result = system.parseString(model)
print(result.dump())

The parsing result is as expected:

[['/comp1'], ['/comp2']]
- interfaces: [['/comp1'], ['/comp2']]
  [0]:
    ['/comp1']
    - interface_name: ['/comp1']
  [1]:
    ['/comp2']
    - interface_name: ['/comp2']

I want to know if there is a way to generate a string based on the grammar mentioned above. Since the only "variables" are comp1 and comp2, I need a function that generates the text:

def generate_string(comps: list):
    # do something
    return result

And the result of generate_string (maybe using originalTextFor?) should be:

"Component { name '/comp1' }, Component { name '/comp2' }"

I have seen examples to edit and to manually insert in ParseResults. But they don't make use of the grammar

1 Answers

I understand the question so that you want to produce strings from the parsing results. If this is what you want, I have an interesting solution.

from pyparsing import *

def to_string_parse_action(to_string_function):
  def parse_action(s, loc, toks):
    return toks, lambda : to_string_function(toks)
  return parse_action

OCB, CCB, SQ = map(Suppress, "{}'")
name = SQ + Word(printables, excludeChars="{},'") + SQ
_name = CaselessKeyword("name").suppress()
_interface = Keyword("Component").suppress()
interface = Group(_interface + OCB + _name + name + CCB)
system = OneOrMore(interface + Optional(",").suppress())("interfaces")

def name_to_string(toks):
  return "name '" + toks[0] + "'"

def interface_to_string(toks):
  result = "Component { " + toks[0][0][1]() + " }"
  return result
  
def system_to_string(toks):
  result = ""
  first=True
  for interface, interface_to_string_function in toks:
    if not(first):
     result = result + ", "
    first = False
    result = result + interface_to_string_function()
  return result

name.setParseAction(to_string_parse_action(name_to_string))
interface.setParseAction(to_string_parse_action(interface_to_string))
system.setParseAction(to_string_parse_action(system_to_string))

model = "Component { name '/comp1' }, \
         Component { name '/comp2' }"
         
results = system.parseString(model)

for result, result_to_string in results:
  print(result_to_string())

I cannot image for what this could be good. But it was an interesting exercise for me. And it works. If you parse something and then you call result_to_string, a string is reproduced back.

The idea is to pass functions through the whole parsing process, which create the strings. At the end of the parsing you have one to_string function for the parsing result. You call it and it calls recursively the integrated to_string functions.

The parse action normally have the purpose to create the tokens. In my version it has the purpose to create the tokens together with the function that produces a string.

You only have to write a to_string function and call to_string_parse_action when setting the parse action. The rest is nearly unchanged from your code.

Strangely it does not work with results names. Therefore I have removed this from your example. But it should be possible to have this mechanism also with results names. Sorry, I found no solution for this.

One other drawback is that the parsing results become more complicated because now they contain all these functions. I assume you want to do all this because you want to create artificially parsing results and then let them generate strings. If this is what you want, you must now also produce the to_string functions, which makes everything a bit more effort. But there should be plenty of alternatives how to do it differently, e.g. by using classes with appropriate constructors or by using a naming convention. I only illustrate the idea here.

If you want, you can remove the lambda and the toks argument in to_string_parse_action. Then you have to call that function explicitly with toks everywhere. For example the very last line would then be

print(result_to_string(result))
Related