Simple template language written in python
git clone git://git.janzachar.dev/stempl.git
0from dataclasses import dataclass, asdict1import lexer234# --- ast nodes ---56class Node:7 ...89@dataclass10class Raw(Node):11 text: str12 right: Optional[Node]1314@dataclass15class Statement(Node):16 child: Node17 right: Optional[Raw | Statement]1819@dataclass20class ConditionalStatement(Node):21 condition: Expression22 body: Raw | Statement23 alternative: Raw | Statement2425@dataclass26class IterativeStatement(Node):27 iterator: Identifier28 sequence: Identifier29 body: Raw | Statement3031@dataclass32class InclusionStatement(Node):33 path: StrConst34 israw: bool3536@dataclass37class Identifier(Node):38 name: str3940@dataclass41class NumConst(Node):42 data: int | float4344@dataclass45class StrConst(Node):46 data: str4748@dataclass49class BinOp(Node):50 left: Identifier | NumConst | StrConst51 op: str52 right: Identifier | NumConst | StrConst5354# --- parser ---5556class TokenStream:57 def __init__(self, tok: List[lexer.Token]):58 self.tok = tok59 self.pos = 06061 def peek(self, dist: int = 0, of_type: Optional[lexer.Token] = None, of_text: Optional[str] = None) -> lexer.Token:62 out = self.tok[self.pos + dist]63 if of_type is None and of_text is None:64 return out6566 flag = True67 flag &= of_type is None or isinstance(out, of_type)68 flag &= of_text is None or out.text == of_text6970 return flag7172 def pop(self, of_type: Optional[lexer.Token] = None, of_text: Optional[str] = None):73 out = self.peek()7475 if of_type is not None and not isinstance(out, of_type):76 raise TypeError(f"Expected {type(of_type).__name__}, got {type(out).__name__} {out.text}")7778 if of_text is not None and of_text != out.text:79 raise TypeError(f"Expected '{of_text}', got '{out.text}'")8081 self.pos += 182 return out8384 def __len__(self):85 return len(self.tok) - self.pos8687 def __bool__(self):88 return bool(len(self))8990class Parser:91 def __init__(self, tok: List[lexer.Token]):92 self.ts = TokenStream(tok)9394 def parse(self) -> Node:95 if not self.ts:96 return None9798 p = self.ts.peek()99 if isinstance(p, lexer.Raw):100 return self.raw()101 else:102 return self.statement()103104 def raw(self) -> Node:105 return Raw(self.ts.pop(lexer.Raw).text, self.parse())106107 def statement(self) -> Node:108 if not self.ts.peek(0, lexer.Operator, '{{'):109 raise TypeError(f"'{{' expected, got {p.text}")110111 Map = [112 ('if', self.conditional),113 ('else', (lambda: None)),114 ('end', (lambda: None)),115 ('for', self.iterative),116 ('paste', self.inclusion),117 ('raw', self.inclusion)118 ]119120 if not self.ts.peek(1, lexer.Keyword):121 self.ts.pop(lexer.Operator, '{{')122 out = self.expr()123 self.ts.pop(lexer.Operator, '}}')124 else:125 for key, func in Map:126 if self.ts.peek(1, lexer.Keyword, key):127 out = func()128 break129130 if out is None:131 return None132133 return Statement(out, self.parse())134135136 def conditional(self) -> Node:137 self.ts.pop(lexer.Operator, "{{")138 self.ts.pop(lexer.Keyword, "if")139 condition = self.expr()140 self.ts.pop(lexer.Operator, "}}")141142 body = self.parse()143 alternative = None144145 self.ts.pop(lexer.Operator, "{{")146 if self.ts.peek(0, lexer.Keyword, "else"):147 self.ts.pop(lexer.Keyword, "else")148 self.ts.pop(lexer.Operator, "}}")149150 alternative = self.parse()151 self.ts.pop(lexer.Operator, "{{")152153 self.ts.pop(lexer.Keyword, "end")154 self.ts.pop(lexer.Operator, "}}")155156 return ConditionalStatement(condition, body, alternative)157158 def iterative(self) -> Node:159 self.ts.pop(lexer.Operator, "{{")160 self.ts.pop(lexer.Keyword, "for")161 iterator = self.ident()162163 self.ts.pop(lexer.Keyword, "in")164 sequence = self.ident()165 self.ts.pop(lexer.Operator, "}}")166167 body = self.parse()168 self.ts.pop(lexer.Operator, "{{")169 self.ts.pop(lexer.Keyword, "end")170 self.ts.pop(lexer.Operator, "}}")171172 return IterativeStatement(iterator, sequence, body)173174 def inclusion(self) -> Node:175 self.ts.pop(lexer.Operator, "{{")176177 if self.ts.peek(0, lexer.Keyword, "paste"):178 israw = False179 elif self.ts.peek(0, lexer.Keyword, "raw"):180 israw = True181 else:182 raise TypeError("Not an inclusion statement!")183184 self.ts.pop()185 path = self.strConst()186 self.ts.pop(lexer.Operator, "}}")187188 return InclusionStatement(path, israw)189190191 def ident(self) -> Node:192 t = self.ts.pop(lexer.Identifier)193 return Identifier(t.text)194195 def numConst(self) -> Node:196 t = self.ts.pop(lexer.NumConst)197 return NumConst(float(t.text))198199 def strConst(self) -> Node:200 t = self.ts.pop(lexer.StrConst)201 return StrConst(t.text[1:-1])202203 def primaryExpr(self) -> Node:204 p = self.ts.peek()205 if isinstance(p, lexer.Identifier):206 return self.ident()207208 if isinstance(p, lexer.NumConst):209 return self.numConst()210211 if isinstance(p, lexer.StrConst):212 return self.strConst()213214 if isinstance(p, lexer.Operator) and p.text == "(":215 self.ts.pop()216 out = self.expr()217 self.ts.pop(lexer.Operator, ")")218219 return out220221 raise TypeError(f"PrimaryExpr expected")222223 def __arithmeticExpr(self, childExpr, operators) -> Node:224 out = childExpr()225226 while True:227 p = self.ts.peek()228 if not isinstance(p, lexer.Operator):229 break230231 if p.text not in operators:232 break233234 self.ts.pop(lexer.Operator)235 out = BinOp(out, p.text, childExpr())236237 return out238239 def multiExpr(self) -> Node:240 return self.__arithmeticExpr(241 self.primaryExpr,242 ("*", "/", "//", "%"))243244 def additiveExpr(self) -> Node:245 return self.__arithmeticExpr(246 self.multiExpr,247 ("+", "-"))248249 def shiftExpr(self) -> Node:250 return self.__arithmeticExpr(251 self.additiveExpr,252 ("<<", ">>"))253254 def compExpr(self) -> Node:255 return self.__arithmeticExpr(256 self.shiftExpr,257 ("<", ">", "<=", ">=", "==", "!="))258259 def bitwiseExpr(self) -> Node:260 return self.__arithmeticExpr(261 self.compExpr,262 ("&", "|", "^"))263264 def logicalExpr(self) -> Node:265 return self.__arithmeticExpr(266 self.bitwiseExpr,267 ("&&", "||"))268269 def expr(self) -> Node:270 return self.__arithmeticExpr(271 self.logicalExpr,272 (","))273274