import pandas as pd
import numpy as np
import sklearn.datasets
import copy
from sklearn.linear_model import LinearRegression, Ridge, Lasso
import matplotlib.pyplot as plt
import docplex.mp.model as cpx
from numpy import inf
from numpy import zeros
from sklearn import ensemble
from sklearn.ensemble import RandomForestRegressor

'''数据介绍
文件名称："JD_2018_3_Sales_Dataset.csv"
数据来源：JD.com: Transaction Level  Data for the 2020 MSOM Data Driven Research Challenge
数据的简介：来源于京东2018年3月份某一种商品的销售数据，包含销售量和促销数据
属性介绍：
1.overall_discount(总促销折扣)
2.direct_discount（直接折扣）
3.quantity_discount（凑单折扣）
4.coupon_discount(优惠券折扣)
5.overall_sale（促销活动后总销售数据）
数据大小：31×5'''

#   步骤1：将原始的京东销售量数据进行转化，转化为6天数据合为1条数据的数据类型。
#   读取原始数据
order_daily = pd.read_csv('JD_2018_3_Sales_Dataset.csv')
#   对数据进行转换
new_order = []  # 创建一个空列表，存放转换后的数据
for i in range(26): # 原来是31条数据，6条数据转换为1条数据后变为26条数据
    j, k, m, n = i+1, i+2, i+3, i+4
    new_order.append(np.array(order_daily[i:i+1][:].values.tolist()
                              +order_daily[j:j+1][:].values.tolist()
                              +order_daily[k:k+1][:].values.tolist()
                              +order_daily[m:m+1][:].values.tolist()
                              +order_daily[n:n+1][:].values.tolist()
                              +order_daily[n+1:n+2][:].values.tolist()).flatten())

new_order = pd.DataFrame(new_order, columns=['overall_discount1', 'direct_discount1', 'quantity_discount1', 'coupon_discount1', 'overall_sale1',
                                            'overall_discount2', 'direct_discount2', 'quantity_discount2', 'coupon_discount2', 'overall_sale2',
                                            'overall_discount3', 'direct_discount3', 'quantity_discount3', 'coupon_discount3', 'overall_sale3',
                                            'overall_discount4', 'direct_discount4', 'quantity_discount4', 'coupon_discount4', 'overall_sale4',
                                            'overall_discount5', 'direct_discount5', 'quantity_discount5', 'coupon_discount5', 'overall_sale5',
                                            'overall_discount6', 'direct_discount6', 'quantity_discount6', 'coupon_discount6', 'overall_sale6'])
print(new_order)

#   步骤2：定义误差计算方法（MAPE)
def mape(y_true, y_pred):
    return np.mean(np.abs((y_pred - y_true) / y_true)) * 100

#   步骤3：使用模型来进行预测，并对结果进行作图，预测的方法如下介绍
'''输入x为:'overall_discount1', 'direct_discount1', 'quantity_discount1', 'coupon_discount1', 'overall_sale1',
                                            'overall_discount2', 'direct_discount2', 'quantity_discount2', 'coupon_discount2', 'overall_sale2',
                                            'overall_discount3', 'direct_discount3', 'quantity_discount3', 'coupon_discount3', 'overall_sale3',
                                            'overall_discount4', 'direct_discount4', 'quantity_discount4', 'coupon_discount4', 'overall_sale4',
                                            'overall_discount5', 'direct_discount5', 'quantity_discount5', 'coupon_discount5', 'overall_sale5',
                                            'overall_discount6', 'direct_discount6', 'quantity_discount6', 'coupon_discount6'
    输出为：'overall_sale6'
    预测的目的就是通过前5天的销售数据来预测第6天的销售量，因为在第6天开始销售前，是可以提前确定第6天的促销活动，包括各个折扣的力度多少，所以
    'overall_discount6', 'direct_discount6', 'quantity_discount6', 'coupon_discount6'是作为输入
'''
#   3.1 定义线性回归、lasso、ridge回归的类方法
#   1.Linear Regression
class linearRegression():
    def __init__(self):
        self.ws=None
        pass

    def fit(self,x,y):
        m = x.shape[0]
        X = np.concatenate((np.ones((m, 1)), x), axis=1)
        xMat = np.mat(X)
        yMat = np.mat(y.reshape(-1, 1))
        xTx = xMat.T * xMat

        ws = xTx.I * xMat.T * yMat
        #ws[0]就是b
        self.ws=ws
        return ws

    def predict(self,x_test):
        x_test=np.mat(x_test)
        y_pred=self.ws[1:,:]*x_test+self.ws[0,:]
        return y_pred

#   LassoRegression
class LassoRegression():
    def __init__(self):
        self.w=None
        pass
    def fit(self,x,y,epochs,lr,lambda0):
        m = x.shape[0]
        X=np.concatenate((np.ones((m,1)),x),axis=1)
        x_mat=np.mat(X)
        y_mat=np.mat(y.reshape(-1,1))
        w = np.ones(X.shape[1]).reshape(-1, 1)
        out_w=copy.copy(w)
        for i, item in enumerate(w):
            for j in range(epochs):
                h = x_mat * w
                gradient = x_mat[:,i].T * (h - y_mat)/m + lambda0 * np.sign(w[i])
                w[i] = w[i] - gradient * lr
                if abs(gradient) < 1e-3:
                    break
            out_w = np.array(list(map(lambda x: abs(x) < 1e-3, out_w - w)))
            if out_w.all():
                break
        self.w=w
        return w

    def predict(self,x_test):
        x_test = np.mat(x_test)
        y_pred = self.w[1:, :]*x_test + self.w[0, :]
        return y_pred

#   RidgeRegression
class RidgeRegression():
    def __init__(self):
        self.w=None
        pass

    def fit(self,x,y,epoch,lr, lambda1):
        m = x.shape[0]
        X = np.concatenate((np.ones((m, 1)), x), axis=1)
        n = X.shape[1]
        w = np.mat(np.ones((n, 1)))
        x_mat=np.mat(X)
        y_mat=np.mat((y.reshape(-1,1)))
        for i in range(epoch):
            gradient = x_mat.T * (x_mat * w - y_mat) / m + lambda1 * w
            w=w-lr*gradient

        self.w=w
        return w

    def predict(self, x_test):
        x_test1 = np.mat(x_test)
        y_pred = self.w[1:,:]*x_test1 + self.w[0, :]
        return y_pred

#   3.2 进行拟合，用前10条数据作为train集合，第11条数据作为test集合，第二次用2-11条数据作为train，第12条数据作为test
#   先选择x和y
x = new_order.drop(['overall_sale6'], axis=1)
y = new_order.loc[:, 'overall_sale6']
#   设置误差存放列表
linear_mape = []
ridge_mape = []
lasso_mape = []
for i in range(1, 15):
    x_train = np.array(x.loc[i:i+9, :])
    y_train = np.array(y.loc[i:i+9])
    x_test = np.array(x.loc[i+10:i+10, :])
    y_test = np.array(y.loc[i+10:i+10])
    '''model1 = linearRegression()
    model2 = RidgeRegression()
    model3 = LassoRegression()
    model1.fit(x_train, y_train)
    model2.fit(x_train, y_train, 10000, 0.05, 40)   # 参数选择依据经验值
    model3.fit(x_train, y_train, 10000, 0.05, 40)
    linear_mape.append(mape(y_test, model1.predict(x_test)))
    ridge_mape.append(mape(y_test, model2.predict(x_test)))
    lasso_mape.append(mape(y_test, model3.predict(x_test)))'''  # 本来想用自己写的类来做，但是最后结果出来误差非常大，而且有的根本算不出来，所以决定用sklearn
    model1 = LinearRegression()
    model2 = Ridge()
    model3 = Lasso()
    model1.fit(x_train, y_train)
    model2.fit(x_train, y_train)
    model3.fit(x_train, y_train)
    linear_mape.append(mape(y_test, model1.predict(x_test)))
    ridge_mape.append(mape(y_test, model2.predict(x_test)))
    lasso_mape.append(mape(y_test, model3.predict(x_test)))

print(linear_mape, ridge_mape, lasso_mape, sep='\n')

#   3.3 编写SVR函数，并用其进行拟合
opt_model = cpx.Model(name="QP Model")

def linear_kernel(x, y):
    return np.dot(x, y.T)

def poly_kernel(x, y, cef_):
    return (1 + np.dot(x, y.T)) ** cef_

class dual_svr():
    def __init__(self, x, y, c=1.2, fai=1, kernel='poly_kernel'):
        self.x = x
        self.xx = np.dot(x, x.T)
        self.y = y
        m, n = self.xx.shape
        self.set_m = range(m)
        self.set_n = range(n)
        self.c = c
        self.fai = fai

    def fit(self):
        ##添加变量
        aphli1 = {(m): opt_model.continuous_var(name="aphli1_{0}".format(m)) for m in self.set_m}
        aphli2 = {(m): opt_model.continuous_var(name="aphli2_{0}".format(m)) for m in self.set_m}
        # aphlj1 = {(n): opt_model.continuous_var(name="aphlj1_{0}".format(n)) for n in self.set_n}
        # aphlj2 = {(n): opt_model.continuous_var(name="aphlj2_{0}".format(n)) for n in self.set_n}
        b = {(n): opt_model.continuous_var(name="b_{0}".format(n)) for n in self.set_n}
        songchi1 = {(m): opt_model.continuous_var(name="songchi1_{0}".format(m)) for m in self.set_m}
        songchi2 = {(m): opt_model.continuous_var(name="songchi2_{0}".format(m)) for m in self.set_m}
        ##添加约束

        opt_model.add_constraint(opt_model.sum(aphli1[m] - aphli2[m] for m in self.set_m) == 0)
        for m in self.set_m:
            opt_model.add_constraint(aphli2[m] >= 0)
        for m in self.set_m:
            opt_model.add_constraint(aphli1[m] <= self.c)
        '''
        ##kkt
        for m in self.set_m:
            for n in self.set_n:

        fx=opt_model.sum((aphli1[m]-aphli2[m])*self.xx[m] for m in self.set_m)+b1
        for m in self.set_m:
            opt_model.add_constraint(aphli1[m]*(self.y[m]-fx-self.fai-songchi1)==0)
        for m in self.set_m:
            opt_model.add_constraint(aphli2[m]*(fx-self.y[m]-self.fai-songchi2)==0)
        for m in self.set_m:
            opt_model.add_constraint(aphli1[m]*aphli2[m]==0)

        for m in self.set_m:
            opt_model.add_constraint((self.c-aphli1[m])*songchi1==0)

        for m in self.set_m:
            opt_model.add_constraint((self.c-aphli2[m])*songchi2==0)
        '''

        ##添加目标函数
        sum1 = opt_model.sum(
            self.y[m] * (aphli1[m] - aphli2[m]) - self.fai * (aphli1[m] + aphli2[m]) for m in self.set_m)
        sum2 = opt_model.sum(
            (aphli1[m] - aphli2[m]) * (aphli1[n] - aphli2[n]) * self.xx[m][n] for m in self.set_m for n in self.set_n)
        obj = sum1 - 1 / 2 * sum2
        opt_model.maximize(obj)
        opt_model.solve()
        list_aphli1 = []
        list_aphli2 = []
        w = 0
        temp2 = 0
        for i in self.set_m:
            list_aphli1.append(aphli1[i].solution_value)
            list_aphli2.append(aphli2[i].solution_value)
            w += (aphli1[i].solution_value - aphli2[i].solution_value) * self.x[i]
            temp2 += ((aphli1[i].solution_value - aphli2[i].solution_value) * self.x[i].T)
        b = y[i] + self.fai - np.dot(temp2, self.x[i])
        return w, b

# dataset = np.array([[3, 3, 5], [4, 3, 6], [1, 1, 3]])
# y = np.array([10, 11, -12])
#
#
# du_svr = dual_svr(dataset, y)
# w, b = du_svr.fit()
# print(np.dot(w, np.array([1, 2, 3]))+b)
SVR = []
for i in range(1, 15):
    x_train = np.array(x.loc[i:i+9, :])
    y_train = np.array(y.loc[i:i+9])
    x_test = np.array(x.loc[i+10:i+10, :])
    y_test = np.array(y.loc[i+10:i+10])

    model = dual_svr(x_train, y_train)
    w, b = model.fit()
    SVR.append(mape(y_test, np.dot(np.array(w), x_test.T)+b))
print(SVR)

# 3.4 编写随机森林类，并进行拟合预测
def get_Datasets(x,y):
    return np.concatenate((x, y.reshape((-1, 1))), axis=1)

def splitDataSet(x,n_folds):
    fold_size=len(x)/n_folds
    data_split=[]
    begin=0
    end=fold_size
    for i in range(n_folds):
        data_split.append(x[begin:end,:])
        begin=end
        end+=fold_size
    return data_split

def get_subsamples(x,n):
    subDataSet=[]
    for i in range(n):
        index=[]
        for k in range(len(x)):
            index.append(np.random.randint(len(x)))
        subDataSet.append(x[index,:])
    return subDataSet

def binSplitDataSet(x, feature, value):
    mat0 = x[np.nonzero(x[:, feature] > value)[0], :]
    mat1 = x[np.nonzero(x[:, feature] < value)[0], :]
    return mat0, mat1

def regErr(x):
    return np.var(x[:,-1])*np.shape(x)[0]

def regLeaf(x):
    return np.mean(x[:,-1])

def MostNumber(x):
    len0=len(np.nonzero(x[:,-1]==0)[0])
    len1=len(np.nonzero(x[:,-1]==1)[0])
    if len0>len1:
        return 0
    else:
        return 1

def gini(x):
    corr=0.0
    for i in set(x[:,-1]):
        corr+=(len(np.nonzero(x[:,-1]==i)[0])/len(x))**2
    return 1-corr

def select_best_feature(x, m, alpha="huigui"):
    f = x.shape[1]
    index = []
    bestS = inf;
    bestfeature = 0;
    bestValue = 0;
    if alpha == "huigui":
        S = regErr(x)
    else:
        S = gini(x)

    for i in range(m):
        index.append(np.random.randint(f))
        for feature in index:
            for splitVal in set(x[:, feature]):  # set() 函数创建一个无序不重复元素集，用于遍历这个特征下所有的值
                mat0, mat1 = binSplitDataSet(x, feature, splitVal)
                if alpha == "huigui":
                    newS = regErr(mat0) + regErr(mat1)  # 计算每个分支的回归方差
                else:
                    newS = gini(mat0) + gini(mat1)  # 计算被分错率
                if bestS > newS:
                    bestfeature = feature
                    bestValue = splitVal
                    bestS = newS
        if (S - bestS) < 0.001 and alpha == "huigui":
            return None, regLeaf(x)
        elif (S - bestS) < 0.001:

            return None, MostNumber(x)
        return bestfeature, bestValue

def createTree(x,alpha="huigui",m=20,max_level=10):
    bestfeature,bestValue=select_best_feature(x,m,alpha=alpha)
    if bestfeature==None:
        return bestValue
    retTree={}
    max_level-=1
    if max_level<0:
        return regLeaf(x)
    retTree['bestFeature']=bestfeature
    retTree['bestVal']=bestValue
    lSet,rSet=binSplitDataSet(x,bestfeature,bestValue)
    retTree['right']=createTree(rSet,alpha,m,max_level)
    retTree['left']=createTree(lSet,alpha,m,max_level)
    return retTree

def RondomForest(x,n,alpha="huigui"):
    Trees=[]
    for i in range(n):
        #X_train, X_test, y_train, y_test = train_test_split(x[:,:-1], x[:,-1], test_size=0.33, random_state=42)
        #X_train=np.concatenate((X_train,y_train.reshape((-1,1))),axis=1)
        Trees.append(createTree(x,alpha=alpha))
    return Trees

def treeForecast(trees, x, alpha="huigui"):
    if alpha == "huigui":
        if not isinstance(trees, dict):
            return float(trees)

        if x[trees['bestFeature']] > trees['bestVal']:
            if type(trees['left']) == 'float':
                return trees['left']
            else:
                return treeForecast(trees['left'], x, alpha)
        else:
            if type(trees['right']) == 'float':
                return trees['right']
            else:
                return treeForecast(trees['right'], x, alpha)
    else:
        if not isinstance(trees, dict):
            return int(trees)

        if x[trees['bestFeature']] > trees['bestVal']:
            if type(trees['left']) == 'int':
                return trees['left']
            else:
                return treeForecast(trees['left'], x, alpha)
        else:
            if type(trees['right']) == 'int':
                return trees['right']
            else:
                return treeForecast(trees['right'], x, alpha)

def createForeCast(trees,test,alpha="huigui"):
    cm=len(test)
    yhat=np.mat(zeros((cm,1)))
    for i in range(cm):                                     #
        yhat[i,0]=treeForecast(trees,test[i,:],alpha)    #
    return yhat

def predictTree(Trees,test,alpha="huigui"):
    cm=len(test)
    yhat=np.mat(zeros((cm,1)))
    for trees in Trees:
        yhat+=createForeCast(trees,test,alpha)
    if alpha=="huigui": yhat/=len(Trees)
    else:
        for i in range(len(yhat)):

            if yhat[i,0]>len(Trees)/2:
                yhat[i,0]=1
            else:
                yhat[i,0]=0
    return yhat

def getall_data(data1,data2):
    return np.concatenate((data1,data2),axis=0)

x_train = np.array([[8,9,10,11,12,13,15], [11,12,13,14,16,17,18], [15,16,17,18,19,20,21]])
y_train = np.array([20, 33, 42])
x_test = np.array([[88,96,105,119,122,134,159]])
y_test = np.array([200])
dataSet=get_Datasets(x_train,y_train)
dataSet1=get_Datasets(x_test,y_test)
data=getall_data(dataSet,dataSet1)
RomdomTrees=RondomForest(data,4,alpha="huigui")
test=data
yhat = predictTree(RomdomTrees, test, alpha="huigui")
print(yhat.T)
print(data[-1,-1].T - yhat[-1])

'''在算完之后发现随机森林误差非常大，跟我们预测中的误差不符合，因为集成学习对付这种数据应该非常厉害，那可能我们类某些地方写错，但是因为实在不会改了，就用了sklearn中ensemble来拟合
#   存放随机森林的误差
RandomForests = []
for i in range(1, 15):
    x_train = np.array(x.loc[i:i+9, :])
    y_train = np.array(y.loc[i:i+9])
    x_test = np.array(x.loc[i+10:i+10, :])
    y_test = np.array(y.loc[i+10:i+10])
    dataSet = get_Datasets(x_train, y_train)
    dataSet1 = get_Datasets(x_test, y_test)
    data = getall_data(dataSet, dataSet1)
    RomdomTrees = RondomForest(data, 4, alpha="huigui")
    test = data
    yhat = predictTree(RomdomTrees, test, alpha="huigui")
    RandomForests.append(mape(y_test, data[-1, -1].T - yhat[-1]))
'''
RandomForests = []
for i in range(1, 15):
    x_train = np.array(x.loc[i:i+9, :])
    y_train = np.array(y.loc[i:i+9])
    x_test = np.array(x.loc[i+10:i+10, :])
    y_test = np.array(y.loc[i+10:i+10])
    clf = RandomForestRegressor(bootstrap=True,
                             max_depth=10,
                             max_features='auto',
                             min_samples_leaf=1,
                             min_samples_split=2,
                             n_estimators=20)
    clf.fit(x_train, y_train)
    RandomForests.append(mape(y_test, clf.predict(x_test)))

# 3.5 对不同模型的误差分布作图,最后发现竟然是lasso表现最好！！！！！！而不是随机森林！！！！！但是本次作业中都没进行参数选择，所以得到的结果具有一定缺陷
MAPE_3_linearmodels = np.array([linear_mape, ridge_mape, lasso_mape, SVR, RandomForests])
ax = plt.subplot()
ax.boxplot(MAPE_3_linearmodels.T) # 绘图
ax.set_xlabel('模型名称')
ax.set_xticklabels(['linear', 'ridge', 'lasso', 'svr', 'random forests'], fontsize=10)
ax.set_ylabel('MAPE%')
ax.set_title('不同模型的MAPE分布图')
plt.show()


import pynbody
import numpy as np

snap_name = "np_sim62002_snapshot_0067_p200_a800_r600_b60_rdw50.gic.ok"
glass_name = "ic18145.ic"
snap = pynbody.load(snap_name)

glass = pynbody.load(glass_name)

glass['mass'] = np.ones(len(glass['mass']), dtype=np.float32) * -1

f = pynbody.new(gas=len(snap.gas), star=len(snap.star), dm=len(snap.dm)+len(glass.gas))

# Adjust DM
for block in ['pos', 'vel', 'iord', 'mass']:
    f.dm[block] = np.concatenate((glass[block], snap.dm[block]))

for block, families in snap.get_block_list().items():
    print('block=', block)
    # if block in ['pos', 'vel', 'iord', 'mass']:
    #     f[block] = snap[block]
    for family in families:
        if family.name != 'dm':
            print('  family=', family.name)
            getattr(f, family.name)[block] = getattr(snap, family.name)[block]

f.properties = {**snap.properties, **dict(#time=snap.properties[np.float32(snap.header.time),
                                          boxsize=snap.header.BoxSize * pynbody.units.kpc,
                                          z=snap.header.redshift,
                                          a=1+1/snap.header.redshift,
                                          omegaL0=0.72,
                                          omegaM0=0.28,
                                          h=0.7)}
print(f.properties)
f.write(filename="snap_glass.ic", fmt=pynbody.gadget.GadgetSnap, use_time=True)


import matplotlib.pyplot as plt
import numpy as np

plt.figure()
plt.axhline(0, color='black', lw=1)
plt.axvline(0, color='black', lw=1)
plt.xlim([-15, 15])
plt.ylim([-15, 15])
ax = plt.gca()
ax.set_aspect('equal')

# unit ball
theta = np.linspace(0, 2 * np.pi, 100)
x = np.cos(theta)
y = np.sin(theta)
unit_ball = np.array([x, y])
plt.plot(x, y)

# image
sqrt2 = np.sqrt(2)
A = np.array([[-2, 11], [-10, 5]])
S = np.array([-10 * sqrt2, -5 * sqrt2])
U = np.array([[1, -1], [1, 1]]) / sqrt2
V = np.array([[3, 4], [-4, 3]]) / 5
image = A @ unit_ball
plt.plot(image[0], image[1])
# σu1 and σu2
for i in range(2):
    plt.arrow(0,
              0,
              U[0][i] * S[i],
              U[1][i] * S[i],
              head_width=0.5,
              length_includes_head=True,
              color='red')
    plt.text(U[0][i] * S[i] / 2,
             U[1][i] * S[i] / 2,
             'σu' + str(i),
             fontsize=12,
             ha='center',
             va='center')
    plt.text(U[0][i] * S[i],
             U[1][i] * S[i],
             "[%.2f, %.2f]" % (U[0][i] * S[i], U[1][i] * S[i]),
             fontsize=12,
             ha='center',
             va='center')
plt.savefig("unit ball & image.png")

# singular vectors
plt.figure()
plt.plot(x, y)
plt.axhline(0, color='black', lw=1)
plt.axvline(0, color='black', lw=1)
ax = plt.gca()
ax.set_aspect('equal')

for i in range(2):
    plt.arrow(0,
              0,
              V[0][i],
              V[1][i],
              head_width=0.05,
              length_includes_head=True,
              color='green')
    plt.text(V[0][i] / 2,
             V[1][i] / 2,
             "v" + str(i),
             fontsize=12,
             ha='center',
             va='center')
    plt.text(V[0][i],
             V[1][i],
             "[%.2f, %.2f]" % (V[0][i], V[1][i]),
             fontsize=12,
             ha='center',
             va='center')

plt.savefig("singular vectors.png")


import copy

from pycparser import c_parser, c_ast, parse_file, c_generator
import compGlobals as globals
from compScopeController import *
from compClasses import *

def getArrayElementPointer(arrayRef, scope, indentLevel=0):
	'''
	Get a pointer to an array element into a register

	Returns:
		<tuple> ( <instructionList> instructions , <str> pointerRegister , <int> size, <str> pointerVariableName)
	'''
	instructions = instructionList(scope)
	indentString = "".join(["\t" for i in range(indentLevel)])

	#Get name of array root
	arrayRootName = arrayRef.name
	while (not isinstance(arrayRootName, str)):
		arrayRootName = arrayRootName.name

	subscript = arrayRef.subscript

	#Get pointer to start of array
	pointerVariableName  = None
	rootVariable = scope.getVariable(arrayRootName)
	if (isinstance(rootVariable.type, c_ast.PtrDecl)):
		instructions += scope.loadStack(arrayRootName, indentLevel=indentLevel)
		currentPointerRegister = scope.getRegisterLocation(arrayRootName)  #we don't want to modify this, so we will copy to another register
		instructionsTemp, arrayPointerReg = scope.getFreeRegister(preferTemp=True, tag="arrayPointerReg", indentLevel=indentLevel)
		instructions += instructionsTemp
		instructions.append("{}mv {}, {}".format(indentString, arrayPointerReg, currentPointerRegister))
	else:
		instructionsTemp, arrayPointerReg, pointerVariableName = scope.getPointer(arrayRootName, indentLevel=indentLevel)
		instructions += instructionsTemp

	#Determine element length and starting offset from pointer
	subElementSize = None
	elementLength = None  #Size of root element in array

	if isinstance(rootVariable.type, c_ast.ArrayDecl):
		subElementSize = rootVariable.subElementSize
		elementLength = rootVariable.subElementSize[-1]
	elif isinstance(rootVariable.type, c_ast.PtrDecl):
		elementLength = globals.typeSizeDictionary[" ".join(rootVariable.type.type.type.names)]
		subElementSize = [elementLength]
	elif isinstance(rootVariable.type.type, c_ast.Struct):
		structDefObject = globals.structDictionary[rootVariable.type.type.name]
		#Get member name
		tempRef = copy.deepcopy(arrayRef)
		while isinstance(tempRef, c_ast.ArrayRef):
			tempRef = tempRef.name

		memberName = tempRef.field.name
		while (not isinstance(tempRef.name, c_ast.ID)):
			tempRef = tempRef.name
			memberName = tempRef.field.name + "." + memberName

		#Get element length
		subElementSize = structDefObject.members[memberName].subDimensions
		elementLength = structDefObject.members[memberName].subDimensions[-1]

		#Increase pointer reg by member offset
		memberOffset = structDefObject.members[memberName].offset
		instructions.append("{}addi {}, {}, {}".format(indentString, arrayPointerReg, arrayPointerReg, memberOffset))  #<TODO> handle offsets too large for immediate
	else:
		raise Exception("UNSUPPORTED ARRAY ROOT TYPE | {}\n{}".format(globals.cFileCoord, rootVariable))

	#Determine subscript depth (>1 for multi-dimensional arrays)
	subscriptDepth = 1
	tempArrayRef = copy.deepcopy(arrayRef)
	while isinstance(tempArrayRef.name, c_ast.ArrayRef):
		subscriptDepth += 1
		tempArrayRef = tempArrayRef.name

	#Increase root pointer by offset
	for i in range(subscriptDepth, 0, -1):
		#Get size of iTh dimenstion
		subSize = subElementSize[-1*i]

		#Get correct subscript item
		tempArrayRef = copy.deepcopy(arrayRef)
		for j in range(0, i-1, 1):
			tempArrayRef = tempArrayRef.name

		subscript = tempArrayRef.subscript

		#Get pointer offset from subscript into register
		instructionsTemp, offsetIndex = operandToRegister(subscript, scope, indentLevel=indentLevel)
		instructions += instructionsTemp
		instructionsTemp, elementLengthReg = scope.getFreeRegister(preferTemp=True, tag="elementLength_dim{}".format(subscriptDepth-i), indentLevel=indentLevel)
		instructions += instructionsTemp
		instructions.append("{}addi {}, zero, {}".format(indentString, elementLengthReg, subSize))  #<TODO> handle element lengths too long for immediate
		offsetReg = elementLengthReg
		instructions.append("{}mul {}, {}, {}".format(indentString, offsetReg, offsetIndex, elementLengthReg))

		#Combine offset and array pointer
		elementPointerReg = arrayPointerReg
		instructions.append("{}add {}, {}, {}".format(indentString, elementPointerReg, arrayPointerReg, offsetReg))

		#Release offset register
		instructions += scope.releaseRegister(offsetReg, indentLevel=indentLevel)

	return instructions, elementPointerReg, elementLength, pointerVariableName

def getStructMemberPointer(structRef, scope, indentLevel=0):
	'''
	Get a pointer to a struct member into a register

	Returns:
		<tuple> ( <instructionList> instructions , <str> pointerRegister , <int> size, <str> pointerVariableName)
	'''
	instructions = instructionList(scope)
	indentString = "".join(["\t" for i in range(indentLevel)])

	#Get name of root struct
	structName = structRef.name
	while (not isinstance(structName, str)):
		structName = structName.name

	#Get pointer to start of struct
	pointerVariableName = None
	structVariable = scope.getVariable(structName)
	if (isinstance(structVariable.type, c_ast.PtrDecl)):
		structVariable = structVariable.type
		instructions += scope.loadStack(structName, indentLevel=indentLevel)
		currentPointerRegister = scope.getRegisterLocation(structName)  #we don't want to modify this, so we will copy to another register
		instructionsTemp, structPointerReg = scope.getFreeRegister(preferTemp=True, tag="structMemberPointerReg", indentLevel=indentLevel)
		instructions += instructionsTemp
		instructions.append("{}mv {}, {}".format(indentString, structPointerReg, currentPointerRegister))
	else:
		instructionsTemp, structPointerReg, pointerVariableName = scope.getPointer(structName, indentLevel=indentLevel)
		instructions += instructionsTemp

	#Get member name
	tempRef = copy.deepcopy(structRef)
	memberName = tempRef.field.name
	while (not isinstance(tempRef.name, c_ast.ID)):
		tempRef = tempRef.name
		memberName = tempRef.field.name + "." + memberName

	#Get offset of member
	structDefObject = globals.structDictionary[structVariable.type.type.name]
	offset = structDefObject.members[memberName].offset

	#Combine offset and struct pointer
	memberPointerReg = structPointerReg
	if (offset > 0):
		instructions.append("{}addi {}, {}, {}".format(indentString, memberPointerReg, structPointerReg, offset))  #<TODO> handle offsets too large for immediate

	#Get member size
	size = structDefObject.members[memberName].size

	return instructions, memberPointerReg, size, pointerVariableName

def operandToRegister(operandItem, scope, targetReg=None, indentLevel=0):
	'''
	Moved the operandItem into an register.

	params:
		targetReg - if degined, operand will be stored in specified register
	returnType:
		( <instructionList> instructions, <str> registerName )
	'''
	instructions = instructionList(scope)
	indentString = "".join(["\t" for i in range(indentLevel)])
	globals.cFileCoord = operandItem.coord

	operandReg = ""
	if isinstance(operandItem, c_ast.ID):
		#operand is variable
		if (targetReg):
			operandReg = targetReg
			instructions += scope.moveVariable(operandItem.name, operandReg)
		else:
			operandReg = scope.getRegisterLocation(operandItem.name)
			if (operandReg == None):
				instructions += scope.loadStack(operandItem.name, indentLevel=indentLevel)
				operandReg = scope.getRegisterLocation(operandItem.name)
	elif isinstance(operandItem, c_ast.Constant):
		# operand is contant
		value = None
		if (operandItem.type == "int"):
			value = int(operandItem.value)
		elif (operandItem.type == "double"):
			value = float(operandItem.value)
		elif (operandItem.type == "float"):
			value = float(operandItem.value)

		#Get operand into temp register
		if (value == 0):
			if (targetReg):
				operandReg = targetReg
				instructions.append("{}mv {}, zero".format(indentString, operandReg))
			else:
				operandReg = "zero"
		elif (value < 2048) and (value >= -2048):
			#Value is small enough for addi
			if (targetReg):
				instructionsTemp, operandReg = scope.getFreeRegister(regOverride=targetReg, tag="CONSTANT", indentLevel=indentLevel)
				instructions += instructionsTemp
			else:
				instructionsTemp, operandReg = scope.getFreeRegister(preferTemp=True, tag="CONSTANT", indentLevel=indentLevel)
				instructions += instructionsTemp
			instructions.append("{}addi {}, zero, {}".format(indentString, operandReg, value))
		else:
			#Too large for immediate. Build using LUI+ADDI
			#<TODO> handle non-ints
			upperValue = None
			lowerValue = None
			if (value > 0):
				upperValue = int(value/4096)
				lowerValue = value%4096
			else:
				negativePortion = -1*(2**32)
				positivePortion = value - negativePortion
				lowerValue = positivePortion%4096
				upperValue = int(positivePortion/4096) + 2**20
			#Load into register
			instructionsTemp, operandReg = scope.getFreeRegister(preferTemp=True, tag="CONSTANT", indentLevel=indentLevel, regOverride=targetReg)
			instructions += instructionsTemp
			if (int(lowerValue/2048) == 1):
				# Sad :(  <TODO> don't do this awefulness when we have better instruction support
				#Get lower val into register
				instructions.append("{}lui {}, 1  #Loading val {}".format(indentString, operandReg, value))
				instructions.append("{}addi {}, {}, {}".format(indentString, operandReg, operandReg, lowerValue))
				if (upperValue > 0):
					#Get upper val into temp register
					instructionsTemp, tempRegister = scope.getFreeRegister(preferTemp=True, tag="largeValTemp", indentLevel=indentLevel)
					instructions += instructionsTemp
					instructions.append("{}lui {}, {}".format(indentString, tempRegister, upperValue))
					#Combine them
					instructions.append("{}add {}, {}, {}".format(indentString, operandReg, operandReg, tempRegister))

					instructions += scope.releaseRegister(tempRegister, indentLevel=indentLevel)
			else:
				instructions.append("{}lui {}, {} #Loading val {}".format(indentString, operandReg, upperValue, value))
				instructions.append("{}addi {}, {}, {}".format(indentString, operandReg, operandReg, lowerValue))

	elif isinstance(operandItem, c_ast.BinaryOp):
		#Operand is binary op
		instructionsTemp, resultVariableName = convertBinaryOpItem(operandItem, scope, targetReg=targetReg, indentLevel=indentLevel)
		instructions += instructionsTemp
		operandReg = scope.getRegisterLocation(resultVariableName)
	elif isinstance(operandItem, c_ast.UnaryOp):
		#Operand is unary op
		instructionsTemp, resultVariableName = convertUnaryOpItem(operandItem, scope, targetReg=targetReg, indentLevel=indentLevel)
		instructions += instructionsTemp
		operandReg = scope.getRegisterLocation(resultVariableName)
	elif isinstance(operandItem, c_ast.FuncCall):
		#Operand is function result
		instructions += convertFuncCallItem(operandItem, scope, indentLevel=indentLevel)
		operandReg = "a0"
		if (targetReg):
			instructionsTemp, operandReg = scope.getFreeRegister(regOverride=targetReg, tag="funcResult", indentLevel=indentLevel)
			instructions += instructionsTemp
			instructions.append("{}mv {}, a0".format(indentString, operandReg))
	elif isinstance(operandItem, c_ast.ArrayRef):
		#Get pointer to array element
		instructionsTemp, pointerRegister, elementSize, pointerVariableName = getArrayElementPointer(operandItem, scope, indentLevel=indentLevel)
		instructions += instructionsTemp
		#Load element into operandReg
		instructionsTemp, operandReg = scope.getFreeRegister(preferTemp=True, tag="arrayElement", regOverride=targetReg, indentLevel=indentLevel)
		instructions += instructionsTemp

		if (elementSize == 4):
			instructions.append("{}lw {}, 0({})".format(indentString, operandReg, pointerRegister)) #<TODO> handle unsigned values
		elif (elementSize == 2):
			instructions.append("{}lh {}, 0({})".format(indentString, operandReg, pointerRegister))
		elif (elementSize == 1):
			instructions.append("{}lb {}, 0({})".format(indentString, operandReg, pointerRegister))
		#Release pointer register
		instructions += scope.releaseRegister(pointerRegister, indentLevel=indentLevel)
	elif isinstance(operandItem, c_ast.StructRef):
		#Get pointer to struct member
		instructionsTemp, pointerRegister, memberSize, pointerVariableName = getStructMemberPointer(operandItem, scope, indentLevel=indentLevel)
		instructions += instructionsTemp

		#Load member into operandReg
		instructionsTemp, operandReg = scope.getFreeRegister(preferTemp=True, tag="structMember", regOverride=targetReg, indentLevel=indentLevel)
		instructions += instructionsTemp

		if (memberSize == 4):
			instructions.append("{}lw {}, 0({})".format(indentString, operandReg, pointerRegister)) #<TODO> handle unsigned values
		elif (memberSize == 2):
			instructions.append("{}lh {}, 0({})".format(indentString, operandReg, pointerRegister))
		elif (memberSize == 1):
			instructions.append("{}lb {}, 0({})".format(indentString, operandReg, pointerRegister))
		#Release pointer register
		instructions += scope.releaseRegister(pointerRegister, indentLevel=indentLevel)
	else:
		errorStr = "UNSUPPORTED OPERAND | {}\n{}".format(globals.cFileCoord, operandItem)
		raise Exception(errorStr)

	return instructions, operandReg

def convertUnaryOpItem(item, scope, branch=None, targetVariableName=None, targetReg=None, resultSize=4, resultType=None, indentLevel=0):
	'''
	Converts a c_ast.UnaryOp object into an assembly snippet.
	Returns an instructionList item, as well as the variable name of the operation result

	params:
		branch - if defined and unary op is logical, will branch to specified label if result=True
		targetVariableName - if defined, result will be stored in specified variable
		targetReg - if degined, result will be stored in specified register

	returnType:
		( <instructionList> instructions, <str> variableName )
	'''

	instructions = instructionList(scope)
	indentString = "".join(["\t" for i in range(indentLevel)])
	globals.cFileCoord = item.coord

	resultVariableName = None

	#Determine operation to perform
	operator = item.op

	if (operator == "p++"):
		#Get operand into register
		instructionsTemp, operandReg = operandToRegister(item.expr, scope, indentLevel=indentLevel)
		instructions += instructionsTemp

		#Increment  #<TODO> handle pointer incrementation based on size
		instructions.append("{}addi {}, {}, 1".format(indentString, operandReg, operandReg))
		if isinstance(item.expr, c_ast.ID):
			resultVariableName = item.expr.name

	elif (operator == "p--"):
		#Get operand into register
		instructionsTemp, operandReg = operandToRegister(item.expr, scope, indentLevel=indentLevel)
		instructions += instructionsTemp

		#Decrement  #<TODO> handle pointer decrementation based on size
		instructions.append("{}addi {}, {}, -1".format(indentString, operandReg, operandReg))
		if isinstance(item.expr, c_ast.ID):
			resultVariableName = item.expr.name

	elif (operator == "-"):
		#Get operand into register
		instructionsTemp, operandReg = operandToRegister(item.expr, scope, indentLevel=indentLevel)
		instructions += instructionsTemp

		#Get a register to store result
		regDest = ""
		if (targetReg):
			instructions += scope.releaseRegister(targetReg, indentLevel=indentLevel)
			regDest = targetReg
		else:
			instructionsTemp, regDest = scope.getFreeRegister(preferTemp=True, tag="negation", indentLevel=indentLevel)
			instructions += instructionsTemp

		#Get or create variable name for result of negation
		if (targetVariableName):
			instructions += scope.addVariable(targetVariableName, size=resultSize, register=regDest, varType=resultType, indentLevel=indentLevel)

			resultVariableName = targetVariableName
		else:
			instructionsTemp, resultVariableName = scope.createExpressionResult(register=regDest, indentLevel=indentLevel)
			instructions += instructionsTemp

		#Negate operand
		instructionsTemp, tempRegister = scope.getFreeRegister(preferTemp=True, tag="temp", indentLevel=indentLevel)
		instructions += instructionsTemp

		instructions.append("{}addi {}, zero, -1".format(indentString, tempRegister))
		instructions.append("{}mul {}, {}, {}".format(indentString, regDest, operandReg, tempRegister))

		instructions += scope.releaseRegister(tempRegister)

	elif (operator == "!"):
		#Get operand into register
		instructionsTemp, operandReg = operandToRegister(item.expr, scope, indentLevel=indentLevel)
		instructions += instructionsTemp

		#Logical invert
		if (branch):
			instructions.append("{}beq {}, zero, {}".format(indentString, operandReg, branch))
		else:
			#Get or create variable name for result
			if (targetVariableName):
				instructions += scope.addVariable(targetVariableName, size=resultSize, varType=resultType, indentLevel=indentLevel)

				resultVariableName = targetVariableName
			else:
				instructionsTemp, resultVariableName = scope.createExpressionResult(indentLevel=indentLevel)
				instructions += instructionsTemp

			#Get a register to store result
			regDest = ""
			if (targetReg):
				instructions += scope.releaseRegister(targetReg, indentLevel=indentLevel)
				regDest = targetReg
			else:
				instructionsTemp, regDest = scope.getFreeRegister(preferTemp=True, tag="logicalInvert", indentLevel=indentLevel)
				instructions += instructionsTemp

			#Logically invert operand
			instructions.append("{}sltu {}, {}, zero".format(indentString, regDest, operandReg))

	elif (operator == "&"):
		#Get memory location of variable
		if isinstance(item.expr, c_ast.StructRef):
			instructionsTemp, regDest, size, resultVariableName= getStructMemberPointer(item.expr, scope, indentLevel=indentLevel)
			instructions += instructionsTemp
		elif isinstance(item.expr, c_ast.ArrayRef):
			instructionsTemp, regDest, size, resultVariableName = getArrayElementPointer(item.expr, scope, indentLevel=indentLevel)
			instructions += instructionsTemp
		else:
			instructionsTemp, regDest, resultVariableName = scope.getPointer(item.expr.name, regDestOverride=targetReg, indentLevel=indentLevel)
			instructions += instructionsTemp
	elif (operator == "*"):
		if isinstance(item.expr, c_ast.ID):
			#Get memory location of variable
			instructions += scope.loadStack(item.expr.name, indentLevel=indentLevel)
			regPointer = scope.getRegisterLocation(item.expr.name)

			#Load value from memory
			#<TODO> handle values smaller than 4 bytes
			instructionsTemp, regDest = scope.getFreeRegister(regOverride=targetReg, tag="dereference", preferTemp=True, indentLevel=indentLevel)
			instructions += instructionsTemp

			varSize = scope.variableDict[item.expr.name].size
			if (varSize == 4):
				instructions.append("{}lw {}, 0({})".format(indentString, regDest, regPointer)) #<TODO> handle unsigned values
			elif (varSize == 2):
				instructions.append("{}lh {}, 0({})".format(indentString, regDest, regPointer))
			elif (varSize == 1):
				instructions.append("{}lb {}, 0({})".format(indentString, regDest, regPointer))

			#Get or create variable name for reference result
			if (targetVariableName):
				instructions += scope.addVariable(targetVariableName, register=regDest, size=resultSize, varType=resultType, indentLevel=indentLevel)

				resultVariableName = targetVariableName
			else:
				instructionsTemp, resultVariableName = scope.createExpressionResult(register=regDest, indentLevel=indentLevel)
				instructions += instructionsTemp
		else:
			raise Exception("ATTEMPT TO DEREFERENCE CONSTANT | convertUnaryOpItem\n{}".format(item.coord))

	else:
		print(item)
		raise Exception("UNSUPPORTED OPERATOR \"{}\"| convertUnaryOpItem".format(operator))

	#Update value in memory if variable and modified
	if isinstance(item.expr, c_ast.ID):
		#Operand is a variable. Did we change it?
		if ((operator == "p++") or (operator == "p--")):
			#Modified variable. Update in memory
			instructions += scope.storeStack(item.expr.name, indentLevel=indentLevel)

	return instructions, resultVariableName

def convertBinaryOpItem(item, scope, branch=None, targetVariableName=None, targetReg=None, resultSize=4, resultType=None, indentLevel=0):
	'''
	Converts a c_ast.BinaryOp object into an assembly snippet.
	Returns an instructionList item, as well as the variable name of the operation result

	params:
		branch - if defined and binary op is logical, will branch to specified label if result=True
		targetVariableName - if defined, result will be stored in specified variable
		targetReg - if degined, result will be stored in specified register

	returnType:
		( <instructionList> instructions, <str> variableName )
	'''
	instructions = instructionList(scope)
	indentString = "".join(["\t" for i in range(indentLevel)])
	globals.cFileCoord = item.coord

	#Get variable and register for result
	resultVariableName = ""
	if (not branch):
		if (targetVariableName):
			instructions += scope.addVariable(targetVariableName, size=resultSize, varType=resultType, indentLevel=indentLevel)

			resultVariableName = targetVariableName
		else:
			instructionsTemp, resultVariableName = scope.createExpressionResult(indentLevel=indentLevel)
			instructions += instructionsTemp

	#Get a register to store result
	regDest = ""
	if (not branch):
		if (targetReg):
			instructions += scope.releaseRegister(targetReg, indentLevel=indentLevel)
			regDest = targetReg
		else:
			instructionsTemp, regDest = scope.getFreeRegister(preferTemp=True, tag="binaryOp", indentLevel=indentLevel)
			instructions += instructionsTemp

	if (not branch):
		#Instantiate variable into destinamtion register
		instructions += scope.moveVariable(resultVariableName, regDest, indentLevel=indentLevel)

	#Get left operand into register  #<TODO> handle values small enought for immediates
	leftOperand = item.left
	instructionsTemp, leftOperandReg = operandToRegister(leftOperand, scope, indentLevel=indentLevel)
	instructions += instructionsTemp

	#Get right operand into register  #<TODO> handle values small enought for immediates
	rightOperand = item.right
	instructionsTemp, rightOperandReg = operandToRegister(rightOperand, scope, indentLevel=indentLevel)
	instructions += instructionsTemp

	#Handler operators
	#<TODO>, handle unsigned operands
	operator = item.op

	#Numeric results
	if (operator == "*"):
		instructions.append("{}mul {}, {}, {}".format(indentString, regDest, leftOperandReg, rightOperandReg))
	elif (operator == "/"):
		instructions.append("{}div {}, {}, {}".format(indentString, regDest, leftOperandReg, rightOperandReg))
	elif (operator == "%"):
		instructions.append("{}rem {}, {}, {}".format(indentString, regDest, leftOperandReg, rightOperandReg))
	elif (operator == "+"):
		instructions.append("{}add {}, {}, {}".format(indentString, regDest, leftOperandReg, rightOperandReg))
		#<TODO> use addi if one operand is a small constant
	elif (operator == "-"):
		instructions.append("{}sub {}, {}, {}".format(indentString, regDest, leftOperandReg, rightOperandReg))
		#<TODO> use addi if one operand is a small constant
	#Boolean results
	elif (operator == "=="):
		if (branch):
			instructions.append("{}beq {}, {}, {}".format(indentString, leftOperandReg, rightOperandReg, branch))
		else:
			instructionsTemp, tempRegister = scope.getFreeRegister(preferTemp=True, tag="temp", indentLevel=indentLevel)
			instructions += instructionsTemp
			instructions.append("{}slt {}, {}, {}".format(indentString, regDest, leftOperandReg, rightOperandReg))
			instructions.append("{}slt {}, {}, {}".format(indentString, tempRegister, rightOperandReg, leftOperandReg))
			instructions.append("{}add {}, {}, {}".format(indentString, regDest, regDest, tempRegister))
			instructions.append("{}slti {}, {}, 1".format(indentString, regDest, regDest))
			instructions += scope.releaseRegister(tempRegister)
	elif (operator == ">="):
		if (branch):
			instructions.append("{}bge {}, {}, {}".format(indentString, leftOperandReg, rightOperandReg, branch))
		else:
			instructions.append("{}slt {}, {}, {}".format(indentString, regDest, leftOperandReg, rightOperandReg))
			instructions.append("{}slti {}, {}, 1".format(indentString, regDest, regDest))
	elif (operator == "<="):
		if (branch):
			#Swap operands to save an instruction
			instructions.append("{}bge {}, {}, {}".format(indentString, rightOperandReg, leftOperandReg, branch))
		else:
			instructions.append("{}slt {}, {}, {}".format(indentString, regDest, rightOperandReg, leftOperandReg))
			instructions.append("{}slti {}, {}, 1".format(indentString, regDest, regDest))
	elif (operator == ">"):
		if (branch):
			#Swap operands to save an instruction
			instructions.append("{}blt {}, {}, {}".format(indentString, rightOperandReg, leftOperandReg, branch))
		else:
			instructions.append("{}slt {}, {}, {}".format(indentString, regDest, rightOperandReg, leftOperandReg))
	elif (operator == "<"):
		if (branch):
			instructions.append("{}blt {}, {}, {}".format(indentString, leftOperandReg, rightOperandReg, branch))
		else:
			instructions.append("{}slt {}, {}, {}".format(indentString, regDest, leftOperandReg, rightOperandReg))
	elif (operator == "!="):
		if (branch):
			instructions.append("{}bne {}, {}, {}".format(indentString, leftOperandReg, rightOperandReg, branch))
		else:
			instructionsTemp, tempRegister = scope.getFreeRegister(preferTemp=True, tag="temp", indentLevel=indentLevel)
			instructions += instructionsTemp
			instructions.append("{}slt {}, {}, {}".format(indentString, regDest, leftOperandReg, rightOperandReg))
			instructions.append("{}slt {}, {}, {}".format(indentString, tempRegister, rightOperandReg, leftOperandReg))
			instructions.append("{}add {}, {}, {}".format(indentString, regDest, regDest, tempRegister))
			instructions += scope.releaseRegister(tempRegister)
	elif (operator == "&&"):
		if (branch):
			instructionsTemp, tempRegister = scope.getFreeRegister(preferTemp=True, tag="temp", indentLevel=indentLevel)
			instructions += instructionsTemp
			instructions.append("{}mul {}, {}, {}".format(indentString, tempRegister, leftOperandReg, rightOperandReg))
			instructions.append("{}bne {}, zero, {}".format(indentString, tempRegister, branch))
			instructions += scope.releaseRegister(tempRegister)
		else:
			instructions.append("{}mul {}, {}, {}".format(indentString, regDest, leftOperandReg, rightOperandReg))
	else:
		raise Exception("UNSUPPORTED OPERATOR \"{}\"| convertBinaryOpItem".format(operator))

	#Free temp constant registers
	if isinstance(leftOperand, c_ast.Constant):
		instructions += scope.releaseRegister(leftOperandReg, indentLevel=indentLevel)
	if isinstance(rightOperand, c_ast.Constant):
		instructions += scope.releaseRegister(rightOperandReg, indentLevel=indentLevel)

	return instructions, resultVariableName

def convertIfItem(item, scope, indentLevel=0):
	'''
	Converts a c_ast.If object into an assembly snippet.
	Returns an instructionList item

	returnType:
		<instructionList> instructions
	'''

	instructions = instructionList(scope)
	indentString = "".join(["\t" for i in range(indentLevel)])
	globals.cFileCoord = item.coord

	#Handle condition branching
	onTrueLabel = "true_{}".format(globals.ifCounter)
	onFalseLabel = "false_{}".format(globals.ifCounter)
	ifExitLabel = "ifElseExit_{}".format(globals.ifCounter)
	globals.ifCounter += 1

	conditionItem = item.cond
	if isinstance(conditionItem, c_ast.BinaryOp):
		instructionsTemp, conditionVariableName = convertBinaryOpItem(conditionItem, scope, branch=onTrueLabel, indentLevel=indentLevel)
		instructions += instructionsTemp
	elif isinstance(conditionItem, c_ast.UnaryOp):
		instructionsTemp, conditionVariableName = convertUnaryOpItem(conditionItem, scope, branch=onTrueLabel, indentLevel=indentLevel)
		instructions += instructionsTemp
	elif isinstance(conditionItem, c_ast.ArrayRef):
		instructionsTemp, conditionValReg = operandToRegister(conditionItem, scope, indentLevel=indentLevel)
		instructions += instructionsTemp
		instructions.append("{}bne {}, zero, {}".format(indentString, conditionValReg, onTrueLabel))
	else:
		raise Exception("UNSUPPORTED CONDITION | convertIfItem\n{}".format(conditionItem))

	instructions.append("{}j {}".format(indentString, onFalseLabel))

	#Contruct true/false code blocks
	instructions.append("{}{}:".format(indentString, onTrueLabel))
	scopeBranch = copy.deepcopy(scope)
	instructions += convertAstItem(item.iftrue, scopeBranch, indentLevel=indentLevel+1)
	instructions += scope.mergeScopeBranch(scopeBranch, indentLevel=indentLevel+1)  #<TODO> Skip this part if the true section ends with a return
	instructions.append("{}j {}".format(indentString, ifExitLabel))

	instructions.append("{}{}:".format(indentString, onFalseLabel))
	if (item.iffalse):
		scopeBranch = copy.deepcopy(scope)
		instructions += convertAstItem(item.iffalse, scopeBranch, indentLevel=indentLevel+1)
		instructions += scope.mergeScopeBranch(scopeBranch, indentLevel=indentLevel+1)  #<TODO> Skip this part if the false section ends with a return
	instructions.append("{}{}:".format(indentString, ifExitLabel))

	return instructions

def convertFuncCallItem(item, scope, indentLevel=0):
	'''
	Converts a c_ast.FuncCall object into an assembly snippet.
	Returns an instructionList item

	returnType:
		<instructionList> instructions
	'''
	instructions = instructionList(scope)
	indentString = "".join(["\t" for i in range(indentLevel)])
	globals.cFileCoord = item.coord

	functionName = item.name.name

	#Check for inline assembly
	if (functionName == "asm") or ((functionName == "_asm_")):
		inlineAssembly = item.args.exprs[0].value
		inlineAssembly = inlineAssembly.replace("\"","")
		instructions.append("{}{}".format(indentString, inlineAssembly))

		return instructions

	#Sets up function parameters into argument registers
	argIndex = 0
	for argument in item.args.exprs:
		argumentRegister = "a{}".format(argIndex)

		if isinstance(argument, c_ast.ID):
			#Function argument is variable
			instructions += scope.moveVariable(argument.name, argumentRegister, indentLevel=indentLevel)

		elif isinstance(argument, c_ast.BinaryOp):
			#Function argument is binary op
			instructionsTemp, resultVariableName = convertBinaryOpItem(argument, scope, targetReg=argumentRegister, indentLevel=indentLevel)
			instructions += instructionsTemp
		elif isinstance(argument, c_ast.UnaryOp):
			#Function argument is binary op
			instructionsTemp, resultVariableName = convertUnaryOpItem(argument, scope, targetReg=argumentRegister, indentLevel=indentLevel)
			instructions += instructionsTemp
		elif isinstance(argument, c_ast.Constant):
			#Function argument is a contant
			instructionsTemp, operandReg = operandToRegister(argument, scope, targetReg=argumentRegister, indentLevel=indentLevel)
			instructions += instructionsTemp
		elif isinstance(argument, c_ast.FuncCall):
			#Function argument is another function call
			argumentSaves = {}
			if (argIndex != 0):
				#Save current values of prepared arugements
				for saveIndex in range(0,argIndex):
					instructionsTemp, tempSaveReg = scope.getFreeRegister(tempsAllowed=False, tag="argumentSave", indentLevel=indentLevel)
					instructions += instructionsTemp
					instructions.append("{}mv {}, a{}".format(indentString, tempSaveReg, saveIndex))

					argumentSaves[saveIndex] = tempSaveReg

			instructions += convertFuncCallItem(argument, scope, indentLevel=indentLevel)

			if (argIndex != 0):
				#Move return value into required argument register
				instructions.append("{}mv {}, a0".format(indentString, argumentRegister))

				#Restore previous argument values
				for saveIndex in range(0,argIndex):
					tempSaveReg = argumentSaves[saveIndex]
					instructions.append("{}mv a{}, {}".format(indentString, saveIndex, tempSaveReg))
					scope.releaseRegister(tempSaveReg)
		elif isinstance(argument, c_ast.ArrayRef):
			#Function argument is a contant
			instructionsTemp, operandReg = operandToRegister(argument, scope, targetReg=argumentRegister, indentLevel=indentLevel)
			instructions += instructionsTemp
		elif isinstance(argument, c_ast.StructRef):
			#Function argument is a contant
			instructionsTemp, operandReg = operandToRegister(argument, scope, targetReg=argumentRegister, indentLevel=indentLevel)
			instructions += instructionsTemp

		else:
			raise Exception("UNSUPPORTED ITEM | convertFuncCallItem\n{}".format(item))

		argIndex += 1

	#Save return address to stack
	instructions += scope.saveReturnAddress(indentLevel=indentLevel)

	#Save temps
	instructions += scope.saveTemps(indentLevel=indentLevel)

	#Align stack
	instructions += scope.alignStack(indentLevel=indentLevel)

	#Call function
	instructions.append("{}jal {}".format(indentString, functionName))

	return instructions

def convertAssignmentItem(item, scope, indentLevel=0):
	'''
	Converts a c_ast.Assignment object into an assembly snippet.
	Returns an instructionList item

	returnType:
		<instructionList> instructions
	'''
	instructions = instructionList(scope)
	indentString = "".join(["\t" for i in range(indentLevel)])
	globals.cFileCoord = item.coord

	#Get right value into register  #<TODO> handle values small enought for immediates
	#<TODO> remove uneeded expressionResult variables. Perhaps add volatile arg to createExpressionResult
	rightOperand = item.rvalue
	instructionsTemp, rightValReg = operandToRegister(rightOperand, scope, indentLevel=indentLevel)
	instructions += instructionsTemp

	#########
	# Get left value into register  #<TODO> handle constant values small enough for immediates
	#########
	leftOperand = item.lvalue
	leftValReg = None
	leftSize = None

	#Handle pointer dereference
	isPointerDereference = False
	pointerRegister = None

	if isinstance(leftOperand, c_ast.UnaryOp):
		if (leftOperand.op == "*"):
			isPointerDereference = True
			leftSize = 4;

			#Get pointer into register
			if isinstance(leftOperand.expr, c_ast.ID):
				#Pointer is a variable
				instructions += scope.loadStack(leftOperand.expr.name, indentLevel=indentLevel)
				pointerRegister = scope.getRegisterLocation(leftOperand.expr.name)
			elif isinstance(leftOperand.expr, c_ast.Constant):
				#Pointer is a constant
				instructionsTemp, pointerRegister = scope.getFreeRegister(preferTemp=True, tag="pointer", indentLevel=indentLevel)
				instructions += instructionsTemp

				pointerValue = int(leftOperand.expr.value)
				if (pointerValue < 2048):
					#Pointer small enough for immediate
					instructions.append("{}addi {}, zero, {}".format(indentString, pointerRegister, pointerValue))
				else:
					#Pointer value too large. Add to data segment
					dataType = "pointer"
					dataLabelName = "data_{}_{}".format(dataType, pointerValue)
					globals.dataSegment[dataLabelName] = dataElement(dataLabelName, value=pointerValue, size=4)
					instructions.append("{}lw {}, {}".format(indentString, pointerRegister, dataLabelName))
			else:
				raise Exception("UNSUPPORTED DEREFERENCE TYPE | {}".format(globals.cFileCoord))

	#Handle array element references
	isArrayReference = False

	if isinstance(leftOperand, c_ast.ArrayRef):
		isArrayReference = True

		#Get pointer to array element
		instructionsTemp, pointerRegister, leftSize, pointerVariableName = getArrayElementPointer(leftOperand, scope, indentLevel=indentLevel)
		instructions += instructionsTemp
		#Load element into leftValReg
		instructionsTemp, leftValReg = scope.getFreeRegister(preferTemp=True, tag="assignLeft", indentLevel=indentLevel)
		instructions += instructionsTemp

		if (leftSize == 4):
			instructions.append("{}lw {}, 0({})".format(indentString, leftValReg, pointerRegister)) #<TODO> handle unsigned values
		elif (leftSize == 2):
			instructions.append("{}lh {}, 0({})".format(indentString, leftValReg, pointerRegister))
		elif (leftSize == 1):
			instructions.append("{}lb {}, 0({})".format(indentString, leftValReg, pointerRegister))

	#Handle struct member references
	isStructReference = False
	if isinstance(leftOperand, c_ast.StructRef):
		isStructReference = True

		#Get pointer to struct member
		instructionsTemp, pointerRegister, leftSize, pointerVariableName = getStructMemberPointer(leftOperand, scope, indentLevel=indentLevel)
		instructions += instructionsTemp

		#Load member into leftValReg
		instructionsTemp, leftValReg = scope.getFreeRegister(preferTemp=True, tag="assignLeft", indentLevel=indentLevel)
		instructions += instructionsTemp

		if (leftSize == 4):
			instructions.append("{}lw {}, 0({})".format(indentString, leftValReg, pointerRegister)) #<TODO> handle unsigned values
		elif (leftSize == 2):
			instructions.append("{}lh {}, 0({})".format(indentString, leftValReg, pointerRegister))
		elif (leftSize == 1):
			instructions.append("{}lb {}, 0({})".format(indentString, leftValReg, pointerRegister))

	#Get left value into register if not already handled
	if ((not isArrayReference) and (not isStructReference)):
		instructionsTemp, leftValReg = operandToRegister(leftOperand, scope, indentLevel=indentLevel)
		instructions += instructionsTemp

	#########
	# Handler operators
	#########
	#<TODO>, handle unsigned operands
	operator = item.op

	if (operator == "="):
		instructions.append("{}mv {}, {}".format(indentString, leftValReg, rightValReg))
	elif (operator == "+="):
		instructions.append("{}add {}, {}, {}".format(indentString, leftValReg, leftValReg, rightValReg))
	elif (operator == "-="):
		instructions.append("{}sub {}, {}, {}".format(indentString, leftValReg, leftValReg, rightValReg))
	else:
		raise Exception("UNSUPPORTED OPERATOR \"{}\" | convertAssignmentItem".format(operator))

	#Update values in memory
	if (isPointerDereference or isArrayReference or isStructReference):
		#Update memory with value
		if (leftSize == 4):
			instructions.append("{}sw {}, 0({})".format(indentString, leftValReg, pointerRegister))
		elif (leftSize == 2):
			instructions.append("{}sh {}, 0({})".format(indentString, leftValReg, pointerRegister))
		elif (leftSize == 1):
			instructions.append("{}sb {}, 0({})".format(indentString, leftValReg, pointerRegister))
		else:
			raise Exception("leftSize is not defined | convertAssignmentItem")
		instructions += scope.releaseRegister(leftValReg, indentLevel=indentLevel)
	elif isinstance(leftOperand, c_ast.ID):
		#Left operand is a variable. Update in memory
		instructions += scope.storeStack(leftOperand.name, indentLevel=indentLevel)

	#Release pointer register
	if (pointerRegister):
		instructions += scope.releaseRegister(pointerRegister, indentLevel=indentLevel)

	return instructions

def convertReturnItem(item, scope, indentLevel=0):
	'''
	Converts a c_ast.Return object into an assembly snippet.
	Returns an instructionList item

	returnType:
		<instructionList> instructions
	'''
	#<TODO> Prevent uneeded saving of variables onto stack when we are returning
	instructions = instructionList(scope)
	indentString = "".join(["\t" for i in range(indentLevel)])
	globals.cFileCoord = item.coord

	if isinstance(item.expr, c_ast.ID):
		#Return variable
		variableName = item.expr.name
		currentRegister = scope.getRegisterLocation(variableName)

		if (not currentRegister):
			#variable on stack. Load into a0
			instructions += scope.loadStack(variableName, regDestOverride="a0", indentLevel=indentLevel)
		else:
			#variable in register. Move into a0
			instructions += scope.moveVariable(variableName, "a0", indentLevel=indentLevel)

		#Restore save variables, deallocate scope, then return
		instructions += scope.restoreSaves(indentLevel=indentLevel)
		instructions += scope.deallocateScope(indentLevel=indentLevel)
		instructions.append("{}jr ra".format(indentString))

	elif isinstance(item.expr, c_ast.FuncCall):
		#Call function, then return result
		instructions += convertFuncCallItem(item.expr, scope, indentLevel=indentLevel)

		#Restore save variables, deallocate scope, then return
		instructions += scope.restoreSaves(indentLevel=indentLevel)
		instructions += scope.deallocateScope(indentLevel=indentLevel)
		instructions.append("{}jr ra".format(indentString))

	elif isinstance(item.expr, c_ast.Constant):
		#Return constant value
		value = int(item.expr.value) #<TODO> Handle floats and doubles
		if (value < 2048) and (value >= -2048):
			#Value is small enough for addi
			scope.removeVariable(scope.getVariableName("a0"))
			instructions.append("{}addi {}, zero, {}".format(indentString, "a0", value))
		else:
			#Too large for immediate. Build using LUI+ADDI
			#<TODO> handle non-ints
			upperValue = None
			lowerValue = None
			if (value > 0):
				upperValue = int(value/4096)
				lowerValue = value%4096
			else:
				negativePortion = -1*(2**32)
				positivePortion = value - negativePortion
				lowerValue = positivePortion%4096
				upperValue = int(positivePortion/4096) + 2**20

			#Load into register
			if (int(lowerValue/2048) == 1):
				# Sad :(
				#Get lower val into register
				instructions.append("{}lui a0, 1 #Loading val {}".format(indentString, value))
				instructions.append("{}addi a0, a0, {}".format(indentString, lowerValue))
				if (upperValue > 0):
					#Get upper val into temp register a2
					instructions.append("{}lui a2, {}".format(indentString, upperValue))
					#Combine them
					instructions.append("{}add a0, a0, a2".format(indentString))
			else:
				instructions.append("{}lui a0, {} #Loading val {}".format(indentString, upperValue, value))
				instructions.append("{}addi a0, a0, {}".format(indentString, lowerValue))

		#Restore save variables, deallocate scope, then return
		instructions += scope.restoreSaves(indentLevel=indentLevel)
		instructions += scope.deallocateScope(indentLevel=indentLevel)
		instructions.append("{}jr ra".format(indentString))

	elif isinstance(item.expr, c_ast.BinaryOp):
		instructionsTemp, resultVariableName = convertBinaryOpItem(item.expr, scope, targetReg="a0", indentLevel=indentLevel)
		instructions += instructionsTemp

		#Restore save variables, deallocate scope, then return
		instructions += scope.restoreSaves(indentLevel=indentLevel)
		instructions += scope.deallocateScope(indentLevel=indentLevel)
		instructions.append("{}jr ra".format(indentString))
	elif isinstance(item.expr, c_ast.UnaryOp):
		instructionsTemp, resultVariableName = convertUnaryOpItem(item.expr, scope, targetReg="a0", indentLevel=indentLevel)
		instructions += instructionsTemp

		#Restore save variables, deallocate scope, then return
		instructions += scope.restoreSaves(indentLevel=indentLevel)
		instructions += scope.deallocateScope(indentLevel=indentLevel)
		instructions.append("{}jr ra".format(indentString))
	else:
		raise Exception("UNSUPPORTED ITEM | convertReturnItem\n{}".format(item))

	return instructions

def convertDeclItem(item, scope, indentLevel=0):
	'''
	Converts a c_ast.Decl(Declaration) object into an assembly snippet.
	Returns an instructionList item, along with the variableName of the declared variable

	returnType:
		( <instructionList> instructions, <str> variableName )
	'''
	instructions = instructionList(scope)
	indentString = "".join(["\t" for i in range(indentLevel)])
	globals.cFileCoord = item.coord

	variableName = item.name

	if (item.init):
		#Declared with initial value
		if isinstance(item.init, c_ast.Constant):
			#Initialized variable to a constant value
			instructionsTemp, destinationRegister = scope.getFreeRegister(tag="declare", indentLevel=indentLevel)
			instructions += instructionsTemp
			instructions += scope.addVariable(variableName, register=destinationRegister, varType=item.type, signed=True, indentLevel=indentLevel)

			value = int(item.init.value) #<TODO> Handle floats and doubles
			if (value < 2048) and (value >= -2048):
				#Value is small enough for addi
				instructions.append("{}addi {}, zero, {}".format(indentString, destinationRegister, value))
			else:
				#Too large for immediate. Build using LUI+ADDI
				#<TODO> handle non-ints
				upperValue = None
				lowerValue = None
				if (value > 0):
					upperValue = int(value/4096)
					lowerValue = value%4096
				else:
					negativePortion = -1*(2**32)
					positivePortion = value - negativePortion
					lowerValue = positivePortion%4096
					upperValue = int(positivePortion/4096) + 2**20

				#Load into register
				if (int(lowerValue/2048) == 1):
					# Sad :(  <TODO> don't do this awefulness when we have better instruction support
					#Get lower val into register
					instructions.append("{}lui {}, 1  #Loading val {}".format(indentString, destinationRegister, value))
					instructions.append("{}addi {}, {}, {}".format(indentString, destinationRegister, destinationRegister, lowerValue))
					if (upperValue > 0):
						#Get upper val into temp register
						instructionsTemp, tempRegister = scope.getFreeRegister(preferTemp=True, tag="largeValTemp", indentLevel=indentLevel)
						instructions += instructionsTemp
						instructions.append("{}lui {}, {}".format(indentString, tempRegister, upperValue))
						#Combine them
						instructions.append("{}add {}, {}, {}".format(indentString, destinationRegister, destinationRegister, tempRegister))

						instructions += scope.releaseRegister(tempRegister, indentLevel=indentLevel)
				else:
					instructions.append("{}lui {}, {} #Loading val {}".format(indentString, destinationRegister, upperValue, value))
					instructions.append("{}addi {}, {}, {}".format(indentString, destinationRegister, destinationRegister, lowerValue))

		elif isinstance(item.init, c_ast.ID):
			#Initialized variable is set to another variable
			instructionsTemp, destinationRegister = scope.getFreeRegister(tag="declare", indentLevel=indentLevel)
			instructions += instructionsTemp
			instructions += scope.addVariable(variableName, register=destinationRegister, varType=item.type, signed=True, indentLevel=indentLevel)

			instructionsTemp, valueReg = operandToRegister(item.init, scope, indentLevel=indentLevel)
			instructions += instructionsTemp

			instructions.append("{}mv {}, {}".format(indentString, destinationRegister, valueReg))

		elif isinstance(item.init, c_ast.BinaryOp):
			#Initialized variable is result of binary expression
			instructionsTemp, resultVariableName = convertBinaryOpItem(item.init, scope, targetVariableName=variableName, indentLevel=indentLevel)
			instructions += instructionsTemp

		elif isinstance(item.init, c_ast.UnaryOp):
			#Initialized variable is result of binary expression
			instructionsTemp, resultVariableName = convertUnaryOpItem(item.init, scope, targetVariableName=variableName, indentLevel=indentLevel)
			instructions += instructionsTemp

		elif isinstance(item.init, c_ast.FuncCall):
			#Initialized variable is result of function call
			instructions += convertFuncCallItem(item.init, scope, indentLevel=indentLevel)
			instructions += scope.addVariable(variableName, register="a0", varType=item.type, indentLevel=indentLevel)
		elif isinstance(item.init, c_ast.InitList):
			if isinstance(item.type, c_ast.ArrayDecl):
				#Add array to scope
				instructions += scope.addVariable(variableName, varType=item.type, signed=True, indentLevel=indentLevel)

				#Get pointer to start of array
				instructionsTemp, pointerReg, pointerVariableName = scope.getPointer(variableName, indentLevel=indentLevel)
				instructions += instructionsTemp

				for arrayIndex in range(0, len(item.init.exprs)):
					expression = item.init.exprs[arrayIndex]
					if isinstance(expression, c_ast.Constant):
						#Get constant into a register
						instructionsTemp, tempReg = scope.getFreeRegister(preferTemp=True, tag="temp", indentLevel=indentLevel)
						instructions += instructionsTemp

						instructionsTemp, valueReg = operandToRegister(expression, scope, targetReg=tempReg, indentLevel=indentLevel)
						instructions += instructionsTemp

						#Determine pointer offset
						elementSize = globals.typeSizeDictionary[expression.type]
						pointerOffset = elementSize * arrayIndex

						#Store value onto stack
						if (elementSize == 4):
							instructions.append("{}sw {}, {}({})".format(indentString, valueReg, pointerOffset, pointerReg))
						elif (elementSize == 2):
							instructions.append("{}sh {}, {}({})".format(indentString, valueReg, pointerOffset, pointerReg))
						elif (elementSize == 1):
							instructions.append("{}sb {}, {}({})".format(indentString, valueReg, pointerOffset, pointerReg))
						else:
							raise Exception("UNSUPPORTED ARRAY ELEMENT SIZE | {}".format(globals.cFileCoord))

						#Release temp registers
						instructions += scope.releaseRegister(tempReg)
						instructions += scope.releaseRegister(valueReg)
					else:
						raise Exception("UNSUPPORTED INITIAL VALUE FOR ARRAY | {}".format(globals.cFileCoord))

				instructions += scope.releaseRegister(pointerReg)
			else:
				raise Exception("Cannot initialize non-array to a list of values | {}".format(globals.cFileCoord))

		elif isinstance(item.init, c_ast.ArrayRef):
			#Get destination register
			instructionsTemp, destinationRegister = scope.getFreeRegister(tag="declare", indentLevel=indentLevel)
			instructions += instructionsTemp
			instructions += scope.addVariable(variableName, register=destinationRegister, varType=item.type, signed=True, indentLevel=indentLevel)

			#Get pointer to array element
			instructionsTemp, pointerRegister, size, pointerVariableName = getArrayElementPointer(item.init, scope, indentLevel=indentLevel)
			instructions += instructionsTemp

			#Load element into destination register
			if (size == 4):
				instructions.append("{}lw {}, 0({})".format(indentString, destinationRegister, pointerRegister)) #<TODO> handle unsigned values
			elif (size == 2):
				instructions.append("{}lh {}, 0({})".format(indentString, destinationRegister, pointerRegister))
			elif (size == 1):
				instructions.append("{}lb {}, 0({})".format(indentString, destinationRegister, pointerRegister))

		elif isinstance(item.init, c_ast.StructRef):
			#Get destination register
			instructionsTemp, destinationRegister = scope.getFreeRegister(tag="declare", indentLevel=indentLevel)
			instructions += instructionsTemp
			instructions += scope.addVariable(variableName, register=destinationRegister, varType=item.type, signed=True, indentLevel=indentLevel)

			#Get pointer to struct element
			instructionsTemp, pointerRegister, size, pointerVariableName = getStructMemberPointer(item.init, scope, indentLevel=indentLevel)
			instructions += instructionsTemp

			#Load element into destination register
			if (size == 4):
				instructions.append("{}lw {}, 0({})".format(indentString, destinationRegister, pointerRegister)) #<TODO> handle unsigned values
			elif (size == 2):
				instructions.append("{}lh {}, 0({})".format(indentString, destinationRegister, pointerRegister))
			elif (size == 1):
				instructions.append("{}lb {}, 0({})".format(indentString, destinationRegister, pointerRegister))

		else:
			raise Exception("UNSUPPORTED DECLARATION | convertDeclItem\n{}".format(item))
	else:
		#Declared without initial value
		instructions += scope.addVariable(variableName, varType=item.type, indentLevel=indentLevel)

	#Allocate space on stack for variable
	instructions += scope.storeStack(variableName, indentLevel=indentLevel)

	return instructions, variableName

def convertForItem(item, scope, indentLevel=0):
	'''
	Converts a c_ast.For(For Loop) object into an assembly snippet.
	Returns an instructionList item

	returnType:
		<instructionList> instructions
	'''
	instructions = instructionList(scope)
	indentString = "".join(["\t" for i in range(indentLevel)])
	globals.cFileCoord = item.coord

	#Initialize for loop counting variable
	if isinstance(item.init, c_ast.DeclList):
		for declareItem in item.init.decls:
			instructionsTemp, variableName = convertDeclItem(declareItem, scope, indentLevel=indentLevel)
			instructions += instructionsTemp
			instructions += scope.storeStack(variableName, indentLevel=indentLevel)
	else:
		raise Exception("UNSUPPORTED ITEM | convertForItem\n{}".format(item.init))

	#For loop start/end labels
	startLabel = "forLoopStart_{}".format(globals.forCounter)
	endLabel = "forLoopEnd_{}".format(globals.forCounter)
	bodyLabel = "forLoopBody_{}".format(globals.forCounter)
	globals.forCounter += 1

	instructions.append("{}{}:".format(indentString, startLabel))

	#For loop condition check
	conditionItem = item.cond
	conditionVariableName = None
	if isinstance(conditionItem, c_ast.BinaryOp):
		instructionsTemp, conditionVariableName = convertBinaryOpItem(conditionItem, scope, branch=bodyLabel, indentLevel=indentLevel+1)
		instructions += instructionsTemp
		instructions.append("{}\tj {}".format(indentString, endLabel))

	else:
		raise Exception("UNSUPPORTED CONDITION | convertForItem\n{}".format(conditionItem))

	#For loop body
	scopeBranch = copy.deepcopy(scope)
	loopBodyItem = item.stmt
	instructions.append("{}\t{}:".format(indentString, bodyLabel))
	instructions += convertAstItem(loopBodyItem, scopeBranch, indentLevel=indentLevel+2)
	instructions += scope.mergeScopeBranch(scopeBranch, indentLevel=indentLevel+1)

	#For loop increment
	nextItem = item.next
	if isinstance(nextItem, c_ast.UnaryOp):
		instructionsTemp, resultVariableName = convertUnaryOpItem(nextItem, scope, indentLevel=indentLevel+1)
		instructions += instructionsTemp
	elif isinstance(nextItem, c_ast.Assignment):
		raise Exception("UNSUPPORTED NEXT ITEM | convertForItem\n{}".format(nextItem))
	else:
		raise Exception("UNSUPPORTED NEXT ITEM | convertForItem".format(nextItem))

	#For loop exit
	instructions.append("{}\tj {}".format(indentString, startLabel))
	instructions.append("{}{}:".format(indentString, endLabel))

	return instructions

def convertWhileItem(item, scope, indentLevel=0):
	'''
	Converts a c_ast.For(For Loop) object into an assembly snippet.
	Returns an instructionList item

	returnType:
		<instructionList> instructions
	'''
	instructions = instructionList(scope)
	indentString = "".join(["\t" for i in range(indentLevel)])
	globals.cFileCoord = item.coord

	#While loop start/end labels
	startLabel = "whileLoopStart_{}".format(globals.whileCounter)
	endLabel = "whileLoopEnd_{}".format(globals.whileCounter)
	bodyLabel = "whileLoopBody_{}".format(globals.whileCounter)
	globals.whileCounter += 1

	instructions.append("{}{}:".format(indentString, startLabel))

	#While loop condition check
	conditionItem = item.cond
	conditionVariableName = None
	if isinstance(conditionItem, c_ast.BinaryOp):
		instructionsTemp, conditionVariableName = convertBinaryOpItem(conditionItem, scope, branch=bodyLabel, indentLevel=indentLevel+1)
		instructions += instructionsTemp
		instructions.append("{}\tj {}".format(indentString, endLabel))
	if isinstance(conditionItem, c_ast.Constant):
		if (int(conditionItem.value) == 0):
			#Condition is 0. Loop will never run. Return empty instructions
			instructions = instructionList(scope)
			return instructions
		else:
			#Infinite loop
			instructions.append("{}\tj {}".format(indentString, bodyLabel))
	elif isinstance(conditionItem, c_ast.ID):
		#Get condition value into register
		instructionsTemp, conditionReg = operandToRegister(conditionItem, scope, indentLevel=indentLevel)
		instructions += instructionsTemp

		#Continue if value != 0
		instructions.append("{}bne {}, zero, {}".format(indentString, conditionReg, bodyLabel))
		instructions.append("{}\tj {}".format(indentString, endLabel))

	else:
		raise Exception("UNSUPPORTED CONDITION | convertWhileItem\n{}".format(conditionItem))

	#While loop body
	scopeBranch = copy.deepcopy(scope)
	loopBodyItem = item.stmt
	instructions.append("{}\t{}:".format(indentString, bodyLabel))
	instructions += convertAstItem(loopBodyItem, scopeBranch, indentLevel=indentLevel+2)
	instructions += scope.mergeScopeBranch(scopeBranch, indentLevel=indentLevel+1)

	#While loop exit
	instructions.append("{}\tj {}".format(indentString, startLabel))
	instructions.append("{}{}:".format(indentString, endLabel))

	return instructions

def convertAstItem(item, scope, indentLevel=0, freeTempRegisters=False):
	'''
	Converts a c_ast object into an assembly snippet.
	Returns an instructionList item

	returnType:
		<instructionList> instructions
	'''
	instructions = instructionList(scope)
	indentString = "".join(["\t" for i in range(indentLevel)])

	if isinstance(item, c_ast.Compound):
		for subItem in item.block_items:
			instructions += convertAstItem(subItem, scope, indentLevel=indentLevel, freeTempRegisters=freeTempRegisters)
	elif isinstance(item, c_ast.If):
		instructions += convertIfItem(item, scope, indentLevel=indentLevel)
	elif isinstance(item, c_ast.Return):
		instructions += convertReturnItem(item, scope, indentLevel=indentLevel)
	elif isinstance(item, c_ast.For):
		instructions += convertForItem(item, scope, indentLevel=indentLevel)
	elif isinstance(item, c_ast.Decl):
		instructionsTemp, variableName = convertDeclItem(item, scope, indentLevel=indentLevel)
		instructions += instructionsTemp
	elif isinstance(item, c_ast.Assignment):
		instructions += convertAssignmentItem(item, scope, indentLevel=indentLevel)
	elif isinstance(item, c_ast.While):
		instructions += convertWhileItem(item, scope, indentLevel=indentLevel)
	elif isinstance(item, c_ast.UnaryOp):
		instructionsTemp, variableName  = convertUnaryOpItem(item, scope, indentLevel=indentLevel)
		instructions += instructionsTemp
	elif isinstance(item, c_ast.BinaryOp):
		instructionsTemp, variableName  = convertBinaryOpItem(item, scope, indentLevel=indentLevel)
		instructions += instructionsTemp
	elif isinstance(item, c_ast.FuncCall):
		instructions += convertFuncCallItem(item, scope, indentLevel=indentLevel)
	else:
		raise Exception("UNSUPPORTED ITEM | convertAstItem\n{}".format(item))

	if (freeTempRegisters):
		scope.regexReleaseVariable("<TAG_", indentLevel=indentLevel)

	return instructions

def covertFuncToAssembly(funcDef):
	'''
	Converts the c_ast.funcDef object into an assembly snippet.
	Writes list of instructions to object variable funcDef.coord
	'''
	scope = scopeController(funcDef.decl.name)
	instructions = instructionList(scope)

	#Function label
	instructions.append("{}:".format(funcDef.decl.name))

	#Add input variables too function scope
	if (funcDef.decl.type.args):
		inputParameters = funcDef.decl.type.args.params

		argIndex = 0
		for arg in inputParameters:
			registerName = "a{}".format(argIndex)
			instructions += scope.addVariable(arg.name, register=registerName, varType=arg.type, signed=True, indentLevel=1)

			argIndex += 1

	#Write function body
	instructions += convertAstItem(funcDef.body, scope, indentLevel=1, freeTempRegisters=True)

	#Restore save variables
	instructions += scope.restoreSaves(indentLevel=1)

	#Deallocate local stack
	instructions += scope.deallocateScope(indentLevel=1)

	#Return to caller
	instructions.append("\tjr ra")

	funcDef.coord = instructions  #Borrow coord variable, since it seems to be unused by pycparser


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import json
import re
from os.path import join as pjoin
from io import StringIO
import numpy as np
import pandas as pd
import os
import sys
from glob import glob
from natsort import natsorted

LABDATA_FILE= pjoin(os.path.expanduser('~'),'labdatatools','preferences.json')

default_labdata_preferences = {'paths':[pjoin(os.path.expanduser('~'),'data')],
                               'path_format':'{subject}/{session}/{datatype}',
                               'remote_queue': None,
                               #'remote_queue': dict(remote='hoffman2.idre.ucla.edu',
                               #                     user='username'),
                               'rclone' : dict(drive = 'churchland_data',
                                               folder = 'data'),
                               'archives': None,
                               #'archives': [dict(drive = 'globus_shared',
                               #                         folder = 'data')],
                               'plugins_folder':pjoin(os.path.expanduser('~'),
                                                      'labdatatools','analysis')}

default_excludes = ['**.phy**',                # skip .phy folders
                    '**temp_wh.dat',           # skip kilosort temp_wh files
                    '**suite2p**data.bin',     # skip suite2p corrected files
                    '**.ipynb_checkpoints**',
                    '**._.DS_Store**',
                    '**.DS_Store**',
                    '**dummy**',
                    '**filtered_recording.ap.bin', # filtered recording from spks sorting
                    '**FakeSubject**']

def list_subjects():
    '''
    List subjects in the data path. Takes no arguments.
    Returns a pandas dataframe with the subject name and paths

Example:
    subjects = list_subjects()

    '''
    subjects = []
    for path in labdata_preferences['paths']:
        tmp = glob(pjoin(path,'*'))
        for t in tmp:
            if not os.path.isdir(t):
                continue
            if not os.path.basename(t) in [s['subject'] for s in subjects]:
                subjects.append(dict(subject=os.path.basename(t),paths = []))
            subjects[[s['subject'] for s in subjects].index(os.path.basename(t))]['paths'].append(t)

    return pd.DataFrame(subjects)

def list_files(subject = '', extension=''):
    '''
    Lists the files for a subject.
    Arguments:
        subject (string)
        extension (string)

    Example:
        files = list_files(extension = 'txt') # lists all text files
        files = list_files(subject = 'JC044', extension = 'txt') # lists all text files for JC044

    '''

    paths = []
    for server in labdata_preferences['paths']:
        if len(subject):
            if type(subject) is list:
                for s in subject:
                    paths.append(pjoin(server,'{subject}'.format(subject = s)))
            else:
                paths.append(pjoin(server,'{subject}'.format(subject = subject)))
        else:
            paths.append(server)

    files = []
    # recursive search
    for path in paths:
        tmp = [y for x in os.walk(path)
                      for y in glob(pjoin(x[0], '*' + extension))]
        tmp = list(filter(os.path.isfile,tmp))
        for f in natsorted(np.array(tmp)):
            folder = f.replace(pjoin(path,''),'').split(os.path.sep)[0]

        for t in tmp:
            stats = os.stat(t)
            files.append(dict(filename = os.path.basename(t),
                              filepath = t,
                              relativepath = t.replace(path,''),
                              filesize = stats.st_size,
                              serverpath = '/'.join(
                                  os.path.normpath(t.replace(
                                      path.replace(subject,''),'')).split(
                                          os.path.sep)),
                              mtime = stats.st_mtime))
    return pd.DataFrame(files)

def get_filepath(subject,
                 session,
                 subfolders,
                 datapath = None,
                 filename = '*',
                 extension = '',
                 fetch = False,
                 **kwargs):
    '''
    Get a local filepath; retrieve from the remote if not present.
    Arguments:
       subject (string; required)
       session (string; required)
       subfolders (list of strings - use multiple levels if needed; required)
       datapath (the path of data; default is the first datapath in prefs)
       filename (the key in the filename; default '*')
       extension (default '')
       fetch (get data from the server if not present; default False)

    Example:
       session_files = get_filepath(subject = 'JC086',
                                    session = '20230113_131309',
                                    subfolders = ['DropletsTask'])

    '''
    if datapath is None:
        datapath = labdata_preferences['paths'][0]
    files = glob(pjoin(datapath,
                       subject,
                       session,
                       pjoin(*subfolders),
                       filename+extension))
    if len(files) == 1:
        files = files[0]
    if not len(files):
        files = None
    # If the file is not there return None but if fetch is TRUE go to the rclone to get it
    if fetch and files is None:
        print('Could not find file, trying to get it from the cloud')
        from .rclone import rclone_get_data
        rclone_get_data(subject=subject,
                        session = session,
                        datatype = subfolders[0],
                        includes = [filename+extension])
        # do the rclone stuff only once (if recursive it would just keep doing it)
        files = glob(pjoin(datapath,
                           subject,
                           session,
                           pjoin(*subfolders),
                           filename+extension))
        if len(files) == 1:
            files = files[0]
        if not len(files):
            files = None # return None if the file is not on the rclone nor in the local folder
    return files

def get_labdata_preferences(prefpath = None):
    ''' Reads the user parameters from the home directory.

    pref = get_labdata_preferences(filename)

    User parameters like folder location, file preferences, paths...

    '''
    if prefpath is None:
        prefpath = LABDATA_FILE
    preffolder = os.path.dirname(prefpath)
    if not os.path.exists(preffolder):
        os.makedirs(preffolder)
    if not os.path.isfile(prefpath):
        with open(prefpath, 'w') as outfile:
            json.dump(default_labdata_preferences,
                      outfile,
                      sort_keys = True,
                      indent = 4)
            print('Saving default preferences to: ' + prefpath)
    with open(prefpath, 'r') as infile:
        pref = json.load(infile)
    for k in default_labdata_preferences:
        if not k in pref.keys():
            pref[k] = default_labdata_preferences[k]
    return pref

labdata_preferences = get_labdata_preferences()


from . import profile
from flask import render_template, redirect, session, url_for
from flask_login import LoginManager, login_user, logout_user, login_required, current_user
from .. import db
from .forms import ProfileFormI,CompanyFormI
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField, PasswordField, DecimalField
from flask import render_template, redirect, session, url_for
from wtforms.validators import DataRequired
from ..helper import formatString

#To update and create the profile of student
@profile.route('/edit/', methods = ['GET', 'POST'])
@login_required
def edit_profile():
	sql1 = f"SELECT stu_uid from profile where stu_uid = '{session['user']['userid']}'"
	profile = db.engine.execute(sql1).first()
	sql4 = f"SELECT * FROM profile where stu_uid = '{session['user']['userid']}'"
	prof = db.engine.execute(sql4).first()
	form = ProfileFormI()
	if form.validate_on_submit():
		fname = form.fname.data
		lname = form.lname.data
		cgpa = form.cgpa.data
		branch = form.branch.data
		contact = form.contact_no.data
		email = form.email.data
		resume_link = form.resume_link.data
		#to check the profile is already created

		#if exists just update the table
		if profile:
			sql2 = f"UPDATE profile SET fname='{fname}', lname='{lname}', cgpa={cgpa}, branch='{branch}', contact_no='{contact}', email_id='{email}', resume_link='{resume_link}' WHERE stu_uid = '{profile['stu_uid']}'"
			db.engine.execute(sql2)
			db.session.commit()
		#if not exists insert to profile
		else:
			sql3 = f"INSERT INTO profile(stu_uid,fname,lname,cgpa,branch,contact_no,email_id,resume_link,placement_status) VALUES('{session['user']['userid']}', '{fname}', '{lname}', {cgpa}, '{branch}', '{contact}', '{email}', '{resume_link}', 'Not Placed')"
			db.engine.execute(sql3)
			db.session.commit()
		return redirect(url_for('main.home'))

	if not profile:
		form = ProfileFormI()
		return render_template('edit_profile.html',form=form)
	else:
		class ProfileFormE(FlaskForm):
			fname = StringField('First Name', validators = [DataRequired()], render_kw = {'autocomplete':'off', 'class':'input-form', 'value':prof['fname']})
			lname = StringField('Last Name', validators = [DataRequired()], render_kw = {'autocomplete':'off','class':'input-form', 'value':prof['lname']})
			cgpa = DecimalField('CGPA', validators = [DataRequired()],rounding=2, render_kw = {'autocomplete':'off','class':'input-form', 'value':str(prof['cgpa'])})
			branch = StringField('Branch', validators = [DataRequired()], render_kw = {'autocomplete':'off','class':'input-form', 'value':prof['branch']})
			resume_link = StringField('Resume Link', validators = [DataRequired()], render_kw = {'autocomplete':'off','class':'input-form', 'value':prof['resume_link']})
			contact_no = StringField('Contact Number', validators = [DataRequired()], render_kw = {'autocomplete':'off','class':'input-form', 'value':prof['contact_no']})
			email = StringField('Email', validators = [DataRequired()], render_kw = {'autocomplete':'off','class':'input-form', 'value':prof['email_id']})
			submit = SubmitField('Submit', render_kw = {'class':'submit-button'})
		form = ProfileFormE()
		return render_template('edit_profile.html', form=form)

#To view the profile of student
@profile.route('/view/<string:stu_uid>', methods = ['GET', 'POST'])
@login_required
def view_profile(stu_uid):
	stu_uid=formatString(stu_uid)
	sql1 = f"SELECT * from profile where stu_uid = '{stu_uid}'"
	profile = db.engine.execute(sql1).first()
	return render_template('view_profile.html', profile=profile, stu_uid=stu_uid)

#To create the profile of company
@profile.route('/com_profile/', methods = ['GET', 'POST'])
@login_required
def com_profile():
	sql1 = f"SELECT com_uid from company where com_uid = '{session['user']['userid']}'"
	profile = db.engine.execute(sql1).first()
	sql4 = f"SELECT * FROM company where com_uid = '{session['user']['userid']}'"
	prof = db.engine.execute(sql4).first()
	form = CompanyFormI()
	if form.validate_on_submit():
		company_name = form.company_name.data
		company_link = form.company_link.data

		if profile:
			sql2 = f"UPDATE company SET company_name='{company_name}', company_link='{company_link}' WHERE com_uid = '{profile['com_uid']}'"
			db.engine.execute(sql2)
			db.session.commit()
		#if not exists insert to profile
		else:
			sql3 = f"INSERT INTO company(com_uid,company_name,company_link) VALUES('{session['user']['userid']}', '{company_name}', '{company_link}')"
			db.engine.execute(sql3)
			db.session.commit()
		return redirect(url_for('main.home'))

	if not profile:
		form = CompanyFormI()
		return render_template('company.html',form=form)
	else:
		class CompanyFormE(FlaskForm):
			company_name = StringField('Company Name', validators = [DataRequired()], render_kw = {'autocomplete':'off', 'class':'input-form', 'value':prof['company_name']})
			company_link = StringField('Company Link', validators = [DataRequired()], render_kw = {'autocomplete':'off','class':'input-form', 'value':prof['company_link']})
			submit = SubmitField('Submit', render_kw = {'class':'submit-button'})
		form = CompanyFormE()
		return render_template('company.html', form=form)


import pytest

from tests.conftest import TEST_PORTS, TEST_TARGETS_SET
from utils.custom_dataclasses import (
    DirectoryBruteforceInput,
    ReconInput,
    ScanInput,
    PortScanInput,
    ZapSpiderInput,
)

class TestSteeringModule:
    testing_targets = TEST_TARGETS_SET
    test_ports = TEST_PORTS
    # couldn't use this as a fixture ¯\_(ツ)_/¯
    test_recon_input = ReconInput(
        ZapSpiderInput(as_user=False, enhanced=False),
        DirectoryBruteforceInput(list_size="small", recursive=False),
    )
    test_single_module_directory_bruteforce_input = ReconInput(
        ZapSpiderInput(as_user=None, enhanced=None),
        DirectoryBruteforceInput(list_size="small", recursive=False),
    )
    test_single_module_zap_spider_input = ReconInput(
        ZapSpiderInput(as_user=False, enhanced=False),
        DirectoryBruteforceInput(list_size=None, recursive=None),
    )
    test_recon_input_empty = ReconInput(
        ZapSpiderInput(as_user=None, enhanced=None),
        DirectoryBruteforceInput(list_size=None, recursive=None),
    )
    test_scan_input = ScanInput(
        PortScanInput(port_scan_type="custom", ports=test_ports)  # type: ignore
    )
    test_scan_input_empty = ScanInput(
        PortScanInput(port_scan_type="custom", ports=set())
    )

    @pytest.mark.parametrize(
        "steering_module_with_input, expected_output",
        [
            (
                pytest.lazy_fixture("steering_module_for_single_module_zap_spider_fixture"),  # type: ignore
                {
                    "use_type": "single_module",
                    "phase": "recon",
                    "module": "zap_spider",
                    "targets": testing_targets,
                    "context_file_name": None,
                    "include_targets": None,
                    "exclude_targets": None,
                    "recon": test_single_module_zap_spider_input,
                    "scan": test_scan_input_empty,
                    "output_after_every_finding": True,
                },
            ),
            (
                pytest.lazy_fixture("steering_module_for_single_module_directory_bruteforce_fixture"),  # type: ignore
                {
                    "use_type": "single_module",
                    "phase": "recon",
                    "module": "directory_bruteforce",
                    "targets": testing_targets,
                    "context_file_name": None,
                    "include_targets": None,
                    "exclude_targets": None,
                    "recon": test_single_module_directory_bruteforce_input,
                    "scan": test_scan_input_empty,
                    "output_after_every_finding": True,
                },
            ),
            (
                pytest.lazy_fixture("steering_module_for_single_module_port_scan_fixture"),  # type: ignore
                {
                    "use_type": "single_module",
                    "phase": "scan",
                    "module": "port_scan",
                    "targets": testing_targets,
                    "context_file_name": None,
                    "include_targets": None,
                    "exclude_targets": None,
                    "recon": test_recon_input_empty,
                    "scan": test_scan_input,
                    "output_after_every_finding": True,
                },
            ),
            (
                pytest.lazy_fixture("steering_module_for_single_phase_recon_fixture"),  # type: ignore
                {
                    "use_type": "single_phase",
                    "phase": "recon",
                    "module": None,
                    "targets": testing_targets,
                    "context_file_name": None,
                    "include_targets": None,
                    "exclude_targets": None,
                    "recon": test_recon_input,
                    "scan": test_scan_input_empty,
                    "output_after_every_finding": True,
                },
            ),
            (
                pytest.lazy_fixture("steering_module_for_single_phase_scan_fixture"),  # type: ignore
                {
                    "use_type": "single_phase",
                    "phase": "scan",
                    "module": None,
                    "targets": testing_targets,
                    "context_file_name": None,
                    "include_targets": None,
                    "exclude_targets": None,
                    "recon": test_recon_input_empty,
                    "scan": test_scan_input,
                    "output_after_every_finding": True,
                },
            ),
            (
                pytest.lazy_fixture("steering_module_for_all_fixture"),  # type: ignore
                {
                    "use_type": "all",
                    "phase": "",
                    "module": None,
                    "targets": testing_targets,
                    "context_file_name": None,
                    "include_targets": None,
                    "exclude_targets": None,
                    "recon": test_recon_input,
                    "scan": test_scan_input,
                    "output_after_every_finding": True,
                },
            ),
        ],
    )
    def test_assign_class_attributes_for_single_module_directory_bruteforce(
        self,
        steering_module_with_input,
        expected_output,
    ):
        sm = steering_module_with_input
        assert sm.use_type == expected_output["use_type"]
        assert sm.phase == expected_output["phase"]
        assert sm.module == expected_output["module"]
        assert sm.targets == expected_output["targets"]
        assert sm.context_file_name == expected_output["context_file_name"]
        assert sm.include_targets == expected_output["include_targets"]
        assert sm.exclude_targets == expected_output["exclude_targets"]
        assert sm.recon_input == expected_output["recon"]
        assert sm.scan_input == expected_output["scan"]
        assert (
            sm.output_after_every_finding
            == expected_output["output_after_every_finding"]
        )


def moveOnePeriod(direction,ms):
    for j in range(0,4,1):      # cycle for power supply order
        for i in range(0,4,1):  # assign to each pin
            if (direction == 1):# power supply order clockwise
                # print("moving right")
                GPIO.output(motorPins[i],((CCWStep[j] == 1<<i) and GPIO.HIGH or GPIO.LOW))
            else :              # power supply order anticlockwise
                # print("moving left")
                GPIO.output(motorPins[i],((CWStep[j] == 1<<i) and GPIO.HIGH or GPIO.LOW))
        if(ms<3):       # the delay can not be less than 3ms, otherwise it will exceed speed limit of the motor
            ms = 3
        time.sleep(ms*0.001)

def moveSteps(direction, ms, steps):
    for i in range(steps):
        moveOnePeriod(direction, ms)

moveSteps(0,3,512)