commit
9aa440f0d0
17 changed files with 582 additions and 0 deletions
-
10.clang-format
-
7.clang-tidy
-
5.gitignore
-
39.pylintrc
-
8.style.yapf
-
21LICENSE
-
BINa.out
-
1c2logic/__init__.py
-
2c2logic/__main__.py
-
282c2logic/compiler.py
-
118c2logic/instructions.py
-
29c2logic/operations.py
-
4compile_flags.txt
-
21examples/test.c
-
15include/mindustry.h
-
3mypy.ini
-
17setup.py
@ -0,0 +1,10 @@ |
|||
BasedOnStyle: Google |
|||
UseTab: Always |
|||
IndentWidth: 4 |
|||
TabWidth: 4 |
|||
BreakBeforeBraces: Attach |
|||
AllowShortBlocksOnASingleLine: Empty |
|||
AllowShortFunctionsOnASingleLine: Empty |
|||
AllowShortIfStatementsOnASingleLine: false |
|||
AllowShortLoopsOnASingleLine: false |
|||
ColumnLimit: 100 |
|||
@ -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" |
|||
|
|||
@ -0,0 +1,5 @@ |
|||
.hypothesis |
|||
.mypy_cache |
|||
.pytest_cache |
|||
.vscode |
|||
__pycache__ |
|||
@ -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] |
|||
|
|||
@ -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 |
|||
@ -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. |
|||
@ -0,0 +1 @@ |
|||
from .compiler import Compiler |
|||
@ -0,0 +1,2 @@ |
|||
from .compiler import main |
|||
main() |
|||
@ -0,0 +1,282 @@ |
|||
from pycparser.c_ast import Compound, Constant, DeclList, Enum, FileAST, FuncDecl, Struct, TypeDecl |
|||
from .instructions import BinaryOp, Enable, JumpCondition, Print, PrintFlush, Radar, RawAsm, RelativeJump, Sensor, Shoot, UnaryOp, Instruction, Set, Noop |
|||
from pycparser import c_parser, c_ast, parse_file |
|||
from dataclasses import dataclass |
|||
""" |
|||
@dataclass |
|||
class LoopState(): |
|||
start: int |
|||
end: int |
|||
cond_jump_offset: int |
|||
""" |
|||
|
|||
@dataclass |
|||
class Function(): |
|||
name: str |
|||
args: list |
|||
instructions: list |
|||
start: int |
|||
|
|||
""" |
|||
@dataclass |
|||
class Variable(): |
|||
type: str |
|||
name: str |
|||
""" |
|||
|
|||
class Compiler(c_ast.NodeVisitor): |
|||
"""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 |
|||
optimization levels: |
|||
1: assignments set directly to the variable instead of indirectly through __rax |
|||
""" |
|||
def __init__(self, opt_level=0): |
|||
self.opt_level = opt_level |
|||
|
|||
def compile(self, filename: str): |
|||
self.functions = [] |
|||
self.curr_function = None |
|||
self.loop_start = None |
|||
self.cond_jump_offset = None |
|||
ast = parse_file(filename, use_cpp=True, cpp_args=["-I", "include/"]) |
|||
print(ast) |
|||
self.visit(ast) |
|||
#TODO actually handle functions properly |
|||
out = [] |
|||
offset = 0 |
|||
for function in self.functions: |
|||
function.start = offset |
|||
offset += len(function.instructions) |
|||
for function in self.functions: |
|||
instructions = function.instructions |
|||
out2 = [] |
|||
for instruction in instructions: |
|||
if isinstance(instruction, RelativeJump): |
|||
instruction.func_start = function.start #FIXME |
|||
out2.append(str(instruction)) |
|||
out.append("\n".join(out2)) |
|||
return "\n\n".join(out) |
|||
|
|||
#utilities |
|||
def push(self, instruction: Instruction): |
|||
self.curr_function.instructions.append(instruction) |
|||
|
|||
def pop(self): |
|||
return self.curr_function.instructions.pop() |
|||
|
|||
def peek(self): |
|||
return self.curr_function.instructions[-1] |
|||
|
|||
def can_avoid_indirection(self, var="__rax"): |
|||
top = self.peek() |
|||
return self.opt_level >= 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() |
|||
@ -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 |
|||
@ -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"} |
|||
@ -0,0 +1,4 @@ |
|||
-I ./include |
|||
-Wall |
|||
-Wextra |
|||
-std=c11 |
|||
@ -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); |
|||
} |
|||
@ -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 |
|||
@ -0,0 +1,3 @@ |
|||
[mypy] |
|||
ignore_missing_imports=True |
|||
check_untyped_defs=True |
|||
@ -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"] |
|||
) |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue