Ho un dilemma. Diciamo che ho un AST che descrive alcune grammatiche, per esempio. Posso scrivere una funzione per stringere questo AST in un formato BNF leggibile dall'uomo o generare un parser da esso. Posso e voglio generare entrambi da dato ast. Posso farlo o definendo due funzioni corrispondenti, ast => string
e ast => parser
oppure è meglio creare una singola funzione che attraversa l'ast una volta e produce un paio di ast => [stringified,parsified]
per ogni nodo Ast in single pass?
Il codice per il primo sarà simile a
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))
Si vede che la struttura è quasi identica, sia in stringify
che in parsify
, perché questa è una funzione visit
tranne per il fatto che parsify
ha bisogno di un dizionario con scope di etichette. Ho quindi iniziato a guardare l'alternativa in cui produco entrambi in una sola volta
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...
dove ogni caso restituisce sia stringa che parser. Si può notare che se si avvicinano prima i duplicati (DRY) dei casi, il secondo sembra duplicare il modello di coppia [,]
in ogni caso. Cosa c'è di meglio?
È un dilemma noto nella programmazione?