From 9aa440f0d0d0fac676b26194320e4191cbceea1e Mon Sep 17 00:00:00 2001 From: Larry Xue Date: Sat, 22 Aug 2020 22:52:50 -0400 Subject: [PATCH] initial commit --- .clang-format | 10 ++ .clang-tidy | 7 + .gitignore | 5 + .pylintrc | 39 ++++++ .style.yapf | 8 ++ LICENSE | 21 +++ a.out | Bin 0 -> 16320 bytes c2logic/__init__.py | 1 + c2logic/__main__.py | 2 + c2logic/compiler.py | 282 ++++++++++++++++++++++++++++++++++++++++ c2logic/instructions.py | 118 +++++++++++++++++ c2logic/operations.py | 29 +++++ compile_flags.txt | 4 + examples/test.c | 21 +++ include/mindustry.h | 15 +++ mypy.ini | 3 + setup.py | 17 +++ 17 files changed, 582 insertions(+) create mode 100755 .clang-format create mode 100755 .clang-tidy create mode 100755 .gitignore create mode 100755 .pylintrc create mode 100755 .style.yapf create mode 100755 LICENSE create mode 100755 a.out create mode 100644 c2logic/__init__.py create mode 100644 c2logic/__main__.py create mode 100644 c2logic/compiler.py create mode 100644 c2logic/instructions.py create mode 100644 c2logic/operations.py create mode 100644 compile_flags.txt create mode 100644 examples/test.c create mode 100644 include/mindustry.h create mode 100755 mypy.ini create mode 100755 setup.py diff --git a/.clang-format b/.clang-format new file mode 100755 index 0000000..35d0d96 --- /dev/null +++ b/.clang-format @@ -0,0 +1,10 @@ +BasedOnStyle: Google +UseTab: Always +IndentWidth: 4 +TabWidth: 4 +BreakBeforeBraces: Attach +AllowShortBlocksOnASingleLine: Empty +AllowShortFunctionsOnASingleLine: Empty +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +ColumnLimit: 100 \ No newline at end of file diff --git a/.clang-tidy b/.clang-tidy new file mode 100755 index 0000000..3168dfe --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,7 @@ +Checks: "*,-abseil-*,-android*,-fuchsia-*,-readability-magic-numbers,-readability-redundant-declaration,\ +-cppcoreguidelines-pro-bounds-pointer-arithmetic,-cppcoreguidelines-avoid-magic-numbers,\ +-bugprone-exception-escape,-hicpp-explicit-conversions,-cert-dcl21-cpp,-modernize-use-trailing-return-type,\ +-google-readability-todo,-misc-non-private-member-variables-in-classes,-google-runtime-references,\ +-google-objc-function-naming,-cppcoreguidelines-pro-type-vararg,-hicpp-signed-bitwise,-modernize-use-using,\ +-modernize-redundant-void-arg" + diff --git a/.gitignore b/.gitignore new file mode 100755 index 0000000..2149277 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.hypothesis +.mypy_cache +.pytest_cache +.vscode +__pycache__ \ No newline at end of file diff --git a/.pylintrc b/.pylintrc new file mode 100755 index 0000000..5355bef --- /dev/null +++ b/.pylintrc @@ -0,0 +1,39 @@ +[MASTER] +jobs=0 +[MESSAGES CONTROL] + +disable=missing-docstring,bad-continuation,trailing-whitespace,logging-not-lazy,logging-fstring-interpolation,no-else-return,no-else-raise +enable=similarities +[REPORTS] + +[REFACTORING] + +[BASIC] + +[FORMAT] +max-line-length=130 +# e for error; f for file; fn for function +good-names=i,j,k,x,y,e,f,fn +indent-string="\t" +# allows global variables to be normal +const-rgx=[a-zA-Z_][a-zA-Z0-9_]{2,30}$ +[LOGGING] + +[MISCELLANEOUS] + +[SIMILARITIES] + +[SPELLING] + +[TYPECHECK] + +[VARIABLES] + +[CLASSES] + +[DESIGN] + +[IMPORTS] + +[EXCEPTIONS] + diff --git a/.style.yapf b/.style.yapf new file mode 100755 index 0000000..4f8b383 --- /dev/null +++ b/.style.yapf @@ -0,0 +1,8 @@ +[style] +based_on_style = facebook +use_tabs = true +column_limit = 100 +blank_lines_around_top_level_definition = 1 +blank_line_before_nested_class_or_def = false +indent_blank_lines = true +continuation_align_style = fixed \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100755 index 0000000..91e2258 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 SuperStormer + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/a.out b/a.out new file mode 100755 index 0000000000000000000000000000000000000000..4f2f6cb6fb9c57cc4bd416d2d0ab707f47f289d2 GIT binary patch literal 16320 zcmeHOU2Ggz6~4Rc#M}I=*G}7*Iw(`YgG2Gej*2m~NoH-YJ(irrP3-;%oXL21Y;V~g zYj?J?D+PjFsw^eNFQ7^w$OD21^@)cPfrM=w)e#b?eF3C`h)^O-o1`wH$Vh3HbMBn8 zo*8e{N=Q7EInvCz=iKw1d+xb+cJKV$uO){E!l9603X3BGB_*y&m2(zisYi;W>k&JJ zCiaOou^luGaA>jyxl>|39d;_1N69V%9C4SZ{!&;231tl#_;!dnM~ED8o1{_E;jL83 zT*RGf#BIYO$%6XF{1!F9ob77uDF_BqG|8AhO*rOm9Ogz-9dqr7gJZr%xOL);!*n2S zgm5E-V}6nBnR7m|+jv}|d|nA_Afc=w6aDhM$R(SPIpzfm*;#%q$ z9{`u?L8OFx6;nH(1xGvy927(L(83-~EWFW{ukbxirK?Z^he7rKC z&)D<8*R7$-o2T{4U-kLx>m#E>t4r6KHKDIApOb7= z0aktB#{mCl)Bb)TRwmV^{VzzklrOhyV15|S_MXeA$7b$ZL`C)5=BxiP;Ksw6l5{u?RXf#k-)buC8=iKm7y9R4|YN<2AK`F&z+sPcYdG*P)TreEl| z1nN`62R^_tT03=dYT7S`EB_d--0rXZEm6JyFZ%pSNdLm0%Wvb@eQ7-L z<-~a6bi#lh3mi!~#-34?0SBib13?CY3@(2;LFS z6f=2S$vAv6TP{s2IjdAMr>rg!YKuIE?|$XG-t}trX()FB$6Ne$;ICk=-m6ye#{3NM zKLTF@uFy+Hd@qX>dj6yc&BjA*yBeblp~g7Ar`!j}#doXKN6-MMzOD046K#J4w*2lA z|4Ajm+Ffgl4x27(L(83;1)vC9Cw|3!L(zNhAUpgmG6#4h6eo%L># zuM_SQB=dLCZ6xz|%@2{RAJJq9@9U7bzQGYiG@{AU`**7aEGQ()Z@xi3PcnbAtdT75 z9ihGlK|nhlCR&LpIc?BO-AltW|(w~bO>qIy#Q z&lrDK{$ULc_Vs;HY45itGG<=s>QuYb&W`Q_R1&05WzGDQ@|;yHWeU;?>^izW%r$(6 z9jblz?>3IWvxzHV&8?E<2q4z#>KkhFzzvGPm;XE;y!!2e=Ydz>Seq|ieN@yNrwEKX zkFVE$hv0eQ)x#{~{Rgg!2wW{nUHxvs*Ne*>vJexz55cQ%s(r`r)yKsd`aHp_Zx($0 zdG#%VuRE`PkEl0qBJc!WnXR zpat@Ojq<>GI}8P-&bY@x9|ygE+;@P!CA@Y1??pZMkCT7?9p5MQGFZ>)G4k)9Z#w7| zkEJ6#1^VXjgK&9ip4v3(UDqA||CB(F@tmW0p>suI;nS91Di$!zJu<~|s zPD~cfoMoiTx!fE;T#^Bnw#U@AN;bq}7y~C0$C5_!c)wwQ)+k!pLdvu) z^k!#L#!Loa#zd(^?&ZcH!x%l*N7)c+X)b4*6Tt1F!>73fPxYIR4vYP8{0r8gj8_P+5T5(*$CDdz916H*X1qpt zjqn&B67~`E%z2J389F#zN9acO0$N!s9 zpS!Vugz&5$? zyk6E-Py(@?BW=p8{~c7|*mL{59^?JL7qN3dIRB6s$K#0WGBja4ufNw1qBX7=yu^6s ze}ZZ^zP3*9bZ~r~vKPiNzv094`l^-i_iCU0y#odG#qslcrJeBH4%<^YSQP6}LLHgc z^o)PJrf~|4cXOV5ZqsRUc#$3;;B$!2z`^lwUhw+h*xmRj{;&gG=7t= 1 and isinstance(top, Set) and top.dest == var + + def set_to_rax(self, varname: str): + top = self.peek() + if self.opt_level >= 1 and hasattr(top, "dest") and top.dest == "__rax": + #avoid indirection through __rax + self.curr_function.instructions[-1].dest = varname + + #self.push(Set(varname, self.pop().src)) + else: + self.push(Set(varname, "__rax")) + + #visitors + def visit_FuncDef(self, node): # function definitions + func_decl = node.decl.type + args = [arg_decl.name for arg_decl in func_decl.args.params] + + self.curr_function = Function(node.decl.name, args, [], None) + self.visit(node.body) + #in case for loop is the last thing in a function to ensure the jump target is valid + #TODO avoid this when for loop isn't last thing + self.push(Noop()) + + print(self.curr_function) + self.functions.append(self.curr_function) + + def visit_Decl(self, node): + if isinstance(node.type, TypeDecl): # variable declaration + if node.init is not None: + self.visit(node.init) + self.set_to_rax(node.name) + elif isinstance(node.type, FuncDecl): + #TODO actually process func declarations + pass + elif isinstance(node.type, Struct): + if node.type.name != "MindustryObject": + #TODO structs + raise NotImplementedError(node) + elif isinstance(node.type, Enum): + if node.type.name != "RadarTarget": + #TODO enums + raise NotImplementedError(node) + else: + raise NotImplementedError(node) + + def visit_Assignment(self, node): + self.visit(node.rvalue) + varname = node.lvalue.name + if node.op == "=": #normal assignment + self.set_to_rax(varname) + else: #augmented assignment(+=,-=,etc) + if self.can_avoid_indirection(): + #avoid indirection through __rax + self.push(BinaryOp(varname, varname, self.pop().src, node.op[0])) + else: + self.push(BinaryOp(varname, varname, "__rax", node.op[0])) + + def visit_Constant(self, node): # literals + self.push(Set("__rax", node.value)) + + def visit_ID(self, node): # identifier + self.push(Set("__rax", node.name)) + + def visit_BinaryOp(self, node): + self.visit(node.left) + self.set_to_rax("__rbx") + self.visit(node.right) + left = "__rbx" + right = "__rax" + if self.can_avoid_indirection(): + right = self.pop().src + if self.can_avoid_indirection("__rbx"): + left = self.pop().src + self.push(BinaryOp("__rax", left, right, node.op)) + + def visit_UnaryOp(self, node): + if node.op == "p++" or node.op == "p--": #postincrement/decrement + varname = node.expr.name + self.push(Set("__rax", varname)) + self.push(BinaryOp(varname, varname, "1", node.op[1])) + else: + self.visit(node.expr) + self.push(UnaryOp("__rax", "__rax", node.op)) + + def visit_For(self, node): + self.visit(node.init) + self.loop_start = len(self.curr_function.instructions) + self.visit(node.cond) + # jump over loop body when cond is false + print(self.peek()) + if isinstance(self.peek(), BinaryOp): + self.push(RelativeJump(None, JumpCondition.from_binaryop(self.pop()))) + else: + self.push(RelativeJump(None, JumpCondition("==", "__rax", "0"))) + self.cond_jump_offset = len(self.curr_function.instructions) - 1 + self.visit(node.stmt) + self.visit(node.next) + #jump to start of loop + self.push(RelativeJump(self.loop_start, JumpCondition("==", "0", "0"))) + self.curr_function.instructions[self.cond_jump_offset].offset = len( + self.curr_function.instructions + ) + + def visit_While(self, node): + self.loop_start = len(self.curr_function.instructions) + self.visit(node.cond) + # jump over loop body when cond is false + print(self.peek()) + if isinstance(self.peek(), BinaryOp): + self.push(RelativeJump(None, JumpCondition.from_binaryop(self.pop()))) + else: + self.push(RelativeJump(None, JumpCondition("==", "__rax", "0"))) + self.loop_end_jumps = [len(self.curr_function.instructions) - 1] # also used for breaks + self.visit(node.stmt) + #jump to start of loop + self.push(RelativeJump(self.loop_start, JumpCondition("==", "0", "0"))) + for offset in self.loop_end_jumps: + self.curr_function.instructions[offset].offset = len(self.curr_function.instructions) + + def visit_FuncCall(self, node): + name = node.name.name + if name == "_asm": + arg = node.args.exprs[0] + if not isinstance(arg, Constant) or arg.type != "string": + raise TypeError("Non-string argument to _asm", node) + self.push(RawAsm(arg.value[1:-1])) + elif name in ("print", "printd"): + self.visit(node.args.exprs[0]) + if self.can_avoid_indirection(): + self.push(Print(self.pop().src)) + else: + self.push(Print("__rax")) + elif name == "printflush": + self.visit(node.args.exprs[0]) + if self.can_avoid_indirection(): + self.push(PrintFlush(self.pop().src)) + else: + self.push(PrintFlush("__rax")) + elif name == "radar": + args = [] + for i, arg in enumerate(node.args.exprs): + if 1 <= i <= 4: + if not isinstance(arg, Constant) or arg.type != "string": + raise TypeError("Non-string argument to radar", node) + self.push(Set("__rax", arg.value[1:-1])) + else: + self.visit(arg) + self.set_to_rax(f"__radar_arg{i}") + args.append(f"__radar_arg{i}") + + for i, arg in reversed(list(enumerate(args))): + if self.can_avoid_indirection(arg): + args[i] = self.pop().src + else: + break + self.push(Radar("__rax", *args)) #pylint: disable=no-value-for-parameter + elif name == "sensor": + self.visit(node.args.exprs[0]) + self.set_to_rax("__sensor_arg0") + arg = node.args.exprs[1] + if not isinstance(arg, Constant) or arg.type != "string": + raise TypeError("Non-string argument to sensor", node) + self.push(Set("__rax", arg.value[1:-1])) + src = "__sensor_arg0" + prop = "__rax" + if self.can_avoid_indirection(): + prop = self.pop().src + if self.can_avoid_indirection("__sensor_arg0"): + src = self.pop().src + self.push(Sensor("__rax", src, prop)) + elif name == "enable": + self.visit(node.args.exprs[0]) + self.set_to_rax("__enable_arg0") + self.visit(node.args.exprs[1]) + src = "__enable_arg0" + prop = "__rax" + if self.can_avoid_indirection(): + prop = self.pop().src + if self.can_avoid_indirection("__enable_arg0"): + src = self.pop().src + self.push(Enable(src, prop)) + elif name == "shoot": + args = [] + for i, arg in enumerate(node.args.exprs): + self.visit(arg) + self.set_to_rax(f"__shoot_arg{i}") + args.append(f"__shoot_arg{i}") + + for i, arg in reversed(list(enumerate(args))): + if self.can_avoid_indirection(arg): + args[i] = self.pop().src + else: + break + self.push(Shoot(*args)) #pylint: disable=no-value-for-parameter + + else: + raise NotImplementedError(node) + + def generic_visit(self, node): + if isinstance(node, (FileAST, Compound, DeclList)): + super().generic_visit(node) + else: + raise NotImplementedError(node) + +def main(): + print(Compiler(opt_level=1).compile("examples/test.c")) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/c2logic/instructions.py b/c2logic/instructions.py new file mode 100644 index 0000000..5105d94 --- /dev/null +++ b/c2logic/instructions.py @@ -0,0 +1,118 @@ +from .operations import binary_ops, condition_ops, unary_ops + +class Instruction: + pass + +class Noop(Instruction): + def __str__(self): + return "noop" + +class Set(Instruction): + def __init__(self, dest, src): + self.src = src + self.dest = dest + + def __str__(self): + return f"set {self.dest} {self.src}" + +class BinaryOp(Instruction): + def __init__(self, dest, left, right, op): + self.left = left + self.right = right + self.op = op + self.dest = dest + + def __str__(self): + return f"bop {binary_ops[self.op]} {self.left} {self.right} {self.dest}" + +class UnaryOp(Instruction): + def __init__(self, dest, src, op): + self.src = src + self.dest = dest + self.op = op + + def __str__(self): + return f"uop {unary_ops[self.op]} {self.src} {self.dest}" + +class JumpCondition: + def __init__(self, op: str, left, right): + self.op = op + self.left = left + self.right = right + + @classmethod + def from_binaryop(cls, binop: BinaryOp): + return JumpCondition(binop.op, binop.left, binop.right) + + def __str__(self): + return f"{condition_ops[self.op]} {self.left} {self.right}" + +class RelativeJump(Instruction): + def __init__(self, offset: int, cond: JumpCondition): + self.offset = offset + self.func_start = None + self.cond = cond + + def __str__(self): + return f"jump {self.func_start+self.offset} {self.cond}" + +class Print(Instruction): + def __init__(self, val): + self.val = val + + def __str__(self): + return f"print {self.val}" + +class PrintFlush(Instruction): + def __init__(self, val): + self.val = val + + def __str__(self): + return f"printflush {self.val}" + +class Radar(Instruction): + def __init__(self, dest, src, target1, target2, target3, sort, index): + self.src = src + self.dest = dest + self.target1 = target1 + self.target2 = target2 + self.target3 = target3 + self.sort = sort + self.index = index + + def __str__(self): + return f"radar {self.target1} {self.target2} {self.target3} {self.sort} {self.src} {self.index} {self.dest}" + +class Sensor(Instruction): + def __init__(self, dest, src, prop): + self.dest = dest + self.src = src + self.prop = prop + + def __str__(self): + return f"sensor {self.dest} {self.src} @{self.prop}" + +class Enable(Instruction): + def __init__(self, obj, enabled): + self.obj = obj + self.enabled = enabled + + def __str__(self): + return f"control enabled {self.obj} {self.enabled} 0 0 0" + +class Shoot(Instruction): + def __init__(self, obj, x, y, shoot): + self.obj = obj + self.x = x + self.y = y + self.shoot = shoot + + def __str__(self): + return f"control shoot {self.obj} {self.x} {self.y} {self.shoot} 0" + +class RawAsm(Instruction): + def __init__(self, code: str): + self.code = code + + def __str__(self): + return self.code \ No newline at end of file diff --git a/c2logic/operations.py b/c2logic/operations.py new file mode 100644 index 0000000..c3914d4 --- /dev/null +++ b/c2logic/operations.py @@ -0,0 +1,29 @@ +binary_ops = { + "+": "add", + "-": "sub", + "*": "mul", + "/": "div", + "%": "mod", + "==": "equal", + "!=": "notEqual", + "<": "lessThan", + "<=": "lessThanEq", + ">": "greaterThan", + ">=": "greaterThanEq", + "^": "pow", + ">>": "shl", + "<<": "shr", + "|": "or", + "&": "and", + "^": "xor" +} +condition_ops = { + "==": "equal", + "!=": "notEqual", + "<": "lessThan", + "<=": "lessThanEq", + ">": "greaterThan", + ">=": "greaterThanEq" +} + +unary_ops = {"-": "negate", "~": "not"} diff --git a/compile_flags.txt b/compile_flags.txt new file mode 100644 index 0000000..c313eec --- /dev/null +++ b/compile_flags.txt @@ -0,0 +1,4 @@ +-I ./include +-Wall +-Wextra +-std=c11 \ No newline at end of file diff --git a/examples/test.c b/examples/test.c new file mode 100644 index 0000000..a370e8d --- /dev/null +++ b/examples/test.c @@ -0,0 +1,21 @@ +#include "mindustry.h" +extern struct MindustryObject message1; +extern struct MindustryObject swarmer1; +extern struct MindustryObject conveyor1; +void main(void) { + /*double i = 0; + while (i < 10) { + print(i); + printflush(message1); + i += 1; + }*/ + struct MindustryObject player = radar(swarmer1, "player", "any", "any", "distance", 0); + double x = sensor(player, "x"); + double y = sensor(player, "y"); + printd(x); + print("\n"); + printd(y); + printflush(message1); + enable(conveyor1, x < 10); + shoot(swarmer1, 0, 0, x > 10); +} \ No newline at end of file diff --git a/include/mindustry.h b/include/mindustry.h new file mode 100644 index 0000000..fa80d30 --- /dev/null +++ b/include/mindustry.h @@ -0,0 +1,15 @@ +#ifndef MINDUSTRY_H +#define MINDUSTRY_H +struct MindustryObject {}; +void _asm(char* code); +void print(char* s); +void printd(double s); +void printflush(struct MindustryObject); + +struct MindustryObject radar(struct MindustryObject obj, char* target1, char* target2, + char* target3, char* sort, double index); +double sensor(struct MindustryObject obj, char* prop); +void enable(struct MindustryObject obj, double enabled); +void shoot(struct MindustryObject obj, double x, double y, double shoot); +void end(); +#endif \ No newline at end of file diff --git a/mypy.ini b/mypy.ini new file mode 100755 index 0000000..5015e0c --- /dev/null +++ b/mypy.ini @@ -0,0 +1,3 @@ +[mypy] +ignore_missing_imports=True +check_untyped_defs=True \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100755 index 0000000..0c1b1fc --- /dev/null +++ b/setup.py @@ -0,0 +1,17 @@ +import setuptools +with open("README.md", "r") as f: + long_description = f.read() +setuptools.setup( + name="c2logic", + version="0.1", + descripton="Compile c to mindustry logic.", + long_description=long_description, + long_description_content_type="text/markdown", + packages=["c2logic"], + license="MIT", + author="SuperStormer", + url="https://github.com/SuperStormer/c2logic", + project_urls={"Source Code": "https://github.com/SuperStormer/c2logic"}, + entry_points={"console_scripts": ["c2logic=c2logic:main"]}, + install_requires=["pycparser>=2.20"] +) \ No newline at end of file