tsolc 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- transpiler/main.py +1238 -0
- tsolc-0.1.0.dist-info/METADATA +19 -0
- tsolc-0.1.0.dist-info/RECORD +6 -0
- tsolc-0.1.0.dist-info/WHEEL +5 -0
- tsolc-0.1.0.dist-info/entry_points.txt +2 -0
- tsolc-0.1.0.dist-info/top_level.txt +1 -0
transpiler/main.py
ADDED
|
@@ -0,0 +1,1238 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Sol Compiler - Transpiles Sol language to C, then compiles and runs with clang.
|
|
4
|
+
|
|
5
|
+
Usage:
|
|
6
|
+
python sol.py <filename.sol> [--run] [--keep-c]
|
|
7
|
+
|
|
8
|
+
Supports:
|
|
9
|
+
get("path/to/file.sol") -- import another Sol file
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import sys
|
|
13
|
+
import os
|
|
14
|
+
import stat
|
|
15
|
+
import subprocess
|
|
16
|
+
from enum import Enum, auto
|
|
17
|
+
from typing import List, Dict, Optional, Tuple, Any
|
|
18
|
+
from dataclasses import dataclass
|
|
19
|
+
|
|
20
|
+
# =============================================================================
|
|
21
|
+
# TOKEN DEFINITIONS
|
|
22
|
+
# =============================================================================
|
|
23
|
+
|
|
24
|
+
class TokenType(Enum):
|
|
25
|
+
IDENTIFIER = auto()
|
|
26
|
+
TYPE = auto()
|
|
27
|
+
NUMBER = auto()
|
|
28
|
+
FLOAT = auto()
|
|
29
|
+
STRING = auto()
|
|
30
|
+
CHAR = auto()
|
|
31
|
+
|
|
32
|
+
PUB = auto()
|
|
33
|
+
IN = auto()
|
|
34
|
+
OUT = auto()
|
|
35
|
+
IF = auto()
|
|
36
|
+
ELSEIF = auto()
|
|
37
|
+
ELSE = auto()
|
|
38
|
+
FOR = auto()
|
|
39
|
+
END = auto()
|
|
40
|
+
STRUCT = auto()
|
|
41
|
+
GET = auto()
|
|
42
|
+
MAIN = auto()
|
|
43
|
+
RETURN = auto()
|
|
44
|
+
BREAK = auto()
|
|
45
|
+
CONTINUE = auto()
|
|
46
|
+
|
|
47
|
+
ASSIGN = auto()
|
|
48
|
+
COMMA = auto()
|
|
49
|
+
LBRACE = auto()
|
|
50
|
+
RBRACE = auto()
|
|
51
|
+
LPAREN = auto()
|
|
52
|
+
RPAREN = auto()
|
|
53
|
+
SEMICOLON = auto()
|
|
54
|
+
LBRACKET = auto()
|
|
55
|
+
RBRACKET = auto()
|
|
56
|
+
PLUSPLUS = auto()
|
|
57
|
+
PLUS = auto()
|
|
58
|
+
MINUS = auto()
|
|
59
|
+
MULTIPLY = auto()
|
|
60
|
+
DIVIDE = auto()
|
|
61
|
+
MODULO = auto()
|
|
62
|
+
EQUAL = auto()
|
|
63
|
+
NOTEQUAL = auto()
|
|
64
|
+
LESS = auto()
|
|
65
|
+
LESSEQUAL = auto()
|
|
66
|
+
GREATER = auto()
|
|
67
|
+
GREATEREQUAL = auto()
|
|
68
|
+
AND = auto()
|
|
69
|
+
OR = auto()
|
|
70
|
+
NOT = auto()
|
|
71
|
+
DOT = auto()
|
|
72
|
+
COLON = auto()
|
|
73
|
+
ARROW = auto()
|
|
74
|
+
|
|
75
|
+
EOF = auto()
|
|
76
|
+
|
|
77
|
+
KEYWORDS = {
|
|
78
|
+
'pub': TokenType.PUB,
|
|
79
|
+
'in': TokenType.IN,
|
|
80
|
+
'out': TokenType.OUT,
|
|
81
|
+
'if': TokenType.IF,
|
|
82
|
+
'elseif': TokenType.ELSEIF,
|
|
83
|
+
'else': TokenType.ELSE,
|
|
84
|
+
'for': TokenType.FOR,
|
|
85
|
+
'end': TokenType.END,
|
|
86
|
+
'struct': TokenType.STRUCT,
|
|
87
|
+
'get': TokenType.GET,
|
|
88
|
+
'main': TokenType.MAIN,
|
|
89
|
+
'return': TokenType.RETURN,
|
|
90
|
+
'break': TokenType.BREAK,
|
|
91
|
+
'continue': TokenType.CONTINUE,
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
TYPES = {
|
|
95
|
+
'int', 'float', 'char', 'bool', 'string', 'void',
|
|
96
|
+
'int8', 'int16', 'int32', 'int64',
|
|
97
|
+
'uint8', 'uint16', 'uint32', 'uint64',
|
|
98
|
+
'double', 'long', 'short', 'byte',
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
class Token:
|
|
102
|
+
def __init__(self, type: TokenType, value: Any = None, line: int = 0, column: int = 0):
|
|
103
|
+
self.type = type
|
|
104
|
+
self.value = value
|
|
105
|
+
self.line = line
|
|
106
|
+
self.column = column
|
|
107
|
+
|
|
108
|
+
def __repr__(self):
|
|
109
|
+
return f"Token({self.type.name}, {self.value!r}, line={self.line}, col={self.column})"
|
|
110
|
+
|
|
111
|
+
# =============================================================================
|
|
112
|
+
# LEXER
|
|
113
|
+
# =============================================================================
|
|
114
|
+
|
|
115
|
+
class Lexer:
|
|
116
|
+
def __init__(self, source: str):
|
|
117
|
+
self.source = source
|
|
118
|
+
self.position = 0
|
|
119
|
+
self.line = 1
|
|
120
|
+
self.column = 1
|
|
121
|
+
self.tokens = []
|
|
122
|
+
|
|
123
|
+
def error(self, msg: str):
|
|
124
|
+
raise SyntaxError(f"{msg} at line {self.line}, column {self.column}")
|
|
125
|
+
|
|
126
|
+
def advance(self) -> str:
|
|
127
|
+
char = self.source[self.position]
|
|
128
|
+
self.position += 1
|
|
129
|
+
if char == '\n':
|
|
130
|
+
self.line += 1
|
|
131
|
+
self.column = 1
|
|
132
|
+
else:
|
|
133
|
+
self.column += 1
|
|
134
|
+
return char
|
|
135
|
+
|
|
136
|
+
def peek(self, offset: int = 0) -> str:
|
|
137
|
+
pos = self.position + offset
|
|
138
|
+
if pos >= len(self.source):
|
|
139
|
+
return '\0'
|
|
140
|
+
return self.source[pos]
|
|
141
|
+
|
|
142
|
+
def add_token(self, type: TokenType, value: Any = None):
|
|
143
|
+
self.tokens.append(Token(type, value, self.line, self.column))
|
|
144
|
+
|
|
145
|
+
def tokenize(self) -> List[Token]:
|
|
146
|
+
while self.position < len(self.source):
|
|
147
|
+
char = self.peek()
|
|
148
|
+
|
|
149
|
+
if char.isspace():
|
|
150
|
+
self.advance()
|
|
151
|
+
continue
|
|
152
|
+
|
|
153
|
+
if char == '/' and self.peek(1) == '/':
|
|
154
|
+
while self.position < len(self.source) and self.peek() != '\n':
|
|
155
|
+
self.advance()
|
|
156
|
+
continue
|
|
157
|
+
|
|
158
|
+
if char == '/' and self.peek(1) == '*':
|
|
159
|
+
self.advance(); self.advance()
|
|
160
|
+
while self.position < len(self.source) - 1:
|
|
161
|
+
if self.peek() == '*' and self.peek(1) == '/':
|
|
162
|
+
self.advance(); self.advance()
|
|
163
|
+
break
|
|
164
|
+
self.advance()
|
|
165
|
+
continue
|
|
166
|
+
|
|
167
|
+
if char == '-' and self.peek(1) == '>':
|
|
168
|
+
self.advance(); self.advance()
|
|
169
|
+
self.add_token(TokenType.ASSIGN, '->')
|
|
170
|
+
continue
|
|
171
|
+
|
|
172
|
+
if char == '+' and self.peek(1) == '+':
|
|
173
|
+
self.advance(); self.advance()
|
|
174
|
+
self.add_token(TokenType.PLUSPLUS, '++')
|
|
175
|
+
continue
|
|
176
|
+
|
|
177
|
+
if char == '&' and self.peek(1) == '&':
|
|
178
|
+
self.advance(); self.advance()
|
|
179
|
+
self.add_token(TokenType.AND, '&&')
|
|
180
|
+
continue
|
|
181
|
+
|
|
182
|
+
if char == '|' and self.peek(1) == '|':
|
|
183
|
+
self.advance(); self.advance()
|
|
184
|
+
self.add_token(TokenType.OR, '||')
|
|
185
|
+
continue
|
|
186
|
+
|
|
187
|
+
if char == '=' and self.peek(1) == '=':
|
|
188
|
+
self.advance(); self.advance()
|
|
189
|
+
self.add_token(TokenType.EQUAL, '==')
|
|
190
|
+
continue
|
|
191
|
+
|
|
192
|
+
if char == '!' and self.peek(1) == '=':
|
|
193
|
+
self.advance(); self.advance()
|
|
194
|
+
self.add_token(TokenType.NOTEQUAL, '!=')
|
|
195
|
+
continue
|
|
196
|
+
|
|
197
|
+
if char == '<' and self.peek(1) == '=':
|
|
198
|
+
self.advance(); self.advance()
|
|
199
|
+
self.add_token(TokenType.LESSEQUAL, '<=')
|
|
200
|
+
continue
|
|
201
|
+
|
|
202
|
+
if char == '>' and self.peek(1) == '=':
|
|
203
|
+
self.advance(); self.advance()
|
|
204
|
+
self.add_token(TokenType.GREATEREQUAL, '>=')
|
|
205
|
+
continue
|
|
206
|
+
|
|
207
|
+
if char == '=' and self.peek(1) == '>':
|
|
208
|
+
self.advance(); self.advance()
|
|
209
|
+
self.add_token(TokenType.ARROW, '=>')
|
|
210
|
+
continue
|
|
211
|
+
|
|
212
|
+
single_tokens = {
|
|
213
|
+
',': TokenType.COMMA,
|
|
214
|
+
'{': TokenType.LBRACE,
|
|
215
|
+
'}': TokenType.RBRACE,
|
|
216
|
+
'(': TokenType.LPAREN,
|
|
217
|
+
')': TokenType.RPAREN,
|
|
218
|
+
';': TokenType.SEMICOLON,
|
|
219
|
+
'[': TokenType.LBRACKET,
|
|
220
|
+
']': TokenType.RBRACKET,
|
|
221
|
+
'+': TokenType.PLUS,
|
|
222
|
+
'-': TokenType.MINUS,
|
|
223
|
+
'*': TokenType.MULTIPLY,
|
|
224
|
+
'/': TokenType.DIVIDE,
|
|
225
|
+
'%': TokenType.MODULO,
|
|
226
|
+
'<': TokenType.LESS,
|
|
227
|
+
'>': TokenType.GREATER,
|
|
228
|
+
'!': TokenType.NOT,
|
|
229
|
+
'.': TokenType.DOT,
|
|
230
|
+
':': TokenType.COLON,
|
|
231
|
+
}
|
|
232
|
+
if char in single_tokens:
|
|
233
|
+
self.advance()
|
|
234
|
+
self.add_token(single_tokens[char], char)
|
|
235
|
+
continue
|
|
236
|
+
|
|
237
|
+
if char == '"':
|
|
238
|
+
self.advance()
|
|
239
|
+
start_line, start_col = self.line, self.column
|
|
240
|
+
value = ""
|
|
241
|
+
while self.position < len(self.source) and self.peek() != '"':
|
|
242
|
+
if self.peek() == '\\':
|
|
243
|
+
self.advance()
|
|
244
|
+
esc = self.advance()
|
|
245
|
+
value += {'n': '\n', 't': '\t', 'r': '\r', '\\': '\\', '"': '"', '0': '\0'}.get(esc, esc)
|
|
246
|
+
else:
|
|
247
|
+
value += self.advance()
|
|
248
|
+
if self.position >= len(self.source):
|
|
249
|
+
self.error("Unterminated string literal")
|
|
250
|
+
self.advance()
|
|
251
|
+
self.tokens.append(Token(TokenType.STRING, value, start_line, start_col))
|
|
252
|
+
continue
|
|
253
|
+
|
|
254
|
+
if char == "'":
|
|
255
|
+
self.advance()
|
|
256
|
+
start_line, start_col = self.line, self.column
|
|
257
|
+
value = ""
|
|
258
|
+
if self.peek() == '\\':
|
|
259
|
+
self.advance()
|
|
260
|
+
esc = self.advance()
|
|
261
|
+
value = {'n': '\n', 't': '\t', 'r': '\r', '\\': '\\', '"': '"', '0': '\0', "'": "'"}.get(esc, esc)
|
|
262
|
+
else:
|
|
263
|
+
value = self.advance()
|
|
264
|
+
if self.peek() != "'":
|
|
265
|
+
self.error("Unterminated char literal")
|
|
266
|
+
self.advance()
|
|
267
|
+
self.tokens.append(Token(TokenType.CHAR, value, start_line, start_col))
|
|
268
|
+
continue
|
|
269
|
+
|
|
270
|
+
if char.isdigit():
|
|
271
|
+
start_line, start_col = self.line, self.column
|
|
272
|
+
value = ""
|
|
273
|
+
while self.position < len(self.source) and self.peek().isdigit():
|
|
274
|
+
value += self.advance()
|
|
275
|
+
if self.peek() == '.' and self.peek(1).isdigit():
|
|
276
|
+
value += self.advance()
|
|
277
|
+
while self.position < len(self.source) and self.peek().isdigit():
|
|
278
|
+
value += self.advance()
|
|
279
|
+
self.tokens.append(Token(TokenType.FLOAT, float(value), start_line, start_col))
|
|
280
|
+
else:
|
|
281
|
+
self.tokens.append(Token(TokenType.NUMBER, int(value), start_line, start_col))
|
|
282
|
+
continue
|
|
283
|
+
|
|
284
|
+
if char.isalpha() or char == '_':
|
|
285
|
+
start_line, start_col = self.line, self.column
|
|
286
|
+
value = ""
|
|
287
|
+
while self.position < len(self.source) and (self.peek().isalnum() or self.peek() == '_'):
|
|
288
|
+
value += self.advance()
|
|
289
|
+
if value in KEYWORDS:
|
|
290
|
+
self.tokens.append(Token(KEYWORDS[value], value, start_line, start_col))
|
|
291
|
+
elif value in TYPES:
|
|
292
|
+
self.tokens.append(Token(TokenType.TYPE, value, start_line, start_col))
|
|
293
|
+
else:
|
|
294
|
+
self.tokens.append(Token(TokenType.IDENTIFIER, value, start_line, start_col))
|
|
295
|
+
continue
|
|
296
|
+
|
|
297
|
+
self.error(f"Unexpected character: {char!r}")
|
|
298
|
+
|
|
299
|
+
self.add_token(TokenType.EOF, None)
|
|
300
|
+
return self.tokens
|
|
301
|
+
|
|
302
|
+
# =============================================================================
|
|
303
|
+
# AST NODES
|
|
304
|
+
# =============================================================================
|
|
305
|
+
|
|
306
|
+
@dataclass
|
|
307
|
+
class Program:
|
|
308
|
+
declarations: List[Any]
|
|
309
|
+
|
|
310
|
+
@dataclass
|
|
311
|
+
class ImportStmt:
|
|
312
|
+
path: str
|
|
313
|
+
|
|
314
|
+
@dataclass
|
|
315
|
+
class FuncDecl:
|
|
316
|
+
name: str
|
|
317
|
+
params: List[Tuple[str, str]]
|
|
318
|
+
return_type: Optional[str]
|
|
319
|
+
body: 'Block'
|
|
320
|
+
is_pub: bool = False
|
|
321
|
+
|
|
322
|
+
@dataclass
|
|
323
|
+
class StructDecl:
|
|
324
|
+
name: str
|
|
325
|
+
fields: List[Tuple[str, str]]
|
|
326
|
+
is_pub: bool = False
|
|
327
|
+
|
|
328
|
+
@dataclass
|
|
329
|
+
class Block:
|
|
330
|
+
statements: List[Any]
|
|
331
|
+
|
|
332
|
+
@dataclass
|
|
333
|
+
class VarDecl:
|
|
334
|
+
name: str
|
|
335
|
+
var_type: str
|
|
336
|
+
initializer: Optional[Any] = None
|
|
337
|
+
|
|
338
|
+
@dataclass
|
|
339
|
+
class Assignment:
|
|
340
|
+
target: Any
|
|
341
|
+
value: Any
|
|
342
|
+
|
|
343
|
+
@dataclass
|
|
344
|
+
class IfStmt:
|
|
345
|
+
condition: Any
|
|
346
|
+
then_branch: Block
|
|
347
|
+
elseifs: List[Tuple[Any, Block]]
|
|
348
|
+
else_branch: Optional[Block]
|
|
349
|
+
|
|
350
|
+
@dataclass
|
|
351
|
+
class ForStmt:
|
|
352
|
+
init: Optional[Any]
|
|
353
|
+
condition: Optional[Any]
|
|
354
|
+
increment: Optional[Any]
|
|
355
|
+
body: Block
|
|
356
|
+
|
|
357
|
+
@dataclass
|
|
358
|
+
class ForEachStmt:
|
|
359
|
+
var_name: str
|
|
360
|
+
iterable: Any
|
|
361
|
+
body: Block
|
|
362
|
+
|
|
363
|
+
@dataclass
|
|
364
|
+
class ReturnStmt:
|
|
365
|
+
value: Optional[Any]
|
|
366
|
+
|
|
367
|
+
@dataclass
|
|
368
|
+
class BreakStmt:
|
|
369
|
+
pass
|
|
370
|
+
|
|
371
|
+
@dataclass
|
|
372
|
+
class ContinueStmt:
|
|
373
|
+
pass
|
|
374
|
+
|
|
375
|
+
@dataclass
|
|
376
|
+
class ExprStmt:
|
|
377
|
+
expr: Any
|
|
378
|
+
|
|
379
|
+
@dataclass
|
|
380
|
+
class BinaryOp:
|
|
381
|
+
op: str
|
|
382
|
+
left: Any
|
|
383
|
+
right: Any
|
|
384
|
+
|
|
385
|
+
@dataclass
|
|
386
|
+
class UnaryOp:
|
|
387
|
+
op: str
|
|
388
|
+
operand: Any
|
|
389
|
+
|
|
390
|
+
@dataclass
|
|
391
|
+
class CallExpr:
|
|
392
|
+
name: str
|
|
393
|
+
args: List[Any]
|
|
394
|
+
|
|
395
|
+
@dataclass
|
|
396
|
+
class GetExpr:
|
|
397
|
+
obj: Any
|
|
398
|
+
field: str
|
|
399
|
+
|
|
400
|
+
@dataclass
|
|
401
|
+
class IndexExpr:
|
|
402
|
+
obj: Any
|
|
403
|
+
index: Any
|
|
404
|
+
|
|
405
|
+
@dataclass
|
|
406
|
+
class Identifier:
|
|
407
|
+
name: str
|
|
408
|
+
|
|
409
|
+
@dataclass
|
|
410
|
+
class NumberLiteral:
|
|
411
|
+
value: int
|
|
412
|
+
|
|
413
|
+
@dataclass
|
|
414
|
+
class FloatLiteral:
|
|
415
|
+
value: float
|
|
416
|
+
|
|
417
|
+
@dataclass
|
|
418
|
+
class StringLiteral:
|
|
419
|
+
value: str
|
|
420
|
+
|
|
421
|
+
@dataclass
|
|
422
|
+
class CharLiteral:
|
|
423
|
+
value: str
|
|
424
|
+
|
|
425
|
+
@dataclass
|
|
426
|
+
class BoolLiteral:
|
|
427
|
+
value: bool
|
|
428
|
+
|
|
429
|
+
@dataclass
|
|
430
|
+
class ArrayLiteral:
|
|
431
|
+
elements: List[Any]
|
|
432
|
+
|
|
433
|
+
# =============================================================================
|
|
434
|
+
# PARSER
|
|
435
|
+
# =============================================================================
|
|
436
|
+
|
|
437
|
+
class Parser:
|
|
438
|
+
def __init__(self, tokens: List[Token]):
|
|
439
|
+
self.tokens = tokens
|
|
440
|
+
self.position = 0
|
|
441
|
+
|
|
442
|
+
def current(self) -> Token:
|
|
443
|
+
return self.tokens[self.position]
|
|
444
|
+
|
|
445
|
+
def peek(self, offset: int = 0) -> Token:
|
|
446
|
+
pos = self.position + offset
|
|
447
|
+
if pos >= len(self.tokens):
|
|
448
|
+
return self.tokens[-1]
|
|
449
|
+
return self.tokens[pos]
|
|
450
|
+
|
|
451
|
+
def advance(self) -> Token:
|
|
452
|
+
tok = self.current()
|
|
453
|
+
if self.position < len(self.tokens) - 1:
|
|
454
|
+
self.position += 1
|
|
455
|
+
return tok
|
|
456
|
+
|
|
457
|
+
def expect(self, type: TokenType, msg: str = None) -> Token:
|
|
458
|
+
if self.current().type != type:
|
|
459
|
+
raise SyntaxError(
|
|
460
|
+
f"Expected {type.name} but got {self.current().type.name}"
|
|
461
|
+
f"{(' (' + msg + ')') if msg else ''} at line {self.current().line}"
|
|
462
|
+
)
|
|
463
|
+
return self.advance()
|
|
464
|
+
|
|
465
|
+
def match(self, *types: TokenType) -> bool:
|
|
466
|
+
return self.current().type in types
|
|
467
|
+
|
|
468
|
+
def parse_identifier(self) -> Token:
|
|
469
|
+
if self.match(TokenType.IDENTIFIER, TokenType.MAIN, TokenType.GET):
|
|
470
|
+
return self.advance()
|
|
471
|
+
raise SyntaxError(f"Expected identifier but got {self.current().type.name} at line {self.current().line}")
|
|
472
|
+
|
|
473
|
+
def parse(self) -> Program:
|
|
474
|
+
declarations = []
|
|
475
|
+
while not self.match(TokenType.EOF):
|
|
476
|
+
declarations.append(self.parse_declaration())
|
|
477
|
+
return Program(declarations)
|
|
478
|
+
|
|
479
|
+
def parse_declaration(self):
|
|
480
|
+
is_pub = False
|
|
481
|
+
if self.match(TokenType.PUB):
|
|
482
|
+
self.advance()
|
|
483
|
+
is_pub = True
|
|
484
|
+
|
|
485
|
+
if self.match(TokenType.STRUCT):
|
|
486
|
+
return self.parse_struct_decl(is_pub)
|
|
487
|
+
elif self.match(TokenType.GET):
|
|
488
|
+
return self.parse_import_stmt()
|
|
489
|
+
else:
|
|
490
|
+
return self.parse_func_decl(is_pub)
|
|
491
|
+
|
|
492
|
+
def parse_import_stmt(self) -> ImportStmt:
|
|
493
|
+
self.expect(TokenType.GET)
|
|
494
|
+
self.expect(TokenType.LPAREN)
|
|
495
|
+
path = self.expect(TokenType.STRING).value
|
|
496
|
+
self.expect(TokenType.RPAREN)
|
|
497
|
+
self.expect(TokenType.SEMICOLON)
|
|
498
|
+
return ImportStmt(path)
|
|
499
|
+
|
|
500
|
+
def parse_struct_decl(self, is_pub: bool) -> StructDecl:
|
|
501
|
+
self.expect(TokenType.STRUCT)
|
|
502
|
+
name = self.parse_identifier().value
|
|
503
|
+
self.expect(TokenType.LBRACE)
|
|
504
|
+
fields = []
|
|
505
|
+
while not self.match(TokenType.RBRACE):
|
|
506
|
+
# C-style: type name;
|
|
507
|
+
ftype = self.parse_type()
|
|
508
|
+
fname = self.parse_identifier().value
|
|
509
|
+
self.expect(TokenType.SEMICOLON)
|
|
510
|
+
fields.append((fname, ftype))
|
|
511
|
+
self.expect(TokenType.RBRACE)
|
|
512
|
+
return StructDecl(name, fields, is_pub)
|
|
513
|
+
|
|
514
|
+
def parse_func_decl(self, is_pub: bool) -> FuncDecl:
|
|
515
|
+
name = self.parse_identifier().value
|
|
516
|
+
self.expect(TokenType.LPAREN)
|
|
517
|
+
params = []
|
|
518
|
+
if not self.match(TokenType.RPAREN):
|
|
519
|
+
params.append(self.parse_param())
|
|
520
|
+
while self.match(TokenType.COMMA):
|
|
521
|
+
self.advance()
|
|
522
|
+
params.append(self.parse_param())
|
|
523
|
+
self.expect(TokenType.RPAREN)
|
|
524
|
+
|
|
525
|
+
return_type = None
|
|
526
|
+
if self.match(TokenType.OUT):
|
|
527
|
+
self.advance()
|
|
528
|
+
return_type = self.parse_type()
|
|
529
|
+
|
|
530
|
+
body = self.parse_block()
|
|
531
|
+
return FuncDecl(name, params, return_type, body, is_pub)
|
|
532
|
+
|
|
533
|
+
def parse_param(self) -> Tuple[str, str]:
|
|
534
|
+
if self.match(TokenType.IN):
|
|
535
|
+
self.advance()
|
|
536
|
+
# C-style: type name
|
|
537
|
+
ptype = self.parse_type()
|
|
538
|
+
name = self.parse_identifier().value
|
|
539
|
+
return (name, ptype)
|
|
540
|
+
|
|
541
|
+
def parse_type(self) -> str:
|
|
542
|
+
if self.match(TokenType.TYPE):
|
|
543
|
+
base = self.advance().value
|
|
544
|
+
elif self.match(TokenType.IDENTIFIER):
|
|
545
|
+
base = self.advance().value
|
|
546
|
+
else:
|
|
547
|
+
raise SyntaxError(f"Expected type at line {self.current().line}")
|
|
548
|
+
if self.match(TokenType.LBRACKET):
|
|
549
|
+
self.advance()
|
|
550
|
+
self.expect(TokenType.RBRACKET)
|
|
551
|
+
base += "[]"
|
|
552
|
+
return base
|
|
553
|
+
|
|
554
|
+
def parse_block(self) -> Block:
|
|
555
|
+
self.expect(TokenType.LBRACE)
|
|
556
|
+
statements = []
|
|
557
|
+
while not self.match(TokenType.RBRACE):
|
|
558
|
+
statements.append(self.parse_statement())
|
|
559
|
+
self.expect(TokenType.RBRACE)
|
|
560
|
+
return Block(statements)
|
|
561
|
+
|
|
562
|
+
def parse_statement(self):
|
|
563
|
+
if self.match(TokenType.IF):
|
|
564
|
+
return self.parse_if_stmt()
|
|
565
|
+
if self.match(TokenType.FOR):
|
|
566
|
+
return self.parse_for_stmt()
|
|
567
|
+
if self.match(TokenType.OUT):
|
|
568
|
+
return self.parse_return_stmt()
|
|
569
|
+
if self.match(TokenType.RETURN):
|
|
570
|
+
return self.parse_return_stmt()
|
|
571
|
+
if self.match(TokenType.BREAK):
|
|
572
|
+
self.advance()
|
|
573
|
+
self.expect(TokenType.SEMICOLON)
|
|
574
|
+
return BreakStmt()
|
|
575
|
+
if self.match(TokenType.CONTINUE):
|
|
576
|
+
self.advance()
|
|
577
|
+
self.expect(TokenType.SEMICOLON)
|
|
578
|
+
return ContinueStmt()
|
|
579
|
+
if self.match(TokenType.GET):
|
|
580
|
+
return self.parse_import_stmt()
|
|
581
|
+
|
|
582
|
+
# Variable declaration: type name -> value; OR name type -> value;
|
|
583
|
+
if self.match(TokenType.IDENTIFIER, TokenType.MAIN, TokenType.GET, TokenType.TYPE):
|
|
584
|
+
if self.peek(1).type == TokenType.TYPE or self.peek(1).type == TokenType.IDENTIFIER:
|
|
585
|
+
return self.parse_var_decl()
|
|
586
|
+
expr = self.parse_expression()
|
|
587
|
+
if self.match(TokenType.ASSIGN):
|
|
588
|
+
self.advance()
|
|
589
|
+
value = self.parse_expression()
|
|
590
|
+
self.expect(TokenType.SEMICOLON)
|
|
591
|
+
return Assignment(expr, value)
|
|
592
|
+
self.expect(TokenType.SEMICOLON)
|
|
593
|
+
return ExprStmt(expr)
|
|
594
|
+
|
|
595
|
+
expr = self.parse_expression()
|
|
596
|
+
self.expect(TokenType.SEMICOLON)
|
|
597
|
+
return ExprStmt(expr)
|
|
598
|
+
|
|
599
|
+
def parse_var_decl(self) -> VarDecl:
|
|
600
|
+
# C-style: type name -> value; (primary)
|
|
601
|
+
# Also supports: name type -> value; (legacy)
|
|
602
|
+
if self.match(TokenType.TYPE):
|
|
603
|
+
vtype = self.parse_type()
|
|
604
|
+
name = self.parse_identifier().value
|
|
605
|
+
else:
|
|
606
|
+
name = self.parse_identifier().value
|
|
607
|
+
vtype = self.parse_type()
|
|
608
|
+
init = None
|
|
609
|
+
if self.match(TokenType.ASSIGN):
|
|
610
|
+
self.advance()
|
|
611
|
+
init = self.parse_expression()
|
|
612
|
+
self.expect(TokenType.SEMICOLON)
|
|
613
|
+
return VarDecl(name, vtype, init)
|
|
614
|
+
|
|
615
|
+
def parse_if_stmt(self) -> IfStmt:
|
|
616
|
+
self.expect(TokenType.IF)
|
|
617
|
+
condition = self.parse_expression()
|
|
618
|
+
then_branch = self.parse_block()
|
|
619
|
+
elseifs = []
|
|
620
|
+
while self.match(TokenType.ELSEIF):
|
|
621
|
+
self.advance()
|
|
622
|
+
cond = self.parse_expression()
|
|
623
|
+
block = self.parse_block()
|
|
624
|
+
elseifs.append((cond, block))
|
|
625
|
+
else_branch = None
|
|
626
|
+
if self.match(TokenType.ELSE):
|
|
627
|
+
self.advance()
|
|
628
|
+
else_branch = self.parse_block()
|
|
629
|
+
self.expect(TokenType.END)
|
|
630
|
+
return IfStmt(condition, then_branch, elseifs, else_branch)
|
|
631
|
+
|
|
632
|
+
def parse_for_stmt(self):
|
|
633
|
+
self.expect(TokenType.FOR)
|
|
634
|
+
if self.match(TokenType.IDENTIFIER, TokenType.MAIN, TokenType.GET) and self.peek(1).type == TokenType.IN:
|
|
635
|
+
var_name = self.parse_identifier().value
|
|
636
|
+
self.advance() # consume 'in'
|
|
637
|
+
iterable = self.parse_expression()
|
|
638
|
+
body = self.parse_block()
|
|
639
|
+
self.expect(TokenType.END)
|
|
640
|
+
return ForEachStmt(var_name, iterable, body)
|
|
641
|
+
|
|
642
|
+
init = None
|
|
643
|
+
if not self.match(TokenType.SEMICOLON):
|
|
644
|
+
if (self.match(TokenType.IDENTIFIER, TokenType.MAIN, TokenType.GET, TokenType.TYPE) and
|
|
645
|
+
(self.peek(1).type == TokenType.TYPE or self.peek(1).type == TokenType.IDENTIFIER)):
|
|
646
|
+
init = self.parse_var_decl()
|
|
647
|
+
else:
|
|
648
|
+
init = self.parse_expression()
|
|
649
|
+
if self.match(TokenType.ASSIGN):
|
|
650
|
+
self.advance()
|
|
651
|
+
val = self.parse_expression()
|
|
652
|
+
init = Assignment(init, val)
|
|
653
|
+
self.expect(TokenType.SEMICOLON)
|
|
654
|
+
|
|
655
|
+
if init is None or isinstance(init, (VarDecl, Assignment)):
|
|
656
|
+
if init is None:
|
|
657
|
+
self.expect(TokenType.SEMICOLON)
|
|
658
|
+
|
|
659
|
+
condition = None
|
|
660
|
+
if not self.match(TokenType.SEMICOLON):
|
|
661
|
+
condition = self.parse_expression()
|
|
662
|
+
self.expect(TokenType.SEMICOLON)
|
|
663
|
+
|
|
664
|
+
increment = None
|
|
665
|
+
if not self.match(TokenType.LBRACE):
|
|
666
|
+
increment = self.parse_expression()
|
|
667
|
+
if self.match(TokenType.ASSIGN):
|
|
668
|
+
self.advance()
|
|
669
|
+
val = self.parse_expression()
|
|
670
|
+
increment = Assignment(increment, val)
|
|
671
|
+
|
|
672
|
+
body = self.parse_block()
|
|
673
|
+
self.expect(TokenType.END)
|
|
674
|
+
return ForStmt(init, condition, increment, body)
|
|
675
|
+
|
|
676
|
+
def parse_return_stmt(self) -> ReturnStmt:
|
|
677
|
+
if self.match(TokenType.OUT):
|
|
678
|
+
self.advance()
|
|
679
|
+
elif self.match(TokenType.RETURN):
|
|
680
|
+
self.advance()
|
|
681
|
+
value = None
|
|
682
|
+
if not self.match(TokenType.SEMICOLON):
|
|
683
|
+
value = self.parse_expression()
|
|
684
|
+
self.expect(TokenType.SEMICOLON)
|
|
685
|
+
return ReturnStmt(value)
|
|
686
|
+
|
|
687
|
+
def parse_expression(self):
|
|
688
|
+
return self.parse_or()
|
|
689
|
+
|
|
690
|
+
def parse_or(self):
|
|
691
|
+
left = self.parse_and()
|
|
692
|
+
while self.match(TokenType.OR):
|
|
693
|
+
op = self.advance().value
|
|
694
|
+
right = self.parse_and()
|
|
695
|
+
left = BinaryOp(op, left, right)
|
|
696
|
+
return left
|
|
697
|
+
|
|
698
|
+
def parse_and(self):
|
|
699
|
+
left = self.parse_equality()
|
|
700
|
+
while self.match(TokenType.AND):
|
|
701
|
+
op = self.advance().value
|
|
702
|
+
right = self.parse_equality()
|
|
703
|
+
left = BinaryOp(op, left, right)
|
|
704
|
+
return left
|
|
705
|
+
|
|
706
|
+
def parse_equality(self):
|
|
707
|
+
left = self.parse_comparison()
|
|
708
|
+
while self.match(TokenType.EQUAL, TokenType.NOTEQUAL):
|
|
709
|
+
op = self.advance().value
|
|
710
|
+
right = self.parse_comparison()
|
|
711
|
+
left = BinaryOp(op, left, right)
|
|
712
|
+
return left
|
|
713
|
+
|
|
714
|
+
def parse_comparison(self):
|
|
715
|
+
left = self.parse_term()
|
|
716
|
+
while self.match(TokenType.LESS, TokenType.LESSEQUAL, TokenType.GREATER, TokenType.GREATEREQUAL):
|
|
717
|
+
op = self.advance().value
|
|
718
|
+
right = self.parse_term()
|
|
719
|
+
left = BinaryOp(op, left, right)
|
|
720
|
+
return left
|
|
721
|
+
|
|
722
|
+
def parse_term(self):
|
|
723
|
+
left = self.parse_factor()
|
|
724
|
+
while self.match(TokenType.PLUS, TokenType.MINUS):
|
|
725
|
+
op = self.advance().value
|
|
726
|
+
right = self.parse_factor()
|
|
727
|
+
left = BinaryOp(op, left, right)
|
|
728
|
+
return left
|
|
729
|
+
|
|
730
|
+
def parse_factor(self):
|
|
731
|
+
left = self.parse_unary()
|
|
732
|
+
while self.match(TokenType.MULTIPLY, TokenType.DIVIDE, TokenType.MODULO):
|
|
733
|
+
op = self.advance().value
|
|
734
|
+
right = self.parse_unary()
|
|
735
|
+
left = BinaryOp(op, left, right)
|
|
736
|
+
return left
|
|
737
|
+
|
|
738
|
+
def parse_unary(self):
|
|
739
|
+
if self.match(TokenType.NOT, TokenType.MINUS):
|
|
740
|
+
op = self.advance().value
|
|
741
|
+
operand = self.parse_unary()
|
|
742
|
+
return UnaryOp(op, operand)
|
|
743
|
+
return self.parse_postfix()
|
|
744
|
+
|
|
745
|
+
def parse_postfix(self):
|
|
746
|
+
expr = self.parse_primary()
|
|
747
|
+
while True:
|
|
748
|
+
if self.match(TokenType.LPAREN):
|
|
749
|
+
expr = self.parse_call(expr)
|
|
750
|
+
elif self.match(TokenType.LBRACKET):
|
|
751
|
+
self.advance()
|
|
752
|
+
index = self.parse_expression()
|
|
753
|
+
self.expect(TokenType.RBRACKET)
|
|
754
|
+
expr = IndexExpr(expr, index)
|
|
755
|
+
elif self.match(TokenType.DOT):
|
|
756
|
+
self.advance()
|
|
757
|
+
field = self.parse_identifier().value
|
|
758
|
+
expr = GetExpr(expr, field)
|
|
759
|
+
elif self.match(TokenType.PLUSPLUS):
|
|
760
|
+
self.advance()
|
|
761
|
+
expr = UnaryOp('++', expr)
|
|
762
|
+
else:
|
|
763
|
+
break
|
|
764
|
+
return expr
|
|
765
|
+
|
|
766
|
+
def parse_call(self, callee):
|
|
767
|
+
self.expect(TokenType.LPAREN)
|
|
768
|
+
args = []
|
|
769
|
+
if not self.match(TokenType.RPAREN):
|
|
770
|
+
args.append(self.parse_expression())
|
|
771
|
+
while self.match(TokenType.COMMA):
|
|
772
|
+
self.advance()
|
|
773
|
+
args.append(self.parse_expression())
|
|
774
|
+
self.expect(TokenType.RPAREN)
|
|
775
|
+
name = callee.name if isinstance(callee, Identifier) else None
|
|
776
|
+
return CallExpr(name, args)
|
|
777
|
+
|
|
778
|
+
def parse_primary(self):
|
|
779
|
+
if self.match(TokenType.NUMBER):
|
|
780
|
+
return NumberLiteral(self.advance().value)
|
|
781
|
+
if self.match(TokenType.FLOAT):
|
|
782
|
+
return FloatLiteral(self.advance().value)
|
|
783
|
+
if self.match(TokenType.STRING):
|
|
784
|
+
return StringLiteral(self.advance().value)
|
|
785
|
+
if self.match(TokenType.CHAR):
|
|
786
|
+
return CharLiteral(self.advance().value)
|
|
787
|
+
if self.match(TokenType.IDENTIFIER, TokenType.MAIN, TokenType.GET):
|
|
788
|
+
return Identifier(self.advance().value)
|
|
789
|
+
if self.match(TokenType.LPAREN):
|
|
790
|
+
self.advance()
|
|
791
|
+
expr = self.parse_expression()
|
|
792
|
+
self.expect(TokenType.RPAREN)
|
|
793
|
+
return expr
|
|
794
|
+
if self.match(TokenType.LBRACKET):
|
|
795
|
+
return self.parse_array_literal()
|
|
796
|
+
raise SyntaxError(f"Unexpected token {self.current().type.name} at line {self.current().line}")
|
|
797
|
+
|
|
798
|
+
def parse_array_literal(self):
|
|
799
|
+
self.expect(TokenType.LBRACKET)
|
|
800
|
+
elements = []
|
|
801
|
+
if not self.match(TokenType.RBRACKET):
|
|
802
|
+
elements.append(self.parse_expression())
|
|
803
|
+
while self.match(TokenType.COMMA):
|
|
804
|
+
self.advance()
|
|
805
|
+
elements.append(self.parse_expression())
|
|
806
|
+
self.expect(TokenType.RBRACKET)
|
|
807
|
+
return ArrayLiteral(elements)
|
|
808
|
+
|
|
809
|
+
# =============================================================================
|
|
810
|
+
# C CODE GENERATOR
|
|
811
|
+
# =============================================================================
|
|
812
|
+
|
|
813
|
+
class CGenerator:
|
|
814
|
+
def __init__(self):
|
|
815
|
+
self.output = []
|
|
816
|
+
self.indent_level = 0
|
|
817
|
+
self.structs = {}
|
|
818
|
+
self.functions = {}
|
|
819
|
+
self.local_vars = []
|
|
820
|
+
|
|
821
|
+
def indent(self):
|
|
822
|
+
return " " * self.indent_level
|
|
823
|
+
|
|
824
|
+
def emit(self, line: str = ""):
|
|
825
|
+
if line:
|
|
826
|
+
self.output.append(self.indent() + line)
|
|
827
|
+
else:
|
|
828
|
+
self.output.append("")
|
|
829
|
+
|
|
830
|
+
def generate(self, program: Program) -> str:
|
|
831
|
+
# Deduplicate while building lookup tables
|
|
832
|
+
seen_structs = set()
|
|
833
|
+
seen_funcs = set()
|
|
834
|
+
for decl in program.declarations:
|
|
835
|
+
if isinstance(decl, StructDecl):
|
|
836
|
+
if decl.name not in seen_structs:
|
|
837
|
+
seen_structs.add(decl.name)
|
|
838
|
+
self.structs[decl.name] = decl
|
|
839
|
+
elif isinstance(decl, FuncDecl):
|
|
840
|
+
if decl.name not in seen_funcs:
|
|
841
|
+
seen_funcs.add(decl.name)
|
|
842
|
+
self.functions[decl.name] = decl
|
|
843
|
+
|
|
844
|
+
self.emit("#include <stdio.h>")
|
|
845
|
+
self.emit("#include <stdlib.h>")
|
|
846
|
+
self.emit("#include <string.h>")
|
|
847
|
+
self.emit("#include <stdint.h>")
|
|
848
|
+
self.emit("#include <stdbool.h>")
|
|
849
|
+
self.emit("")
|
|
850
|
+
|
|
851
|
+
# Struct declarations first
|
|
852
|
+
for name in self.structs:
|
|
853
|
+
self.emit_struct_decl(self.structs[name])
|
|
854
|
+
self.emit("")
|
|
855
|
+
|
|
856
|
+
# Forward declarations
|
|
857
|
+
for name in self.functions:
|
|
858
|
+
self.emit_forward_decl(self.functions[name])
|
|
859
|
+
self.emit("")
|
|
860
|
+
|
|
861
|
+
# Function definitions
|
|
862
|
+
for name in self.functions:
|
|
863
|
+
self.emit_func_decl(self.functions[name])
|
|
864
|
+
|
|
865
|
+
return "\n".join(self.output)
|
|
866
|
+
|
|
867
|
+
def emit_forward_decl(self, decl: FuncDecl):
|
|
868
|
+
rtype = self.map_type(decl.return_type) if decl.return_type else "void"
|
|
869
|
+
params = ", ".join(f"{self.map_type(t)} {n}" for n, t in decl.params) if decl.params else "void"
|
|
870
|
+
self.emit(f"{rtype} {decl.name}({params});")
|
|
871
|
+
|
|
872
|
+
def emit_struct_decl(self, decl: StructDecl):
|
|
873
|
+
self.emit(f"typedef struct {decl.name} {decl.name};")
|
|
874
|
+
self.emit(f"struct {decl.name} {{")
|
|
875
|
+
self.indent_level += 1
|
|
876
|
+
for fname, ftype in decl.fields:
|
|
877
|
+
self.emit(f"{self.map_type(ftype)} {fname};")
|
|
878
|
+
self.indent_level -= 1
|
|
879
|
+
self.emit("};")
|
|
880
|
+
|
|
881
|
+
def emit_func_decl(self, decl: FuncDecl):
|
|
882
|
+
rtype = self.map_type(decl.return_type) if decl.return_type else "void"
|
|
883
|
+
params = ", ".join(f"{self.map_type(t)} {n}" for n, t in decl.params) if decl.params else "void"
|
|
884
|
+
self.emit(f"{rtype} {decl.name}({params}) {{")
|
|
885
|
+
self.indent_level += 1
|
|
886
|
+
self.local_vars = [{}]
|
|
887
|
+
for stmt in decl.body.statements:
|
|
888
|
+
self.emit_stmt(stmt)
|
|
889
|
+
self.indent_level -= 1
|
|
890
|
+
self.emit("}")
|
|
891
|
+
self.emit("")
|
|
892
|
+
|
|
893
|
+
def map_type(self, sol_type: str) -> str:
|
|
894
|
+
if sol_type is None:
|
|
895
|
+
return "void"
|
|
896
|
+
if sol_type.endswith("[]"):
|
|
897
|
+
base = sol_type[:-2]
|
|
898
|
+
return f"{self.map_type(base)}*"
|
|
899
|
+
mapping = {
|
|
900
|
+
'int': 'int',
|
|
901
|
+
'float': 'float',
|
|
902
|
+
'double': 'double',
|
|
903
|
+
'char': 'char',
|
|
904
|
+
'bool': 'bool',
|
|
905
|
+
'string': 'char*',
|
|
906
|
+
'void': 'void',
|
|
907
|
+
'int8': 'int8_t',
|
|
908
|
+
'int16': 'int16_t',
|
|
909
|
+
'int32': 'int32_t',
|
|
910
|
+
'int64': 'int64_t',
|
|
911
|
+
'uint8': 'uint8_t',
|
|
912
|
+
'uint16': 'uint16_t',
|
|
913
|
+
'uint32': 'uint32_t',
|
|
914
|
+
'uint64': 'uint64_t',
|
|
915
|
+
'long': 'long',
|
|
916
|
+
'short': 'short',
|
|
917
|
+
'byte': 'unsigned char',
|
|
918
|
+
}
|
|
919
|
+
return mapping.get(sol_type, sol_type)
|
|
920
|
+
|
|
921
|
+
def emit_stmt(self, stmt):
|
|
922
|
+
if isinstance(stmt, VarDecl):
|
|
923
|
+
self.emit_var_decl(stmt)
|
|
924
|
+
elif isinstance(stmt, Assignment):
|
|
925
|
+
target = self.emit_expr(stmt.target)
|
|
926
|
+
value = self.emit_expr(stmt.value)
|
|
927
|
+
self.emit(f"{target} = {value};")
|
|
928
|
+
elif isinstance(stmt, IfStmt):
|
|
929
|
+
self.emit_if_stmt(stmt)
|
|
930
|
+
elif isinstance(stmt, ForStmt):
|
|
931
|
+
self.emit_for_stmt(stmt)
|
|
932
|
+
elif isinstance(stmt, ForEachStmt):
|
|
933
|
+
self.emit_foreach_stmt(stmt)
|
|
934
|
+
elif isinstance(stmt, ReturnStmt):
|
|
935
|
+
if stmt.value:
|
|
936
|
+
self.emit(f"return {self.emit_expr(stmt.value)};")
|
|
937
|
+
else:
|
|
938
|
+
self.emit("return;")
|
|
939
|
+
elif isinstance(stmt, BreakStmt):
|
|
940
|
+
self.emit("break;")
|
|
941
|
+
elif isinstance(stmt, ContinueStmt):
|
|
942
|
+
self.emit("continue;")
|
|
943
|
+
elif isinstance(stmt, ExprStmt):
|
|
944
|
+
self.emit(f"{self.emit_expr(stmt.expr)};")
|
|
945
|
+
elif isinstance(stmt, Block):
|
|
946
|
+
self.emit_block(stmt)
|
|
947
|
+
else:
|
|
948
|
+
raise RuntimeError(f"Unknown statement type: {type(stmt)}")
|
|
949
|
+
|
|
950
|
+
def _emit_var_init(self, stmt: VarDecl) -> str:
|
|
951
|
+
"""Generate the initialization part of a variable declaration."""
|
|
952
|
+
ctype = self.map_type(stmt.var_type)
|
|
953
|
+
if stmt.initializer:
|
|
954
|
+
if isinstance(stmt.initializer, ArrayLiteral):
|
|
955
|
+
elements = ", ".join(self.emit_expr(e) for e in stmt.initializer.elements)
|
|
956
|
+
base_type = self.map_type(stmt.var_type[:-2]) # strip "[]"
|
|
957
|
+
return f"{ctype} {stmt.name} = ({base_type}[]){{{elements}}}"
|
|
958
|
+
else:
|
|
959
|
+
init = self.emit_expr(stmt.initializer)
|
|
960
|
+
if isinstance(stmt.initializer, NumberLiteral):
|
|
961
|
+
init = self.emit_unsigned_literal(stmt.initializer.value, stmt.var_type)
|
|
962
|
+
return f"{ctype} {stmt.name} = {init}"
|
|
963
|
+
else:
|
|
964
|
+
return f"{ctype} {stmt.name}"
|
|
965
|
+
|
|
966
|
+
def emit_var_decl(self, stmt: VarDecl):
|
|
967
|
+
self.emit(self._emit_var_init(stmt) + ";")
|
|
968
|
+
self.local_vars[-1][stmt.name] = stmt.var_type
|
|
969
|
+
|
|
970
|
+
def emit_unsigned_literal(self, value: int, sol_type: str) -> str:
|
|
971
|
+
if sol_type in ('uint8', 'uint16', 'uint32'):
|
|
972
|
+
return f"{value}U"
|
|
973
|
+
if sol_type == 'uint64':
|
|
974
|
+
return f"{value}ULL"
|
|
975
|
+
return str(value)
|
|
976
|
+
|
|
977
|
+
def emit_if_stmt(self, stmt: IfStmt):
|
|
978
|
+
cond = self.emit_expr(stmt.condition)
|
|
979
|
+
self.emit(f"if ({cond}) {{")
|
|
980
|
+
self.indent_level += 1
|
|
981
|
+
for s in stmt.then_branch.statements:
|
|
982
|
+
self.emit_stmt(s)
|
|
983
|
+
self.indent_level -= 1
|
|
984
|
+
self.emit("}")
|
|
985
|
+
for cond, block in stmt.elseifs:
|
|
986
|
+
c = self.emit_expr(cond)
|
|
987
|
+
self.emit(f"else if ({c}) {{")
|
|
988
|
+
self.indent_level += 1
|
|
989
|
+
for s in block.statements:
|
|
990
|
+
self.emit_stmt(s)
|
|
991
|
+
self.indent_level -= 1
|
|
992
|
+
self.emit("}")
|
|
993
|
+
if stmt.else_branch:
|
|
994
|
+
self.emit("else {")
|
|
995
|
+
self.indent_level += 1
|
|
996
|
+
for s in stmt.else_branch.statements:
|
|
997
|
+
self.emit_stmt(s)
|
|
998
|
+
self.indent_level -= 1
|
|
999
|
+
self.emit("}")
|
|
1000
|
+
|
|
1001
|
+
def emit_for_stmt(self, stmt: ForStmt):
|
|
1002
|
+
parts = []
|
|
1003
|
+
if stmt.init:
|
|
1004
|
+
if isinstance(stmt.init, VarDecl):
|
|
1005
|
+
parts.append(self._emit_var_init(stmt.init))
|
|
1006
|
+
elif isinstance(stmt.init, Assignment):
|
|
1007
|
+
parts.append(f"{self.emit_expr(stmt.init.target)} = {self.emit_expr(stmt.init.value)}")
|
|
1008
|
+
else:
|
|
1009
|
+
parts.append(self.emit_expr(stmt.init))
|
|
1010
|
+
else:
|
|
1011
|
+
parts.append("")
|
|
1012
|
+
parts.append(self.emit_expr(stmt.condition) if stmt.condition else "")
|
|
1013
|
+
if stmt.increment:
|
|
1014
|
+
if isinstance(stmt.increment, Assignment):
|
|
1015
|
+
parts.append(f"{self.emit_expr(stmt.increment.target)} = {self.emit_expr(stmt.increment.value)}")
|
|
1016
|
+
else:
|
|
1017
|
+
parts.append(self.emit_expr(stmt.increment))
|
|
1018
|
+
else:
|
|
1019
|
+
parts.append("")
|
|
1020
|
+
self.emit(f"for ({parts[0]}; {parts[1]}; {parts[2]}) {{")
|
|
1021
|
+
self.indent_level += 1
|
|
1022
|
+
for s in stmt.body.statements:
|
|
1023
|
+
self.emit_stmt(s)
|
|
1024
|
+
self.indent_level -= 1
|
|
1025
|
+
self.emit("}")
|
|
1026
|
+
|
|
1027
|
+
def emit_foreach_stmt(self, stmt: ForEachStmt):
|
|
1028
|
+
self.emit(f"// foreach: {stmt.var_name} in iterable")
|
|
1029
|
+
self.emit("{")
|
|
1030
|
+
self.indent_level += 1
|
|
1031
|
+
for s in stmt.body.statements:
|
|
1032
|
+
self.emit_stmt(s)
|
|
1033
|
+
self.indent_level -= 1
|
|
1034
|
+
self.emit("}")
|
|
1035
|
+
|
|
1036
|
+
def emit_block(self, block: Block):
|
|
1037
|
+
self.emit("{")
|
|
1038
|
+
self.indent_level += 1
|
|
1039
|
+
for stmt in block.statements:
|
|
1040
|
+
self.emit_stmt(stmt)
|
|
1041
|
+
self.indent_level -= 1
|
|
1042
|
+
self.emit("}")
|
|
1043
|
+
|
|
1044
|
+
def emit_expr(self, expr) -> str:
|
|
1045
|
+
if isinstance(expr, NumberLiteral):
|
|
1046
|
+
return str(expr.value)
|
|
1047
|
+
if isinstance(expr, FloatLiteral):
|
|
1048
|
+
return str(expr.value)
|
|
1049
|
+
if isinstance(expr, StringLiteral):
|
|
1050
|
+
escaped = expr.value.replace('\\', '\\\\').replace('"', '\\"').replace('\n', '\\n').replace('\t', '\\t')
|
|
1051
|
+
return f'"{escaped}"'
|
|
1052
|
+
if isinstance(expr, CharLiteral):
|
|
1053
|
+
return f"'{expr.value}'"
|
|
1054
|
+
if isinstance(expr, BoolLiteral):
|
|
1055
|
+
return "true" if expr.value else "false"
|
|
1056
|
+
if isinstance(expr, Identifier):
|
|
1057
|
+
return expr.name
|
|
1058
|
+
if isinstance(expr, BinaryOp):
|
|
1059
|
+
left = self.emit_expr(expr.left)
|
|
1060
|
+
right = self.emit_expr(expr.right)
|
|
1061
|
+
return f"{left} {expr.op} {right}"
|
|
1062
|
+
if isinstance(expr, UnaryOp):
|
|
1063
|
+
operand = self.emit_expr(expr.operand)
|
|
1064
|
+
if expr.op == '++':
|
|
1065
|
+
return f"{operand}++"
|
|
1066
|
+
return f"{expr.op}{operand}"
|
|
1067
|
+
if isinstance(expr, CallExpr):
|
|
1068
|
+
args = ", ".join(self.emit_expr(a) for a in expr.args)
|
|
1069
|
+
return f"{expr.name}({args})"
|
|
1070
|
+
if isinstance(expr, GetExpr):
|
|
1071
|
+
obj = self.emit_expr(expr.obj)
|
|
1072
|
+
return f"{obj}.{expr.field}"
|
|
1073
|
+
if isinstance(expr, IndexExpr):
|
|
1074
|
+
obj = self.emit_expr(expr.obj)
|
|
1075
|
+
index = self.emit_expr(expr.index)
|
|
1076
|
+
return f"{obj}[{index}]"
|
|
1077
|
+
if isinstance(expr, ArrayLiteral):
|
|
1078
|
+
elements = ", ".join(self.emit_expr(e) for e in expr.elements)
|
|
1079
|
+
return f"{{{elements}}}"
|
|
1080
|
+
raise RuntimeError(f"Unknown expression type: {type(expr)}")
|
|
1081
|
+
|
|
1082
|
+
# =============================================================================
|
|
1083
|
+
# IMPORT RESOLVER
|
|
1084
|
+
# =============================================================================
|
|
1085
|
+
|
|
1086
|
+
class ImportResolver:
|
|
1087
|
+
"""Resolves get("path") imports by recursively loading and merging Sol files."""
|
|
1088
|
+
|
|
1089
|
+
def __init__(self, base_dir: str):
|
|
1090
|
+
self.base_dir = base_dir
|
|
1091
|
+
self.loaded = set()
|
|
1092
|
+
self.all_declarations = []
|
|
1093
|
+
self.seen_funcs = set()
|
|
1094
|
+
self.seen_structs = set()
|
|
1095
|
+
|
|
1096
|
+
def resolve(self, source_path: str) -> Program:
|
|
1097
|
+
self._load_file(source_path)
|
|
1098
|
+
return Program(self.all_declarations)
|
|
1099
|
+
|
|
1100
|
+
def _resolve_path(self, path: str) -> str:
|
|
1101
|
+
if os.path.isabs(path):
|
|
1102
|
+
return path
|
|
1103
|
+
return os.path.normpath(os.path.join(self.base_dir, path))
|
|
1104
|
+
|
|
1105
|
+
def _load_file(self, source_path: str):
|
|
1106
|
+
abs_path = self._resolve_path(source_path)
|
|
1107
|
+
abs_path = os.path.abspath(abs_path)
|
|
1108
|
+
|
|
1109
|
+
if abs_path in self.loaded:
|
|
1110
|
+
return
|
|
1111
|
+
self.loaded.add(abs_path)
|
|
1112
|
+
|
|
1113
|
+
if not os.path.exists(abs_path):
|
|
1114
|
+
raise FileNotFoundError(f"Import not found: {source_path} (resolved to {abs_path})")
|
|
1115
|
+
|
|
1116
|
+
with open(abs_path, 'r') as f:
|
|
1117
|
+
source = f.read()
|
|
1118
|
+
|
|
1119
|
+
lexer = Lexer(source)
|
|
1120
|
+
tokens = lexer.tokenize()
|
|
1121
|
+
|
|
1122
|
+
parser = Parser(tokens)
|
|
1123
|
+
ast = parser.parse()
|
|
1124
|
+
|
|
1125
|
+
file_dir = os.path.dirname(abs_path)
|
|
1126
|
+
|
|
1127
|
+
for decl in ast.declarations:
|
|
1128
|
+
if isinstance(decl, ImportStmt):
|
|
1129
|
+
imported_path = decl.path
|
|
1130
|
+
if not os.path.isabs(imported_path):
|
|
1131
|
+
imported_path = os.path.join(file_dir, imported_path)
|
|
1132
|
+
self._load_file(imported_path)
|
|
1133
|
+
elif isinstance(decl, FuncDecl):
|
|
1134
|
+
if decl.name not in self.seen_funcs:
|
|
1135
|
+
self.seen_funcs.add(decl.name)
|
|
1136
|
+
self.all_declarations.append(decl)
|
|
1137
|
+
elif isinstance(decl, StructDecl):
|
|
1138
|
+
if decl.name not in self.seen_structs:
|
|
1139
|
+
self.seen_structs.add(decl.name)
|
|
1140
|
+
self.all_declarations.append(decl)
|
|
1141
|
+
else:
|
|
1142
|
+
self.all_declarations.append(decl)
|
|
1143
|
+
|
|
1144
|
+
# =============================================================================
|
|
1145
|
+
# DRIVER
|
|
1146
|
+
# =============================================================================
|
|
1147
|
+
|
|
1148
|
+
def find_c_compiler() -> str:
|
|
1149
|
+
for compiler in ['clang', 'gcc', 'cc']:
|
|
1150
|
+
try:
|
|
1151
|
+
subprocess.run([compiler, '--version'], capture_output=True, check=False)
|
|
1152
|
+
return compiler
|
|
1153
|
+
except FileNotFoundError:
|
|
1154
|
+
continue
|
|
1155
|
+
raise RuntimeError("No C compiler found (tried: clang, gcc, cc). Please install clang or gcc.")
|
|
1156
|
+
|
|
1157
|
+
def compile_sol(source_path: str, run: bool = False, keep_c: bool = False) -> str:
|
|
1158
|
+
base_dir = os.path.dirname(os.path.abspath(source_path)) or os.getcwd()
|
|
1159
|
+
|
|
1160
|
+
resolver = ImportResolver(base_dir)
|
|
1161
|
+
ast = resolver.resolve(source_path)
|
|
1162
|
+
|
|
1163
|
+
generator = CGenerator()
|
|
1164
|
+
c_code = generator.generate(ast)
|
|
1165
|
+
|
|
1166
|
+
base = os.path.splitext(source_path)[0]
|
|
1167
|
+
c_path = base + ".c"
|
|
1168
|
+
with open(c_path, 'w') as f:
|
|
1169
|
+
f.write(c_code)
|
|
1170
|
+
print(f"Generated C code: {c_path}")
|
|
1171
|
+
|
|
1172
|
+
compiler = find_c_compiler()
|
|
1173
|
+
exe_path = base
|
|
1174
|
+
if sys.platform == 'win32':
|
|
1175
|
+
exe_path += ".exe"
|
|
1176
|
+
|
|
1177
|
+
cmd = [compiler, c_path, "-o", exe_path, "-Wall", "-Wextra", "-std=c11"]
|
|
1178
|
+
print(f"Running: {' '.join(cmd)}")
|
|
1179
|
+
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
1180
|
+
if result.returncode != 0:
|
|
1181
|
+
print("Compilation failed:")
|
|
1182
|
+
print(result.stderr)
|
|
1183
|
+
raise RuntimeError("Compilation failed")
|
|
1184
|
+
|
|
1185
|
+
# FIX: Make executable on Unix-like systems — catch ALL exceptions, OR permissions
|
|
1186
|
+
if sys.platform != 'win32':
|
|
1187
|
+
try:
|
|
1188
|
+
current_mode = os.stat(exe_path).st_mode
|
|
1189
|
+
os.chmod(exe_path, current_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
|
|
1190
|
+
print(f"Set executable permissions on {exe_path}")
|
|
1191
|
+
except Exception as e:
|
|
1192
|
+
print(f"Warning: Could not chmod {exe_path}: {e}")
|
|
1193
|
+
|
|
1194
|
+
print(f"Compiled executable: {exe_path}")
|
|
1195
|
+
|
|
1196
|
+
if not keep_c:
|
|
1197
|
+
os.remove(c_path)
|
|
1198
|
+
print(f"Removed intermediate C file: {c_path}")
|
|
1199
|
+
|
|
1200
|
+
if run:
|
|
1201
|
+
# Ensure executable permissions before running, then run with ./ prefix
|
|
1202
|
+
if sys.platform != 'win32':
|
|
1203
|
+
try:
|
|
1204
|
+
current_mode = os.stat(exe_path).st_mode
|
|
1205
|
+
if not (current_mode & stat.S_IXUSR):
|
|
1206
|
+
os.chmod(exe_path, current_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
|
|
1207
|
+
print(f"Set executable permissions before running")
|
|
1208
|
+
except Exception as e:
|
|
1209
|
+
print(f"Warning: Could not ensure executable permissions: {e}")
|
|
1210
|
+
|
|
1211
|
+
print(f"\n--- Running {exe_path} ---")
|
|
1212
|
+
result = subprocess.run([f"./{os.path.basename(exe_path)}"],
|
|
1213
|
+
capture_output=True, text=True,
|
|
1214
|
+
cwd=os.path.dirname(exe_path) or '.')
|
|
1215
|
+
print(result.stdout, end="")
|
|
1216
|
+
if result.stderr:
|
|
1217
|
+
print("stderr:", result.stderr, end="")
|
|
1218
|
+
print(f"--- Exit code: {result.returncode} ---")
|
|
1219
|
+
|
|
1220
|
+
return exe_path
|
|
1221
|
+
|
|
1222
|
+
def main():
|
|
1223
|
+
if len(sys.argv) < 2:
|
|
1224
|
+
print(__doc__)
|
|
1225
|
+
sys.exit(1)
|
|
1226
|
+
|
|
1227
|
+
source_path = sys.argv[1]
|
|
1228
|
+
run = '--run' in sys.argv
|
|
1229
|
+
keep_c = '--keep-c' in sys.argv
|
|
1230
|
+
|
|
1231
|
+
if not os.path.exists(source_path):
|
|
1232
|
+
print(f"Error: File not found: {source_path}")
|
|
1233
|
+
sys.exit(1)
|
|
1234
|
+
|
|
1235
|
+
compile_sol(source_path, run=run, keep_c=keep_c)
|
|
1236
|
+
|
|
1237
|
+
if __name__ == "__main__":
|
|
1238
|
+
main()
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tsolc
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: TSol Compiler - Transpiles Sol language to C
|
|
5
|
+
Author-email: stefand-0 <stefan.dragan95@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/stefand-0/tsol
|
|
8
|
+
Project-URL: Repository, https://github.com/stefand-0/tsol
|
|
9
|
+
Keywords: compiler,transpiler,sol,c,tsol
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Topic :: Software Development :: Compilers
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
transpiler/main.py,sha256=wJ-yJkzGh82POC4aFjed7EeP74hnQYQC5jwnI66hQhc,41405
|
|
2
|
+
tsolc-0.1.0.dist-info/METADATA,sha256=OWBUnyY6-T-MTp6EEA1XT1YF_8I88LYO9nPN-ZIUM3g,784
|
|
3
|
+
tsolc-0.1.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
4
|
+
tsolc-0.1.0.dist-info/entry_points.txt,sha256=hikr79otpj8XZm6Gun-tTSz47nvuq9LCT3_uwQCRUkE,47
|
|
5
|
+
tsolc-0.1.0.dist-info/top_level.txt,sha256=oNUWmZYv0ZDdMeCbKrtZ8VHcO_hIcDZx6kg8sEON1Ro,11
|
|
6
|
+
tsolc-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
transpiler
|