I want to define my own simple language for evaluating literals.
Requirements:
- Based on python to allow easy use/addition of Python functions (not shown in the example)
- Token should be iterpreted as Literal if not preceded by
$. $before token means it is a Variable (Similar to bash)
TL;DR:
I want dollar_solution(**VAR_DICT) to return "Minimum Working Example"
Due diligence (Ignore if not helpful):
- So far, I have been able to accomplish this but with the special character being Unary Subtraction (
USub[-]). - The part (of
dollar_solution) that fails is duringast.parse. Looking at the code forast.parse, it just calls thebuiltins:compile. Does this mean I need to call my own compile with my own .asdl? - Half-baked idea based on my research (in order of cleanliness):
- Define unary operator
$(in .asdl) and handle it inMyTransormer::visit_UnaryOp. - Define my own
.asdlto allow dollar as a valid character in names and handle it in MyTransformer
- Define unary operator
- It smells like I might need to define my own language. Where do I start with that with a parser in Python?
- Is there some library that does that already?
import ast
from _ast import Name, Constant, BinOp, Add, UnaryOp, USub
from typing import Any
EXPECTED_RESULT = "Minimum Working Example"
VAR_DICT = {"mini": "Minimum", "ex": "Example"}
MINUS_EXPRESSION = "-mini + ' ' + Working + ' ' + -ex"
DOLLAR_EXPRESSION = "$mini + ' ' + Working + ' ' + $ex"
def minus_solution(**kwargs):
tree = ast.parse(MINUS_EXPRESSION, mode="eval")
safe_tree = MyTransformer(var_dict=kwargs).visit(tree)
return ast.literal_eval(safe_tree)
def dollar_solution(**kwargs):
tree = ast.parse(DOLLAR_EXPRESSION, mode="eval")
safe_tree = MyTransformer(var_dict=kwargs).visit(tree)
return ast.literal_eval(safe_tree)
def main():
try:
assert dollar_solution(**VAR_DICT) == EXPECTED_RESULT
except:
print("Dollar Solution Failed!!!")
else:
print("Dollar Solution Success!!!")
try:
assert minus_solution(**VAR_DICT) == EXPECTED_RESULT
except:
print("Minus Solution Failed!!!")
else:
print("Minus Solution Success!!!")
class MyTransformer(ast.NodeTransformer):
def __init__(self, var_dict):
self._var_dict = var_dict
def visit_Name(self, node: Name) -> Any:
return Constant(
value=node.id,
col_offset=node.col_offset,
lineno=node.lineno,
)
def visit_BinOp(self, node: BinOp) -> Any:
super().generic_visit(node)
assert (
isinstance(node.op, Add)
and isinstance(node.left, Constant)
and isinstance(node.right, Constant)
), f"Bad operand or operator"
return Constant(
value=str(node.left.value) + str(node.right.value),
col_offset=node.col_offset,
lineno=node.lineno,
)
def visit_UnaryOp(self, node: UnaryOp) -> Any:
assert isinstance(node.operand, Name) and isinstance(
node.op, USub
), "Not Name in expression"
return Constant(
value=self._var_dict[node.operand.id],
col_offset=node.col_offset,
lineno=node.lineno,
)
if __name__ == "__main__":
main()