I have a dilemma. Let's say I have an AST that describes some grammar, for instance. I can write a function to stringify this AST back into human-readable BNF form or generate a parser from it. I can and want to generate both from given ast. I can do it either by defining two corresponding functions, ast => string
and ast => parser
or it is better to create a single function that traverses the ast one time and produces a couple of ast => [stringified,parsified]
for every ast node in single pass?
The code for the first will look like
stringify(ast) = case ast.tag of
":" => ast.value + ":" + stringify(ast.children) // label
"//" => "/" + ast.value + "/" // regex
QQ => '"' + ast.value + '"' // literal
"{}" => '{' + ast.value + '}' // user semantic action
"()" => '(' + stringify(ast.children) + ')' // parenthized expression
"&" => stringify(ast.children).join("&")
"|" => stringify(ast.children).join("|")
parsify(ast, stack) =
const childrenWithNewFrame = parseify(ast.children, stack.push({}))
return case ast.tag of
":" => stack.last(ast.value) = parsify(ast.children, stack)
"//" => reParser(ast.value) // matches a regex
QQ => literal(ast.value) // matches given literal
"{}" => userAction(stack) // applies user code to the stack
"()" => childrenWithNewFrame()
"&" => [h, tail] = childrenWithNewFrame(); tail.reduce((acc,p) => acc.andThen(p),h)
"|" => oneOf(parsify(ast.children))
You see that the structure is almost identical, in both stringify
and parsify
, because this is a visit
function except that parsify
needs a scoped dictionary of labels. I therefore started to look at the alternative where I produce both in one run
both(ast, stack) =
const childrenWithNewFrame = parseify(ast.children, stack.push({}))
return case ast.tag of
"//" => ["/"+ast.value+"/", reParser(ast.value)]
QQ => ['"'+ast.value+'"', literal(ast.value)]
"{}" => ['"'+ast.value+'"', userAction(stack)]
and so on...
where every case returns both string and parser. You may notice that if first approach duplicates (DRY) the cases, the second seems to duplicate the pair pattern [,]
in every case. What is better?
Is it a known dilemma in programming?