#!/usr/bin/env python3 # pylint: disable=missing-function-docstring,missing-class-docstring,missing-module-docstring,line-too-long import abc import sys import time from pathlib import Path from typing import Self class SB: "String builder" def __init__(self, init: str = ""): self.value = init self.padding = 0 def indent(self) -> None: self.padding += 1 def dedent(self) -> None: self.padding -= 1 assert self.padding >= 0 def line(self, line: str = ""): self.write(f"{line}\n") def write(self, value: str): if not self.value or self.value[-1] == "\n": self.value += " " * self.padding self.value += value def __str__(self) -> str: return self.value class Generator(metaclass=abc.ABCMeta): @abc.abstractmethod def generate_members(self) -> str: pass @abc.abstractmethod def generate_visitor(self) -> str: pass class GenerateStruct(Generator): def __init__(self, base: str, name: str, rules: list[str]) -> None: self.base = base self.name = name self.rules = rules def generate_members(self) -> str: sb = SB() sb.line("#[derive(Debug)]") sb.line(f"pub struct {self.name}{self.base} " + "{") sb.indent() for member in self.rules: sb.write(f"pub {member}") sb.line(",") sb.dedent() sb.line("}") sb.line() sb.line(f"impl {self.base} for {self.name}{self.base} " + "{") sb.indent() sb.line( f"fn accept(&self, visitor: &mut dyn {self.base}Visitor) -> Result<(), Error> " + "{" ) sb.indent() sb.line(f"visitor.visit_{self.name.lower()}_{self.base.lower()}(self)") sb.dedent() sb.line("}") sb.line() sb.line("fn as_any(self: Box) -> Box { self }") sb.line("fn as_any_ref(&self) -> &dyn Any { self }") sb.dedent() sb.write("}") return str(sb) def generate_visitor(self) -> str: return f"fn visit_{self.name.lower()}_{self.base.lower()}(&mut self, {self.base.lower()}: &{self.name}{self.base}) -> Result<(), Error>;" class GenerateEnum(Generator): def __init__( self, base: str, name: str, variants: list[tuple[str, str | list[str]]] ): self.base = base self.name = name self.variants = [ (variant, ([members] if isinstance(members, str) else members)) for (variant, members) in variants ] def generate_members(self) -> str: sb = SB() sb.line("#[derive(Debug)]") sb.line(f"pub enum {self.name}{self.base} " + "{") sb.indent() for variant, members in self.variants: sb.write(f"{variant}") if members: struct_like = any(":" in member for member in members) if struct_like: sb.line(" {") sb.indent() for member in members: sb.line(f"{member},") sb.dedent() sb.line("},") else: sb.write("(") sb.write(", ".join(members)) sb.line("),") else: sb.line(",") sb.dedent() sb.line("}") sb.line() sb.line(f"impl {self.base} for {self.name}{self.base} " + "{") sb.indent() sb.line( f"fn accept(&self, visitor: &mut dyn {self.base}Visitor) -> Result<(), Error> " + "{" ) sb.indent() sb.line(f"visitor.visit_{self.name.lower()}_{self.base.lower()}(self)") sb.dedent() sb.line("}") sb.line() sb.line("fn as_any(self: Box) -> Box { self }") sb.line("fn as_any_ref(&self) -> &dyn Any { self }") sb.dedent() sb.write("}") return str(sb) def generate_visitor(self) -> str: return f"fn visit_{self.name.lower()}_{self.base.lower()}(&mut self, {self.base.lower()}: &{self.name}{self.base}) -> Result<(), Error>;" class GenerateGroup(Generator): def __init__(self, name: str, group: list[Generator] | None = None) -> None: self.name = name if not group: group = [] self.group = group def generate(self) -> str: sb = SB() sb.line(self.generate_visitor()) sb.line() sb.line(self.generate_members()) return str(sb) def add_enum(self, name: str, variants: list[tuple[str, str | list[str]]]) -> Self: self.group += [GenerateEnum(self.name, name, variants)] return self def add_struct(self, name: str, rules: list[str]) -> Self: self.group += [GenerateStruct(self.name, name, rules)] return self def generate_members(self) -> str: sb = SB() # Trait and acceptor sb.line(f"pub trait {self.name}: Debug + Any " + "{") sb.indent() sb.line( f"fn accept(&self, visitor: &mut dyn {self.name}Visitor) -> Result<(), Error>;" ) sb.line("fn as_any(self: Box) -> Box;") sb.line("fn as_any_ref(&self) -> &dyn Any;") sb.dedent() sb.line("}") sb.line() # Type sb.line(f"pub type {self.name}P = Box;") sb.line() # All members and rules for g in self.group: sb.line(g.generate_members()) sb.line() return str(sb).strip() def generate_visitor(self) -> str: sb = SB() # Visitor sb.line(f"pub trait {self.name}Visitor " + "{") sb.indent() for g in self.group: sb.line(g.generate_visitor()) sb.dedent() sb.line("}") return str(sb).strip() GENERATE = [ # Expr GenerateGroup("Expr") .add_struct("Binary", ["lhs: ExprP", "op: Token", "rhs: ExprP"]) .add_struct("Unary", ["op: Token", "expr: ExprP"]) .add_struct("Call", ["expr: ExprP", "args: Vec", "rparen: Token"]) .add_struct("Get", ["expr: ExprP", "name: Token"]) .add_struct("Index", ["expr: ExprP", "index: ExprP", "rbracket: Token"]) .add_struct("Primary", ["token: Token"]) .add_struct( "Function", [ "lparen: Token", "params: Vec<(Token, Option)>", "return_type: Option", "body: Vec", "rbrace: Token", ], ) .add_struct("List", ["lbracket: Token", "exprs: Vec", "rbracket: Token"]) .add_struct( "Map", ["lbracket: Token", "pairs: Vec<(ExprP, ExprP)>", "rbracket: Token"] ), # Stmt GenerateGroup("Stmt") .add_struct("Import", ["import_kw: Token", "what: Vec", "module: Token"]) .add_struct("Expr", ["expr: ExprP"]) .add_struct("Assign", ["lhs: Token", "op: Token", "rhs: ExprP"]) .add_struct("Set", ["expr: ExprP", "name: Token", "op: Token", "rhs: ExprP"]) .add_struct("Block", ["lbrace: Token", "stmts: Vec", "rbrace: Token"]) .add_struct("Return", ["return_kw: Token", "expr: Option"]) .add_struct( "If", [ "if_kw: Token", "condition: ExprP", "then_branch: BlockStmt", "else_branch: Vec", ], ), ] def main(): if len(sys.argv) == 1: path = Path(__file__).parent / "../src/ast.rs" else: path = Path(sys.argv[1]) path = path.resolve() print(f"Using {path}") sb = SB() # File headers sb.line( "// This is an auto-generated file. Any changes made to this file may be overwritten." ) sb.line("// This file was created at: " + time.strftime("%Y-%m-%d %H:%M:%S")) sb.line("#![allow(dead_code)]") # Imports sb.line("use std::fmt::Debug;") sb.line("use std::any::Any;") sb.line() sb.line("use crate::token::Token;") sb.line() sb.line("type Error = Box;") sb.line() for g in GENERATE: sb.line(g.generate()) with open(path, "w", encoding="utf-8") as fp: fp.write(str(sb).strip()) if __name__ == "__main__": main()