Creación de un lenguaje propio

Así es, para DSLD me han pedido que cree un lenguaje de programación propio. Tras algo de brainstorming se me ha ocurrido hacer un animador por máquinas de estados. En el definirías texturas, animaciones, estados y transiciones, además de la lógica para cambiar entre ellos.

AnimationStateMachine "Spaceship" {
    var fuel = 100 clamp 0 100
    var heat = 0 clamp 0 100
    var speed = 0 clamp 0 3
    var shields = 100 clamp 0 100

    Inputs {
        "w" -> thrust
        "s" -> brake
        "d" -> dock
        "o" -> overdrive
    }

    Texture "ship_docked" {
        "  [=======]  "
        "  | DOCK  |  "
        " /|       |\ "
        "/_========= _\"
    }

    Texture "ship_idle" {
        "      ^      "
        "     / \     "
        "    | O |    "
        "   /|___|\   "
        "  /_/   \_\  "
    }

    Texture "ship_thrust_1" {
        "      ^      "
        "     / \     "
        "    | O |    "
        "   /|___|\   "
        "  /_/ W \_\  "
    }

    Texture "ship_thrust_2" {
        "      ^      "
        "     / \     "
        "    | O |    "
        "   /|___|\   "
        "  /_/ w \_\  "
    }

    Texture "ship_overheat" {
        "      ^      "
        "     /!\     "
        "    | O |    "
        "   /|___|\   "
        "  /_/!!!\_\  "
    }

    Texture "ship_destroyed" {
        "             "
        "    \ | /    "
        "  -  X X  -  "
        "    / | \    "
        "             "
    }

    Animation "thrust_anim" {
        cyclic: true
        frames: ["ship_thrust_1", "ship_thrust_1", "ship_thrust_2", "ship_thrust_2"]
    }

    initial_state: Docked

    State Docked {
        on_update {
            fuel = fuel + 5
            if (heat > 0) { heat = heat - 5 }
            if (shields < 100) { shields = shields + 2 }
        }

        on_signal "thrust" {
            if (fuel > 10) {
                speed = 1
                transition_to Cruising
            }
        }

        render {
            use "ship_docked"
        }
    }

    State Cruising {
        on_update {
            if (speed > 0) {
                fuel = fuel - speed
                heat = heat + speed

                if (heat > 80) {
                    transition_to Overheating
                } else {
                    if (fuel == 0) {
                        speed = 0
                        transition_to Stranded
                    }
                }
            } else {
                if (heat > 0) { heat = heat - 1 }
            }
        }

        on_signal "thrust" {
            if (speed < 3) { speed = speed + 1 }
        }

        on_signal "brake" {
            if (speed > 0) { speed = speed - 1 }
        }

        on_signal "dock" {
            if (speed == 0) { transition_to Docked }
        }

        on_signal "overdrive" {
            if (fuel > 20) {
                speed = 3
                heat = heat + 30
            }
        }

        render {
            if (speed == 0) {
                use "ship_idle"
            } else {
                use "thrust_anim"
            }
        }
    }

    State Overheating {
        on_enter {
            print "WARNING: CRITICAL CORE TEMPERATURE"
        }

        on_update {
            shields = shields - 5
            if (heat > 0) { heat = heat - 2 }

            if (shields == 0) {
                transition_to Destroyed
            } else {
                if (heat < 50) {
                    transition_to Cruising
                }
            }
        }

        on_signal "brake" {
            speed = 0
        }

        render {
            use "ship_overheat"
        }
    }

    State Stranded {
        on_update {
            if (heat > 0) { heat = heat - 1 }
        }

        render {
            use "ship_idle"
        }
    }

    State Destroyed {
        on_enter {
            speed = 0
            fuel = 0
        }

        render {
            use "ship_destroyed"
        }
    }
}

El código anterior sería compilado a este programa en python:

import time
import os
import sys
import select
import termios
import tty

def clear_screen():
    os.system('cls' if os.name == 'nt' else 'clear')

class SpaceshipAnimation:
    def __init__(self):
        self.state = "Docked"
        self.state_time = 0.0

        self._fuel = 100
        self.fuel_max = 100
        self.fuel_min = 0
        self._heat = 0
        self.heat_max = 100
        self.heat_min = 0
        self._speed = 0
        self.speed_max = 3
        self.speed_min = 0
        self._shields = 100
        self.shields_max = 100
        self.shields_min = 0

        self.input_map = {
            'w': 'thrust',
            's': 'brake',
            'd': 'dock',
            'o': 'overdrive'
        }

    @property
    def fuel(self): return self._fuel
    @fuel.setter
    def fuel(self, val): self._fuel = max(self.fuel_min, min(self.fuel_max, val))

    @property
    def heat(self): return self._heat
    @heat.setter
    def heat(self, val): self._heat = max(self.heat_min, min(self.heat_max, val))

    @property
    def speed(self): return self._speed
    @speed.setter
    def speed(self, val): self._speed = max(self.speed_min, min(self.speed_max, val))

    @property
    def shields(self): return self._shields
    @shields.setter
    def shields(self, val): self._shields = max(self.shields_min, min(self.shields_max, val))

    def send_signal(self, signal):
        if self.state == "Docked":
            if signal == "thrust":
                if self.fuel > 10:
                    self.speed = 1
                    self.change_state("Cruising")

        elif self.state == "Cruising":
            if signal == "thrust":
                if self.speed < self.speed_max:
                    self.speed += 1
            elif signal == "brake":
                if self.speed > 0:
                    self.speed -= 1
            elif signal == "dock":
                if self.speed == 0:
                    self.change_state("Docked")
            elif signal == "overdrive":
                if self.fuel > 20:
                    self.speed = 3
                    self.heat += 30

        elif self.state == "Overheating":
            if signal == "brake":
                self.speed = 0

    def change_state(self, new_state):
        self.state = new_state
        self.state_time = 0.0

        if self.state == "Overheating":
            pass # Conceptual "WARNING" print on DSL
        elif self.state == "Destroyed":
            self.speed = 0
            self.fuel = 0

    def update(self, dt):
        self.state_time += dt

        if self.state == "Docked":
            self.fuel += 5
            self.heat -= 5
            self.shields += 2

        elif self.state == "Cruising":
            if self.speed > 0:
                self.fuel -= self.speed
                self.heat += self.speed

                if self.heat > 80:
                    self.change_state("Overheating")
                else:
                    if self.fuel == 0:
                        self.speed = 0
                        self.change_state("Stranded")
            else:
                self.heat -= 1

        elif self.state == "Overheating":
            self.shields -= 5
            self.heat -= 2

            if self.shields == 0:
                self.change_state("Destroyed")
            else:
                if self.heat < 50:
                    self.change_state("Cruising")

        elif self.state == "Stranded":
            self.heat -= 1

    def render(self):
        clear_screen()
        print("=== SPACESHIP ANIMATION DSL SIMULATOR ===")
        print("Controls: [W] Thrust | [S] Brake | [O] Overdrive | [D] Dock | [Q] Quit")
        print("=========================================")
        print(f"Current State: {self.state}")
        print(f"Fuel:    {self.fuel:3d}/{self.fuel_max}   Heat: {self.heat:3d}/{self.heat_max}")
        print(f"Shields: {self.shields:3d}/{self.shields_max}   Speed: {self.speed}/{self.speed_max}")
        print("=========================================")
        print("-" * 40)

        t_docked = "  [=======]  \n  | DOCK  |  \n /|       |\\ \n/_========= _\\"
        t_idle = "      ^      \n     / \\     \n    | O |    \n   /|___|\\   \n  /_/   \\_\\  "
        t_thrust_1 = "      ^      \n     / \\     \n    | O |    \n   /|___|\\   \n  /_/ W \\_\\  "
        t_thrust_2 = "      ^      \n     / \\     \n    | O |    \n   /|___|\\   \n  /_/ w \\_\\  "
        t_overheat = "      ^      \n     /!\\     \n    | O |    \n   /|___|\\   \n  /_/!!!\\_\\  "
        t_destroyed = "             \n    \\ | /    \n  -  X X  -  \n    / | \\    \n             "

        anim_thrust = [t_thrust_1, t_thrust_1, t_thrust_2, t_thrust_2]

        if self.state == "Docked":
            print(t_docked)
        elif self.state == "Cruising":
            if self.speed == 0:
                print(t_idle)
            else:
                print(anim_thrust[int(self.state_time * 10) % len(anim_thrust)])
        elif self.state == "Overheating":
            print(t_overheat)
        elif self.state == "Stranded":
            print(t_idle)
        elif self.state == "Destroyed":
            print(t_destroyed)

        if self.state == "Overheating":
            print("\n!!! WARNING: CRITICAL CORE TEMPERATURE !!!")

        print("-" * 40)

def is_data():
    return select.select([sys.stdin], [], [], 0) == ([sys.stdin], [], [])

if __name__ == "__main__":
    anim = SpaceshipAnimation()
    old_settings = termios.tcgetattr(sys.stdin)
    last_time = time.time()

    try:
        tty.setcbreak(sys.stdin.fileno())
        while True:
            current_time = time.time()
            dt = current_time - last_time
            last_time = current_time

            if is_data():
                c = sys.stdin.read(1).lower()
                if c == 'q':
                    break 
                elif c in anim.input_map:
                    anim.send_signal(anim.input_map[c])

            anim.update(dt) 
            anim.render()
            time.sleep(0.1)
    finally:
        termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_settings)
        clear_screen()

Es más de lo que nos piden para el trabajo y pinta bastante divertido de hacer. Iré actualizando con como queda.