stempl

Simple template language written in python

git clone git://git.janzachar.dev/stempl.git


0from dataclasses import dataclass, asdict
1import lexer
2
3
4# --- ast nodes ---
5
6class Node:
7    ...
8
9@dataclass
10class Raw(Node):
11    text: str
12    right: Optional[Node]
13
14@dataclass
15class Statement(Node):
16    child: Node
17    right: Optional[Raw | Statement]
18
19@dataclass
20class ConditionalStatement(Node):
21    condition: Expression
22    body: Raw | Statement
23    alternative: Raw | Statement
24
25@dataclass
26class IterativeStatement(Node):
27    iterator: Identifier
28    sequence: Identifier
29    body: Raw | Statement
30
31@dataclass
32class InclusionStatement(Node):
33    path: StrConst
34    israw: bool
35
36@dataclass
37class Identifier(Node):
38    name: str
39
40@dataclass
41class NumConst(Node):
42    data: int | float
43
44@dataclass
45class StrConst(Node):
46    data: str
47
48@dataclass
49class BinOp(Node):
50    left: Identifier | NumConst | StrConst
51    op: str
52    right: Identifier | NumConst | StrConst
53
54# --- parser ---
55
56class TokenStream:
57    def __init__(self, tok: List[lexer.Token]):
58        self.tok = tok
59        self.pos = 0
60
61    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 out
65
66        flag = True
67        flag &= of_type is None or isinstance(out, of_type)
68        flag &= of_text is None or out.text == of_text
69
70        return flag
71
72    def pop(self, of_type: Optional[lexer.Token] = None, of_text: Optional[str] = None):
73        out = self.peek()
74
75        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}")
77
78        if of_text is not None and of_text != out.text:
79            raise TypeError(f"Expected '{of_text}', got '{out.text}'")
80
81        self.pos += 1
82        return out
83
84    def __len__(self):
85        return len(self.tok) - self.pos
86
87    def __bool__(self):
88        return bool(len(self))
89
90class Parser:
91    def __init__(self, tok: List[lexer.Token]):
92        self.ts = TokenStream(tok)
93
94    def parse(self) -> Node:
95        if not self.ts:
96            return None
97
98        p = self.ts.peek()
99        if isinstance(p, lexer.Raw):
100            return self.raw()
101        else:
102            return self.statement()
103
104    def raw(self) -> Node:
105        return Raw(self.ts.pop(lexer.Raw).text, self.parse())
106
107    def statement(self) -> Node:
108        if not self.ts.peek(0, lexer.Operator, '{{'):
109            raise TypeError(f"'{{' expected, got {p.text}")
110
111        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        ]
119
120        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                    break
129
130        if out is None:
131            return None
132
133        return Statement(out, self.parse())
134        
135
136    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, "}}")
141
142        body = self.parse()
143        alternative = None
144
145        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, "}}")
149
150            alternative = self.parse()
151            self.ts.pop(lexer.Operator, "{{")
152
153        self.ts.pop(lexer.Keyword, "end")
154        self.ts.pop(lexer.Operator, "}}")
155
156        return ConditionalStatement(condition, body, alternative)
157
158    def iterative(self) -> Node:
159        self.ts.pop(lexer.Operator, "{{")
160        self.ts.pop(lexer.Keyword, "for")
161        iterator = self.ident()
162        
163        self.ts.pop(lexer.Keyword, "in")
164        sequence = self.ident()
165        self.ts.pop(lexer.Operator, "}}")
166
167        body = self.parse()
168        self.ts.pop(lexer.Operator, "{{")
169        self.ts.pop(lexer.Keyword, "end")
170        self.ts.pop(lexer.Operator, "}}")
171
172        return IterativeStatement(iterator, sequence, body)
173        
174    def inclusion(self) -> Node:
175        self.ts.pop(lexer.Operator, "{{")
176
177        if self.ts.peek(0, lexer.Keyword, "paste"):
178            israw = False
179        elif self.ts.peek(0, lexer.Keyword, "raw"):
180            israw = True
181        else:
182            raise TypeError("Not an inclusion statement!")
183
184        self.ts.pop()
185        path = self.strConst()
186        self.ts.pop(lexer.Operator, "}}")
187
188        return InclusionStatement(path, israw)
189
190
191    def ident(self) -> Node:
192        t = self.ts.pop(lexer.Identifier)
193        return Identifier(t.text)
194
195    def numConst(self) -> Node:
196        t = self.ts.pop(lexer.NumConst)
197        return NumConst(float(t.text))
198
199    def strConst(self) -> Node:
200        t = self.ts.pop(lexer.StrConst)
201        return StrConst(t.text[1:-1])
202
203    def primaryExpr(self) -> Node:
204        p = self.ts.peek()
205        if isinstance(p, lexer.Identifier):
206            return self.ident()
207
208        if isinstance(p, lexer.NumConst):
209            return self.numConst()
210
211        if isinstance(p, lexer.StrConst):
212            return self.strConst()
213
214        if isinstance(p, lexer.Operator) and p.text == "(":
215            self.ts.pop()
216            out = self.expr()
217            self.ts.pop(lexer.Operator, ")")
218
219            return out
220
221        raise TypeError(f"PrimaryExpr expected")
222
223    def __arithmeticExpr(self, childExpr, operators) -> Node:
224        out = childExpr()
225
226        while True:
227            p = self.ts.peek()
228            if not isinstance(p, lexer.Operator):
229                break
230
231            if p.text not in operators:
232                break
233
234            self.ts.pop(lexer.Operator)
235            out = BinOp(out, p.text, childExpr())
236
237        return out
238
239    def multiExpr(self) -> Node:
240        return self.__arithmeticExpr(
241                self.primaryExpr,
242                ("*", "/", "//", "%"))
243
244    def additiveExpr(self) -> Node:
245        return self.__arithmeticExpr(
246                self.multiExpr,
247                ("+", "-"))
248
249    def shiftExpr(self) -> Node:
250        return self.__arithmeticExpr(
251                self.additiveExpr,
252                ("<<", ">>"))
253
254    def compExpr(self) -> Node:
255        return self.__arithmeticExpr(
256                self.shiftExpr,
257                ("<", ">", "<=", ">=", "==", "!="))
258
259    def bitwiseExpr(self) -> Node:
260        return self.__arithmeticExpr(
261                self.compExpr,
262                ("&", "|", "^"))
263
264    def logicalExpr(self) -> Node:
265        return self.__arithmeticExpr(
266                self.bitwiseExpr,
267                ("&&", "||"))
268
269    def expr(self) -> Node:
270        return self.__arithmeticExpr(
271                self.logicalExpr,
272                (","))
273
274