dsl in python

August 2023

"""
This is how to design and use embedded
domain-specific languages in Python.
"""

import sys

"""
functions
    add     : sum all arguments
    sub     : subtract tail from head
types
    int
    float
arguments
    out     : stdin to use previous output as input
            : stdout to print
"""
program = """
Module add 1 2 3 type=float out=stdin
Module sub stdin 1 2 type=int out=stdout
"""

# Module definition
class Module:
    # Add function
    def add(*args, **kwargs):
        type_ = globals()["__builtins__"].getattr(globals()["__builtins__"], kwargs["type"])
        return sum(map(type_, args))
    # Sub function
    def sub(*args, **kwargs):
        type_ = globals()["__builtins__"].getattr(globals()["__builtins__"], kwargs["type"])
        return float(args[0]) if type_ == 'float' else int(args[0]) - sum(map(type_, args[1:]))

def get_args(tokens, STDIN):
    args = []
    kwargs = {}
    for token in tokens:
        # kwargs
        if '=' in token:
            k, v = token.split('=', 1)
            kwargs[k] = v
        # args
        else:
            # replace stdin with previous output
            if token == "stdin":
                if STDIN != "": args.append(STDIN)
                else: raise Exception("no previous output")
            else:
                args.append(token)
    return args, kwargs

def interpret(program):
    STDIN = ""
    lines = [line for line in program.splitlines() if line]
    # print(lines)
    # ['Module add 1 2 3 type=float out=stdin', 'Module sub stdin 1 2 type=int out=stdout']

    for line in lines:
        tokens = line.split()
        args, kwargs = get_args(tokens[2:], STDIN)

        # print(args, kwargs)
        # ['1', '2', '3'] {'type': 'float', 'out': 'stdin'}
        # [6.0, '1', '2'] {'type': 'int', 'out': 'stdout'}

        # print(getattr(sys.modules[__name__], tokens[0]))
        # <class '__main__.Module'>
        # <class '__main__.Module'>

        # print(getattr(getattr(sys.modules[__name__], tokens[0]), tokens[1]))
        # <function Module.add at 0x1024545e0>
        # <function Module.sub at 0x108ed9620>

        result = getattr(getattr(sys.modules[__name__], tokens[0]), tokens[1])(*args, **kwargs)
        if "out" in kwargs:
            if kwargs["out"] == "stdin": STDIN = result
            if kwargs["out"] == "stdout": print(result)

interpret(program)
# 3