As a best-selling author, I invite you to explore my books on Amazon. Don't forget to follow me on Medium and show your support. Thank you! Your support means the world!
Creating tailored languages and interpreters in Python allows me to solve specialized problems with elegant, readable tools. When building domain-specific tools, I focus on techniques that maintain Python's clarity while extending its capabilities. Here are eight methods I regularly use, each with practical applications.
Text command parsing turns natural language into actions. I often use regex with dataclasses to process user inputs cleanly. This approach works well for chatbots and CLI tools where intuitive commands matter.
python
import re
from dataclasses import dataclass
@dataclass
class Command:
verb: str
subject: str
modifiers: dict
def interpret(input_text):
cmd_pattern = r"^(?P\w+)\s+(?P\w+)(?:\s+using\s+(?P.*))?$"
match = re.match(cmd_pattern, input_text)
if not match:
return None
mods = {}
if mod_str := match.group("mods"):
pairs = [p.split(":") for p in mod_str.split(";")]
mods = {k.strip(): v.strip() for k,v in pairs}
return Command(
verb=match.group("verb").lower(),
subject=match.group("subject").lower(),
modifiers=mods
)
Example usage
user_cmd = interpret("resize image using width:800; height:600")
print(f"Action: {user_cmd.verb}, Object: {user_cmd.subject}, Settings: {user_cmd.modifiers}")
Operator overloading creates intuitive domain objects. By defining special methods like `__add__` or `__mul__`, I build expressive APIs for scientific computing. This technique makes complex operations feel native.
python
class ChemicalCompound:
def init(self, elements):
self.composition = elements
def __add__(self, other):
new_comp = {}
for elem, count in {**self.composition, **other.composition}.items():
new_comp[elem] = self.composition.get(elem,0) + other.composition.get(elem,0)
return ChemicalCompound(new_comp)
def __repr__(self):
return "+".join(f"{count}{elem}" for elem, count in self.composition.items())
Chemical reaction simulation
water = ChemicalCompound({"H":2, "O":1})
oxygen = ChemicalCompound({"O":2})
reaction = water + oxygen
print(reaction) # 2H+3O
AST transformations modify code behavior during compilation. I use Python's `ast` module to inject domain logic directly into the parse tree. This is powerful for adding custom optimizations.
python
import ast
class LogInjector(ast.NodeTransformer):
def visit_FunctionDef(self, node):
log_stmt = ast.Expr(value=ast.Call(
func=ast.Name(id='print', ctx=ast.Load()),
args=[ast.Constant(value=f"Calling {node.name}")],
keywords=[]
))
node.body.insert(0, log_stmt)
return node
Adds logging to functions automatically
source_code = """
def calculate(a, b):
return a * b
"""
tree = ast.parse(source_code)
modified = LogInjector().visit(tree)
exec(compile(modified, "", "exec"))
calculate(3, 4) # Prints "Calling calculate"
Parser combinators handle complex grammars elegantly. Libraries like `parsy` let me construct recursive parsers through composition. I find this ideal for SQL-like mini-languages.
python
from parsy import string, regex, seq
Config file parser
key = regex(r"[a-zA-Z_][\w]*")
value = regex(r"[^\n]+")
assignment = seq(
key << string("="),
value
).combine(lambda k, v: (k, v.strip()))
config_parser = assignment.sep_by(regex(r"\s*"))
Parsing key-value pairs
config_data = config_parser.parse("""
color = blue
size = large
shape = circle
""")
print(dict(config_data)) # {'color':'blue','size':'large','shape':'circle'}
Symbol tables manage execution contexts. I implement custom environments for safe evaluation, which is crucial when processing untrusted inputs.
python
class SafeEnvironment:
def init(self):
self.variables = {}
self.allowed_functions = {"min": min, "max": max}
def set(self, name, value):
self.variables[name] = value
def run(self, expr):
return eval(expr, {"__builtins__": None}, {**self.variables, **self.allowed_functions})
Restricted evaluation
env = SafeEnvironment()
env.set("x", 10)
env.set("y", 20)
result = env.run("min(x, y) + 5")
print(result) # 15
Metaclasses shape class behavior at definition time. I use them to enforce domain rules automatically, such as validation for financial models.
python
class FieldValidator(type):
def new(cls, name, bases, dct):
fields = [k for k, v in dct.items() if isinstance(v, Field)]
dct['fields'] = fields
return super().new_(cls, name, bases, dct)
class Field:
def init(self, min_val, max_val):
self.min = min_val
self.max = max_val
class Trade(metaclass=FieldValidator):
amount = Field(1, 10000)
def __init__(self, amount):
if not (self._fields[0].min <= amount <= self._fields[0].max):
raise ValueError("Invalid trade amount")
self.amount = amount
Usage
try:
t = Trade(15000) # Raises ValueError
except ValueError as e:
print(e)
Recursive descent parsers handle nested structures. When I need full control over parsing, I implement token-by-token processing.
python
class MathParser:
def init(self, expression):
self.tokens = iter(expression.replace(" ", ""))
self.current = next(self.tokens, None)
def advance(self):
self.current = next(self.tokens, None)
def parse(self):
return self.expr()
def expr(self):
result = self.term()
while self.current in ('+', '-'):
op = self.current
self.advance()
term = self.term()
result = result + term if op == '+' else result - term
return result
def term(self):
result = self.factor()
while self.current in ('*', '/'):
op = self.current
self.advance()
fac = self.factor()
result = result * fac if op == '*' else result / fac
return result
def factor(self):
if self.current == '(':
self.advance()
result = self.expr()
if self.current != ')':
raise SyntaxError("Mismatched parentheses")
self.advance()
return result
else:
return self.number()
def number(self):
num_str = ''
while self.current and self.current.isdigit():
num_str += self.current
self.advance()
return int(num_str)
Calculate expression
calc = MathParser("(3+2)*4")
print(calc.parse()) # 20
Decorators extend functions for domain tasks. I wrap core logic with context managers to handle resources like database connections automatically.
python
def database_transaction(func):
def wrapper(*args, **kwargs):
print("Opening database connection")
result = func(*args, **kwargs)
print("Committing transaction")
return result
return wrapper
@database_transaction
def save_record(data):
print(f"Persisting {data}")
Automated transaction handling
save_record({"id": 101, "status": "active"})
These techniques form a versatile toolkit for building specialized languages. Each approach balances expressiveness with Python's inherent readability. When I design domain-specific tools, I start with the simplest method that solves the problem, gradually adopting more advanced techniques as requirements evolve. The real power comes from combining these approaches - like using parser combinators with AST transformations or decorators with operator overloading. This flexibility lets me create solutions that feel like natural extensions of Python rather than foreign constructs.
📘 Checkout my latest ebook for free on my channel!
Be sure to like, share, comment, and subscribe to the channel!
101 Books
101 Books is an AI-driven publishing company co-founded by author Aarav Joshi. By leveraging advanced AI technology, we keep our publishing costs incredibly low—some books are priced as low as $4—making quality knowledge accessible to everyone.
Check out our book Golang Clean Code available on Amazon.
Stay tuned for updates and exciting news. When shopping for books, search for Aarav Joshi to find more of our titles. Use the provided link to enjoy special discounts!
Our Creations
Be sure to check out our creations:
Investor Central | Investor Central Spanish | Investor Central German | Smart Living | Epochs & Echoes | Puzzling Mysteries | Hindutva | Elite Dev | JS Schools
We are on Medium
Tech Koala Insights | Epochs & Echoes World | Investor Central Medium | Puzzling Mysteries Medium | Science & Epochs Medium | Modern Hindutva
Top comments (0)