├── LICENSE ├── snek.py └── tests ├── addition.py ├── asm.py ├── for.py ├── hello.py └── vars.py /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /snek.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3.10 2 | 3 | import sys 4 | import ast 5 | 6 | # lol global variables go brrr 7 | output = None 8 | counter = 0 9 | 10 | # Why does this even exist? 11 | def parse(string): 12 | return ast.parse(string) 13 | 14 | # moment 15 | def write_to_file(str): 16 | global output 17 | 18 | output.write(str) 19 | 20 | # Python cannot do pointers, so I have a builtin function to write to memory 21 | def builtin_writemem(args): 22 | global counter 23 | 24 | write_to_file(f"volatile {py_type_to_c_type(args[2].id)} *buf{counter} = ({py_type_to_c_type(args[2].id)}*)") 25 | codegen_expr(args[0]) 26 | write_to_file(";\n") 27 | write_to_file(f"*buf{counter} = "); 28 | codegen_expr(args[1]) 29 | 30 | counter += 1 31 | return str 32 | 33 | # Output asm C-style 34 | def builtin_asm(args): 35 | write_to_file(f"asm volatile (\"{args[0].value}\")") 36 | 37 | # self explanatory 38 | builtin_funcs = {"__builtin_write_mem" : builtin_writemem, "asm" : builtin_asm} 39 | 40 | def codegen_func_call(call): 41 | 42 | # If function is not builtin, don't do special stuff, just codegen it 43 | if call.func.id not in builtin_funcs: 44 | write_to_file(f"{call.func.id}(") 45 | 46 | for arg in call.args: 47 | codegen_expr(arg) 48 | 49 | if arg != call.args[-1]: 50 | write_to_file(",") 51 | 52 | write_to_file(")") 53 | 54 | # Else, codegen its special string 55 | else: 56 | builtin_funcs[call.func.id](call.args) 57 | 58 | 59 | def codegen_op_from_node(op): 60 | match op.__class__: 61 | case ast.Add: 62 | write_to_file("+") 63 | case ast.Sub: 64 | write_to_file("-") 65 | case ast.Mult: 66 | write_to_file("*") 67 | case ast.Div: 68 | write_to_file("/") 69 | case ast.Mod: 70 | write_to_file("%") 71 | case ast.LShift: 72 | write_to_file("<<") 73 | case ast.RShift: 74 | write_to_file(">>") 75 | case ast.BitOr: 76 | write_to_file("|") 77 | case ast.BitXor: 78 | write_to_file("^") 79 | case ast.BitAnd: 80 | write_to_file("&") 81 | 82 | # An expr is basically something that can be evaluated 83 | # So like 10 is an expression, so is 10+10 and function calls are too 84 | def codegen_expr(node): 85 | match node.__class__: 86 | case ast.Constant: 87 | if type(node.value) == str: 88 | if len(node.value) > 1: 89 | # Replace newlines with string containing '\n' 90 | new_str = node.value.replace("\n", "\\n") 91 | write_to_file(f"\"{new_str}\"") 92 | else: 93 | write_to_file(f"\'{node.value}\'") 94 | else: 95 | write_to_file(f"{node.value}") 96 | 97 | case ast.Name: 98 | write_to_file(f"{node.id}") 99 | 100 | case ast.List: 101 | write_to_file("{") 102 | for i in node.elts: 103 | codegen_expr(i) 104 | if i != node.elts[-1]: 105 | write_to_file(",") 106 | write_to_file("}") 107 | 108 | case ast.BinOp: 109 | codegen_expr(node.left) 110 | codegen_op_from_node(node.op) 111 | codegen_expr(node.right) 112 | 113 | case ast.Call: 114 | codegen_func_call(node) 115 | 116 | case ast.Subscript: 117 | codegen_expr(node.value) 118 | write_to_file("[") 119 | codegen_expr(node.slice) 120 | write_to_file("]") 121 | 122 | def codegen_subscript(node, name=""): 123 | match node.value.id: 124 | case "list": 125 | write_to_file(f"{py_type_to_c_type(node.slice.id)} {name}[]") 126 | 127 | # Well that's pretty much self explanatory 128 | # God I'm overcommenting, am I? 129 | def py_type_to_c_type(t): 130 | match t: 131 | case "int": 132 | return "int" 133 | case "float": 134 | return "float" 135 | case "str": 136 | return "char*" 137 | case "char": 138 | return "char" 139 | case "bool": 140 | return "int" 141 | case "u16": 142 | return "uint16_t" 143 | case "u32": 144 | return "uint32_t" 145 | case "u64": 146 | return "uint64_t" 147 | case _: 148 | return "void" 149 | 150 | def codegen_annotation(ann, arg): 151 | match ann.__class__: 152 | case ast.Name: 153 | return write_to_file(py_type_to_c_type(ann.id) + " " + arg) 154 | case ast.Subscript: 155 | return codegen_subscript(ann, arg) 156 | 157 | # Codegen parameters 158 | # Basically you put a comma if the parameter isn't the last one 159 | def codegen_args(args): 160 | for arg in args: 161 | codegen_annotation(arg.annotation, arg.arg) 162 | 163 | if arg != args[-1]: 164 | write_to_file(",") 165 | 166 | def codegen_func_def(node): 167 | # If you don't know the return type, it's void 168 | if isinstance(node.returns, ast.Constant) or node.returns is None: 169 | write_to_file(f"void {node.name}(") 170 | 171 | # Else, it's specified 172 | else: 173 | write_to_file(f"{py_type_to_c_type(node.returns.id)} {node.name}(") 174 | # Codegen parameters 175 | codegen_args(node.args.args) 176 | 177 | # Start of function body 178 | write_to_file(")\n{\n") 179 | 180 | # Iterate through each node of the body and codegen it 181 | for i in node.body: 182 | codegen_node(i) 183 | 184 | # End of function body 185 | write_to_file("}\n") 186 | 187 | def codegen_assign(node): 188 | codegen_annotation(node.annotation, node.target.id) 189 | 190 | write_to_file(f" = ") 191 | 192 | codegen_expr(node.value) 193 | 194 | def codegen_for(node): 195 | write_to_file("for (") 196 | if isinstance(node.iter, ast.Call): 197 | if node.iter.func.id == "range": 198 | step = 1 199 | 200 | if len(node.iter.args) == 3: 201 | step = node.iter.args[2].value 202 | 203 | write_to_file(f"int {node.target.id} = ") 204 | codegen_expr(node.iter.args[0]) 205 | write_to_file(f"; {node.target.id} < ") 206 | codegen_expr(node.iter.args[1]) 207 | write_to_file(f";{node.target.id} += {step})\n{{\n") 208 | else: 209 | codegen_func_call(node.iter) 210 | else: 211 | write_to_file(f"int __list_iter = 0; __list_iter < sizeof({node.iter.id})/sizeof(*{node.iter.id}); __list_iter++)\n{{\n typeof(*{node.iter.id}) {node.target.id} = {node.iter.id}[__list_iter];\n") 212 | 213 | 214 | for i in node.body: 215 | codegen_node(i) 216 | 217 | write_to_file("}\n") 218 | 219 | # Yea so this codegens a node apparently 220 | # It checks for the type of node and calls the appropriate function 221 | def codegen_node(node): 222 | match node.__class__: 223 | case ast.FunctionDef: 224 | codegen_func_def(node) 225 | 226 | case ast.AnnAssign: 227 | codegen_assign(node) 228 | write_to_file(";\n") 229 | 230 | case ast.AugAssign: 231 | codegen_expr(node.target) 232 | write_to_file(" ") 233 | codegen_op_from_node(node.op) 234 | write_to_file("= ") 235 | codegen_expr(node.value) 236 | write_to_file(";\n") 237 | case ast.Expr: 238 | codegen_expr(node.value) 239 | write_to_file(";\n") 240 | 241 | case ast.Return: 242 | write_to_file(f"return ") 243 | codegen_expr(node.value) 244 | write_to_file(";\n") 245 | 246 | case ast.For: 247 | codegen_for(node) 248 | 249 | case ast.Import: 250 | write_to_file(f"#include \"{node.names[0].name}.h\"\n") 251 | 252 | # Generate C code from python AST 253 | def codegen(parsed, debug=False): 254 | if debug: 255 | print(ast.dump(parsed)) 256 | for node in parsed.body: 257 | codegen_node(node) 258 | 259 | 260 | # Main function of the program, idk why I did that but it's here 261 | def main(): 262 | global output 263 | data = "" 264 | 265 | if len(sys.argv) >= 3: 266 | output = open(sys.argv[2], 'w') 267 | 268 | with open(sys.argv[1], 'r') as input: 269 | data = input.read() 270 | else: 271 | print(f"Command syntax is {sys.argv[0]} [FILE] [OUTPUT]") 272 | sys.exit(1) 273 | 274 | parsed = parse(data) 275 | 276 | codegen(parsed, True) 277 | 278 | 279 | if __name__ == "__main__": 280 | main() 281 | 282 | 283 | 284 | -------------------------------------------------------------------------------- /tests/addition.py: -------------------------------------------------------------------------------- 1 | def add_two_nums(a: int, b: int) -> int: 2 | return a + b 3 | 4 | def main() -> int: 5 | return add_two_nums(1, 2) 6 | -------------------------------------------------------------------------------- /tests/asm.py: -------------------------------------------------------------------------------- 1 | 2 | def main(): 3 | asm ("hlt") 4 | -------------------------------------------------------------------------------- /tests/for.py: -------------------------------------------------------------------------------- 1 | import stdio 2 | 3 | def main(): 4 | for i in range(1,10): 5 | printf("%d ", i) 6 | 7 | 8 | -------------------------------------------------------------------------------- /tests/hello.py: -------------------------------------------------------------------------------- 1 | import stdio 2 | 3 | def main(argc: int, argv: list[str]): 4 | printf("Hello, world!\n") 5 | 6 | -------------------------------------------------------------------------------- /tests/vars.py: -------------------------------------------------------------------------------- 1 | def main() -> int: 2 | a: int = 10 3 | 4 | a += 1 5 | 6 | return 0 7 | --------------------------------------------------------------------------------