Browse Source

initial commit

rlbr-dev
Larry Xue 5 years ago
commit
9aa440f0d0
  1. 10
      .clang-format
  2. 7
      .clang-tidy
  3. 5
      .gitignore
  4. 39
      .pylintrc
  5. 8
      .style.yapf
  6. 21
      LICENSE
  7. BIN
      a.out
  8. 1
      c2logic/__init__.py
  9. 2
      c2logic/__main__.py
  10. 282
      c2logic/compiler.py
  11. 118
      c2logic/instructions.py
  12. 29
      c2logic/operations.py
  13. 4
      compile_flags.txt
  14. 21
      examples/test.c
  15. 15
      include/mindustry.h
  16. 3
      mypy.ini
  17. 17
      setup.py

10
.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

7
.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"

5
.gitignore

@ -0,0 +1,5 @@
.hypothesis
.mypy_cache
.pytest_cache
.vscode
__pycache__

39
.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]

8
.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

21
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.

BIN
a.out

1
c2logic/__init__.py

@ -0,0 +1 @@
from .compiler import Compiler

2
c2logic/__main__.py

@ -0,0 +1,2 @@
from .compiler import main
main()

282
c2logic/compiler.py

@ -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()

118
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

29
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"}

4
compile_flags.txt

@ -0,0 +1,4 @@
-I ./include
-Wall
-Wextra
-std=c11

21
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);
}

15
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

3
mypy.ini

@ -0,0 +1,3 @@
[mypy]
ignore_missing_imports=True
check_untyped_defs=True

17
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"]
)
Loading…
Cancel
Save