# HG changeset patch # User windel # Date 1349622250 -7200 # Node ID 32078200cdd6b8c72636b52429175b34cadc148c # Parent fd7d5069734e26e4dbeab714bbc6238981a95359 Several move action diff -r fd7d5069734e -r 32078200cdd6 python/apps/bouncing_cube.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/python/apps/bouncing_cube.py Sun Oct 07 17:04:10 2012 +0200 @@ -0,0 +1,406 @@ +from PyQt4.QtGui import * +from PyQt4.QtCore import * +from PyQt4.QtOpenGL import QGLWidget +from OpenGL.GL import * +from OpenGL.GLU import gluPerspective +import sys +from random import random +from math import pi, cos, sin, fabs, sqrt +from numpy import mat, array, ones, zeros, eye +from numpy.linalg import norm +import numpy as np +import time +import scipy.integrate +#import pyopencl + +""" + Test script that lets a dice bounce. + Converted from 20-sim equations into python code. + + 20-sim website: + http://www.20sim.com + +""" +def drawCube(w): + glBegin(GL_QUADS) # Start Drawing The Cube + glColor3f(0.0,1.0,0.0) # Set The Color To Blue + glVertex3f( w, w,-w) # Top Right Of The Quad (Top) + glVertex3f(-w, w,-w) # Top Left Of The Quad (Top) + glVertex3f(-w, w, w) # Bottom Left Of The Quad (Top) + glVertex3f( w, w, w) # Bottom Right Of The Quad (Top) + + glColor3f(1.0,0.5,0.0) # Set The Color To Orange + glVertex3f( w,-w, w) # Top Right Of The Quad (Bottom) + glVertex3f(-w,-w, w) # Top Left Of The Quad (Bottom) + glVertex3f(-w,-w,-w) # Bottom Left Of The Quad (Bottom) + glVertex3f( w,-w,-w) # Bottom Right Of The Quad (Bottom) + + glColor3f(1.0,0.0,0.0) # Set The Color To Red + glVertex3f( w, w, w) # Top Right Of The Quad (Front) + glVertex3f(-w, w, w) # Top Left Of The Quad (Front) + glVertex3f(-w,-w, w) # Bottom Left Of The Quad (Front) + glVertex3f( w,-w, w) # Bottom Right Of The Quad (Front) + + glColor3f(1.0,1.0,0.0) # Set The Color To Yellow + glVertex3f( w,-w,-w) # Bottom Left Of The Quad (Back) + glVertex3f(-w,-w,-w) # Bottom Right Of The Quad (Back) + glVertex3f(-w, w,-w) # Top Right Of The Quad (Back) + glVertex3f( w, w,-w) # Top Left Of The Quad (Back) + + glColor3f(0.0,0.0,1.0) # Set The Color To Blue + glVertex3f(-w, w, w) # Top Right Of The Quad (Left) + glVertex3f(-w, w,-w) # Top Left Of The Quad (Left) + glVertex3f(-w,-w,-w) # Bottom Left Of The Quad (Left) + glVertex3f(-w,-w, w) # Bottom Right Of The Quad (Left) + + glColor3f(1.0,0.0,1.0) # Set The Color To Violet + glVertex3f( w, w,-w) # Top Right Of The Quad (Right) + glVertex3f( w, w, w) # Top Left Of The Quad (Right) + glVertex3f( w,-w, w) # Bottom Left Of The Quad (Right) + glVertex3f( w,-w,-w) # Bottom Right Of The Quad (Right) + glEnd() # Done Drawing The Quad + +def drawFloor(w, h): + glBegin(GL_QUADS) # Start Drawing The Cube + + glColor3f(1.0,0.5,0.0) # Set The Color To Orange + glVertex3f( w,-w,h)# Top Right Of The Quad (Bottom) + glVertex3f(-w,-w,h)# Top Left Of The Quad (Bottom) + glVertex3f(-w,w,h)# Bottom Left Of The Quad (Bottom) + glVertex3f( w,w,h)# Bottom Right Of The Quad (Bottom) + glEnd() # Done Drawing The Quad + +def drawAxis(): + glLineWidth(0.5) + glBegin(GL_LINES) + glColor3f(1.0, 0.0, 0.0) + glVertex3f(0,0,0) + glVertex3f(1,0,0) + glColor3f(0.0, 1.0, 0.0) + glVertex3f(0,0,0) + glVertex3f(0,1,0) + glColor3f(0.0, 0.0, 1.0) + glVertex3f(0,0,0) + glVertex3f(0,0,1) + glEnd() + + +def cross(A, B): + a = A.A1 + b = B.A1 + return mat(np.cross(a, b)).T + +def skew(X): + Y = mat(zeros( (3, 3) )) + a,b,c = X.A1 + Y[0,1] = -c + Y[0,2] = b + Y[1,0] = c + Y[1,2] = -a + Y[2,0] = -b + Y[2,1] = a + return Y + +def adjoint(T): + W = T[0:3, 0] + V = T[3:6, 0] + a = mat(zeros( (6,6) ) ) + a[0:3, 0:3] = skew(W) + a[3:6, 0:3] = skew(V) + a[3:6, 3:6] = skew(W) + return a + +def Adjoint(H): + R = H[0:3, 0:3] + P = H[0:3, 3] + a = mat(zeros( (6,6) ) ) + a[0:3, 0:3] = R + a[3:6, 3:6] = R + a[3:6, 0:3] = skew(P) * R + return a + +def quatToR(q): + x, y, z, w = q.A1 + r = mat(eye(3)) + r[0,0] = 1 - (2*y**2+2*z**2) + r[0,1] = 2*x*y+2*z*w + r[0,2] = 2*x*z - 2*y*w + r[1,0] = 2*x*y-2*z*w + r[1,1] = 1 - (2*x**2 + 2*z**2) + r[1,2] = 2*y*z + 2*x*w + r[2,0] = 2*x*z+2*y*w + r[2,1] = 2*y*z - 2*x*w + r[2,2] = 1 - (2*x**2+2*y**2) + return r + +def rotateAbout(axis, angle): + ax, ay, az = (axis/norm(axis)).A1 + qx = ax*sin(angle/2.0) + qy = ay*sin(angle/2.0) + qz = az*sin(angle/2.0) + qw = cos(angle/2.0) + q = mat(array([qx,qy,qz,qw])).T + return q + +def normalizeQuaternion(quat): + x,y,z,w = quat.A1 + magnitude = sqrt(x*x + y*y + z*z + w*w) + x = x / magnitude + y = y / magnitude + z = z / magnitude + w = w / magnitude + quat[0, 0] = x + quat[1, 0] = y + quat[2, 0] = z + quat[3, 0] = w + return quat + +def VTo4x4(V): + v1, v2, v3 = V.A1 + return mat(array( \ + [[0.0, -v3, v2, -v1], \ + [ v3, 0.0, -v1, -v2], \ + [-v2, v1, 0.0, -v3], \ + [v1, v2, v3, 0.0] ])) + +def homogeneous(R,p): + H = mat(eye(4)) + H[0:3, 0:3] = R + H[0:3, 3] = p + return H + +def rateOfChange(states, thetime, parameters): + quat = states[0:4, 0] # Orientation (4) + pos = states[4:7, 0] # Position (3) + P = states[7:13, 0] # Momentum (6) + massI, gravity = parameters + # Rigid body parts: + # Forward Kinematic chain: + H = homogeneous(quatToR(quat), pos) # Forward kinematics + + AdjX2 = mat(eye(6)) # The connectionpoint in the real world + adjAdjX2_1 = adjoint(AdjX2[0:6,0]) + adjAdjX2_2 = adjoint(AdjX2[0:6,1]) + adjAdjX2_3 = adjoint(AdjX2[0:6,2]) + adjAdjX2_4 = adjoint(AdjX2[0:6,3]) + adjAdjX2_5 = adjoint(AdjX2[0:6,4]) + adjAdjX2_6 = adjoint(AdjX2[0:6,5]) + AdjInv2 = Adjoint(H.I) + M2 = AdjInv2.T * (massI * AdjInv2) # Transfor mass to base frame + MassMatrix = M2 + + wrenchGrav2 = mat( zeros((1,6)) ) + wrenchGrav2[0, 0:3] = -cross(gravity, pos).T + wrenchGrav2[0, 3:6] = gravity.T + + Bk = mat( zeros( (6,6) )) + Bk[0:3, 0:3] = skew(P[0:3, 0]) + Bk[3:6, 0:3] = skew(P[3:6, 0]) + Bk[0:3, 3:6] = skew(P[3:6, 0]) + + # TODO: do this a cholesky: + v = np.linalg.solve(MassMatrix, P) # Matrix inverse like thingy ! + + T2_00 = v # Calculate the relative twist! + TM2 = T2_00.T * M2 + twistExternal = T2_00 # Twist van het blokje + TMSum2 = TM2 + + PDotBodies = mat( zeros( (6,1)) ) + PDotBodies[0,0] = TMSum2 * (adjAdjX2_1 * T2_00) + PDotBodies[1,0] = TMSum2 * (adjAdjX2_2 * T2_00) + PDotBodies[2,0] = TMSum2 * (adjAdjX2_3 * T2_00) + PDotBodies[3,0] = TMSum2 * (adjAdjX2_4 * T2_00) + PDotBodies[4,0] = TMSum2 * (adjAdjX2_5 * T2_00) + PDotBodies[5,0] = TMSum2 * (adjAdjX2_6 * T2_00) + + PDot = -PDotBodies - Bk * v + PDot += wrenchGrav2.T + + ##### Contact wrench part: + HB_W = H # Is H-matrix van het blokje + WrenchB = mat(zeros( (1,6) )) + for px in [-0.5, 0.5]: + for py in [-0.5, 0.5]: + for pz in [-0.5, 0.5]: + HB1_B = homogeneous(mat(eye(3)), mat([px,py,pz]).T) + HB1_W = HB_W * HB1_B + HW1_W = homogeneous(mat(eye(3)), HB1_W[0:3,3]) + HW_W1 = HW1_W.I + HB_W1 = HW_W1 * HB_W + + AdjHB_W1 = Adjoint(HB_W1) + TB_W1_W1 = AdjHB_W1 * twistExternal + z = HB1_W[2, 3] + vx, vy, vz = TB_W1_W1[3:6, 0].A1 + if z < 0: + # Contact forces: + Fx = -50.0*vx + Fy = -50.0*vy + Fz = -z*50000.0 + else: + Fx = 0.0 + Fy = 0.0 + Fz = 0.0 + # TODO: reflect impulse + WrenchW1 = mat([0,0,0,0,0,Fz]) + # Transform it back: + WrenchB += (AdjHB_W1.T * WrenchW1.T).T + ##### End of contact wrench + + PDot += (WrenchB * AdjInv2).T + + # Position and orientation rates: + QOmega = VTo4x4(v[0:3, 0]) + quatDot = 0.5 * QOmega * quat + vel = v[3:6, 0] + posDot = skew(v[0:3]) * pos + vel + # The rate vector: + rates = mat(zeros( (13,1) )) + rates[0:4, 0] = quatDot + rates[4:7, 0] = posDot + rates[7:13, 0] = PDot + return rates + +def fWrapper(y, t, parameters): + y = mat(y).T + dy = rateOfChange(y, t, parameters) + return dy.T.A1 + +def SCIPY(endtime, dt, state, parameters): + times = np.arange(0.0, endtime, dt) + y0 = state.T.A1 + res = scipy.integrate.odeint(fWrapper, y0, times, args=(parameters,)) + states = [] + res = res.T + r,c = res.shape + for ci in range(0,c): + states.append(mat(res[:,ci]).T) + return states + +def RK4(endtime, dt, state, parameters): + t = 0.0 + states = [] + while t < endtime: + newstate = mat (zeros( state.shape )) # Create a new object + + #### Runge Kutta integration: + k1 = rateOfChange(state, t, parameters) + k2 = rateOfChange(state + 0.5*dt*k1, t, parameters) + k3 = rateOfChange(state + 0.5*dt*k1, t, parameters) + k4 = rateOfChange(state + dt*k3, t, parameters) + newstate = state + (dt/6.0)*(k1+2*k2+2*k3+k4) + + # Normalize quat: + newstate[0:4, 0] = normalizeQuaternion(newstate[0:4, 0]) + states.append(newstate) + + state = newstate + + t += dt + print state[6,0], t, ' ', (t/endtime)*100.0, '%' + return states + + +def simulate(endtime, dt): + PInitial = mat( zeros((6,1)) ) + posInitial = mat(array([-1.2, -1.3, 2.8])).T + quatInitial = rotateAbout(mat(array([0.2, 1.0, 0.4])).T, 0.5) + # Parameters: + gravity = mat( array([0,0,-9.81]) ).T + massI = mat(eye(6)) * 1.01 # Mass matrix + parameters = (massI, gravity) + + # The state vector! + state = mat(zeros( (13,1) )) + state[0:4, 0] = quatInitial + state[4:7, 0] = posInitial + state[7:13, 0] = PInitial + + return SCIPY(endtime, dt, state, parameters) + +class W(QGLWidget): + time = 0.0 + index = 0 + def __init__(self, states, dt, parent=None): + super(W, self).__init__(parent) + self.firstRun = True + self.savePNGS = False + self.dt = dt + self.resize(500,500) + self.states = states + self.UP() + t = QTimer(self) + t.timeout.connect(self.UP) + t.start(self.dt*1000.0) + + def UP(self): + self.time += self.dt + if self.states: + state = self.states[self.index] + self.Dicequat = state[0:4, 0] + self.Dicepos = state[4:7, 0] + self.index += 1 + if self.index == len(self.states): + self.index = 0 + self.firstRun = False + # Paint: + self.update() + if self.firstRun: + # Create png images for the movie: + if self.savePNGS: + pm = self.renderPixmap() + pm.save('image'+str(self.index)+'.png') + + def initializeGL(self): + glClearColor(0.0, 0.5, 0.0, 1.0) + glEnable(GL_DEPTH_TEST) + glDepthFunc(GL_LESS) + glShadeModel(GL_SMOOTH) + def resizeGL(self,w,h): + glViewport(0, 0, w, h) + glMatrixMode(GL_PROJECTION) + glLoadIdentity() + gluPerspective(45.0, float(w)/float(h), 0.1, 100.0) + glMatrixMode(GL_MODELVIEW) + def paintGL(self): + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) # Clear buffers + glLoadIdentity() # Reset The View + + glLoadIdentity() + glTranslatef(0.0,-2.0,-10.0) # Move Left And Into The Screen + glRotatef(-90.0, 1.0, 0.0, 0.0) + drawFloor(2.0, 0.0) + drawAxis() + self.renderText(1.0, 0.0, 0.0, 'X') + self.renderText(0.0, 1.0, 0.0, 'Y') + self.renderText(0.0, 0.0, 1.0, 'Z') + + self.renderText(0.0,0.0,1.2,str(self.time)) + + x,y,z = self.Dicepos.A1 + R = quatToR(self.Dicequat) + + glTranslatef(x, y, z) + # Trick to rotate the openGL matrix: + r = R.A1 + rotR = (r[0], r[3], r[6], 0.0, + r[1], r[4], r[7], 0.0, + r[2], r[5], r[8], 0.0, + 0.0, 0.0, 0.0, 1.0) + glMultMatrixd(rotR) + + drawCube(0.6) + +et = 20.0 +dt = 0.04 +print 'starting integration... endtime =', et, ' stepsize =', dt +t0 = time.time() +states = simulate(et, dt) +t1 = time.time() +print 'That was heavy, it took me ', t1-t0, ' seconds!' +app = QApplication(sys.argv) +w = W(states, dt) +w.show() +sys.exit(app.exec_()) + diff -r fd7d5069734e -r 32078200cdd6 python/apps/diagrameditor.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/python/apps/diagrameditor.py Sun Oct 07 17:04:10 2012 +0200 @@ -0,0 +1,653 @@ +#!/usr/bin/python + +from PyQt4.QtGui import * +from PyQt4.QtCore import * +import sys +import xml.dom.minidom as md +import xml + +""" + Author: Windel Bouwman + Year: 2012 + Description: This script implements a diagram editor. + run with python 3.x as: + $ python [thisfile.py] +""" + +class ArrowHead(QGraphicsPathItem): + def __init__(self, parent): + super(ArrowHead, self).__init__(parent) + arrowPath = QPainterPath(QPointF(0.0, 0.0)) + arrowPath.lineTo(-6.0, 10.0) + arrowPath.lineTo(6.0, 10.0) + arrowPath.lineTo(0.0, 0.0) + self.setPath(arrowPath) + pen = QPen(Qt.blue, 2) + self.setPen(pen) + self.setBrush(QBrush(pen.color())) + self.myshape = QPainterPath() + +class Connection(QGraphicsPathItem): + """ Implementation of a connection between blocks """ + def __init__(self, fromPort, toPort): + super(Connection, self).__init__() + self.pos1 = None + self.pos2 = None + self.fromPort = None + self.toPort = None + self.setFlag(self.ItemIsSelectable, True) + self.setFlag(self.ItemClipsToShape, True) + self.pen = QPen(Qt.blue, 2) + self.pen.setCapStyle(Qt.RoundCap) + self.setPen(self.pen) + self.arrowhead = ArrowHead(self) + self.vias = [] + self.setFromPort(fromPort) + self.setToPort(toPort) + def mouseDoubleClickEvent(self, event): + pos = event.scenePos() + pts = [self.pos1] + [v.pos() for v in self.vias] + [self.pos2] + idx = 0 + tidx = 0 + for p1, p2 in zip(pts[0:-1], pts[1:]): + l1 = QLineF(p1, p2) + l2 = QLineF(p1, pos) + l3 = QLineF(pos, p2) + d = l2.length() + l3.length() - l1.length() + if d < 5: + tidx = idx + idx += 1 + self.addHandle(pos, tidx) + + def addHandle(self, pos, idx=None): + hi = HandleItem(self) + if idx: + self.vias.insert(idx, hi) + else: + self.vias.append(hi) + def callback(p): + self.updateLineStukken() + return p + hi.posChangeCallbacks.append(callback) + hi.setPos(pos) + self.updateLineStukken() + + def setFromPort(self, fromPort): + if self.fromPort: + self.fromPort.posCallbacks.remove(self.setBeginPos) + self.fromPort.connection = None + self.fromPort = fromPort + if self.fromPort: + self.fromPort.connection = self + self.setBeginPos(fromPort.scenePos()) + self.fromPort.posCallbacks.append(self.setBeginPos) + def setToPort(self, toPort): + if self.toPort: + self.toPort.posCallbacks.remove(self.setEndPos) + self.toPort.connection = None + self.toPort = toPort + if self.toPort: + self.setEndPos(toPort.scenePos()) + self.toPort.connection = self + self.toPort.posCallbacks.append(self.setEndPos) + def releasePorts(self): + self.setFromPort(None) + self.setToPort(None) + def setBeginPos(self, pos1): + self.pos1 = pos1 + self.updateLineStukken() + def setEndPos(self, endpos): + self.pos2 = endpos + self.updateLineStukken() + def itemChange(self, change, value): + if change == self.ItemSelectedHasChanged: + for via in self.vias: + via.setVisible(value) + return super(Connection, self).itemChange(change, value) + def shape(self): + return self.myshape + def updateLineStukken(self): + """ + This algorithm determines the optimal routing of all signals. + TODO: implement nice automatic line router + """ + if self.pos1 is None or self.pos2 is None: + return + pts = [self.pos1] + [v.pos() for v in self.vias] + [self.pos2] + self.arrowhead.setPos(self.pos2) + if pts[-1].x() < pts[-2].x(): + self.arrowhead.setRotation(-90) + else: + self.arrowhead.setRotation(90) + path = QPainterPath(pts[0]) + for pt in pts[1:]: + path.lineTo(pt) + self.setPath(path) + """ Create a shape outline using the path stroker """ + s = super(Connection, self).shape() + pps = QPainterPathStroker() + pps.setWidth(10) + self.myshape = pps.createStroke(s).simplified() + +class ParameterDialog(QDialog): + def __init__(self, block, parent = None): + super(ParameterDialog, self).__init__(parent) + self.block = block + self.button = QPushButton('Ok', self) + l = QGridLayout(self) + l.addWidget(QLabel('Name:', self), 0, 0) + self.nameEdit = QLineEdit(self.block.name) + l.addWidget(self.nameEdit, 0, 1) + l.addWidget(QLabel('Code:', self), 1, 0) + self.codeEdit = QTextEdit(self) + self.codeEdit.setPlainText(self.block.code) + l.addWidget(self.codeEdit, 1, 1) + l.addWidget(self.button, 2, 0, 1, 2) + self.button.clicked.connect(self.OK) + def OK(self): + self.block.setName(self.nameEdit.text()) + self.block.code = self.codeEdit.toPlainText() + self.close() + +class PortItem(QGraphicsPathItem): + """ Represents a port to a subsystem """ + def __init__(self, name, block, direction): + super(PortItem, self).__init__(block) + self.connection = None + path = QPainterPath() + d = 10.0 + if direction == 'input': + path.moveTo(-d, -d) + path.lineTo(0.0, 0.0) + path.lineTo(-d, d) + else: + path.moveTo(0.0, -d) + path.lineTo(d, 0.0) + path.lineTo(0.0, d) + self.setPath(path) + self.direction = direction + self.block = block + self.setCursor(QCursor(Qt.CrossCursor)) + pen = QPen(Qt.blue, 2) + pen.setCapStyle(Qt.RoundCap) + self.setPen(pen) + self.name = name + self.textItem = QGraphicsTextItem(name, self) + self.setName(name) + self.posCallbacks = [] + self.setFlag(self.ItemSendsScenePositionChanges, True) + def setName(self, name): + self.name = name + self.textItem.setPlainText(name) + rect = self.textItem.boundingRect() + lw, lh = rect.width(), rect.height() + if self.direction == 'input': + lx = 3 + else: + lx = -3 - lw + self.textItem.setPos(lx, -lh / 2) + def itemChange(self, change, value): + if change == self.ItemScenePositionHasChanged: + for cb in self.posCallbacks: + cb(value) + return value + return super(PortItem, self).itemChange(change, value) + def mousePressEvent(self, event): + if self.direction == 'output': + self.scene().startConnection(self) + +class OutputPort(PortItem): + # TODO: create a subclass OR make a member porttype + pass + +# Block part: +class HandleItem(QGraphicsEllipseItem): + """ A handle that can be moved by the mouse """ + def __init__(self, parent=None): + dx = 13.0 + super(HandleItem, self).__init__(QRectF(-0.5*dx,-0.5*dx,dx,dx), parent) + self.posChangeCallbacks = [] + self.setBrush(QBrush(Qt.white)) + self.setFlag(self.ItemSendsScenePositionChanges, True) + self.setFlag(self.ItemIsMovable, True) + self.setVisible(False) + self.setCursor(QCursor(Qt.SizeFDiagCursor)) + def mouseMoveEvent(self, event): + """ Move function without moving the other selected elements """ + p = self.mapToParent(event.pos()) + self.setPos(p) + def mySetPos(self, p): + # TODO: use this instead of itemChange? + self.setPos(p) + def itemChange(self, change, value): + if change == self.ItemPositionChange: + for cb in self.posChangeCallbacks: + res = cb(value) + if res: + value = res + return value + # Call superclass method: + return super(HandleItem, self).itemChange(change, value) + +def uniqify(name, names): + newname = name + i = 1 + while newname in names: + newname = name + str(i) + i += 1 + return newname + +class BlockItem(QGraphicsRectItem): + """ + Represents a block in the diagram + Has an x and y and width and height + width and height can only be adjusted with a tip in the lower right corner. + + - in and output ports + - parameters + - description + """ + def __init__(self, name='Untitled', parent=None): + super(BlockItem, self).__init__(parent) + # Properties of the rectangle: + self.setPen(QPen(Qt.blue, 2)) + self.setBrush(QBrush(Qt.lightGray)) + self.setFlags(self.ItemIsSelectable | self.ItemIsMovable) + self.setFlag(self.ItemSendsScenePositionChanges, True) + self.setCursor(QCursor(Qt.PointingHandCursor)) + self.label = QGraphicsTextItem(name, self) + self.name = name + self.code = '' + # Create corner for resize: + self.sizer = HandleItem(self) + self.sizer.posChangeCallbacks.append(self.changeSize) # Connect the callback + button = QPushButton('+in') + button.clicked.connect(self.newInputPort) + self.buttonItemAddInput = QGraphicsProxyWidget(self) + self.buttonItemAddInput.setWidget(button) + self.buttonItemAddInput.setVisible(False) + button = QPushButton('+out') + button.clicked.connect(self.newOutputPort) + self.buttonItemAddOutput = QGraphicsProxyWidget(self) + self.buttonItemAddOutput.setWidget(button) + self.buttonItemAddOutput.setVisible(False) + + # Inputs and outputs of the block: + self.inputs = [] + self.outputs = [] + # Update size: + self.sizer.mySetPos(QPointF(60, 40)) # This is a better resize function + def editParameters(self): + pd = ParameterDialog(self, self.window()) + pd.exec_() + def mouseDoubleClickEvent(self, event): + self.editParameters() + def newInputPort(self): + names = [i.name for i in self.inputs + self.outputs] + self.addInput(PortItem(uniqify('in', names), self, 'input')) + def newOutputPort(self): + names = [i.name for i in self.inputs + self.outputs] + self.addOutput(PortItem(uniqify('out', names), self, 'output')) + def setName(self, name): + self.name = name + self.label.setPlainText(name) + def addInput(self, i): + self.inputs.append(i) + self.updateSize() + def addOutput(self, o): + self.outputs.append(o) + self.updateSize() + + def contextMenuEvent(self, event): + menu = QMenu() + pa = menu.addAction('Parameters') + pa.triggered.connect(self.editParameters) + menu.exec_(event.screenPos()) + def itemChange(self, change, value): + if change == self.ItemSelectedHasChanged: + self.sizer.setVisible(value) + self.buttonItemAddInput.setVisible(value) + self.buttonItemAddOutput.setVisible(value) + return super(BlockItem, self).itemChange(change, value) + + def updateSize(self): + rect = self.rect() + h, w = rect.height(), rect.width() + self.buttonItemAddInput.setPos(0, h + 4) + self.buttonItemAddOutput.setPos(w+10, h+4) + if len(self.inputs) == 1: + self.inputs[0].setPos(0.0, h / 2) + elif len(self.inputs) > 1: + y = 15 + dy = (h - 30) / (len(self.inputs) - 1) + for inp in self.inputs: + inp.setPos(0.0, y) + y += dy + if len(self.outputs) == 1: + self.outputs[0].setPos(w, h / 2) + elif len(self.outputs) > 1: + y = 15 + dy = (h - 30) / (len(self.outputs) - 1) + for outp in self.outputs: + outp.setPos(w, y) + y += dy + + def changeSize(self, p): + """ Resize block function """ + w, h = p.x(), p.y() + # Limit the block size: + if h < 20: + h = 20 + if w < 40: + w = 40 + self.setRect(0.0, 0.0, w, h) + # center label: + rect = self.label.boundingRect() + lw, lh = rect.width(), rect.height() + lx = (w - lw) / 2 + ly = (h - lh) / 2 + self.label.setPos(lx, ly) + # Update port positions: + self.updateSize() + return QPointF(w, h) + +class EditorGraphicsView(QGraphicsView): + def __init__(self, scene, parent=None): + QGraphicsView.__init__(self, scene, parent) + self.setDragMode(QGraphicsView.RubberBandDrag) + def wheelEvent(self, event): + pos = event.pos() + posbefore = self.mapToScene(pos) + degrees = event.delta() / 8.0 + sx = (100.0 + degrees) / 100.0 + self.scale(sx, sx) + event.accept() + def dragEnterEvent(self, event): + if event.mimeData().hasFormat('component/name'): + event.accept() + def dragMoveEvent(self, event): + if event.mimeData().hasFormat('component/name'): + event.accept() + def dropEvent(self, event): + if event.mimeData().hasFormat('component/name'): + name = bytes(event.mimeData().data('component/name')).decode() + pos = self.mapToScene(event.pos()) + self.scene().addNewBlock(pos, name) + +class LibraryModel(QStandardItemModel): + def __init__(self, parent=None): + QStandardItemModel.__init__(self, parent) + def mimeTypes(self): + return ['component/name'] + def mimeData(self, idxs): + mimedata = QMimeData() + for idx in idxs: + if idx.isValid(): + txt = self.data(idx, Qt.DisplayRole) # python 3 + mimedata.setData('component/name', txt) + return mimedata + +class DiagramScene(QGraphicsScene): + """ Save and load and deletion of item""" + def __init__(self, parent=None): + super(DiagramScene, self).__init__(parent) + self.startedConnection = None + + def saveDiagram(self, filename): + items = self.items() + blocks = [item for item in items if type(item) is BlockItem] + connections = [item for item in items if type(item) is Connection] + + doc = md.Document() + modelElement = doc.createElement('system') + doc.appendChild(modelElement) + for block in blocks: + blockElement = doc.createElement("block") + x, y = block.scenePos().x(), block.scenePos().y() + rect = block.rect() + w, h = rect.width(), rect.height() + blockElement.setAttribute("name", block.name) + blockElement.setAttribute("x", str(int(x))) + blockElement.setAttribute("y", str(int(y))) + blockElement.setAttribute("width", str(int(w))) + blockElement.setAttribute("height", str(int(h))) + codeNode = doc.createCDATASection(block.code) + codeElement = doc.createElement('code') + codeElement.appendChild(codeNode) + blockElement.appendChild(codeElement) + for inp in block.inputs: + portElement = doc.createElement("input") + portElement.setAttribute("name", inp.name) + blockElement.appendChild(portElement) + for outp in block.outputs: + portElement = doc.createElement("output") + portElement.setAttribute("name", outp.name) + blockElement.appendChild(portElement) + modelElement.appendChild(blockElement) + for connection in connections: + connectionElement = doc.createElement("connection") + fromPort = connection.fromPort.name + toPort = connection.toPort.name + fromBlock = connection.fromPort.block.name + toBlock = connection.toPort.block.name + connectionElement.setAttribute("fromBlock", fromBlock) + connectionElement.setAttribute("fromPort", fromPort) + connectionElement.setAttribute("toBlock", toBlock) + connectionElement.setAttribute("toPort", toPort) + for via in connection.vias: + viaElement = doc.createElement('via') + viaElement.setAttribute('x', str(int(via.x()))) + viaElement.setAttribute('y', str(int(via.y()))) + connectionElement.appendChild(viaElement) + modelElement.appendChild(connectionElement) + with open(filename, 'w') as f: + f.write(doc.toprettyxml()) + + def loadDiagram(self, filename): + try: + doc = md.parse(filename) + except IOError as e: + print('{0} not found'.format(filename)) + return + except xml.parsers.expat.ExpatError as e: + print('{0}'.format(e)) + return + sysElements = doc.getElementsByTagName('system') + blockElements = doc.getElementsByTagName('block') + for sysElement in sysElements: + blockElements = sysElement.getElementsByTagName('block') + for blockElement in blockElements: + x = float(blockElement.getAttribute('x')) + y = float(blockElement.getAttribute('y')) + w = float(blockElement.getAttribute('width')) + h = float(blockElement.getAttribute('height')) + name = blockElement.getAttribute('name') + block = BlockItem(name) + self.addItem(block) + block.setPos(x, y) + block.sizer.setPos(w, h) + codeElements = blockElement.getElementsByTagName('code') + if codeElements: + cn = codeElements[0].childNodes + cdatas = [cd for cd in cn if type(cd) is md.CDATASection] + if len(cdatas) > 0: + block.code = cdatas[0].data + # Load ports: + portElements = blockElement.getElementsByTagName('input') + for portElement in portElements: + name = portElement.getAttribute('name') + inp = PortItem(name, block, 'input') + block.addInput(inp) + portElements = blockElement.getElementsByTagName('output') + for portElement in portElements: + name = portElement.getAttribute('name') + outp = PortItem(name, block, 'output') + block.addOutput(outp) + connectionElements = sysElement.getElementsByTagName('connection') + for connectionElement in connectionElements: + fromBlock = connectionElement.getAttribute('fromBlock') + fromPort = connectionElement.getAttribute('fromPort') + toBlock = connectionElement.getAttribute('toBlock') + toPort = connectionElement.getAttribute('toPort') + viaElements = connectionElement.getElementsByTagName('via') + fromPort = self.findPort(fromBlock, fromPort) + toPort = self.findPort(toBlock, toPort) + connection = Connection(fromPort, toPort) + for viaElement in viaElements: + x = int(viaElement.getAttribute('x')) + y = int(viaElement.getAttribute('y')) + connection.addHandle(QPointF(x, y)) + self.addItem(connection) + def findPort(self, blockname, portname): + items = self.items() + blocks = [item for item in items if type(item) is BlockItem] + for block in [b for b in blocks if b.name == blockname]: + for port in block.inputs + block.outputs: + if port.name == portname: + return port + def addNewBlock(self, pos, name): + blocknames = [item.name for item in self.items() if type(item) is BlockItem] + b1 = BlockItem(uniqify(name, blocknames)) + b1.setPos(pos) + self.addItem(b1) + def mouseMoveEvent(self, event): + if self.startedConnection: + pos = event.scenePos() + self.startedConnection.setEndPos(pos) + super(DiagramScene, self).mouseMoveEvent(event) + def mouseReleaseEvent(self, event): + if self.startedConnection: + items = self.items(event.scenePos()) + for item in items: + if type(item) is PortItem: + self.startedConnection.setToPort(item) + self.startedConnection = None + return + self.deleteItem(self.startedConnection) + self.startedConnection = None + super(DiagramScene, self).mouseReleaseEvent(event) + def startConnection(self, port): + self.startedConnection = Connection(port, None) + pos = port.scenePos() + self.startedConnection.setEndPos(pos) + self.addItem(self.startedConnection) + def deleteItem(self, item=None): + if item: + if type(item) is BlockItem: + for p in item.inputs + item.outputs: + if p.connection: + self.deleteItem(p.connection) + self.removeItem(item) + elif type(item) is Connection: + item.releasePorts() + self.removeItem(item) + else: + # No item was supplied, try to delete all currently selected items: + items = self.selectedItems() + connections = [item for item in items if type(item) is Connection] + blocks = [item for item in items if type(item) is BlockItem] + for item in connections + blocks: + self.deleteItem(item) + +class DiagramEditor(QWidget): + def __init__(self, parent=None): + QWidget.__init__(self, parent) + + # Widget layout and child widgets: + self.horizontalLayout = QHBoxLayout(self) + self.diagramScene = DiagramScene(self) + self.loadDiagram = self.diagramScene.loadDiagram + self.diagramView = EditorGraphicsView(self.diagramScene, self) + self.horizontalLayout.addWidget(self.diagramView) + + testShortcut = QShortcut(QKeySequence("F12"), self) + testShortcut.activated.connect(self.test) + delShort = QShortcut(QKeySequence.Delete, self) + delShort.activated.connect(self.diagramScene.deleteItem) + + def test(self): + self.diagramView.rotate(30) + self.zoomAll() + def save(self): + self.diagramScene.saveDiagram('diagram2.usd') + def load(self): + filename = QFileDialog.getOpenFileName(self) + self.diagramScene.loadDiagram(filename) + def zoomAll(self): + """ zoom to fit all items """ + rect = self.diagramScene.itemsBoundingRect() + self.diagramView.fitInView(rect, Qt.KeepAspectRatio) + +class LibraryWidget(QListView): + def __init__(self): + super(LibraryWidget, self).__init__(None) + self.libraryModel = LibraryModel(self) + self.libraryModel.setColumnCount(1) + # Create an icon with an icon: + pixmap = QPixmap(60, 60) + pixmap.fill() + painter = QPainter(pixmap) + painter.fillRect(10, 10, 40, 40, Qt.blue) + painter.setBrush(Qt.yellow) + painter.drawEllipse(20, 20, 20, 20) + painter.end() + + # Fill library: + self.libItems = [] + self.libItems.append( QStandardItem(QIcon(pixmap), 'Block') ) + self.libItems.append( QStandardItem(QIcon(pixmap), 'Uber Unit') ) + self.libItems.append( QStandardItem(QIcon(pixmap), 'Device') ) + for i in self.libItems: + self.libraryModel.appendRow(i) + self.setModel(self.libraryModel) + self.setViewMode(self.IconMode) + self.setDragDropMode(self.DragOnly) + +class Main(QMainWindow): + def __init__(self): + super(Main, self).__init__(None) + self.editor = DiagramEditor() + self.setCentralWidget(self.editor) + self.setWindowTitle("Diagram editor") + toolbar = self.addToolBar('Tools') + + saveAction = QAction('Save', self) + saveAction.setShortcuts(QKeySequence.Save) + saveAction.triggered.connect(self.editor.save) + toolbar.addAction(saveAction) + openAction = QAction('Open', self) + openAction.setShortcuts(QKeySequence.Open) + openAction.triggered.connect(self.editor.load) + toolbar.addAction(openAction) + fullScreenAction = QAction('Full screen', self) + fullScreenAction.setShortcuts(QKeySequence("F11")) + fullScreenAction.triggered.connect(self.toggleFullScreen) + toolbar.addAction(fullScreenAction) + zoomAction = QAction('Fit in view', self) + zoomAction.setShortcuts(QKeySequence('F8')) + zoomAction.triggered.connect(self.editor.zoomAll) + toolbar.addAction(zoomAction) + + self.library = LibraryWidget() + libraryDock = QDockWidget('Library', self) + libraryDock.setWidget(self.library) + self.addDockWidget(Qt.LeftDockWidgetArea, libraryDock) + + self.editor.loadDiagram('diagram2.usd') + + def toggleFullScreen(self): + self.setWindowState(self.windowState() ^ Qt.WindowFullScreen) + self.editor.zoomAll() + +if __name__ == '__main__': + if sys.version_info.major != 3: + print('Please use python 3.x') + sys.exit(1) + + app = QApplication(sys.argv) + main = Main() + main.show() + main.resize(700, 500) + main.editor.zoomAll() + app.exec_() + diff -r fd7d5069734e -r 32078200cdd6 python/apps/ide/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/python/apps/ide/__init__.py Sun Oct 07 17:04:10 2012 +0200 @@ -0,0 +1,2 @@ +# Package + diff -r fd7d5069734e -r 32078200cdd6 python/apps/ide/astviewer.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/python/apps/ide/astviewer.py Sun Oct 07 17:04:10 2012 +0200 @@ -0,0 +1,36 @@ +from PyQt4.QtCore import * +from PyQt4.QtGui import * + +def astToNamedElement(astNode, parentNode): + """ Helper to convert and AST tree to NamedElement tree: """ + item = QStandardItem(str(astNode)) + item.setData(astNode) + parentNode.appendRow(item) + for c in astNode.getChildren(): + astToNamedElement(c, item) + +# The actual widget: +class AstViewer(QTreeView): + sigNodeSelected = pyqtSignal(object) + def __init__(self, parent=None): + super(AstViewer, self).__init__(parent) + self.setHeaderHidden(True) + self.clicked.connect(self.selectHandler) + + def setAst(self, ast): + """ Create a new model and add all ast elements to it """ + model = QStandardItemModel() + if ast: + astToNamedElement(ast, model.invisibleRootItem()) + self.setModel( model ) + self.expandAll() + + def selectHandler(self, index): + if not index.isValid(): + return + model = self.model() + item = model.itemFromIndex(index) + node = item.data() + self.sigNodeSelected.emit(node) + + diff -r fd7d5069734e -r 32078200cdd6 python/apps/ide/codeeditor.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/python/apps/ide/codeeditor.py Sun Oct 07 17:04:10 2012 +0200 @@ -0,0 +1,140 @@ +from PyQt4.QtCore import * +from PyQt4.QtGui import * +import compiler.lexer +import os.path + +class MySyntaxHighlighter(QSyntaxHighlighter): + def __init__(self, parent=None): + super(MySyntaxHighlighter, self).__init__(parent) + # Syntax highlighting: + self.rules = [] + fmt = QTextCharFormat() + fmt.setForeground(Qt.darkBlue) + fmt.setFontWeight(QFont.Bold) + for kw in compiler.lexer.keywords: + pattern = '\\b'+kw+'\\b' + self.rules.append( (pattern, fmt) ) + + # Comments: + fmt = QTextCharFormat() + fmt.setForeground(Qt.gray) + fmt.setFontItalic(True) + pattern = '\{.*\}' + self.rules.append( (pattern, fmt) ) + + # Procedure: + fmt = QTextCharFormat() + fmt.setForeground(Qt.blue) + fmt.setFontItalic(True) + #pattern = '(?<=procedure )[A-Za-z]' + # TODO lookbehind does not work, think something else + #self.rules.append( (pattern, fmt) ) + + def highlightBlock(self, text): + for pattern, fmt in self.rules: + expression = QRegExp(pattern) + index = expression.indexIn(text) + while index >= 0: + length = expression.matchedLength() + self.setFormat(index, length, fmt) + index = expression.indexIn(text, index + length) + +class LineNumberArea(QWidget): + def __init__(self, codeedit): + super(LineNumberArea, self).__init__(codeedit) + self.codeedit = codeedit + # TODO: display error in this: self.setToolTip('hello world') + def sizeHint(self): + return QSize(self.codeedit.lineNumberAreaWidth(), 0) + def paintEvent(self, ev): + self.codeedit.lineNumberAreaPaintEvent(ev) + +class CodeEdit(QPlainTextEdit): + def __init__(self, parent=None): + super(CodeEdit, self).__init__(parent) + # members: + self.isUntitled = True + self.filename = None + self.setFont(QFont('Courier')) + self.lineNumberArea = LineNumberArea(self) + + self.blockCountChanged.connect(self.updateLineNumberAreaWidth) + self.updateRequest.connect(self.updateLineNumberArea) + + # Syntax highlighter: + self.highlighter = MySyntaxHighlighter(self.document()) + + def setFileName(self, filename): + self.filename = filename + self.isUntitled = False + self.setWindowTitle(filename) + def setSource(self, source): + self.setPlainText(source) + + def save(self): + pass + def saveAs(self): + pass + + def saveFile(self): + if self.isUntitled: + self.saveAs() + else: + source = str(self.toPlainText()) + f = open(self.filename, 'w') + f.write(source) + f.close() + + def highlightErrorLocation(self, row, col): + tc = QTextCursor(self.document()) + tc.clearSelection() + tc.movePosition(tc.Down, tc.MoveAnchor, row - 1) + tc.movePosition(tc.Right, tc.MoveAnchor, col - 1) + tc.movePosition(tc.NextCharacter, tc.KeepAnchor) # Select 1 character + selection = QTextEdit.ExtraSelection() + lineColor = QColor(Qt.red).lighter(160) + selection.format.setBackground(lineColor) + #selection.format.setProperty(QTextFormat.FullWidthSelection, True) + selection.cursor = tc + self.setExtraSelections( [ selection ] ) + def clearErrors(self): + self.setExtraSelections( [ ] ) + + def lineNumberAreaWidth(self): + digits = 1 + mx = max(1, self.blockCount()) + while mx >= 10: + mx = mx / 10 + digits += 1 + space = 3 + self.fontMetrics().width('8') * digits + return space + def lineNumberAreaPaintEvent(self, ev): + painter = QPainter(self.lineNumberArea) + painter.fillRect(ev.rect(), Qt.lightGray) + block = self.firstVisibleBlock() + blockNumber = block.blockNumber() + top = self.blockBoundingGeometry(block).translated(self.contentOffset()).top() + bottom = top + self.blockBoundingRect(block).height() + while block.isValid() and top <= ev.rect().bottom(): + if block.isVisible() and bottom >= ev.rect().top(): + num = str(blockNumber + 1) + painter.setPen(Qt.black) + painter.drawText(0, top, self.lineNumberArea.width(), self.fontMetrics().height(), Qt.AlignRight, num) + block = block.next() + top = bottom + bottom = top + self.blockBoundingRect(block).height() + blockNumber += 1 + def resizeEvent(self, ev): + super(CodeEdit, self).resizeEvent(ev) + cr = self.contentsRect() + self.lineNumberArea.setGeometry(QRect(cr.left(), cr.top(), self.lineNumberAreaWidth(), cr.height() )) + def updateLineNumberAreaWidth(self, newBlockCount): + self.setViewportMargins(self.lineNumberAreaWidth(), 0, 0, 0) + def updateLineNumberArea(self, rect, dy): + if dy > 0: + self.lineNumberArea.scroll(0, dy) + else: + self.lineNumberArea.update(0, rect.y(), self.lineNumberArea.width(), rect.height()) + if rect.contains(self.viewport().rect()): + self.updateLineNumberAreaWidth(0) + diff -r fd7d5069734e -r 32078200cdd6 python/apps/ide/ide.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/python/apps/ide/ide.py Sun Oct 07 17:04:10 2012 +0200 @@ -0,0 +1,309 @@ +from PyQt4.QtCore import * +from PyQt4.QtGui import * +# ide components: +from .codeeditor import CodeEdit +from .astviewer import AstViewer +import base64 +from project import Project +import os.path + +lcfospng = base64.decodestring(b'iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A\n/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJEhMKBk7B678AAAA/SURBVFjD\n7dbBCQAgDATBi9h/y7EFA4Kf2QLCwH1S6XQu6sqoujublc8BAAAAAAAAAAB8B+zXT6YJAAAAAKYd\nWSgFQNUyijIAAAAASUVORK5CYII=\n') + +class BuildOutput(QTextEdit): + """ Build output component """ + def __init__(self, parent=None): + super(BuildOutput, self).__init__(parent) + self.setCurrentFont(QFont('Courier')) + self.setReadOnly(True) + self.append('Build output will appear here!') + +class BuildErrors(QListView): + sigErrorSelected = pyqtSignal(object) + def __init__(self, parent=None): + super(BuildErrors, self).__init__(parent) + model = QStandardItemModel() + self.setModel(model) + self.clicked.connect(self.itemSelected) + def setErrorList(self, errorlist): + model = QStandardItemModel() + for e in errorlist: + row, col, msg = e + item = QStandardItem(str(msg)) + item.setData(e) + model.appendRow(item) + self.setModel(model) + def itemSelected(self, index): + if not index.isValid(): + return + model = self.model() + item = model.itemFromIndex(index) + err = item.data() + self.sigErrorSelected.emit(err) + +class ProjectView(QWidget): + sigLoadFile = pyqtSignal(str) + def __init__(self, parent=None): + super(ProjectView, self).__init__(parent) + self.treeview = QTreeView(self) + self.treeview.setContextMenuPolicy(Qt.CustomContextMenu) + l = QVBoxLayout(self) + l.addWidget(self.treeview) + pm = QPixmap() + pm.loadFromData(lcfospng) + self.projectIcon = QIcon(pm) + # Connect signals: + self.treeview.activated.connect(self.activate) + self.treeview.customContextMenuRequested.connect(self.contextMenu) + def setProject(self, project): + self.project = project + model = QStandardItemModel() + root = model.invisibleRootItem() + pitem = QStandardItem(self.projectIcon, project.name) + pitem.setEditable(False) + pitem.setData(project) + root.appendRow(pitem) + for f in self.project.files: + fitem = QStandardItem(f) + pitem.appendRow(fitem) + fitem.setEditable(False) + fitem.setData(f) + self.treeview.setModel(model) + self.treeview.expandAll() + def contextMenu(self, pos): + idx = self.treeview.indexAt(pos) + if not idx.isValid(): + return + item = self.treeview.model().itemFromIndex(idx) + def activate(self, index): + if not index.isValid(): + return + model = self.treeview.model() + item = model.itemFromIndex(index) + fn = item.data() + if type(fn) is str: + self.sigLoadFile.emit(fn) + +class AboutDialog(QDialog): + def __init__(self, parent=None): + super(AboutDialog, self).__init__(parent) + self.setWindowTitle('About') + l = QVBoxLayout(self) + txt = QTextEdit(self) + txt.setReadOnly(True) + aboutText = """
An all-in-one IDE for OS development.
+https://www.assembla.com/spaces/lcfOS/wiki
+Author: Windel Bouwman
+ """ + txt.append(aboutText) + l.addWidget(txt) + but = QPushButton('OK') + but.setDefault(True) + but.clicked.connect(self.close) + l.addWidget(but) + +class ProjectOptions(QDialog): + pass + # TODO: project options in here + +class Ide(QMainWindow): + def __init__(self, parent=None): + super(Ide, self).__init__(parent) + self.setWindowTitle('LCFOS IDE') + icon = QPixmap() + icon.loadFromData(lcfospng) + self.setWindowIcon(QIcon(icon)) + + # Create menus: + self.fileMenu = self.menuBar().addMenu('File') + self.viewMenu = self.menuBar().addMenu('View') + self.projectMenu = self.menuBar().addMenu('Project') + self.helpMenu = self.menuBar().addMenu('Help') + + # Create mdi area: + self.mdiArea = QMdiArea() + self.setCentralWidget(self.mdiArea) + + # Create components: + self.buildOutput = BuildOutput() + self.addComponent('Build output', self.buildOutput) + + self.astViewer = AstViewer() + self.addComponent('AST viewer', self.astViewer) + self.astViewer.sigNodeSelected.connect(self.nodeSelected) + + self.builderrors = BuildErrors() + self.addComponent('Build errors', self.builderrors) + self.builderrors.sigErrorSelected.connect(self.errorSelected) + + self.projectview = ProjectView() + self.addComponent('Project', self.projectview) + self.projectview.sigLoadFile.connect(self.loadFile) + + # About dialog: + self.aboutDialog = AboutDialog() + self.aboutDialog.setWindowIcon(QIcon(icon)) + # Create actions: + self.buildAction = QAction('Build!', self) + self.buildAction.setShortcut(QKeySequence('F7')) + self.projectMenu.addAction(self.buildAction) + self.buildAction.triggered.connect(self.buildFile) + self.openProjectAction = QAction("Open project", self) + self.openProjectAction.triggered.connect(self.openProject) + self.projectMenu.addAction(self.openProjectAction) + self.helpAction = QAction('Help', self) + self.helpAction.setShortcut(QKeySequence('F1')) + self.helpMenu.addAction(self.helpAction) + self.aboutAction = QAction('About', self) + self.helpMenu.addAction(self.aboutAction) + self.aboutAction.triggered.connect(self.aboutDialog.open) + + self.newFileAction = QAction("New File", self) + self.fileMenu.addAction(self.newFileAction) + self.newFileAction.triggered.connect(self.newFile) + self.saveFileAction = QAction("Save File", self) + self.fileMenu.addAction(self.saveFileAction) + self.saveFileAction.triggered.connect(self.saveFile) + self.closeFileAction = QAction("Close File", self) + self.fileMenu.addAction(self.closeFileAction) + self.closeFileAction.triggered.connect(self.closeFile) + + cascadeAction = QAction("Cascade windows", self) + cascadeAction.triggered.connect(self.mdiArea.cascadeSubWindows) + self.viewMenu.addAction(cascadeAction) + tileAction = QAction('Tile windows', self) + tileAction.triggered.connect(self.mdiArea.tileSubWindows) + self.viewMenu.addAction(tileAction) + + # Load settings: + self.settings = QSettings('windelsoft', 'lcfoside') + self.loadSettings() + + def addComponent(self, name, widget): + dw = QDockWidget(name) + dw.setWidget(widget) + dw.setObjectName(name) + self.addDockWidget(Qt.RightDockWidgetArea, dw) + self.viewMenu.addAction(dw.toggleViewAction()) + + # File handling: + def newFile(self): + ce = CodeEdit() + w = self.mdiArea.addSubWindow(ce) + ce.show() + + def saveFile(self): + ac = self.activeMdiChild() + if ac: + ac.saveFile() + + def saveAll(self): + pass + + def openFile(self): + # TODO + pass + + def closeFile(self): + ac = self.activeMdiChild() + if ac: + self.mdiArea.removeSubWindow(ac) + + def loadFile(self, filename): + # Find existing mdi widget: + wid = self.findMdiChild(filename) + if wid: + self.mdiArea.setActiveSubWindow(wid.parent()) + return wid + + # Create a new one: + ce = CodeEdit() + source = self.project.loadProjectFile(filename) + ce.setSource(source) + self.mdiArea.addSubWindow(ce) + ce.show() + return ce + + # MDI: + def activeMdiChild(self): + aw = self.mdiArea.activeSubWindow() + if aw: + return aw.widget() + else: + return None + + def findMdiChild(self, filename): + for window in self.mdiArea.subWindowList(): + wid = window.widget() + if wid.filename == filename: + return wid + return None + + def allChildren(self): + c = [] + for window in self.mdiArea.subWindowList(): + wid = window.widget() + c.append(wid) + return c + + # Settings: + def loadSettings(self): + if self.settings.contains('mainwindowstate'): + self.restoreState(self.settings.value('mainwindowstate')) + if self.settings.contains('mainwindowgeometry'): + self.restoreGeometry(self.settings.value('mainwindowgeometry')) + if self.settings.contains('openedproject'): + projectfile = self.settings.value('openedproject') + self.loadProject(projectfile) + + def closeEvent(self, ev): + self.settings.setValue('mainwindowstate', self.saveState()) + self.settings.setValue('mainwindowgeometry', self.saveGeometry()) + if self.project: + self.settings.setValue('openedproject', self.project.filename) + # TODO: ask for save of opened files + ev.accept() + + # Error handling: + def nodeSelected(self, node): + ce = self.activeMdiChild() + if not ce: + return + if node.location: + row, col = node.location + ce.highlightErrorLocation( row, col ) + else: + ce.clearErrors() + + def errorSelected(self, err): + row, col, msg = err + ce = self.activeMdiChild() + if not ce: + return + ce.highlightErrorLocation(row, col) + + # Project loading: + def loadProject(self, filename): + self.project = Project(filename) + self.projectview.setProject(self.project) + + def openProject(self): + filename = QFileDialog.getOpenFileName(self, \ + "Choose project file", "", "lcfos Project files (*.lcp)") + if filename: + self.loadProject(filename) + + # Build recepy: + def buildFile(self): + """ Build project """ + self.saveAll() + self.buildOutput.clear() + self.buildOutput.append(str(self.compiler)) + mods = self.compiler.compileProject(self.project) + + self.builderrors.setErrorList(self.compiler.errorlist) + self.astViewer.setAst(mods[0]) + for err in self.compiler.errorlist: + self.buildOutput.append(str(err)) + self.buildOutput.append("Done!") + diff -r fd7d5069734e -r 32078200cdd6 python/apps/lcfos.png Binary file python/apps/lcfos.png has changed diff -r fd7d5069734e -r 32078200cdd6 python/apps/runbuild.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/python/apps/runbuild.py Sun Oct 07 17:04:10 2012 +0200 @@ -0,0 +1,16 @@ +import sys + +# Compiler imports: +from compiler.compiler import Compiler +from project import Project + +if __name__ == '__main__': + if len(sys.argv) < 2: + print('Use {0} projectfile'.format(sys.argv[0])) + sys.exit(-1) + filename = sys.argv[1] + project = Project() + project.load(filename) + pc = Compiler() + pc.compileProject(project) + diff -r fd7d5069734e -r 32078200cdd6 python/apps/runide.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/python/apps/runide.py Sun Oct 07 17:04:10 2012 +0200 @@ -0,0 +1,14 @@ +import sys +from PyQt4.QtGui import QApplication + +# Compiler imports: +from compiler.compiler import Compiler +from ide.ide import Ide + +if __name__ == '__main__': + app = QApplication(sys.argv) + ide = Ide() + ide.compiler = Compiler() + ide.show() + app.exec_() + diff -r fd7d5069734e -r 32078200cdd6 python/ide/compiler/__init__.py --- a/python/ide/compiler/__init__.py Sun Oct 07 16:56:50 2012 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,2 +0,0 @@ -# File to make this directory a package. - diff -r fd7d5069734e -r 32078200cdd6 python/ide/compiler/assembler.py --- a/python/ide/compiler/assembler.py Sun Oct 07 16:56:50 2012 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,354 +0,0 @@ -""" - Assembler code generation functions -""" - -from .errors import Error - -modrm = {'rax': 0, 'rbx': 1} - -# Table 3.1 of the intel manual: -# use REX.W on the table below: -regs64 = {'rax': 0,'rcx':1,'rdx':2,'rbx':3,'rsp':4,'rbp':5,'rsi':6,'rdi':7,'r8':0,'r9':1,'r10':2,'r11':3,'r12':4,'r13':5,'r14':6,'r15':7} -regs32 = {'eax': 0, 'ecx':1, 'edx':2, 'ebx': 3, 'esp': 4, 'ebp': 5, 'esi':6, 'edi':7} -regs8 = {'al':0,'cl':1,'dl':2,'bl':3,'ah':4,'ch':5,'dh':6,'bh':7} - -# Calculation of the rexb bit: -rexbit = {'rax': 0, 'rcx':0, 'rdx':0, 'rbx': 0, 'rsp': 0, 'rbp': 0, 'rsi':0, 'rdi':0,'r8':1,'r9':1,'r10':1,'r11':1,'r12':1,'r13':1,'r14':1,'r15':1} - -# Helper functions: -def imm64(x): - """ represent 64 bits integer in little endian 8 bytes""" - if x < 0: - x = x + (1 << 64) - x = x & 0xFFFFFFFFFFFFFFFF - return [ (x >> (p*8)) & 0xFF for p in range(8) ] - -def imm32(x): - """ represent 32 bits integer in little endian 4 bytes""" - if x < 0: - x = x + (1 << 32) - x = x & 0xFFFFFFFF - return [ (x >> (p*8)) & 0xFF for p in range(4) ] - -def imm8(x): - if x < 0: - x = x + (1 << 8) - x = x & 0xFF - return [ x ] - -def modrm(mod=0, rm=0, reg=0): - """ Construct the modrm byte from its components """ - assert(mod <= 3) - assert(rm <= 7) - assert(reg <= 7) - return (mod << 6) | (reg << 3) | rm - -def rex(w=0, r=0, x=0, b=0): - """ Create a REX prefix byte """ - assert(w <= 1) - assert(r <= 1) - assert(x <= 1) - assert(b <= 1) - return 0x40 | (w<<3) | (r<<2) | (x<<1) | b - -def sib(ss=0, index=0, base=0): - assert(ss <= 3) - assert(index <= 7) - assert(base <= 7) - return (ss << 6) | (index << 3) | base - -tttn = {'L':0xc,'G':0xf,'NE':0x5,'GE':0xd,'LE':0xe, 'E':0x4} - -# Actual instructions: -def nearjump(distance, condition=None): - """ jmp imm32 """ - lim = (1<<30) - if abs(distance) > lim: - Error('near jump cannot jump over more than {0} bytes'.format(lim)) - if condition: - if distance < 0: - distance -= 6 # Skip own instruction - opcode = 0x80 | tttn[condition] # Jcc imm32 - return [0x0F, opcode] + imm32(distance) - else: - if distance < 0: - distance -= 5 # Skip own instruction - return [ 0xE9 ] + imm32(distance) - -def shortjump(distance, condition=None): - """ jmp imm8 """ - lim = 118 - if abs(distance) > lim: - Error('short jump cannot jump over more than {0} bytes'.format(lim)) - if distance < 0: - distance -= 2 # Skip own instruction - if condition: - opcode = 0x70 | tttn[condition] # Jcc rel8 - else: - opcode = 0xeb # jmp rel8 - return [opcode] + imm8(distance) - -# Helper that determines jump type: -def reljump(distance): - if abs(distance) < 110: - return shortjump(distance) - else: - return nearjump(distance) - -def push(reg): - if reg in regs64: - if rexbit[reg] == 1: - return [0x41, 0x50 + regs64[reg]] - else: - return [0x50 + regs64[reg]] - else: - Error('push for {0} not implemented'.format(reg)) - -def pop(reg): - if reg in regs64: - if rexbit[reg] == 1: - rexprefix = rex(b=1) - opcode = 0x58 + regs64[reg] - return [rexprefix, opcode] - else: - opcode = 0x58 + regs64[reg] - return [ opcode ] - else: - Error('pop for {0} not implemented'.format(reg)) - -def INT(number): - opcode = 0xcd - return [opcode] + imm8(number) - -def syscall(): - return [0x0F, 0x05] - -def call(distance): - if type(distance) is int: - return [0xe8]+imm32(distance) - elif type(distance) is str and distance in regs64: - reg = distance - opcode = 0xFF # 0xFF /2 == call r/m64 - mod_rm = modrm(mod=3, reg=2, rm=regs64[reg]) - if rexbit[reg] == 1: - rexprefix = rex(b=rexbit[reg]) - return [rexprefix, opcode, mod_rm] - else: - return [opcode, mod_rm] - else: - Error('Cannot call to {0}'.format(distance)) - -def ret(): - return [ 0xc3 ] - -def increg64(reg): - assert(reg in regs64) - rexprefix = rex(w=1, b=rexbit[reg]) - opcode = 0xff - mod_rm = modrm(mod=3, rm=regs64[reg]) - return [rexprefix, opcode, mod_rm] - -def prepost8(r8, rm8): - assert(r8 in regs8) - pre = [] - if type(rm8) is list: - # TODO: merge mem access with prepost for 64 bits - if len(rm8) == 1: - base, = rm8 - if type(base) is str and base in regs64: - assert(not base in ['rbp', 'rsp', 'r12', 'r13']) - mod_rm = modrm(mod=0, rm=regs64[base], reg=regs8[r8]) - if rexbit[base] == 1: - pre.append(rex(b=1)) - post = [mod_rm] - else: - Error('One arg of type {0} not implemented'.format(base)) - elif len(rm8) == 2: - base, offset = rm8 - assert(type(offset) is int) - assert(base in regs64) - - if base == 'rsp' or base == 'r12': - Error('Cannot use rsp or r12 as base yet') - if rexbit[base] == 1: - pre.append( rex(b=1) ) - mod_rm = modrm(mod=1, rm=regs64[base], reg=regs8[r8]) - post = [mod_rm] + imm8(offset) - else: - Error('not supporting prepost8 with list len {0}'.format(len(rm8))) - else: - Error('Not supporting move with reg8 {0}'.format(r8)) - return pre, post - -def prepost(r64, rm64): - assert(r64 in regs64) - if type(rm64) is list: - if len(rm64) == 3: - base, index, disp = rm64 - assert(base in regs64) - assert(index in regs64) - assert(type(disp) is int) - # Assert that no special cases are used: - # TODO: swap base and index to avoid special cases - # TODO: exploit special cases and make better code - assert(index != 'rsp') - - rexprefix = rex(w=1, r=rexbit[r64], x=rexbit[index], b=rexbit[base]) - # mod=1 and rm=4 indicates a SIB byte: [--][--]+imm8 - mod_rm = modrm(mod=1, rm=4, reg=regs64[r64]) - si_b = sib(ss=0, index=regs64[index], base=regs64[base]) - return [rexprefix], [mod_rm, si_b] + imm8(disp) - elif len(rm64) == 2: - base, offset = rm64 - assert(type(offset) is int) - if base == 'RIP': - # RIP pointer relative addressing mode! - rexprefix = rex(w=1, r=rexbit[r64]) - mod_rm = modrm(mod=0, rm=5, reg=regs64[r64]) - return [rexprefix], [mod_rm] + imm32(offset) - else: - assert(base in regs64) - - if base == 'rsp' or base == 'r12': - # extended function that uses SIB byte - rexprefix = rex(w=1, r=rexbit[r64], b=rexbit[base]) - # rm=4 indicates a SIB byte follows - mod_rm = modrm(mod=1, rm=4, reg=regs64[r64]) - # index=4 indicates that index is not used - si_b = sib(ss=0, index=4, base=regs64[base]) - return [rexprefix], [mod_rm, si_b] + imm8(offset) - else: - rexprefix = rex(w=1, r=rexbit[r64], b=rexbit[base]) - mod_rm = modrm(mod=1, rm=regs64[base], reg=regs64[r64]) - return [rexprefix], [mod_rm] + imm8(offset) - elif len(rm64) == 1: - offset = rm64[0] - if type(offset) is int: - rexprefix = rex(w=1, r=rexbit[r64]) - mod_rm = modrm(mod=0, rm=4,reg=regs64[r64]) - si_b = sib(ss=0, index=4,base=5) # 0x25 - return [rexprefix], [mod_rm, si_b] + imm32(offset) - else: - Error('Memory reference of type {0} not implemented'.format(offset)) - else: - Error('Memory reference not implemented') - elif rm64 in regs64: - rexprefix = rex(w=1, r=rexbit[r64], b=rexbit[rm64]) - mod_rm = modrm(3, rm=regs64[rm64], reg=regs64[r64]) - return [rexprefix], [mod_rm] - -def leareg64(rega, m): - opcode = 0x8d # lea r64, m - pre, post = prepost(rega, m) - return pre + [opcode] + post - -def mov(rega, regb): - if type(regb) is int: - pre = [rex(w=1, b=rexbit[rega])] - opcode = 0xb8 + regs64[rega] - post = imm64(regb) - elif type(regb) is str: - if regb in regs64: - opcode = 0x89 # mov r/m64, r64 - pre, post = prepost(regb, rega) - elif regb in regs8: - opcode = 0x88 # mov r/m8, r8 - pre, post = prepost8(regb, rega) - else: - Error('Unknown register {0}'.format(regb)) - elif type(rega) is str: - if rega in regs64: - opcode = 0x8b # mov r64, r/m64 - pre, post = prepost(rega, regb) - else: - Error('Unknown register {0}'.format(rega)) - else: - Error('Move of this kind {0}, {1} not implemented'.format(rega, regb)) - return pre + [opcode] + post - -def xorreg64(rega, regb): - rexprefix = rex(w=1, r=rexbit[regb], b=rexbit[rega]) - opcode = 0x31 # XOR r/m64, r64 - # Alternative is 0x33 XOR r64, r/m64 - mod_rm = modrm(3, rm=regs64[rega], reg=regs64[regb]) - return [rexprefix, opcode, mod_rm] - -# integer arithmatic: -def addreg64(rega, regb): - if regb in regs64: - pre, post = prepost(regb, rega) - opcode = 0x01 # ADD r/m64, r64 - return pre + [opcode] + post - elif type(regb) is int: - if regb < 100: - rexprefix = rex(w=1, b=rexbit[rega]) - opcode = 0x83 # add r/m, imm8 - mod_rm = modrm(3, rm=regs64[rega], reg=0) - return [rexprefix, opcode, mod_rm]+imm8(regb) - elif regb < (1<<31): - rexprefix = rex(w=1, b=rexbit[rega]) - opcode = 0x81 # add r/m64, imm32 - mod_rm = modrm(3, rm=regs64[rega], reg=0) - return [rexprefix, opcode, mod_rm]+imm32(regb) - else: - Error('Constant value too large!') - else: - Error('unknown second operand!'.format(regb)) - -def subreg64(rega, regb): - if regb in regs64: - pre, post = prepost(regb, rega) - opcode = 0x29 # SUB r/m64, r64 - return pre + [opcode] + post - elif type(regb) is int: - if regb < 100: - rexprefix = rex(w=1, b=rexbit[rega]) - opcode = 0x83 # sub r/m, imm8 - mod_rm = modrm(3, rm=regs64[rega], reg=5) - return [rexprefix, opcode, mod_rm]+imm8(regb) - elif regb < (1<<31): - rexprefix = rex(w=1, b=rexbit[rega]) - opcode = 0x81 # sub r/m64, imm32 - mod_rm = modrm(3, rm=regs64[rega], reg=5) - return [rexprefix, opcode, mod_rm]+imm32(regb) - else: - Error('Constant value too large!') - - else: - Error('unknown second operand!'.format(regb)) - -def idivreg64(reg): - rexprefix = rex(w=1, b=rexbit[reg]) - opcode = 0xf7 # IDIV r/m64 - mod_rm = modrm(3, rm=regs64[reg], reg=7) - return [rexprefix, opcode, mod_rm] - -def imulreg64_rax(reg): - rexprefix = rex(w=1, b=rexbit[reg]) - opcode = 0xf7 # IMUL r/m64 - mod_rm = modrm(3, rm=regs64[reg], reg=5) - return [rexprefix, opcode, mod_rm] - -def imulreg64(rega, regb): - pre, post = prepost(rega, regb) - opcode = 0x0f # IMUL r64, r/m64 - opcode2 = 0xaf - return pre + [opcode, opcode2] + post - -def cmpreg64(rega, regb): - if regb in regs64: - pre, post = prepost(regb, rega) - opcode = 0x39 # CMP r/m64, r64 - return pre + [opcode] + post - elif type(regb) is int: - rexprefix = rex(w=1, b=rexbit[rega]) - opcode = 0x83 # CMP r/m64, imm8 - mod_rm = modrm(3, rm=regs64[rega], reg=7) - return [rexprefix, opcode, mod_rm] + imm8(regb) - - else: - Error('not implemented cmp64') - -# Mapping that maps string names to the right functions: -opcodes = {'mov':(mov,2), 'lea':(leareg64,2), 'int':(INT,1), 'syscall':(syscall,0)} - diff -r fd7d5069734e -r 32078200cdd6 python/ide/compiler/builtin.py --- a/python/ide/compiler/builtin.py Sun Oct 07 16:56:50 2012 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,10 +0,0 @@ -from .nodes import * - -boolean = BaseType('boolean', 8) # Choose: 1 or 8 bytes? -integer = BaseType('integer', 8) -real = BaseType('real', 8) -char = BaseType('char', 1) -void = BaseType('void', 0) - -chr_func = BuiltinProcedure('chr', ProcedureType([Parameter('value', 'x', integer)], char)) - diff -r fd7d5069734e -r 32078200cdd6 python/ide/compiler/codegenerator.py --- a/python/ide/compiler/codegenerator.py Sun Oct 07 16:56:50 2012 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,487 +0,0 @@ -""" - Code generation for 64 bits intel processors -""" - -from .nodes import * -from .errors import Error -from .builtin import real, integer, boolean, char -from .assembler import * - -class CodeGenerator: - def __init__(self): - self.strings = [] - self.initialize() - def initialize(self): - # Register descriptors: - self.freeregs = 'r8,r9,r10,r11,r12,r13,r14,r15'.split(',') - self.usedregs = [] - # Members to accumulate the result into: - # The result is an image of bytecode and global variable space. - # Global variables a referenced by RIP relative addressing. - self.image = [] - self.rip = 0 # The current instruction pointer location. - # TODO: backpatch list here? - - # Functions to modify the code image - def addCode(self, code): - assert(type(code) is list) - self.image += code - self.rip += len(code) - def fixCode(self, position, code): - self.image[position:position+len(code)] = code - def align(self, b): - while (self.rip % b) != 0: - self.addCode([0]) - - def saveAllRegisters(self): - regs = list(self.usedregs.keys()) - for reg in regs: - code += self.saveRegister(reg) - - def saveRegister(self, reg): - code = [] - if reg in self.usedregs.keys(): - code.append('mov {0}, {1}'.format(self.usedregs[reg], reg)) - del self.usedregs[reg] - self.freeregs.append(reg) - - def getreg(self, node): - """ acquire a working register for a certain node.""" - # Temporary register bypass action: - if len(self.freeregs) > 0: - reg = self.freeregs.pop(0) - self.usedregs.append(reg) - else: - Error('No more free regs') - node.reg = reg - - def freereg(self, node): - reg = node.reg - node.reg = None - self.freeregs.append(reg) - self.usedregs.remove(reg) - - # Helpers to load and retrieve designated objects: - def storeRegInDesignator(self, reg, designator): - assert(type(reg) is str) - assert(type(designator) is Designator) - if len(designator.selectors) > 0: - self.gencode( designator ) # Load the pointer into some register - self.addCode( mov([designator.reg, 0x0], reg) ) - self.freereg( designator ) - else: - if designator.obj.isLocal: - # Relative from rbp register - mem = ['rbp', designator.obj.offset] - self.addCode( mov(mem, reg) ) - else: - # Relative from RIP after move - self.addCode( mov(['RIP', 0x0], reg) ) - self.fixCode(self.rip - 4, imm32(designator.obj.offset - self.rip) ) - - # Code generation functions: - def genexprcode(self, node): - """ - Generate code for expressions! - Recursively evaluates, and ensures a register contains the answer. - register is an integer register or a floating point reg - """ - if isinstance(node, Binop): - """ Handle a binary operation (two arguments) of some kind """ - self.genexprcode(node.a) - self.genexprcode(node.b) - - if node.op == 'mod': - assert(node.typ.isType(integer)) - self.addCode(mov('rax', node.a.reg)) - self.addCode(xorreg64('rdx', 'rdx')) # Extend divided number with zeros - self.addCode(idivreg64(node.b.reg)) # divide rdx:rax with reg - node.reg = node.a.reg - self.freereg(node.b) # give up register that contains b - self.addCode(mov(node.reg, 'rdx')) # move remainder into result - elif node.op == 'div': - assert(node.typ.isType(integer)) - self.addCode(mov('rax', node.a.reg)) - self.addCode(xorreg64('rdx', 'rdx')) # Extend divided number with zeros - self.addCode(idivreg64(node.b.reg)) # divide rdx:rax with reg - node.reg = node.a.reg - self.freereg(node.b) # give up register that contains b - self.addCode(mov(node.reg, 'rax')) # move result into reg - elif node.op == '*': - if node.typ.isType(integer): - self.addCode(imulreg64(node.a.reg, node.b.reg)) - node.reg = node.a.reg - self.freereg(node.b) - else: - Error('{0} for * not implemented'.format(node.typ)) - elif node.op == '+': - if node.typ.isType(integer): - self.addCode(addreg64(node.a.reg, node.b.reg)) - node.reg = node.a.reg - self.freereg(node.b) - else: - Error('{0} for + not implemented'.format(node.typ)) - elif node.op == '-': - if node.typ.isType(integer): - self.addCode(subreg64(node.a.reg, node.b.reg)) - node.reg = node.a.reg - self.freereg(node.b) - else: - Error('{0} for - not implemented'.format(node.typ)) - else: - Error('Unknown Binop {0}'.format(node.op)) - - elif type(node) is Unop: - if node.op == 'INTTOREAL': - self.genexprcode(node.a) - node.reg = node.a.reg - # TODO use 'FILD' instruction - freg = 12 - code.append('Unop inttoreal TODO') - elif node.op == 'ABS': - if isType(node.typ, real): - code = [0xD9, 0xE1] # st(0) = fabs st(0) - Error('ABS error integer') - elif isType(node.typ, integer): - code = [] - Error('ABS error integer') - else: - Error('ABS error') - else: - Error('Unknown Unop {0}'.format(node.op)) - - elif isinstance(node, Designator): - # dereference, array index. Make sure that the result comes into a register - if len(node.selectors) > 0: - self.gencode(node) # Load the pointer into some register - - # Now we can access the object at location '[node.reg]': - if node.typ.isType(integer): - self.addCode( mov(node.reg, [node.reg, 0x0]) ) - else: - Error('Only integer types implemented') - else: - # No selectors, load variable directly - if node.obj.typ.isType(integer): - if type(node.obj) is Constant: - self.genexprcode(node.obj) - node.reg = node.obj.reg - else: - self.getreg(node) - # Get a register to store the integer value - if node.obj.isLocal: - # relative to rbp: - self.addCode( mov(node.reg, ['rbp', node.obj.offset]) ) - else: - self.addCode(mov(node.reg, ['RIP', 0x0])) - self.fixCode(self.rip-4, imm32(node.obj.offset - self.rip)) - else: - Error('Cannot load variable type {0}'.format(node.typ)) - - elif isinstance(node, Relop): - # Create a boolean from operands - # TODO create an alternative for expressions used as conditions. - self.genexprcode(node.a) - self.genexprcode(node.b) - - if node.a.typ.isType(integer): - instructions = {'<': 'L', '>': 'G', '<>': 'NE', '>=': 'GE', '<=': 'LE', '=':'E'} - if not node.relop in instructions.keys(): - Error('Unimplemented relop: '+str(node.relop)) - instr = instructions[node.relop] - - node.reg = node.a.reg - self.addCode( cmpreg64(node.a.reg, node.b.reg) ) - self.addCode( shortjump(0x0, condition=instr) ) # jump over 0 code and jmp - fixloc1 = self.rip - 1 - rip1 = self.rip - self.addCode( xorreg64(node.reg, node.reg) ) - self.addCode( shortjump(0x0) ) # Jump over 1 code - fixloc2 = self.rip - 1 - self.fixCode(fixloc1, imm8(self.rip - rip1)) - rip2 = self.rip - self.addCode( xorreg64(node.reg, node.reg) ) - self.addCode( increg64(node.reg) ) - self.fixCode(fixloc2, imm8(self.rip - rip2)) - - self.freereg(node.b) - else: - Error('Relop not implemented for {0}'.format(node.a.typ)) - - elif type(node) is Constant: - if node.typ.isType(integer): - self.getreg(node) - self.addCode(mov(node.reg, node.value)) - elif node.typ.isType(real): - code += self.getreg(node) - Error('TODO: get real reg') - # TODO: get a fixed point reg, and load the variable in there - else: - Error('Howto generate code for {0}?'.format(node)) - - elif type(node) is ProcedureCall: - if type(node.proc.obj) is BuiltinProcedure: - # Handle builtin procedures different, these not always call - # a function, but generate code. - bi = node.proc.obj - if bi.name == 'chr': - arg = node.args[0] - self.genexprcode(arg) - # Store character in full width register: - # TODO: store in char only register - node.reg = arg.reg - else: - Error('Unknown builtin function {0}'.format(bi.name)) - else: - # Use generic procedure call first - self.gencode(node) - # Retrieve result: - if node.typ.isType(integer): - # Store result! - self.getreg(node) - self.addCode( mov(node.reg, 'rax') ) - else: - Error('Return type not supported {0}'.format(node.typ)) - else: - Error('Cannot generate expression code for: {0}'.format(node)) - - def gencode(self, node): - """ Code generation function for AST nodes """ - if isinstance(node, Module): - # for all imports make a list of pointer to the actual procedures: - for imp in node.imports: - imp.offset = self.rip - self.addCode( [0x0]*8 ) - # global variable storage allocation - variables = node.symtable.getAllLocal(Variable) - for var in variables: - var.isLocal = False - var.offset = self.rip - self.addCode( [0x00] * var.typ.size ) # TODO initial values here? - self.align(8) - # TODO: mark end of data and start of code inside image - # TODO: round data to page size to enable protection by loader. - # Procedure code generation: - procedures = node.symtable.getAllLocal(Procedure) - node.procs = procedures - for proc in procedures: - self.gencode(proc) - # Module init code: - node.initcodeentry = self.rip - self.gencode(node.initcode) - self.addCode( ret() ) - # TODO: how to return from module init code? far return?? - - elif type(node) is Procedure: - # calculate offsets for local variables and parameters - # Variable location relative to 'rbp' register - variables = node.symtable.getAllLocal(Variable) - offset = 0 - paramoffset = 16 - for var in variables: - var.isLocal = True - if not var.isParameter: - offset += var.typ.size - # Offset is negative of rbp in stack frame - var.offset = -offset - node.framesize = offset - # Calculate offsets of parameters relative to rbp register - for par in reversed(node.typ.parameters): - pvar = node.symtable.getLocal(Variable, par.name) - pvar.offset = paramoffset - paramoffset += pvar.typ.size - - # code generation - node.entrypoint = self.rip - self.addCode(push('rbp')) - self.addCode(mov('rbp', 'rsp')) # Setup the base pointer - self.addCode(subreg64('rsp', node.framesize)) # reserve space for locals - self.gencode(node.block) - if node.retexpr: - if node.retexpr.typ.isType(integer): - self.genexprcode(node.retexpr) - self.addCode( mov('rax', node.retexpr.reg) ) - self.freereg(node.retexpr) - else: - Error('Cannot return this kind yet {0}'.format(node.retexpr.typ)) - self.addCode( addreg64('rsp', node.framesize) ) - self.addCode( pop('rbp') ) - self.addCode( ret() ) - assert(len(self.usedregs) == 0) - - elif isinstance(node, StatementSequence): - for s in node.statements: - self.gencode(s) - - elif type(node) is ProcedureCall: - # Prepare parameters on the stack: - stacksize = 0 - assert(len(node.args) == len(node.proc.typ.parameters)) - for arg, param in zip(node.args, node.proc.typ.parameters): - - if param.kind == 'value': - self.genexprcode(arg) - self.addCode( push(arg.reg) ) - self.freereg( arg ) - stacksize += 8 - else: - Error('Parameter kind other than value') - - # Calculate address using designator - if type(node.proc.obj) is Procedure: - self.addCode( call(0x0) ) - self.fixCode( self.rip - 4, imm32(node.proc.obj.entrypoint - self.rip)) - elif type(node.proc.obj) is ImportedSymbol: - # Load the entry point of the import table - self.getreg(node.proc.obj) - # Load the address of the procedure: - self.addCode( mov(node.proc.obj.reg, ['RIP', 0x0]) ) - self.fixCode( self.rip - 4, imm32(node.proc.obj.offset - self.rip) ) - # Call to the address in register: - self.addCode( call(node.proc.obj.reg) ) - # Free register that holds the address of the object - self.freereg( node.proc.obj ) - elif type(node.proc.obj) is BuiltinProcedure: - if node.proc.obj.name == 'chr': - print('int to char') - else: - Error('Unknown builtin function {0}'.format(node.proc.obj.name)) - else: - Error('Cannot call designator of type {0}'.format(node.proc.obj)) - - # Restore stack (pop all arguments of): - self.addCode(addreg64('rsp', stacksize)) - - elif type(node) is Assignment: - if node.lval.typ.isType(integer): - # TODO if node.rval is Constant of some datatype, move it to mem directly - self.genexprcode(node.rval) # Calculate the value that has to be stored. - self.storeRegInDesignator(node.rval.reg, node.lval) - self.freereg(node.rval) - else: - Error('Assignments of other types not implemented') - # TODO if left and right are designators, do some sort of memcpy. - - elif type(node) is IfStatement: - self.genexprcode(node.condition) - self.addCode( cmpreg64(node.condition.reg, 1) ) - self.freereg(node.condition) - if node.falsestatement: - # If with else clause - self.addCode( nearjump(0x0, condition='NE') ) # if Not Equal jump to false - rip1 = self.rip - fixloc1 = self.rip - 4 - self.gencode(node.truestatement) - self.addCode( nearjump( 0x0 ) ) # jump over false code - fixloc2 = self.rip - 4 - self.fixCode(fixloc1, imm32(self.rip - rip1)) - rip2 = self.rip - self.gencode(node.falsestatement) - self.fixCode(fixloc2, imm32(self.rip - rip2)) - else: - # If without else clause - self.addCode( nearjump(0x0, condition='NE') ) # if Not Equal jump to false - rip1 = self.rip - fixloc1 = self.rip - 4 - self.gencode(node.truestatement) - self.fixCode(fixloc1, imm32(self.rip - rip1)) # Fixup near jump over true code. - - elif isinstance(node, WhileStatement): - rip1 = self.rip # Store the start of the while loop - self.genexprcode(node.condition) - self.addCode( cmpreg64(node.condition.reg, 1) ) # Test condition for true-ness - self.freereg(node.condition) - self.addCode( nearjump(0x0, condition='NE') ) # If Not Equal jump over while code AND jump back (fix later) - fixloc1 = self.rip - 4 - rip2 = self.rip - self.gencode(node.dostatements) - self.addCode( nearjump(0x0) ) # JMP to condition, fix exact jump position below - fixloc2 = self.rip - 4 - rip3 = self.rip # end of while loop - self.fixCode(fixloc2, imm32(rip1 - rip3)) # Fixup jump to start of while loop - self.fixCode(fixloc1, imm32(rip3 - rip2)) # Fixup jump out of while loop - - elif type(node) is ForStatement: - # Initial load of iterator variable: - self.genexprcode(node.begin) - self.genexprcode(node.end) - # TODO: link reg with variable so that a register is used instead of a variable - iterreg = node.begin.reg # Get the register used for the loop - #self.addCode(cmpreg64(iterreg, node.endvalue)) - rip1 = self.rip - self.gencode(node.statements) - #self.loadDesignatorInReg(node. - #self.addCode( addreg64(node.variable, node.increment) ) - self.addCode(nearjump(0x0)) - fixloc1 = self.rip - 4 - rip2 = self.rip - self.fixCode(fixloc1, imm32(rip1 - rip2)) - - self.freereg(node.begin) # Release register used in loop - self.freereg(node.end) - Error('No implementation of FOR statement') - - elif type(node) is AsmCode: - def processOperand(op): - if type(op) is list: - if type(op[0]) is Variable: - var = op[0] - if var.isLocal: - return ['rbp', var.offset] - else: - Error('Can only use local variables in inline assembler') - return op - for asmline in node.asmcode: - opcode, operands = asmline - operands = [processOperand(opx) for opx in operands] - print('assembling', opcode, *operands) - func,nargs = opcodes[opcode] - code = func(*operands) - self.addCode(code) - - elif isinstance(node, EmptyStatement): - pass - - - elif type(node) is StringConstant: - self.strings.append(node) - self.data.append(node.value) # Add string to the data section - - elif type(node) is Designator: - if len(node.selectors) > 0: - self.getreg(node) - # Load starting address - if node.obj.isLocal: - self.addCode( leareg64(node.reg, ['rbp', node.obj.offset]) ) - else: - # Global variables need to be relocated... - self.addCode(leareg64(node.reg, ['RIP', 0])) - self.fixCode(self.rip - 4, imm32(node.obj.offset - self.rip)) - # Loop over all designators.. - for selector in node.selectors: - if type(selector) is Index: - # Deref an array index - self.genexprcode(selector.index) - self.getreg(selector) - self.addCode( mov(selector.reg, selector.typ.elementType.size) ) - self.addCode( imulreg64(selector.reg, selector.index.reg ) ) - self.freereg(selector.index) - self.addCode(addreg64(node.reg, selector.reg)) - self.freereg(selector) - elif type(selector) is Field: - print('Field') - Error('Field not implemented') - else: - Error('Unknown selector') - else: - Error('Can only gencode for designator with selectors') - - else: - print('not generating code for {0}'.format(node)) - - def generatecode(self, ast): - """ code generation front end """ - self.initialize() - self.gencode(ast) - ast.image = self.image - diff -r fd7d5069734e -r 32078200cdd6 python/ide/compiler/compiler.py --- a/python/ide/compiler/compiler.py Sun Oct 07 16:56:50 2012 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,53 +0,0 @@ -import hashlib -# Import compiler components: -from . import lexer -from .parser import Parser -from .codegenerator import CodeGenerator -from .nodes import ExportedSymbol -from .errors import CompilerException - -class Compiler: - versie = '0.9.3' - - def __repr__(self): - return 'LCFOS compiler {0}'.format(self.versie) - - def generateSignature(self, src): - return hashlib.md5(bytes(src,encoding='ascii')).hexdigest() - - def compilesource(self, src): - """ Front end that handles the stages: """ - self.errorlist = [] - # Pass 1: parsing and type checking - tokens = lexer.tokenize(src) # Lexical stage - p = Parser(tokens) - try: - ast = p.parseModule() # Parse a module - except CompilerException as e: - p.errorlist.append( (e.row, e.col, e.msg) ) - if len(p.errorlist) > 0: - self.errorlist = p.errorlist - return - # Pass 2: code generation - CodeGenerator().generatecode(ast) - # Attach a signature: - ast.signature = self.generateSignature(src) - # Generate exported symbols: - ast.exports = [] - for proc in ast.procs: - if proc.public: - sym = ExportedSymbol(proc.name, proc.typ) - sym.imageoffset = proc.entrypoint - ast.exports.append(sym) - return ast - - def compileProject(self, project): - mods = [] - for fname in project.files: - print('Compiling {0}...'.format(fname)) - source = project.loadProjectFile(fname) - mod = self.compilesource(source) - mods.append(mod) - return mods - - diff -r fd7d5069734e -r 32078200cdd6 python/ide/compiler/display.py --- a/python/ide/compiler/display.py Sun Oct 07 16:56:50 2012 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,21 +0,0 @@ -from .nodes import * - -def printNode(node, indent=0): - """ - Print visitor - all printing goes in here - """ - print(' '*indent+str(node)) - if type(node) is Procedure: - print(' '*indent+' PARAMETERS:') - for p in node.parameters: - printNode(p, indent+4) - if node.block: - print(' '*indent+' CODE:') - printNode(node.block, indent+4) - elif type(node) is Module: - print(node.symtable) - printNode(node.initcode, indent+2) - else: - for c in node.getChildren(): - printNode(c, indent+2) diff -r fd7d5069734e -r 32078200cdd6 python/ide/compiler/errors.py --- a/python/ide/compiler/errors.py Sun Oct 07 16:56:50 2012 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,47 +0,0 @@ -""" Error handling routines """ - -class CompilerException(Exception): - def __init__(self, msg, row=0, col=0, filename=None): - self.msg = msg - self.row = row - self.col = col - self.filename = filename - def __repr__(self): - return self.msg - def __str__(self): - return self.msg - -class ErrorNode: - def __init__(self, row, col, msg): - self.row, self.col = row,col - self.msg = msg - -def Error(msg, node=None): - if node is None: - raise CompilerException(msg) - else: - raise CompilerException(msg, node.row, node.col) - -def printError(source, e): - def printLine(row, txt): - print(str(row)+':'+txt) - if e.row == 0: - print('Error: {0}'.format(e.msg)) - else: - lines = source.split('\n') - prerow = e.row - 3 - if prerow < 1: - prerow = 1 - afterrow = e.row + 3 - if afterrow > len(lines): - afterrow = len(lines) - - # print preceding source lines: - for r in range(prerow, e.row): - printLine(r, lines[r-1]) - # print source line containing error: - printLine(e.row, lines[e.row-1]) - print(' '*(len(str(e.row)+':')+e.col-1) + '^ Error: {0}'.format(e.msg)) - # print trailing source line: - for r in range(e.row+1, afterrow+1): - printLine(r, lines[r-1]) diff -r fd7d5069734e -r 32078200cdd6 python/ide/compiler/lexer.py --- a/python/ide/compiler/lexer.py Sun Oct 07 16:56:50 2012 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,71 +0,0 @@ -import collections -import re -from .errors import CompilerException - -""" - Lexical analyzer part. Splits the input character stream into tokens. -""" - -# Token is used in the lexical analyzer: -Token = collections.namedtuple('Token', 'typ val row col') - -keywords = ['and', 'array', 'begin', 'by', 'case', 'const', 'div', 'do', \ - 'else', 'elsif', 'end', 'false', 'for', 'if', 'import', 'in', 'is', \ - 'mod', 'module', 'nil', 'not', 'of', 'or', 'pointer', 'procedure', \ - 'record', 'repeat', 'return', 'then', 'to', 'true', 'type', 'until', 'var', \ - 'while', 'asm' ] - -def tokenize(s): - """ - Tokenizer, generates an iterator that - returns tokens! - - This GREAT example was taken from python re doc page! - """ - tok_spec = [ - ('REAL', r'\d+\.\d+'), - ('HEXNUMBER', r'0x[\da-fA-F]+'), - ('NUMBER', r'\d+'), - ('ID', r'[A-Za-z][A-Za-z\d_]*'), - ('NEWLINE', r'\n'), - ('SKIP', r'[ \t]'), - ('COMMENTS', r'{.*}'), - ('LEESTEKEN', r':=|[\.,=:;\-+*\[\]/\(\)]|>=|<=|<>|>|<'), - ('STRING', r"'.*?'") - ] - tok_re = '|'.join('(?P<%s>%s)' % pair for pair in tok_spec) - gettok = re.compile(tok_re).match - line = 1 - pos = line_start = 0 - mo = gettok(s) - while mo is not None: - typ = mo.lastgroup - val = mo.group(typ) - if typ == 'NEWLINE': - line_start = pos - line += 1 - elif typ == 'COMMENTS': - pass - elif typ != 'SKIP': - if typ == 'ID': - if val in keywords: - typ = val - elif typ == 'LEESTEKEN': - typ = val - elif typ == 'NUMBER': - val = int(val) - elif typ == 'HEXNUMBER': - val = int(val[2:], 16) - typ = 'NUMBER' - elif typ == 'REAL': - val = float(val) - elif typ == 'STRING': - val = val[1:-1] - yield Token(typ, val, line, mo.start()-line_start) - pos = mo.end() - mo = gettok(s, pos) - if pos != len(s): - col = pos - line_start - raise CompilerException('Unexpected character {0}'.format(s[pos]), line, col) - yield Token('END', '', line, 0) - diff -r fd7d5069734e -r 32078200cdd6 python/ide/compiler/modules.py --- a/python/ide/compiler/modules.py Sun Oct 07 16:56:50 2012 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,193 +0,0 @@ -import struct -from .errors import Error -from .nodes import * -from .builtin import integer, real, char, boolean, void -import os.path - -""" - File format for compiled modules. - * [11] magic identifier - * [STR] mod name - * [STR] signature, a md5 signature of the module. - * [I32] size of code - * code image - * [I32] entrypoint for initcode - * imported modules - ** [I32] num of imported modules - *** [STR] name of module - *** signature of the module - *** [I32] offset in the process image where the interface symbols must be placed - * public interface - ** [I32] num of interface elements - *** [STR] proc name - *** [I32] offset in code image - *** [type] return type - *** [I32] number of parameters - **** parameter - ***** parameter kind - ***** parameter name - ***** parameter type -""" - -MAGIC = b'LCFOSMODC' - -loadedModules = [] - -def loadModule(modname): - """ returns a Module object specified by a name """ - # Check if the module was already loaded: - for mod in loadedModules: - if mod.name == modname: - return mod - - # Try to load the module from file: - srcfilename = modname + '.mod' - binfilename = modname + '.bin' - sourceExists = os.path.exists(srcfilename) - if os.path.exists(binfilename): - if sourceExists: - compileModule() - else: - return loadModuleFromFile(binfilename) - else: - Error("Cannot load module '{0}'!".format(modname)) - -def loadModuleFromFile(filename): - f = open(filename, 'rb') - magic = f.read(len(MAGIC)) - assert(magic == MAGIC) - - # Helper functions: - def readI32(): - int32, = struct.unpack(' 0: - operands.append( parseOperand() ) - n = n - 1 - while n > 0: - self.Consume(',') - operands.append(parseOperand()) - n = n - 1 - return operands - self.Consume('asm') - asmcode = [] - while self.token.typ != 'end': - opcode = parseOpcode() - func, numargs = assembler.opcodes[opcode] - operands = parseOperands(numargs) - asmcode.append( (opcode, operands) ) - #print('opcode', opcode, operands) - self.Consume('end') - return AsmCode(asmcode) - - def parseStatement(self): - try: - # Determine statement type based on the pending token: - if self.token.typ == 'if': - return self.parseIfStatement() - elif self.token.typ == 'case': - return self.parseCaseStatement() - elif self.token.typ == 'while': - return self.parseWhileStatement() - elif self.token.typ == 'repeat': - return self.parseRepeatStatement() - elif self.token.typ == 'for': - return self.parseForStatement() - elif self.token.typ == 'asm': - return self.parseAsmcode() - elif self.token.typ == 'ID': - # Assignment or procedure call - designator = self.parseDesignator() - if self.token.typ == '(' and type(designator.typ) is ProcedureType: - return self.parseProcedureCall(designator) - elif self.token.typ == ':=': - return self.parseAssignment(designator) - else: - self.Error('Unknown statement following designator: {0}'.format(self.token)) - else: - # TODO: return empty statement??: - return EmptyStatement() - self.Error('Unknown statement {0}'.format(self.token)) - except CompilerException as e: - print(e) - self.errorlist.append( (e.row, e.col, e.msg)) - # Do error recovery by skipping all tokens until next ; or end - while not (self.token.typ == ';' or self.token.typ == 'end'): - self.Consume(self.token.typ) - return EmptyStatement() - - def parseStatementSequence(self): - """ Sequence of statements seperated by ';' """ - statements = [ self.parseStatement() ] - while self.hasConsumed(';'): - statements.append( self.parseStatement() ) - return StatementSequence( statements ) - - # Parsing expressions: - """ - grammar of expressions: - expression = SimpleExpression [ reloperator SimpleExpression ] - reloperator = '=' | '<=' | '>=' | '<>' - Simpleexpression = [ '+' | '-' ] term { addoperator term } - addoperator = '+' | '-' | 'or' - term = factor { muloperator factor } - muloperator = '*' | '/' | 'div' | 'mod' | 'and' - factor = number | nil | true | false | "(" expression ")" | - designator [ actualparameters ] | 'not' factor - """ - def parseExpression(self): - """ The connector between the boolean and expression domain """ - expr = self.parseSimpleExpression() - if self.token.typ in ['>=','<=','<','>','<>','=']: - relop = self.Consume() - expr2 = self.parseSimpleExpression() - # Automatic type convert to reals: - if isType(expr.typ, real) and isType(expr2.typ, integer): - expr2 = Unop(expr2, 'INTTOREAL', real) - if isType(expr2.typ, real) and isType(expr.typ, integer): - expr = Unop(expr, 'INTTOREAL', real) - # Type check: - if not isType(expr.typ, expr2.typ): - self.Error('Type mismatch in relop') - if isType(expr.typ, real) and relop in ['<>', '=']: - self.Error('Cannot check real values for equality') - - expr = Relop(expr, relop, expr2, boolean) - return expr - - # Parsing arithmatic expressions: - def parseTerm(self): - a = self.parseFactor() - while self.token.typ in ['*', '/', 'mod', 'div', 'and']: - loc = self.getLocation() - op = self.Consume() - b = self.parseTerm() - # Type determination and checking: - if op in ['mod', 'div']: - if not isType(a.typ, integer): - self.Error('First operand should be integer, not {0}'.format(a.typ)) - if not isType(b.typ, integer): - self.Error('Second operand should be integer, not {0}'.format(b.typ)) - typ = integer - elif op == '*': - if isType(a.typ, integer) and isType(b.typ, integer): - typ = integer - elif isType(a.typ, real) or isType(b.typ, real): - if isType(a.typ, integer): - # Automatic type cast - a = Unop(a, 'INTTOREAL', real) - if isType(b.typ, integer): - b = Unop(b, 'INTTOREAL', real) - if not isType(a.typ, real): - self.Error('first operand must be a real!') - if not isType(b.typ, real): - self.Error('second operand must be a real!') - typ = real - else: - self.Error('Unknown operands for multiply: {0}, {1}'.format(a, b)) - elif op == '/': - # Division always yields a real result, for integer division use div - if isType(a.typ, integer): - # Automatic type cast - a = Unop(a, 'INTTOREAL', real) - if isType(b.typ, integer): - b = Unop(b, 'INTTOREAL', real) - if not isType(a.typ, real): - self.Error('first operand must be a real!') - if not isType(b.typ, real): - self.Error('second operand must be a real!') - typ = real - elif op == 'and': - if not isType(a.typ, boolean): - self.Error('First operand of and must be boolean') - if not isType(b.typ, boolean): - self.Error('Second operand of and must be boolean') - typ = boolean - else: - self.Error('Unknown operand {0}'.format(op)) - - a = self.setLocation(Binop(a, op, b, typ), loc) - return a - - def parseFactor(self): - if self.hasConsumed('('): - e = self.parseExpression() - self.Consume(')') - return e - elif self.token.typ == 'NUMBER': - loc = self.getLocation() - val = self.Consume('NUMBER') - return self.setLocation(Constant(val, integer), loc) - elif self.token.typ == 'REAL': - loc = self.getLocation() - val = self.Consume('REAL') - return self.setLocation(Constant(val, real), loc) - elif self.token.typ == 'CHAR': - val = self.Consume('CHAR') - return Constant(val, char) - elif self.token.typ == 'STRING': - txt = self.Consume('STRING') - return StringConstant(txt) - elif self.token.typ in ['true', 'false']: - val = self.Consume() - val = True if val == 'true' else False - return Constant(val, boolean) - elif self.hasConsumed('nil'): - return Constant(0, NilType()) - elif self.hasConsumed('not'): - f = self.parseFactor() - if not isType(f.typ, boolean): - self.Error('argument of boolean negation must be boolean type') - return Unop(f, 'not', boolean) - elif self.token.typ == 'ID': - designator = self.parseDesignator() - # TODO: handle functions different here? - if self.token.typ == '(' and type(designator.typ) is ProcedureType: - return self.parseProcedureCall(designator) - else: - return designator - else: - self.Error('Expected NUMBER, ID or ( expr ), got'+str(self.token)) - - def parseSimpleExpression(self): - """ Arithmatic expression """ - if self.token.typ in ['+', '-']: - # Handle the unary minus - op = self.Consume() - a = self.parseTerm() - typ = a.typ - if not isType(typ,real) and not isType(typ, integer): - self.Error('Unary minus or plus can be only applied to real or integers') - if op == '-': - a = Unop(a, op, typ) - else: - a = self.parseTerm() - while self.token.typ in ['+', '-', 'or']: - loc = self.getLocation() - op = self.Consume() - b = self.parseTerm() - if op in ['+', '-']: - if isType(a.typ, real) or isType(b.typ, real): - typ = real - if isType(a.typ, integer): - # Automatic type cast - a = Unop(a, 'INTTOREAL', real) - if not isType(a.typ, real): - self.Error('first operand must be a real!') - if isType(b.typ, integer): - b = Unop(b, 'INTTOREAL', real) - if not isType(b.typ, real): - self.Error('second operand must be a real!') - elif isType(a.typ, integer) and isType(b.typ, integer): - typ = integer - else: - self.Error('Invalid types {0} and {1}'.format(a.typ, b.typ)) - elif op == 'or': - if not isType(a.typ, boolean): - self.Error('first operand must be boolean for or operation') - if not isType(b.typ, boolean): - self.Error('second operand must be boolean for or operation') - typ = boolean - else: - self.Error('Unknown operand {0}'.format(op)) - a = self.setLocation(Binop(a, op, b, typ), loc) - return a - diff -r fd7d5069734e -r 32078200cdd6 python/ide/compiler/symboltable.py --- a/python/ide/compiler/symboltable.py Sun Oct 07 16:56:50 2012 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,80 +0,0 @@ -from .nodes import * -from .errors import Error - -class SymbolTable: - """ - Symbol table for a current scope. - It has functions: - - hasname for checking for a name in current scope or above - - addSymbol to add an object - """ - def __init__(self, parent=None): - self.parent = parent - self.syms = {} - - def __repr__(self): - return 'Symboltable with {0} symbols\n'.format(len(self.syms)) - - def printTable(self, indent=0): - for name in self.syms: - print(self.syms[name]) - - def getAllLocal(self, cls): - """ Get all local objects of a specific type """ - r = [] - for key in self.syms.keys(): - sym = self.syms[key] - if issubclass(type(sym), cls): - r.append(sym) - return r - - def getLocal(self, cls, name): - if name in self.syms.keys(): - sym = self.syms[name] - if isinstance(sym, cls): - return sym - else: - Error('Wrong type found') - else: - Error('Symbol not found') - - # Retrieving of specific classes of items: - def get(self, cls, name): - if self.hasSymbol(name): - sym = self.getSymbol(name) - if issubclass(type(sym), cls): - return sym - raise SymbolException('type {0} undefined'.format(typename)) - - def has(self, cls, name): - if self.hasSymbol(name): - sym = self.getSymbol(name) - if issubclass(type(sym), cls): - return True - return False - - # Adding and retrieving of symbols in general: - def addSymbol(self, sym): - if sym.name in self.syms.keys(): - raise Exception('Symbol "{0}" redefined'.format(sym.name)) - else: - self.syms[sym.name] = sym - - def getSymbol(self, name): - if name in self.syms.keys(): - return self.syms[name] - else: - if self.parent: - return self.parent.getSymbol(name) - else: - Error('Symbol "{0}" undeclared!'.format(name)) - - def hasSymbol(self, name): - if name in self.syms.keys(): - return True - else: - if self.parent: - return self.parent.hasSymbol(name) - else: - return False - diff -r fd7d5069734e -r 32078200cdd6 python/ide/data/lcfos.png Binary file python/ide/data/lcfos.png has changed diff -r fd7d5069734e -r 32078200cdd6 python/ide/ide/__init__.py --- a/python/ide/ide/__init__.py Sun Oct 07 16:56:50 2012 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,2 +0,0 @@ -# Package - diff -r fd7d5069734e -r 32078200cdd6 python/ide/ide/astviewer.py --- a/python/ide/ide/astviewer.py Sun Oct 07 16:56:50 2012 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,36 +0,0 @@ -from PyQt4.QtCore import * -from PyQt4.QtGui import * - -def astToNamedElement(astNode, parentNode): - """ Helper to convert and AST tree to NamedElement tree: """ - item = QStandardItem(str(astNode)) - item.setData(astNode) - parentNode.appendRow(item) - for c in astNode.getChildren(): - astToNamedElement(c, item) - -# The actual widget: -class AstViewer(QTreeView): - sigNodeSelected = pyqtSignal(object) - def __init__(self, parent=None): - super(AstViewer, self).__init__(parent) - self.setHeaderHidden(True) - self.clicked.connect(self.selectHandler) - - def setAst(self, ast): - """ Create a new model and add all ast elements to it """ - model = QStandardItemModel() - if ast: - astToNamedElement(ast, model.invisibleRootItem()) - self.setModel( model ) - self.expandAll() - - def selectHandler(self, index): - if not index.isValid(): - return - model = self.model() - item = model.itemFromIndex(index) - node = item.data() - self.sigNodeSelected.emit(node) - - diff -r fd7d5069734e -r 32078200cdd6 python/ide/ide/codeeditor.py --- a/python/ide/ide/codeeditor.py Sun Oct 07 16:56:50 2012 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,140 +0,0 @@ -from PyQt4.QtCore import * -from PyQt4.QtGui import * -import compiler.lexer -import os.path - -class MySyntaxHighlighter(QSyntaxHighlighter): - def __init__(self, parent=None): - super(MySyntaxHighlighter, self).__init__(parent) - # Syntax highlighting: - self.rules = [] - fmt = QTextCharFormat() - fmt.setForeground(Qt.darkBlue) - fmt.setFontWeight(QFont.Bold) - for kw in compiler.lexer.keywords: - pattern = '\\b'+kw+'\\b' - self.rules.append( (pattern, fmt) ) - - # Comments: - fmt = QTextCharFormat() - fmt.setForeground(Qt.gray) - fmt.setFontItalic(True) - pattern = '\{.*\}' - self.rules.append( (pattern, fmt) ) - - # Procedure: - fmt = QTextCharFormat() - fmt.setForeground(Qt.blue) - fmt.setFontItalic(True) - #pattern = '(?<=procedure )[A-Za-z]' - # TODO lookbehind does not work, think something else - #self.rules.append( (pattern, fmt) ) - - def highlightBlock(self, text): - for pattern, fmt in self.rules: - expression = QRegExp(pattern) - index = expression.indexIn(text) - while index >= 0: - length = expression.matchedLength() - self.setFormat(index, length, fmt) - index = expression.indexIn(text, index + length) - -class LineNumberArea(QWidget): - def __init__(self, codeedit): - super(LineNumberArea, self).__init__(codeedit) - self.codeedit = codeedit - # TODO: display error in this: self.setToolTip('hello world') - def sizeHint(self): - return QSize(self.codeedit.lineNumberAreaWidth(), 0) - def paintEvent(self, ev): - self.codeedit.lineNumberAreaPaintEvent(ev) - -class CodeEdit(QPlainTextEdit): - def __init__(self, parent=None): - super(CodeEdit, self).__init__(parent) - # members: - self.isUntitled = True - self.filename = None - self.setFont(QFont('Courier')) - self.lineNumberArea = LineNumberArea(self) - - self.blockCountChanged.connect(self.updateLineNumberAreaWidth) - self.updateRequest.connect(self.updateLineNumberArea) - - # Syntax highlighter: - self.highlighter = MySyntaxHighlighter(self.document()) - - def setFileName(self, filename): - self.filename = filename - self.isUntitled = False - self.setWindowTitle(filename) - def setSource(self, source): - self.setPlainText(source) - - def save(self): - pass - def saveAs(self): - pass - - def saveFile(self): - if self.isUntitled: - self.saveAs() - else: - source = str(self.toPlainText()) - f = open(self.filename, 'w') - f.write(source) - f.close() - - def highlightErrorLocation(self, row, col): - tc = QTextCursor(self.document()) - tc.clearSelection() - tc.movePosition(tc.Down, tc.MoveAnchor, row - 1) - tc.movePosition(tc.Right, tc.MoveAnchor, col - 1) - tc.movePosition(tc.NextCharacter, tc.KeepAnchor) # Select 1 character - selection = QTextEdit.ExtraSelection() - lineColor = QColor(Qt.red).lighter(160) - selection.format.setBackground(lineColor) - #selection.format.setProperty(QTextFormat.FullWidthSelection, True) - selection.cursor = tc - self.setExtraSelections( [ selection ] ) - def clearErrors(self): - self.setExtraSelections( [ ] ) - - def lineNumberAreaWidth(self): - digits = 1 - mx = max(1, self.blockCount()) - while mx >= 10: - mx = mx / 10 - digits += 1 - space = 3 + self.fontMetrics().width('8') * digits - return space - def lineNumberAreaPaintEvent(self, ev): - painter = QPainter(self.lineNumberArea) - painter.fillRect(ev.rect(), Qt.lightGray) - block = self.firstVisibleBlock() - blockNumber = block.blockNumber() - top = self.blockBoundingGeometry(block).translated(self.contentOffset()).top() - bottom = top + self.blockBoundingRect(block).height() - while block.isValid() and top <= ev.rect().bottom(): - if block.isVisible() and bottom >= ev.rect().top(): - num = str(blockNumber + 1) - painter.setPen(Qt.black) - painter.drawText(0, top, self.lineNumberArea.width(), self.fontMetrics().height(), Qt.AlignRight, num) - block = block.next() - top = bottom - bottom = top + self.blockBoundingRect(block).height() - blockNumber += 1 - def resizeEvent(self, ev): - super(CodeEdit, self).resizeEvent(ev) - cr = self.contentsRect() - self.lineNumberArea.setGeometry(QRect(cr.left(), cr.top(), self.lineNumberAreaWidth(), cr.height() )) - def updateLineNumberAreaWidth(self, newBlockCount): - self.setViewportMargins(self.lineNumberAreaWidth(), 0, 0, 0) - def updateLineNumberArea(self, rect, dy): - if dy > 0: - self.lineNumberArea.scroll(0, dy) - else: - self.lineNumberArea.update(0, rect.y(), self.lineNumberArea.width(), rect.height()) - if rect.contains(self.viewport().rect()): - self.updateLineNumberAreaWidth(0) - diff -r fd7d5069734e -r 32078200cdd6 python/ide/ide/ide.py --- a/python/ide/ide/ide.py Sun Oct 07 16:56:50 2012 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,309 +0,0 @@ -from PyQt4.QtCore import * -from PyQt4.QtGui import * -# ide components: -from .codeeditor import CodeEdit -from .astviewer import AstViewer -import base64 -from project import Project -import os.path - -lcfospng = base64.decodestring(b'iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A\n/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJEhMKBk7B678AAAA/SURBVFjD\n7dbBCQAgDATBi9h/y7EFA4Kf2QLCwH1S6XQu6sqoujublc8BAAAAAAAAAAB8B+zXT6YJAAAAAKYd\nWSgFQNUyijIAAAAASUVORK5CYII=\n') - -class BuildOutput(QTextEdit): - """ Build output component """ - def __init__(self, parent=None): - super(BuildOutput, self).__init__(parent) - self.setCurrentFont(QFont('Courier')) - self.setReadOnly(True) - self.append('Build output will appear here!') - -class BuildErrors(QListView): - sigErrorSelected = pyqtSignal(object) - def __init__(self, parent=None): - super(BuildErrors, self).__init__(parent) - model = QStandardItemModel() - self.setModel(model) - self.clicked.connect(self.itemSelected) - def setErrorList(self, errorlist): - model = QStandardItemModel() - for e in errorlist: - row, col, msg = e - item = QStandardItem(str(msg)) - item.setData(e) - model.appendRow(item) - self.setModel(model) - def itemSelected(self, index): - if not index.isValid(): - return - model = self.model() - item = model.itemFromIndex(index) - err = item.data() - self.sigErrorSelected.emit(err) - -class ProjectView(QWidget): - sigLoadFile = pyqtSignal(str) - def __init__(self, parent=None): - super(ProjectView, self).__init__(parent) - self.treeview = QTreeView(self) - self.treeview.setContextMenuPolicy(Qt.CustomContextMenu) - l = QVBoxLayout(self) - l.addWidget(self.treeview) - pm = QPixmap() - pm.loadFromData(lcfospng) - self.projectIcon = QIcon(pm) - # Connect signals: - self.treeview.activated.connect(self.activate) - self.treeview.customContextMenuRequested.connect(self.contextMenu) - def setProject(self, project): - self.project = project - model = QStandardItemModel() - root = model.invisibleRootItem() - pitem = QStandardItem(self.projectIcon, project.name) - pitem.setEditable(False) - pitem.setData(project) - root.appendRow(pitem) - for f in self.project.files: - fitem = QStandardItem(f) - pitem.appendRow(fitem) - fitem.setEditable(False) - fitem.setData(f) - self.treeview.setModel(model) - self.treeview.expandAll() - def contextMenu(self, pos): - idx = self.treeview.indexAt(pos) - if not idx.isValid(): - return - item = self.treeview.model().itemFromIndex(idx) - def activate(self, index): - if not index.isValid(): - return - model = self.treeview.model() - item = model.itemFromIndex(index) - fn = item.data() - if type(fn) is str: - self.sigLoadFile.emit(fn) - -class AboutDialog(QDialog): - def __init__(self, parent=None): - super(AboutDialog, self).__init__(parent) - self.setWindowTitle('About') - l = QVBoxLayout(self) - txt = QTextEdit(self) - txt.setReadOnly(True) - aboutText = """An all-in-one IDE for OS development.
-https://www.assembla.com/spaces/lcfOS/wiki
-Author: Windel Bouwman
- """ - txt.append(aboutText) - l.addWidget(txt) - but = QPushButton('OK') - but.setDefault(True) - but.clicked.connect(self.close) - l.addWidget(but) - -class ProjectOptions(QDialog): - pass - # TODO: project options in here - -class Ide(QMainWindow): - def __init__(self, parent=None): - super(Ide, self).__init__(parent) - self.setWindowTitle('LCFOS IDE') - icon = QPixmap() - icon.loadFromData(lcfospng) - self.setWindowIcon(QIcon(icon)) - - # Create menus: - self.fileMenu = self.menuBar().addMenu('File') - self.viewMenu = self.menuBar().addMenu('View') - self.projectMenu = self.menuBar().addMenu('Project') - self.helpMenu = self.menuBar().addMenu('Help') - - # Create mdi area: - self.mdiArea = QMdiArea() - self.setCentralWidget(self.mdiArea) - - # Create components: - self.buildOutput = BuildOutput() - self.addComponent('Build output', self.buildOutput) - - self.astViewer = AstViewer() - self.addComponent('AST viewer', self.astViewer) - self.astViewer.sigNodeSelected.connect(self.nodeSelected) - - self.builderrors = BuildErrors() - self.addComponent('Build errors', self.builderrors) - self.builderrors.sigErrorSelected.connect(self.errorSelected) - - self.projectview = ProjectView() - self.addComponent('Project', self.projectview) - self.projectview.sigLoadFile.connect(self.loadFile) - - # About dialog: - self.aboutDialog = AboutDialog() - self.aboutDialog.setWindowIcon(QIcon(icon)) - # Create actions: - self.buildAction = QAction('Build!', self) - self.buildAction.setShortcut(QKeySequence('F7')) - self.projectMenu.addAction(self.buildAction) - self.buildAction.triggered.connect(self.buildFile) - self.openProjectAction = QAction("Open project", self) - self.openProjectAction.triggered.connect(self.openProject) - self.projectMenu.addAction(self.openProjectAction) - self.helpAction = QAction('Help', self) - self.helpAction.setShortcut(QKeySequence('F1')) - self.helpMenu.addAction(self.helpAction) - self.aboutAction = QAction('About', self) - self.helpMenu.addAction(self.aboutAction) - self.aboutAction.triggered.connect(self.aboutDialog.open) - - self.newFileAction = QAction("New File", self) - self.fileMenu.addAction(self.newFileAction) - self.newFileAction.triggered.connect(self.newFile) - self.saveFileAction = QAction("Save File", self) - self.fileMenu.addAction(self.saveFileAction) - self.saveFileAction.triggered.connect(self.saveFile) - self.closeFileAction = QAction("Close File", self) - self.fileMenu.addAction(self.closeFileAction) - self.closeFileAction.triggered.connect(self.closeFile) - - cascadeAction = QAction("Cascade windows", self) - cascadeAction.triggered.connect(self.mdiArea.cascadeSubWindows) - self.viewMenu.addAction(cascadeAction) - tileAction = QAction('Tile windows', self) - tileAction.triggered.connect(self.mdiArea.tileSubWindows) - self.viewMenu.addAction(tileAction) - - # Load settings: - self.settings = QSettings('windelsoft', 'lcfoside') - self.loadSettings() - - def addComponent(self, name, widget): - dw = QDockWidget(name) - dw.setWidget(widget) - dw.setObjectName(name) - self.addDockWidget(Qt.RightDockWidgetArea, dw) - self.viewMenu.addAction(dw.toggleViewAction()) - - # File handling: - def newFile(self): - ce = CodeEdit() - w = self.mdiArea.addSubWindow(ce) - ce.show() - - def saveFile(self): - ac = self.activeMdiChild() - if ac: - ac.saveFile() - - def saveAll(self): - pass - - def openFile(self): - # TODO - pass - - def closeFile(self): - ac = self.activeMdiChild() - if ac: - self.mdiArea.removeSubWindow(ac) - - def loadFile(self, filename): - # Find existing mdi widget: - wid = self.findMdiChild(filename) - if wid: - self.mdiArea.setActiveSubWindow(wid.parent()) - return wid - - # Create a new one: - ce = CodeEdit() - source = self.project.loadProjectFile(filename) - ce.setSource(source) - self.mdiArea.addSubWindow(ce) - ce.show() - return ce - - # MDI: - def activeMdiChild(self): - aw = self.mdiArea.activeSubWindow() - if aw: - return aw.widget() - else: - return None - - def findMdiChild(self, filename): - for window in self.mdiArea.subWindowList(): - wid = window.widget() - if wid.filename == filename: - return wid - return None - - def allChildren(self): - c = [] - for window in self.mdiArea.subWindowList(): - wid = window.widget() - c.append(wid) - return c - - # Settings: - def loadSettings(self): - if self.settings.contains('mainwindowstate'): - self.restoreState(self.settings.value('mainwindowstate')) - if self.settings.contains('mainwindowgeometry'): - self.restoreGeometry(self.settings.value('mainwindowgeometry')) - if self.settings.contains('openedproject'): - projectfile = self.settings.value('openedproject') - self.loadProject(projectfile) - - def closeEvent(self, ev): - self.settings.setValue('mainwindowstate', self.saveState()) - self.settings.setValue('mainwindowgeometry', self.saveGeometry()) - if self.project: - self.settings.setValue('openedproject', self.project.filename) - # TODO: ask for save of opened files - ev.accept() - - # Error handling: - def nodeSelected(self, node): - ce = self.activeMdiChild() - if not ce: - return - if node.location: - row, col = node.location - ce.highlightErrorLocation( row, col ) - else: - ce.clearErrors() - - def errorSelected(self, err): - row, col, msg = err - ce = self.activeMdiChild() - if not ce: - return - ce.highlightErrorLocation(row, col) - - # Project loading: - def loadProject(self, filename): - self.project = Project(filename) - self.projectview.setProject(self.project) - - def openProject(self): - filename = QFileDialog.getOpenFileName(self, \ - "Choose project file", "", "lcfos Project files (*.lcp)") - if filename: - self.loadProject(filename) - - # Build recepy: - def buildFile(self): - """ Build project """ - self.saveAll() - self.buildOutput.clear() - self.buildOutput.append(str(self.compiler)) - mods = self.compiler.compileProject(self.project) - - self.builderrors.setErrorList(self.compiler.errorlist) - self.astViewer.setAst(mods[0]) - for err in self.compiler.errorlist: - self.buildOutput.append(str(err)) - self.buildOutput.append("Done!") - diff -r fd7d5069734e -r 32078200cdd6 python/ide/project.py --- a/python/ide/project.py Sun Oct 07 16:56:50 2012 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,70 +0,0 @@ -""" - Project that can be stored to and loaded from XML. -""" - -from xml.sax import ContentHandler, make_parser -import xml.dom.minidom as md -import os.path - -class ProjectContentHandler(ContentHandler): - def __init__(self, project): - self.project = project - self.inFiles = False - def startElement(self, name, attrs): - if name == 'Project': - self.project.name = attrs['name'] - if name == 'Files': - self.inFiles = True - if name == 'File' and self.inFiles: - self.project.files.append(attrs['Filename']) - def endElement(self, name): - if name == 'Files': - self.inFiles = False - -class ProjectFile: - def __init__(self, filename): - self.filename = filename - -class Project: - def __init__(self, filename, isnew=False): - self.name = "" - self.files = [] - self.settings = {} - self.filename = filename - - if not isnew: - """ Load the project from the XML file """ - parser = make_parser() - handler = ProjectContentHandler(self) - parser.setContentHandler(handler) - parser.parse(filename) - - def save(self, filename): - """ Save the project in XML format """ - # Create document: - doc = md.Document() - # Add project: - project = doc.createElement("Project") - project.setAttribute("name", self.name) - doc.appendChild(project) - - # Add project files: - filesNode = doc.createElement("Files") - project.appendChild(filesNode) - for f in self.files: - fe = doc.createElement("File") - fe.setAttribute("Filename", f) - filesNode.appendChild(fe) - - # Write the XML file: - xml = doc.toprettyxml() - with open(filename, 'w') as f: - f.write(xml) - - def loadProjectFile(self, filename): - basedir = os.path.dirname(self.filename) - filename = os.path.join(basedir, filename) - with open(filename, 'r') as f: - src = f.read() - return src - diff -r fd7d5069734e -r 32078200cdd6 python/ide/runbuild.py --- a/python/ide/runbuild.py Sun Oct 07 16:56:50 2012 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,16 +0,0 @@ -import sys - -# Compiler imports: -from compiler.compiler import Compiler -from project import Project - -if __name__ == '__main__': - if len(sys.argv) < 2: - print('Use {0} projectfile'.format(sys.argv[0])) - sys.exit(-1) - filename = sys.argv[1] - project = Project() - project.load(filename) - pc = Compiler() - pc.compileProject(project) - diff -r fd7d5069734e -r 32078200cdd6 python/ide/runide.py --- a/python/ide/runide.py Sun Oct 07 16:56:50 2012 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,14 +0,0 @@ -import sys -from PyQt4.QtGui import QApplication - -# Compiler imports: -from compiler.compiler import Compiler -from ide.ide import Ide - -if __name__ == '__main__': - app = QApplication(sys.argv) - ide = Ide() - ide.compiler = Compiler() - ide.show() - app.exec_() - diff -r fd7d5069734e -r 32078200cdd6 python/ide/runtests.py --- a/python/ide/runtests.py Sun Oct 07 16:56:50 2012 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,272 +0,0 @@ -import unittest -import os - -from compiler.compiler import Compiler -from compiler.errors import CompilerException, printError -from compiler import lexer -from compiler.parser import Parser -from compiler import assembler -from compiler.codegenerator import CodeGenerator -from project import Project - -class CompilerTestCase(unittest.TestCase): - """ test methods start with 'test*' """ - def testSource1(self): - source = """ - module lcfos; - var - a : integer; - - procedure putchar(num : integer); - begin - end putchar; - - procedure WriteNum( num: integer); - var - d, base : integer; - dgt : integer; - begin - d := 1; - base := 10; - while num div d >= base do - d := d * base - end; - while d <> 0 do - dgt := num div d; - num := num mod d; - d := d div base; - putchar(48 + dgt) - end - end WriteNum; - - begin - a := 1; - while a < 26 - do - putchar(65+a); - a := a * 2 - end; - end lcfos. - """ - pc = Compiler() - pc.compilesource(source) - def testSource2(self): - source = """ - module lcfos; - var - a, b : integer; - arr: array 30 of integer; - arr2: array 10, 12 of integer; - procedure t2*() : integer; - begin - a := 2; - while a < 5 do - b := arr[a-1] + arr[a-2]; - arr2[a,2] := b; - arr2[a,3] := arr2[a,2] + arr2[a,2]*3 + b; - arr[a] := b; - a := a + 1; - end; - return b - end t2; - begin - b := 12; - arr[0] := 1; - arr[1] := 1; - end lcfos. - """ - pc = Compiler() - mod = pc.compilesource(source) - def testSource5(self): - source = """ - module lcfos; - procedure WriteLn() : integer; - const zzz = 13; - var - a, b, c: integer; - begin - a := 2; - b := 7; - c := 10 * a + b*10*a; - return c - end WriteLn; - begin end lcfos. - """ - pc = Compiler() - pc.compilesource(source) - def tstForStatement(self): - source = """ - module fortest; - var - a,b,c : integer; - begin - c := 0; - for a := 1 to 10 by 1 do - b := a + 15; - c := c + b * a; - end; - end fortest. - """ - pc = Compiler() - pc.compilesource(source) - def testSourceIfAndWhilePattern(self): - source = """ - module lcfos; - procedure WriteLn() : integer; - const zzz = 13; - var - a, b, c: integer; - begin - a := 1; - b := 2; - if a * 3 > b then - c := 10*a + b*10*a*a*a*b; - else - c := 13; - end; - while a < 101 do - a := a + 1; - c := c + 2; - end; - return c - end WriteLn; - begin end lcfos. - """ - pc = Compiler() - pc.compilesource(source) - - def testPattern1(self): - """ Test if expression can be compiled into byte code """ - src = "12*13+33-12*2*3" - tokens = lexer.tokenize(src) - ast = Parser(tokens).parseExpression() - code = CodeGenerator().genexprcode(ast) - - def testAssembler(self): - """ Check all kind of assembler cases """ - assert(assembler.shortjump(5) == [0xeb, 0x5]) - assert(assembler.shortjump(-2) == [0xeb, 0xfc]) - assert(assembler.shortjump(10,'GE') == [0x7d, 0xa]) - assert(assembler.nearjump(5) == [0xe9, 0x5,0x0,0x0,0x0]) - assert(assembler.nearjump(-2) == [0xe9, 0xf9, 0xff,0xff,0xff]) - assert(assembler.nearjump(10,'LE') == [0x0f, 0x8e, 0xa,0x0,0x0,0x0]) - - def testCall(self): - assert(assembler.call('r10') == [0x41, 0xff, 0xd2]) - assert(assembler.call('rcx') == [0xff, 0xd1]) - def testXOR(self): - assert(assembler.xorreg64('rax', 'rax') == [0x48, 0x31, 0xc0]) - assert(assembler.xorreg64('r9', 'r8') == [0x4d, 0x31, 0xc1]) - assert(assembler.xorreg64('rbx', 'r11') == [0x4c, 0x31, 0xdb]) - - def testINC(self): - assert(assembler.increg64('r11') == [0x49, 0xff, 0xc3]) - assert(assembler.increg64('rcx') == [0x48, 0xff, 0xc1]) - - def testPush(self): - assert(assembler.push('rbp') == [0x55]) - assert(assembler.push('rbx') == [0x53]) - assert(assembler.push('r12') == [0x41, 0x54]) - def testPop(self): - assert(assembler.pop('rbx') == [0x5b]) - assert(assembler.pop('rbp') == [0x5d]) - assert(assembler.pop('r12') == [0x41, 0x5c]) - - def testAsmLoads(self): - # TODO constant add testcases - assert(assembler.mov('rbx', 'r14') == [0x4c, 0x89, 0xf3]) - assert(assembler.mov('r12', 'r8') == [0x4d, 0x89, 0xc4]) - assert(assembler.mov('rdi', 'rsp') == [0x48, 0x89, 0xe7]) - - def testAsmMemLoads(self): - assert(assembler.mov('rax', ['r8','r15',0x11]) == [0x4b,0x8b,0x44,0x38,0x11]) - assert(assembler.mov('r13', ['rbp','rcx',0x23]) == [0x4c,0x8b,0x6c,0xd,0x23]) - - assert(assembler.mov('r9', ['rbp',-0x33]) == [0x4c,0x8b,0x4d,0xcd]) - #assert(assembler.movreg64('rbx', ['rax']) == [0x48, 0x8b,0x18]) - - assert(assembler.mov('rax', [0xb000]) == [0x48,0x8b,0x4,0x25,0x0,0xb0,0x0,0x0]) - assert(assembler.mov('r11', [0xa0]) == [0x4c,0x8b,0x1c,0x25,0xa0,0x0,0x0,0x0]) - - assert(assembler.mov('r11', ['RIP', 0xf]) == [0x4c,0x8b,0x1d,0x0f,0x0,0x0,0x0]) - - def testAsmMemStores(self): - assert(assembler.mov(['rbp', 0x13],'rbx') == [0x48,0x89,0x5d,0x13]) - assert(assembler.mov(['r12', 0x12],'r9') == [0x4d,0x89,0x4c,0x24,0x12]) - assert(assembler.mov(['rcx', 0x11],'r14') == [0x4c,0x89,0x71,0x11]) - - - assert(assembler.mov([0xab], 'rbx') == [0x48,0x89,0x1c,0x25,0xab,0x0,0x0,0x0]) - assert(assembler.mov([0xcd], 'r13') == [0x4c,0x89,0x2c,0x25,0xcd,0x0,0x0,0x0]) - - assert(assembler.mov(['RIP', 0xf], 'r9') == [0x4c,0x89,0x0d,0x0f,0x0,0x0,0x0]) - - def testAsmMOV8(self): - assert(assembler.mov(['rbp', -8], 'al') == [0x88, 0x45, 0xf8]) - assert(assembler.mov(['r11', 9], 'cl') == [0x41, 0x88, 0x4b, 0x09]) - - assert(assembler.mov(['rbx'], 'al') == [0x88, 0x03]) - assert(assembler.mov(['r11'], 'dl') == [0x41, 0x88, 0x13]) - - def testAsmLea(self): - assert(assembler.leareg64('r11', ['RIP', 0xf]) == [0x4c,0x8d,0x1d,0x0f,0x0,0x0,0x0]) - assert(assembler.leareg64('rsi', ['RIP', 0x7]) == [0x48,0x8d,0x35,0x07,0x0,0x0,0x0]) - - assert(assembler.leareg64('rcx', ['rbp', -8]) == [0x48,0x8d,0x4d,0xf8]) - - def testAssemblerCMP(self): - assert(assembler.cmpreg64('rdi', 'r13') == [0x4c, 0x39, 0xef]) - assert(assembler.cmpreg64('rbx', 'r14') == [0x4c, 0x39, 0xf3]) - assert(assembler.cmpreg64('r12', 'r9') == [0x4d, 0x39, 0xcc]) - - assert(assembler.cmpreg64('rdi', 1) == [0x48, 0x83, 0xff, 0x01]) - assert(assembler.cmpreg64('r11', 2) == [0x49, 0x83, 0xfb, 0x02]) - def testAssemblerADD(self): - assert(assembler.addreg64('rbx', 'r13') == [0x4c, 0x01, 0xeb]) - assert(assembler.addreg64('rax', 'rbx') == [0x48, 0x01, 0xd8]) - assert(assembler.addreg64('r12', 'r13') == [0x4d, 0x01, 0xec]) - - assert(assembler.addreg64('rbx', 0x13) == [0x48, 0x83, 0xc3, 0x13]) - assert(assembler.addreg64('r11', 0x1234567) == [0x49, 0x81, 0xc3, 0x67, 0x45,0x23,0x1]) - assert(assembler.addreg64('rsp', 0x33) == [0x48, 0x83, 0xc4, 0x33]) - - def testAssemblerSUB(self): - assert(assembler.subreg64('rdx', 'r14') == [0x4c, 0x29, 0xf2]) - assert(assembler.subreg64('r15', 'rbx') == [0x49, 0x29, 0xdf]) - assert(assembler.subreg64('r8', 'r9') == [0x4d, 0x29, 0xc8]) - - assert(assembler.subreg64('rsp', 0x123456) == [0x48, 0x81, 0xec, 0x56,0x34,0x12,0x0]) - assert(assembler.subreg64('rsp', 0x12) == [0x48, 0x83, 0xec, 0x12]) - - def testAssemblerIDIV(self): - assert(assembler.idivreg64('r11') == [0x49, 0xf7, 0xfb]) - assert(assembler.idivreg64('rcx') == [0x48, 0xf7, 0xf9]) - assert(assembler.idivreg64('rsp') == [0x48, 0xf7, 0xfc]) - - def testAssemblerIMUL(self): - assert(assembler.imulreg64_rax('rdi') == [0x48, 0xf7, 0xef]) - assert(assembler.imulreg64_rax('r10') == [0x49, 0xf7, 0xea]) - assert(assembler.imulreg64_rax('rdx') == [0x48, 0xf7, 0xea]) - - assert(assembler.imulreg64('r11', 'rdi') == [0x4c, 0xf, 0xaf, 0xdf]) - assert(assembler.imulreg64('r12', 'rbx') == [0x4c, 0xf, 0xaf, 0xe3]) - # nasm generates this machine code: 0x4d, 0x6b, 0xff, 0xee - # This also works: 4D0FAFFE (another variant?? ) - assert(assembler.imulreg64('r15', 'r14') == [0x4d, 0x0f, 0xaf, 0xfe]) - def testProject(self): - p = Project('test.xml', isnew=True) - p.name = "Test project" - p.files.append('main.mod') - p.files.append('test.mod') - p.save('test.xml') - - q = Project('test.xml') - - assert(p.name == q.name) - assert(p.files == q.files) - # TODO: remove test.xml test file - os.remove('test.xml') - -if __name__ == '__main__': - unittest.main() - diff -r fd7d5069734e -r 32078200cdd6 python/ide/testproject/main.mod --- a/python/ide/testproject/main.mod Sun Oct 07 16:56:50 2012 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,25 +0,0 @@ -module main; -var - a,b,c : integer; - -procedure test(x:integer); - var y,z:integer; -begin - y := x * 3 + 2; - z := x + y + a; -end test; - -procedure add(a:integer; b:integer):integer; - var - tmp : integer; - begin - tmp := a + b; - return tmp - end add; - -begin - a := 12; - b := a * 12 + 33; - c := a div b + a * b * 99; -end main. - diff -r fd7d5069734e -r 32078200cdd6 python/ide/testproject/test.lcp --- a/python/ide/testproject/test.lcp Sun Oct 07 16:56:50 2012 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,7 +0,0 @@ - -