diff --git a/README.md b/README.md index b31a626..af1bd4a 100644 --- a/README.md +++ b/README.md @@ -6,44 +6,47 @@ Compiles C code to Mindustry logic. Still in beta, so compiled output may not be `pip install git+https://github.com/SuperStormer/c2logic` -# Usage +# Documentation Run the command line tool using: `c2logic filename -O optimization_level` -where `filename` is a string and `optimization_level` is an optional integer +where `filename` is a string and `optimization_level` is an optional integer. + Optimization Level: 0. completely unoptimized. 1. the default - modify variables without using a temporary 2. turns on some potentially unsafe optimizations - - augmented assignment and pre/postincrement/decrement don't modify \_\_rax + - augmented assignment and pre/postincrement/decrement don't modify `__rax` - returning from main becomes equivalent to `end` -Locals are rewritten as __. Globals are unchanged. +Locals are rewritten as `__`. Globals are unchanged. Special Variables: -- \_\_rax: similar to x86 rax -- \_\_rbx: stores left hand side of binary ops to avoid clobbering by the right side -- \_\_retaddr\_\*: stores return address of func call +- `__rax`: similar to x86 rax +- `__rbx`: stores left hand side of binary ops to avoid clobbering by the right side +- `__retaddr__`: stores return address of func call -When developing your script, you can include `c2logic/builtins.h` located in the python include directory(location depends on system, mine is at `~/.local/include/python3.8/`) +When writing your code, you must include `c2logic/builtins.h`, which is located in the python include directory (location depends on system, mine is at `~/.local/include/python3.8/`). -See [examples](./examples) for API sample usage. +See [include/builtins.h](./include/builtins.h) for API definitions and [examples](./examples) for API sample usage. -# Documentation +# Supported Features -See `include/builtins.h` for API definitions. +- all Mindustry instructions as of BE +- all control flow structures except goto +- functions +- local/global variables # Unsupported Features -- drawing -- getlink -- memory cell read/write -- structs -- enums - -Some of these features may be worked around using `asm()`. +- structs - split it into multiple variables +- enums - use an int plus macros +- block scoped variables - just use locals +- typedefs - use macros +- pointers - don't use them +- goto - don't use it diff --git a/c2logic/compiler.py b/c2logic/compiler.py index 538b3eb..401beeb 100644 --- a/c2logic/compiler.py +++ b/c2logic/compiler.py @@ -7,10 +7,10 @@ from pycparser.c_ast import ( Compound, Constant, DeclList, Enum, FileAST, FuncDecl, Struct, TypeDecl ) -from .consts import builtins, func_binary_ops, func_unary_ops +from .consts import builtins, draw_funcs, func_binary_ops, func_unary_ops from .instructions import ( - BinaryOp, Enable, End, FunctionCall, Instruction, JumpCondition, Print, PrintFlush, Radar, - RawAsm, RelativeJump, Return, Sensor, Set, Shoot, UnaryOp + BinaryOp, Draw, DrawFlush, Enable, End, FunctionCall, GetLink, Instruction, JumpCondition, + Print, PrintFlush, Radar, RawAsm, Read, RelativeJump, Return, Sensor, Set, Shoot, UnaryOp, Write ) @dataclass @@ -46,8 +46,6 @@ class Compiler(c_ast.NodeVisitor): self.curr_function: Function = None self.globals: list = None self.loops: list = None - #self.loop_start: int = None - #self.loop_end_jumps: list = None self.loop_end: int = None def compile(self, filename: str): @@ -55,8 +53,6 @@ class Compiler(c_ast.NodeVisitor): self.curr_function = None self.globals = [] self.loops = [] - #self.loop_start = None - #self.loop_end_jumps = None self.loop_end = None ast = parse_file( filename, use_cpp=True, cpp_args=["-I", site.getuserbase() + "/include/python3.8"] @@ -122,8 +118,6 @@ class Compiler(c_ast.NodeVisitor): 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")) @@ -168,6 +162,34 @@ class Compiler(c_ast.NodeVisitor): break return args + def get_unary_builtin_arg(self, args): + self.visit(args[0]) + if self.can_avoid_indirection(): + return self.pop().src + else: + return "__rax" + + def get_binary_builtin_args(self, args, name): + left_name = f"__{name}_arg0" + self.visit(args[0]) + self.set_to_rax(left_name) + self.visit(args[1]) + left = left_name + right = "__rax" + if self.can_avoid_indirection(): + right = self.pop().src + if self.can_avoid_indirection(left_name): + left = self.pop().src + return left, right + + def get_multiple_psuedofunc_args(self, args): + argnames = [] + for i, arg in enumerate(args): + self.visit(arg) + self.set_to_rax(f"__write_arg{i}") + argnames.append(f"__write_arg{i}") + return self.optimize_psuedofunc_args(argnames) + #visitors def visit_FuncDef(self, node): # function definitions func_name = node.decl.name @@ -311,11 +333,11 @@ class Compiler(c_ast.NodeVisitor): self.curr_function.instructions ) - def visit_Break(self, node): + def visit_Break(self, node): #pylint: disable=unused-argument self.push(RelativeJump(None, JumpCondition.always)) self.loops[-1].end_jumps.append(self.curr_offset()) - def visit_Continue(self, node): + def visit_Continue(self, node): #pylint: disable=unused-argument self.push(RelativeJump(self.loops[-1].start, JumpCondition.always)) def visit_Return(self, node): @@ -328,26 +350,18 @@ class Compiler(c_ast.NodeVisitor): args = node.args.exprs else: args = [] - #TODO avoid duplication in psuedo-function calls + #TODO avoid duplication in builtin calls if name == "asm": arg = args[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(args[0]) - if self.can_avoid_indirection(): - self.push(Print(self.pop().src)) - else: - self.push(Print("__rax")) + self.push(Print(self.get_unary_builtin_arg(args))) elif name == "printflush": - self.visit(args[0]) - if self.can_avoid_indirection(): - self.push(PrintFlush(self.pop().src)) - else: - self.push(PrintFlush("__rax")) + self.push(PrintFlush(self.get_unary_builtin_arg(args))) elif name == "radar": - args = [] + argnames = [] for i, arg in enumerate(args): if 1 <= i <= 4: if not isinstance(arg, Constant) or arg.type != "string": @@ -356,9 +370,9 @@ class Compiler(c_ast.NodeVisitor): else: self.visit(arg) self.set_to_rax(f"__radar_arg{i}") - args.append(f"__radar_arg{i}") - args = self.optimize_psuedofunc_args(args) - self.push(Radar("__rax", *args)) #pylint: disable=no-value-for-parameter + argnames.append(f"__radar_arg{i}") + argnames = self.optimize_psuedofunc_args(argnames) + self.push(Radar("__rax", *argnames)) #pylint: disable=no-value-for-parameter elif name == "sensor": self.visit(args[0]) self.set_to_rax("__sensor_arg0") @@ -374,36 +388,31 @@ class Compiler(c_ast.NodeVisitor): left = self.pop().src self.push(Sensor("__rax", left, right)) elif name == "enable": - self.visit(args[0]) - self.set_to_rax("__enable_arg0") - self.visit(args[1]) - left = "__enable_arg0" - right = "__rax" - if self.can_avoid_indirection(): - right = self.pop().src - if self.can_avoid_indirection("__enable_arg0"): - left = self.pop().src + left, right = self.get_binary_builtin_args(args, "enable") self.push(Enable(left, right)) elif name == "shoot": - args = [] - for i, arg in enumerate(args): - self.visit(arg) - self.set_to_rax(f"__shoot_arg{i}") - args.append(f"__shoot_arg{i}") - args = self.optimize_psuedofunc_args(args) - self.push(Shoot(*args)) #pylint: disable=no-value-for-parameter + self.push(Shoot(*self.get_multiple_psuedofunc_args(args))) #pylint: disable=no-value-for-parameter + elif name == "get_link": + self.visit(args[0]) + if self.can_avoid_indirection(): + self.push(GetLink("__rax", self.pop().src)) + else: + self.push(GetLink("__rax", "__rax")) + elif name == "read": + cell, index = self.get_binary_builtin_args(args, "read") + self.push(Read("__rax", cell, index)) + elif name == "write": + self.push(Write(*self.get_multiple_psuedofunc_args(args))) #pylint: disable=no-value-for-parameter elif name == "end": self.push(End()) + elif name in draw_funcs: + argnames = self.get_multiple_psuedofunc_args(args) + cmd = draw_funcs[name] + self.push(Draw(cmd, *argnames)) + elif name == "drawflush": + self.push(DrawFlush(self.get_unary_builtin_arg(args))) elif name in func_binary_ops: - self.visit(args[0]) - self.set_to_rax("__binary_arg0") - self.visit(args[1]) - left = "__binary_arg0" - right = "__rax" - if self.can_avoid_indirection(): - right = self.pop().src - if self.can_avoid_indirection("__binary_arg0"): - left = self.pop().src + left, right = self.get_binary_builtin_args(args, "binary") self.push(BinaryOp("__rax", left, right, name)) elif name in func_unary_ops: self.visit(args[0]) diff --git a/c2logic/consts.py b/c2logic/consts.py index 6b962f1..bb09834 100644 --- a/c2logic/consts.py +++ b/c2logic/consts.py @@ -36,4 +36,13 @@ func_unary_ops = ["abs", "log", "log10", "sin", "cos", "tan", "floor", "ceil", " binary_ops.update(dict(zip(func_binary_ops, func_binary_ops))) unary_ops.update(dict(zip(func_unary_ops, func_unary_ops))) -builtins = ["print", "printd", "printflush", "radar", "sensor", "enable", "shoot", "end"] +draw_funcs = { + "draw" + func.lower(): func + for func in + ["clear", "color", "stroke", "line", "rect", "lineRect", "poly", "linePoly", "triangle"] +} + +builtins = [ + "print", "printd", "printflush", "radar", "sensor", "enable", "shoot", "get_link", "read", + "write", "drawflush", "end" +] + list(draw_funcs.keys()) diff --git a/c2logic/instructions.py b/c2logic/instructions.py index 2cc05c9..d08f601 100644 --- a/c2logic/instructions.py +++ b/c2logic/instructions.py @@ -87,11 +87,11 @@ class Print(Instruction): return f"print {self.val}" class PrintFlush(Instruction): - def __init__(self, val: str): - self.val = val + def __init__(self, message: str): + self.message = message def __str__(self): - return f"printflush {self.val}" + return f"printflush {self.message}" class Radar(Instruction): def __init__( @@ -135,6 +135,48 @@ class Shoot(Instruction): def __str__(self): return f"control shoot {self.obj} {self.x} {self.y} {self.shoot} 0" +class GetLink(Instruction): + def __init__(self, dest: str, index: str): + self.dest = dest + self.index = index + + def __str__(self): + return f"getlink {self.dest} {self.index}" + +class Read(Instruction): + def __init__(self, dest: str, src: str, index: str): + self.dest = dest + self.src = src + self.index = index + + def __str__(self): + return f"read {self.dest} {self.src} {self.index}" + +class Write(Instruction): + def __init__(self, dest: str, src: str, index: str): + self.dest = dest + self.src = src + self.index = index + + def __str__(self): + return f"write {self.dest} {self.src} {self.index}" + +class Draw(Instruction): + def __init__(self, cmd: str, *args): + self.cmd = cmd + self.args = args + + def __str__(self): + args = list(self.args) + ['0'] * (6 - len(self.args)) + return f"draw {self.cmd} {' '.join(args)}" + +class DrawFlush(Instruction): + def __init__(self, display: str): + self.display = display + + def __str__(self): + return f"drawflush {self.display}" + class End(Instruction): def __str__(self): return "end" diff --git a/examples/drawing.c b/examples/drawing.c new file mode 100644 index 0000000..0a1c59e --- /dev/null +++ b/examples/drawing.c @@ -0,0 +1,9 @@ +#include "c2logic/builtins.h" +extern struct MindustryObject display1; +void main(void) { + drawcolor(128, 128, 0); + drawpoly(40, 40, 6, 10, 0); + drawcolor(0, 128, 128); + drawpoly(40, 40, 3, 10, 180); + drawflush(display1); +} \ No newline at end of file diff --git a/include/builtins.h b/include/builtins.h index 1076f6f..04ad9bb 100644 --- a/include/builtins.h +++ b/include/builtins.h @@ -1,18 +1,34 @@ #ifndef MINDUSTRY_H #define MINDUSTRY_H struct MindustryObject {}; -// void _asm(char* code); // builtin instructions void print(char* s); void printd(double s); -void printflush(struct MindustryObject msg_block); +void printflush(struct MindustryObject message); 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); + +struct MindustryObject get_link(double index); +double read(struct MindustryObject cell, double index); +void write(struct MindustryObject cell, double index, double val); + +void drawclear(double r, double g, double b); +void drawcolor(double r, double g, double b); +void drawstroke(double width); +void drawline(double x1, double y1, double x2, double y2); +void drawrect(double x, double y, double w, double h); +void drawlinerect(double x, double y, double w, double h); +void drawpoly(double x, double y, double sides, double radius, double rotation); +void drawlinepoly(double x, double y, double sides, double radius, double rotation); +void drawtriangle(double x1, double y1, double x2, double y2, double x3, double y3); +void drawflush(struct MindustryObject display); + void end(); + // builtin binary operators double pow(double x, double y); double max(double x, double y);