Files
not-python-rust/tools/genast.py
Alek Ratzloff 224533ea8a Add last-expression-return-value to functions as well
We did it for if and block statements, now functions too support the
last expression being its return value. If there isn't an expression
(e.g. an assign statement) we just return `nil`.

Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2024-10-21 16:00:37 -07:00

321 lines
9.1 KiB
Python
Executable File

#!/usr/bin/env python3
# pylint: disable=missing-function-docstring,missing-class-docstring,missing-module-docstring,line-too-long
import abc
import re
import sys
import time
from pathlib import Path
from typing import Self
def snake_case(s: str):
# This insane regex comes from ChatGPT. I have no idea what the original
# source is, doing a verbatim search on Google and DDG doesn't yield any
# useful results.
#
# Here is the breakdown of how it works:
# (?<!^) - "negative lookbehind". this ensures that the position is not
# preceded by a start character. If this was omitted, it would
# put an underscore before the first capital letter in the string,
# e.g. IndexAssign -> _index_assign
# (?=[A-Z]) - this is a lookahead, asserting that the next character is
# uppercase. It does not consume the character, so doing a .sub()
# on this will not replace it.
return re.sub(r"(?<!^)(?=[A-Z])", "_", s).lower()
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_{snake_case(self.name + self.base)}(self)")
sb.dedent()
sb.line("}")
sb.line()
sb.line("fn as_any(self: Box<Self>) -> Box<dyn Any> { 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_{snake_case(self.name + self.base)}(&mut self, {snake_case(self.base)}: &{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_{snake_case(self.name + self.base)}(self)")
sb.dedent()
sb.line("}")
sb.line()
sb.line("fn as_any(self: Box<Self>) -> Box<dyn Any> { 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_{snake_case(self.name)}(&mut self, {snake_case(self.base)}: &{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<Self>) -> Box<dyn Any>;")
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<dyn {self.name} + 'static>;")
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<ExprP>", "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<ExprP>)>",
"return_type: Option<ExprP>",
"body: BlockExpr",
"rbrace: Token",
],
)
.add_struct("List", ["lbracket: Token", "exprs: Vec<ExprP>", "rbracket: Token"])
.add_struct(
"Map", ["lbracket: Token", "pairs: Vec<(ExprP, ExprP)>", "rbracket: Token"]
)
.add_struct(
"If",
[
"if_kw: Token",
"condition: ExprP",
"then_branch: BlockExpr",
"else_branch: Option<BlockExpr>",
],
)
.add_struct(
"Block",
[
"lbrace: Token",
"stmts: Vec<StmtP>",
"return_expr: Option<ExprP>",
"rbrace: Token",
],
),
# Stmt
GenerateGroup("Stmt")
.add_struct("Import", ["import_kw: Token", "what: Vec<Token>", "module: Token"])
.add_struct("Expr", ["expr: ExprP"])
.add_struct("Assign", ["lhs: Token", "op: Token", "rhs: ExprP"])
.add_struct(
"IndexAssign", ["expr: ExprP", "index: ExprP", "op: Token", "rhs: ExprP"]
)
.add_struct("Set", ["expr: ExprP", "name: Token", "op: Token", "rhs: ExprP"])
.add_struct("Return", ["return_kw: Token", "expr: Option<ExprP>"]),
]
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<dyn std::error::Error>;")
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()