Update AST generator to be more structured
We use objects and builders to make AST items now, it's a lot less clunky that doing string manipulation everywhere in the Python file. It's also more flexible and will open up more options in the future for things like enum AST items Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
This commit is contained in:
293
tools/genast.py
293
tools/genast.py
@@ -1,112 +1,187 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
import abc
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
from pathlib import Path
|
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<(), Box<dyn std::error::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<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_{self.name.lower()}_{self.base.lower()}(&mut self, {self.base.lower()}: &{self.name}{self.base}) -> Result<(), Box<dyn std::error::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(self.generate_members())
|
||||||
|
|
||||||
|
return str(sb)
|
||||||
|
|
||||||
|
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<(), Box<dyn std::error::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>;")
|
||||||
|
|
||||||
|
# All members and rules
|
||||||
|
for g in self.group:
|
||||||
|
sb.line(g.generate_members())
|
||||||
|
sb.line()
|
||||||
|
|
||||||
|
return str(sb)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
GENERATE = [
|
GENERATE = [
|
||||||
(
|
# Expr
|
||||||
"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",
|
||||||
[
|
[
|
||||||
"Binary -> lhs: ExprP, op: Token, rhs: ExprP",
|
"lparen: Token",
|
||||||
"Unary -> op: Token, expr: ExprP",
|
"params: Vec<(Token, Option<ExprP>)>",
|
||||||
"Call -> expr: ExprP, args: Vec<ExprP>, rparen: Token",
|
"return_type: Option<ExprP>",
|
||||||
"Get -> expr: ExprP, name: Token",
|
"body: Vec<StmtP>",
|
||||||
"Index -> expr: ExprP, index: ExprP, rbracket: Token",
|
"rbrace: Token",
|
||||||
"Primary -> token: Token",
|
|
||||||
"Function -> lparen: Token, params: Vec<(Token <COMMA> Option<ExprP>)>, return_type: Option<ExprP>, body: Vec<StmtP>, rbrace: Token",
|
|
||||||
"List -> lbracket: Token, exprs: Vec<ExprP>, rbracket: Token",
|
|
||||||
],
|
],
|
||||||
),
|
)
|
||||||
(
|
.add_struct("List", ["lbracket: Token", "exprs: Vec<ExprP>", "rbracket: Token"]),
|
||||||
"Stmt",
|
# Stmt
|
||||||
|
GenerateGroup("Stmt")
|
||||||
|
.add_struct("Import", ["import_kw: Token", "module: Token"])
|
||||||
|
.add_struct("Expr", ["expr: ExprP"])
|
||||||
|
.add_struct("Assign", ["lhs: Token", "rhs: ExprP"])
|
||||||
|
.add_struct("Set", ["expr: ExprP", "name: Token", "rhs: ExprP"])
|
||||||
|
.add_struct("Block", ["lbrace: Token", "stmts: Vec<StmtP>", "rbrace: Token"])
|
||||||
|
.add_struct("Return", ["return_kw: Token", "expr: Option<ExprP>"])
|
||||||
|
.add_struct(
|
||||||
|
"If",
|
||||||
[
|
[
|
||||||
"Expr -> expr: ExprP",
|
"if_kw: Token",
|
||||||
"Assign -> lhs: Token, rhs: ExprP",
|
"condition: ExprP",
|
||||||
"Set -> expr: ExprP, name: Token, rhs: ExprP",
|
"then_branch: BlockStmt",
|
||||||
"Block -> lbrace: Token, stmts: Vec<StmtP>, rbrace: Token",
|
"else_branch: Vec<StmtP>",
|
||||||
"Return -> return_kw: Token, expr: Option<ExprP>",
|
|
||||||
"If -> if_kw: Token, condition: ExprP, then_branch: BlockStmt, else_branch: Vec<StmtP>",
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class FileWriter:
|
|
||||||
def __init__(self, path: Path) -> None:
|
|
||||||
self.path = path
|
|
||||||
self.fp = open(path, "w")
|
|
||||||
|
|
||||||
def write(self, text: str = "") -> None:
|
|
||||||
self.fp.write(text)
|
|
||||||
|
|
||||||
def line(self, line: str = "") -> None:
|
|
||||||
self.fp.write(line)
|
|
||||||
self.fp.write("\n")
|
|
||||||
|
|
||||||
|
|
||||||
def define_visitor(wr: FileWriter, base: str, type_lines: list[str]):
|
|
||||||
types = []
|
|
||||||
for line in type_lines:
|
|
||||||
parts = line.split("->")
|
|
||||||
members = [p.strip().replace("<COMMA>", ",") for p in parts[1].split(",")]
|
|
||||||
types += [(parts[0].strip(), members)]
|
|
||||||
|
|
||||||
wr.line(f"pub trait {base}Visitor " + "{")
|
|
||||||
|
|
||||||
for type, members in types:
|
|
||||||
wr.line(
|
|
||||||
f" fn visit_{type.lower()}_{base.lower()}(&mut self, {base.lower()}: &{type}{base}) -> Result<(), Box<dyn std::error::Error>>;"
|
|
||||||
)
|
|
||||||
wr.line("}")
|
|
||||||
wr.line()
|
|
||||||
|
|
||||||
|
|
||||||
def define_ast(wr: FileWriter, base: str, type_lines: list[str]):
|
|
||||||
types = []
|
|
||||||
for line in type_lines:
|
|
||||||
parts = line.split("->")
|
|
||||||
members = [p.strip().replace("<COMMA>", ",") for p in parts[1].split(",")]
|
|
||||||
types += [(parts[0].strip(), members)]
|
|
||||||
|
|
||||||
visitor = f"{base}Visitor"
|
|
||||||
|
|
||||||
wr.line(f"pub trait {base}: Debug + Any " + "{")
|
|
||||||
wr.line(
|
|
||||||
f" fn accept(&self, visitor: &mut dyn {visitor}) -> Result<(), Box<dyn std::error::Error>>;"
|
|
||||||
)
|
|
||||||
wr.line(" fn as_any(self: Box<Self>) -> Box<dyn Any>;")
|
|
||||||
wr.line(" fn as_any_ref(&self) -> &dyn Any;")
|
|
||||||
wr.line("}")
|
|
||||||
wr.line()
|
|
||||||
wr.line(f"pub type {base}P = Box<dyn {base} + 'static>;")
|
|
||||||
wr.line()
|
|
||||||
|
|
||||||
for type, members in types:
|
|
||||||
wr.line("#[derive(Debug)]")
|
|
||||||
wr.line(f"pub struct {type}{base} " + "{")
|
|
||||||
for member in members:
|
|
||||||
wr.line(f" pub {member},")
|
|
||||||
wr.line("}")
|
|
||||||
wr.line()
|
|
||||||
wr.line(f"impl {base} for {type}{base} " + "{")
|
|
||||||
wr.line(
|
|
||||||
f" fn accept(&self, visitor: &mut dyn {visitor}) -> Result<(), Box<dyn std::error::Error>> "
|
|
||||||
+ "{"
|
|
||||||
)
|
|
||||||
wr.line(f" visitor.visit_{type.lower()}_{base.lower()}(self)")
|
|
||||||
wr.line(" }")
|
|
||||||
wr.line()
|
|
||||||
wr.line(" fn as_any(self: Box<Self>) -> Box<dyn Any> {")
|
|
||||||
wr.line(" self")
|
|
||||||
wr.line(" }")
|
|
||||||
wr.line()
|
|
||||||
wr.line(" fn as_any_ref(&self) -> &dyn Any {")
|
|
||||||
wr.line(" self")
|
|
||||||
wr.line(" }")
|
|
||||||
wr.line("}")
|
|
||||||
wr.line()
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
if len(sys.argv) == 1:
|
if len(sys.argv) == 1:
|
||||||
path = Path(__file__).parent / "../src/ast.rs"
|
path = Path(__file__).parent / "../src/ast.rs"
|
||||||
@@ -116,29 +191,27 @@ def main():
|
|||||||
path = path.resolve()
|
path = path.resolve()
|
||||||
print(f"Using {path}")
|
print(f"Using {path}")
|
||||||
|
|
||||||
wr = FileWriter(path)
|
sb = SB()
|
||||||
|
|
||||||
# File headers
|
# File headers
|
||||||
wr.line(
|
sb.line(
|
||||||
"// This is an auto-generated file. Any changes made to this file may be overwritten."
|
"// This is an auto-generated file. Any changes made to this file may be overwritten."
|
||||||
)
|
)
|
||||||
wr.line("// This file was created at: " + time.strftime("%Y-%m-%d %H:%M:%S"))
|
sb.line("// This file was created at: " + time.strftime("%Y-%m-%d %H:%M:%S"))
|
||||||
wr.line("#![allow(dead_code)]")
|
sb.line("#![allow(dead_code)]")
|
||||||
|
|
||||||
# Imports
|
# Imports
|
||||||
wr.line("use std::fmt::Debug;")
|
sb.line("use std::fmt::Debug;")
|
||||||
wr.line("use std::any::Any;")
|
sb.line("use std::any::Any;")
|
||||||
wr.line()
|
sb.line()
|
||||||
wr.line("use crate::token::Token;")
|
sb.line("use crate::token::Token;")
|
||||||
wr.line()
|
sb.line()
|
||||||
|
|
||||||
# Visitors
|
for g in GENERATE:
|
||||||
for base, types in GENERATE:
|
sb.line(g.generate())
|
||||||
define_visitor(wr, base, types)
|
|
||||||
|
|
||||||
# AST
|
with open(path, "w") as fp:
|
||||||
for base, types in GENERATE:
|
fp.write(str(sb).strip())
|
||||||
define_ast(wr, base, types)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
Reference in New Issue
Block a user