# HG changeset patch # User Windel Bouwman # Date 1363634037 -3600 # Node ID 8104fc8b5e9068f4d8a970a0b8d6b96707fc0c03 # Parent d8c735dc31f94d892d0e812f244dc16f4c3195fb Added visitor to c3 diff -r d8c735dc31f9 -r 8104fc8b5e90 python/c3/__init__.py --- a/python/c3/__init__.py Sun Mar 10 11:36:55 2013 +0100 +++ b/python/c3/__init__.py Mon Mar 18 20:13:57 2013 +0100 @@ -5,4 +5,5 @@ from .typecheck import TypeChecker from .analyse import Analyzer from .codegenerator import CodeGenerator +from .astprinter import AstPrinter diff -r d8c735dc31f9 -r 8104fc8b5e90 python/c3/analyse.py --- a/python/c3/analyse.py Sun Mar 10 11:36:55 2013 +0100 +++ b/python/c3/analyse.py Mon Mar 18 20:13:57 2013 +0100 @@ -1,5 +1,35 @@ +from .visitor import Visitor +from .astnodes import * class Analyzer: + """ This class checks names and references """ def __init__(self, diag): self.diag = diag + self.visitor = Visitor(self.a1, self.analyze) + def analyzePackage(self, pkg): + self.visitor.visit(pkg) + def resolveDesignator(self, d, referee=None): + assert type(d) is Designator + if d.scope.hasSymbol(d.tname): + s = d.scope.getSymbol(d.tname) + if hasattr(s, 'addRef'): + # TODO: make this nicer + s.addRef(referee) + return s + else: + msg = 'Cannot resolve name {0}'.format(d.tname) + self.diag.error(msg, d.loc) + def a1(self, sym): + pass + def analyze(self, sym): + if type(sym) is Variable: + sym.typ = self.resolveDesignator(sym.typ, sym) + elif type(sym) is VariableUse: + sym.target = self.resolveDesignator(sym.target, sym) + elif type(sym) is ProcedureCall: + sym.proc = self.resolveDesignator(sym.proc, sym) + elif type(sym) is FunctionType: + sym.returntype = self.resolveDesignator(sym.returntype) + elif type(sym) is Constant: + print(sym) diff -r d8c735dc31f9 -r 8104fc8b5e90 python/c3/astnodes.py --- a/python/c3/astnodes.py Sun Mar 10 11:36:55 2013 +0100 +++ b/python/c3/astnodes.py Mon Mar 18 20:13:57 2013 +0100 @@ -3,19 +3,7 @@ """ class Node: - location = None - def getChildren(self): - children = [] - members = dir(self) - for member in members: - member = getattr(self, member) - if isinstance(member, Node): - children.append(member) - elif type(member) is list: - for mi in member: - if isinstance(mi, Node): - children.append(mi) - return children + pass class Designator(Node): def __init__(self, tname): @@ -26,32 +14,6 @@ """ Type classes """ -def isType(a, b): - """ Compare types a and b and check if they are equal """ - if type(a) is type(b): - if type(a) is BaseType: - return (a.name == b.name) and (a.size == b.size) - elif type(a) is ProcedureType: - if len(a.parameters) != len(b.parameters): - print('Number of parameters does not match') - return False - for aparam, bparam in zip(a.parameters, b.parameters): - if not isType(aparam.typ, bparam.typ): - print('Parameter {0} does not match parameter {1}'.format(aparam, bparam)) - return False - if a.result is None: - # TODO: how to handle a None return type?? - pass - if not isType(a.result, b.result): - print('Procedure return value mismatch {0} != {1}'.format(a.result, b.result)) - return False - return True - else: - print(a) - print(b) - Error('Not implemented {0}'.format(a)) - else: - return False class Type(Node): def isType(self, b): @@ -68,7 +30,7 @@ self.parameters = parameters self.returntype = returntype def __repr__(self): - return '[PROCTYPE {0} RET {1}]'.format(self.parameters, self.returntype) + return '[FUNCTYPE {0} RET {1}]'.format(self.parameters, self.returntype) class DefinedType(Type): def __init__(self, name, typ): @@ -79,36 +41,45 @@ # Variables, parameters, local variables, constants: class Symbol(Node): - pass + def __init__(self, name): + self.name = name + self.refs = [] + def addRef(self, r): + self.refs.append(r) + @property + def References(self): + return self.refs class Constant(Symbol): - def __init__(self, value, name=None): - self.name = name + def __init__(self, name, typ, value): + super().__init__(name) + self.typ = typ self.value = value def __repr__(self): return 'CONSTANT {0} = {1}'.format(self.name, self.value) class Variable(Symbol): - def __init__(self, name, typ): - self.name = name + def __init__(self, name, typ, ival=None): + super().__init__(name) self.typ = typ + self.ival = ival self.isLocal = False self.isReadOnly = False self.isParameter = False def __repr__(self): - return 'VAR {0} : {1}'.format(self.name, self.typ) + return 'VAR {0} : {1} usage: {2}'.format(self.name, self.typ, self.References) # Procedure types class Function(Symbol): """ Actual implementation of a function """ def __init__(self, name, typ=None, block=None): - self.name = name + super().__init__(name) self.body = block self.typ = typ def __repr__(self): return 'PROCEDURE {0} {1}'.format(self.name, self.typ) -# Operations: +# Operations / Expressions: class Unop(Node): def __init__(self, a, op): self.a = a @@ -122,13 +93,21 @@ self.b = b self.op = op # Operation: '+', '-', '*', '/', 'mod' def __repr__(self): - return 'BINOP {0}'.format(self.op) + typ = self.typ if hasattr(self, 'typ') else '' + return 'BINOP {0} {1}'.format(self.op, typ) class VariableUse(Node): def __init__(self, target): self.target = target def __repr__(self): - return 'VAR USE {0}'.format(self.target) + nm = self.target.name if hasattr(self.target, 'name') else '' + return 'VAR USE {0}'.format(nm) + +class Literal(Node): + def __init__(self, val): + self.val = val + def __repr__(self): + return 'LITERAL {0}'.format(self.val) # Modules class Package(Node): diff -r d8c735dc31f9 -r 8104fc8b5e90 python/c3/astprinter.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/python/c3/astprinter.py Mon Mar 18 20:13:57 2013 +0100 @@ -0,0 +1,16 @@ +from .astnodes import * +from .scope import * +from .visitor import Visitor + +class AstPrinter: + def __init__(self): + self.visitor = Visitor(self.print1, self.print2) + def printAst(self, pkg): + self.indent = 0 + self.visitor.visit(pkg) + def print1(self, node): + print(' ' * self.indent + str(node)) + self.indent += 2 + def print2(self, node): + self.indent -= 2 + diff -r d8c735dc31f9 -r 8104fc8b5e90 python/c3/lexer.py --- a/python/c3/lexer.py Sun Mar 10 11:36:55 2013 +0100 +++ b/python/c3/lexer.py Mon Mar 18 20:13:57 2013 +0100 @@ -11,7 +11,7 @@ keywords = ['and', 'or', 'not','true', 'false', \ 'else', 'if', 'while', 'return', \ - 'function', 'var', 'type', \ + 'function', 'var', 'type', 'const', \ 'import', 'package' ] def tokenize(s): @@ -62,7 +62,7 @@ val = float(val) elif typ == 'STRING': val = val[1:-1] - loc = SourceLocation(line, mo.start()-line_start) + loc = SourceLocation(line, mo.start()-line_start, mo.end() - mo.start()) yield Token(typ, val, loc) pos = mo.end() mo = gettok(s, pos) diff -r d8c735dc31f9 -r 8104fc8b5e90 python/c3/parser.py --- a/python/c3/parser.py Sun Mar 10 11:36:55 2013 +0100 +++ b/python/c3/parser.py Mon Mar 18 20:13:57 2013 +0100 @@ -61,6 +61,8 @@ self.parseFunctionDefinition() elif self.Peak == 'var': self.parseVarDef() + elif self.Peak == 'const': + self.parseConstDef() else: self.Error('Expected function or variable') @@ -89,6 +91,19 @@ parseVar() self.Consume(';') + def parseConstDef(self): + self.Consume('const') + t = self.parseType() + def parseConst(): + name = self.Consume('ID') + self.Consume('=') + val = self.parseExpression() + self.sema.actOnConstDef(name.val, name.loc, t, val) + parseConst() + while self.hasConsumed(','): + parseConst() + self.Consume(';') + # Procedures def parseFunctionDefinition(self): self.Consume('function') @@ -193,6 +208,9 @@ elif self.Peak == 'NUMBER': val = self.Consume('NUMBER') return self.sema.actOnNumber(val.val, val.loc) + elif self.Peak == 'REAL': + val = self.Consume('REAL') + return self.sema.actOnNumber(val.val, val.loc) elif self.Peak == 'ID': d = self.parseDesignator() if self.Peak == '(': diff -r d8c735dc31f9 -r 8104fc8b5e90 python/c3/scope.py --- a/python/c3/scope.py Sun Mar 10 11:36:55 2013 +0100 +++ b/python/c3/scope.py Mon Mar 18 20:13:57 2013 +0100 @@ -23,9 +23,17 @@ def addSymbol(self, sym): self.symbols[sym.name] = sym +# buildin types: +intType = astnodes.BaseType('int') +doubleType = astnodes.BaseType('double') +voidType = astnodes.BaseType('void') +boolType = astnodes.BaseType('void') + def createBuiltins(scope): - for tn in ['int', 'u32', 'u16', 'double', 'void']: + for tn in ['u64', 'u32', 'u16', 'u8']: scope.addSymbol(astnodes.BaseType(tn)) + for t in [intType, doubleType, voidType, boolType]: + scope.addSymbol(t) topScope = Scope() createBuiltins(topScope) diff -r d8c735dc31f9 -r 8104fc8b5e90 python/c3/semantics.py --- a/python/c3/semantics.py Sun Mar 10 11:36:55 2013 +0100 +++ b/python/c3/semantics.py Mon Mar 18 20:13:57 2013 +0100 @@ -19,6 +19,10 @@ s = astnodes.Variable(name, t) s.loc = loc self.addSymbol(s) + def actOnConstDef(self, name, loc, t, val): + s = astnodes.Constant(name, t, val) + s.loc = loc + self.addSymbol(s) def actOnFuncDef1(self, name, loc): self.curFunc = astnodes.Function(name) self.curFunc.loc = loc @@ -48,7 +52,7 @@ bo.loc = loc return bo def actOnNumber(self, num, loc): - n = astnodes.Constant(num) + n = astnodes.Literal(num) n.loc = loc return n def actOnVariableUse(self, d): diff -r d8c735dc31f9 -r 8104fc8b5e90 python/c3/typecheck.py --- a/python/c3/typecheck.py Sun Mar 10 11:36:55 2013 +0100 +++ b/python/c3/typecheck.py Mon Mar 18 20:13:57 2013 +0100 @@ -1,61 +1,67 @@ -from .astnodes import BaseType, Variable, Designator, Function -from .astnodes import CompoundStatement, Assignment, VariableUse -from .astnodes import Binop, Unop, Constant -from .astnodes import IfStatement, WhileStatement, ReturnStatement, ProcedureCall -from .astnodes import FunctionType, BaseType -from . import astnodes -from .scope import topScope +from .astnodes import * +from .scope import * +from .visitor import Visitor + +def equalTypes(a, b): + """ Compare types a and b for equality. Not equal until proven otherwise. """ + if type(a) is type(b): + if type(a) is BaseType: + return a.name == b.name + return False class TypeChecker: def __init__(self, diag): self.diag = diag + self.visitor = Visitor(self.precheck, self.check2) def checkPackage(self, pkg): - for s in pkg.scope: - self.check(s) - def resolveDesignator(self, d): - if d.scope.hasSymbol(d.tname): - return d.scope.getSymbol(d.tname) - else: - msg = 'Cannot resolve name {0}'.format(d.tname) - self.diag.error(msg, d.loc) - def check(self, sym): - if type(sym) is Variable: - if type(sym.typ) is Designator: - sym.typ = self.resolveDesignator(sym.typ) - elif type(sym) is Function: - for s in sym.scope: - self.check(s) - self.check(sym.typ) - self.check(sym.body) - elif type(sym) is CompoundStatement: - for s in sym.statements: - self.check(s) + self.visitor.visit(pkg) + def precheck(self, sym): + pass + def check2(self, sym): + if type(sym) is Function: + pass elif type(sym) is IfStatement: - self.check(sym.condition) - self.check(sym.truestatement) - self.check(sym.falsestatement) - elif type(sym) is VariableUse: - if type(sym.target) is Designator: - sym.target = self.resolveDesignator(sym.target) - elif type(sym) is ProcedureCall: - if type(sym.proc) is Designator: - sym.proc = self.resolveDesignator(sym.proc) + print(sym.condition) + if not equalTypes(sym.condition.typ, boolType): + self.diag.error('Condition must be a boolean expression', sym.condition.loc) elif type(sym) is Assignment: - self.check(sym.lval) - self.check(sym.rval) + pass elif type(sym) is ReturnStatement: - self.check(sym.expr) - elif type(sym) is Constant: - if type(sym.value) is int: - sym.typ = topScope.getSymbol('int') - elif type(sym) is FunctionType: - if type(sym.returntype) is Designator: - sym.returntype = self.resolveDesignator(sym.returntype) - self.check(sym.returntype) + pass + elif type(sym) is ProcedureCall: + # Check arguments: + + # determine return type: + sym.typ = sym.proc.typ.returntype + elif type(sym) is VariableUse: + if sym.target: + sym.typ = sym.target.typ + else: + sym.typ = voidType + elif type(sym) is Literal: + if type(sym.val) is int: + sym.typ = intType + elif type(sym.val) is float: + sym.typ = doubleType + else: + self.diag.error('Unknown literal type', sym.loc) elif type(sym) is Binop: - self.check(sym.a) - self.check(sym.b) - if type(sym.a) is Constant and type(sym.b) is Constant: - # Possibly fold expression - pass + if sym.op in ['+', '-', '*', '/']: + if equalTypes(sym.a.typ, sym.b.typ): + sym.typ = sym.a.typ + else: + # assume void here? + sym.typ = voidType + self.diag.error('Types unequal', sym.loc) + elif sym.op in ['>', '<']: + if equalTypes(sym.a.typ, sym.b.typ): + sym.typ = boolType + else: + sym.typ = voidType + self.diag.error('Types unequal', sym.loc) + else: + sym.typ = voidType + print('unknown binop', sym.op) + else: + print('Unknown type check', sym) diff -r d8c735dc31f9 -r 8104fc8b5e90 python/c3/visitor.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/python/c3/visitor.py Mon Mar 18 20:13:57 2013 +0100 @@ -0,0 +1,45 @@ +from .astnodes import * + +class Visitor: + """ Visitor that visits all nodes in the ast and runs the function 'f' """ + def __init__(self, f1, f2): + self.f1 = f1 + self.f2 = f2 + def visit(self, node): + # Run visitor: + self.f1(node) + # Descent into subnodes: + if type(node) is Package: + for s in node.scope: + self.visit(s) + elif type(node) is Function: + for s in node.scope: + self.visit(s) + self.visit(node.typ) + self.visit(node.body) + elif type(node) is CompoundStatement: + for s in node.statements: + self.visit(s) + elif type(node) is IfStatement: + self.visit(node.condition) + self.visit(node.truestatement) + self.visit(node.falsestatement) + elif type(node) is ProcedureCall: + pass + # TODO + elif type(node) is Assignment: + self.visit(node.lval) + self.visit(node.rval) + elif type(node) is ReturnStatement: + self.visit(node.expr) + elif type(node) is Binop: + self.visit(node.a) + self.visit(node.b) + elif type(node) in [EmptyStatement, Constant, VariableUse, Variable, Literal, FunctionType]: + # Those nodes do not have child nodes. + pass + else: + print('UNK visit', node) + self.f2(node) + + diff -r d8c735dc31f9 -r 8104fc8b5e90 python/codeedit.py --- a/python/codeedit.py Sun Mar 10 11:36:55 2013 +0100 +++ b/python/codeedit.py Mon Mar 18 20:13:57 2013 +0100 @@ -19,6 +19,8 @@ self.scrollArea = scrollArea self.setFont(QFont('Courier', 16)) self.setFocusPolicy(Qt.StrongFocus) + # TODO: only beam cursor in text area.. + self.setCursor(Qt.IBeamCursor) h = QFontMetrics(self.font()).height() self.errorPixmap = QPixmap('error.png').scaled(h, h) self.blinkcursor = False @@ -32,7 +34,8 @@ t.start() def updateCursor(self): self.blinkcursor = not self.blinkcursor - self.update(self.cursorX, self.cursorY, self.charWidth, self.charHeight) + self.update() + #self.update(self.cursorX, self.cursorY, self.charWidth, self.charHeight) def setSource(self, src): self.src = src self.adjust() @@ -43,8 +46,6 @@ self.update() def setCursorPosition(self, c): self.cursorPosition = clipVal(c, 0, len(self.src)) - self.cursorX = self.CursorCol * self.charWidth + self.xposTXT - self.charWidth - self.cursorY = self.CursorRow * self.charHeight - self.charHeight self.update() CursorPosition = property(lambda self: self.cursorPosition, setCursorPosition) @property @@ -65,9 +66,9 @@ def CurrentLine(self): return self.getRow(self.CursorRow) def setRowCol(self, r, c): - prevRows = self.Rows[:r] + prevRows = self.Rows[:r-1] txt = '\n'.join(prevRows) - c = clipVal(c, 1, len(self.getRow(r+1))) + c = clipVal(c, 1, len(self.getRow(r))) self.CursorPosition = len(txt) + c + 1 def getRow(self, r): rows = self.Rows @@ -76,6 +77,8 @@ return '' else: return rows[r] + def showRow(self, r): + self.scrollArea.ensureVisible(self.xposTXT, r * self.charHeight, 4, self.charHeight) # Annotations: def addAnnotation(self, row, col, ln, msg): pass @@ -104,12 +107,14 @@ self.CursorPosition += len(curLine) else: self.CursorPosition += c + self.showRow(self.CursorRow) def GotoPrevLine(self): c = self.CursorCol - 1 # go to zero based self.CursorPosition -= c + 1 # line break char! curLine = self.CurrentLine if len(curLine) > c: self.CursorPosition -= len(curLine) - c + self.showRow(self.CursorRow) def paintEvent(self, event): # Helper variables: er = event.rect() @@ -118,27 +123,39 @@ # Background: painter.fillRect(er, self.palette().color(QPalette.Base)) painter.fillRect(QRect(self.xposLNA, er.top(), 4 * chw, er.bottom() + 1), Qt.gray) - painter.fillRect(er.left(), (self.CursorRow - 1) * chh, er.width(), chh, Qt.yellow) errorPen = QPen(Qt.red, 3) # first and last row: row1 = max(int(er.top() / chh) - 1, 1) row2 = max(int(er.bottom() / chh) + 1, 1) # Draw contents: + ypos = row1 * chh - self.charDescent + curRow = self.CursorRow + ydt = -chh + self.charDescent for row in range(row1, row2 + 1): - ypos = row * chh - self.charDescent + if curRow == row: + painter.fillRect(self.xposTXT, ypos + ydt, er.width(), chh, Qt.yellow) + # cursor + if self.blinkcursor: + cursorX = self.CursorCol * self.charWidth + self.xposTXT - self.charWidth + cursorY = ypos + ydt + painter.fillRect(cursorX, cursorY, 2, chh, Qt.black) painter.setPen(Qt.black) painter.drawText(self.xposLNA, ypos, '{0}'.format(row)) xpos = self.xposTXT painter.drawText(xpos, ypos, self.getRow(row)) - for e in self.errorlist: - if e.loc.row == row: - painter.drawPixmap(self.xposERR, ypos - chh + self.charDescent, self.errorPixmap) + curErrors = [e for e in self.errorlist if e.loc.row == row] + for e in curErrors: + painter.drawPixmap(self.xposERR, ypos + ydt, self.errorPixmap) painter.setPen(errorPen) - x = self.xposTXT + (e.loc.col - 1) * chw - painter.drawLine(x, ypos+1, x + 100, ypos+1) - # cursor - if self.blinkcursor: - painter.fillRect(self.cursorX, self.cursorY, 2, chh, Qt.black) + x = self.xposTXT + (e.loc.col - 1) * chw - 2 + wt = e.loc.length * chw + 4 + painter.drawRoundedRect(x, ypos + ydt, wt, chh, 7, 7) + # print error balloon + painter.drawText(x, ypos + chh, e.msg) + if len(curErrors) > 0: + ypos += chh + + ypos += chh def keyPressEvent(self, event): if event.matches(QKeySequence.MoveToNextChar): self.GotoNextChar() @@ -174,7 +191,7 @@ pos = event.pos() if pos.x() > self.xposTXT and pos.x(): c = round((pos.x() - self.xposTXT) / self.charWidth) - r = int(pos.y() / self.charHeight) + r = int(pos.y() / self.charHeight) + 1 self.setRowCol(r, c) def adjust(self): metrics = self.fontMetrics() @@ -199,6 +216,8 @@ self.setWidgetResizable(True) self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) self.setFocusPolicy(Qt.NoFocus) + self.showRow = self.ic.showRow + self.setRowCol = self.ic.setRowCol Source = property(lambda s: s.ic.getSource(), lambda s, v: s.ic.setSource(v)) def setErrors(self, el): self.ic.setErrors(el) diff -r d8c735dc31f9 -r 8104fc8b5e90 python/ide.py --- a/python/ide.py Sun Mar 10 11:36:55 2013 +0100 +++ b/python/ide.py Mon Mar 18 20:13:57 2013 +0100 @@ -191,6 +191,8 @@ addMenuEntry('Cascade windows', self.viewMenu, self.mdiArea.cascadeSubWindows) addMenuEntry('Tile windows', self.viewMenu, self.mdiArea.tileSubWindows) + sb = self.statusBar() + # Load settings: self.settings = QSettings('windelsoft', 'lcfoside') self.loadSettings() @@ -327,7 +329,7 @@ ce = self.activeMdiChild() if not ce: return - ce.highlightErrorLocation(err.loc.row, err.loc.col) + ce.setRowCol(err.loc.row, err.loc.col) # Project loading: diff -r d8c735dc31f9 -r 8104fc8b5e90 python/ppci/common.py --- a/python/ppci/common.py Sun Mar 10 11:36:55 2013 +0100 +++ b/python/ppci/common.py Mon Mar 18 20:13:57 2013 +0100 @@ -1,6 +1,12 @@ from collections import namedtuple -SourceLocation = namedtuple('SourceLocation', ['row', 'col']) +#SourceLocation = namedtuple('SourceLocation', ['row', 'col']) +class SourceLocation: + def __init__(self, row, col, ln): + self.row = row + self.col = col + self.length = ln + SourceRange = namedtuple('SourceRange', ['p1', 'p2']) diff -r d8c735dc31f9 -r 8104fc8b5e90 python/testc3.py --- a/python/testc3.py Sun Mar 10 11:36:55 2013 +0100 +++ b/python/testc3.py Mon Mar 18 20:13:57 2013 +0100 @@ -4,12 +4,16 @@ testsrc = """package test; var u32 c, d; +var double e; +var int f; + +const int A = 1337; function void test1() { - var u32 b; + var u32 bdd; var int a = 10; - b = 20; + bd = 20; var int buf; var int i; i = 2; @@ -41,31 +45,24 @@ """ -def printAst(ast, indent=''): - print(indent + str(ast)) - if isinstance(ast, c3.astnodes.Package): - for s in ast.scope: - printAst(s, indent + ' ') - if isinstance(ast, c3.astnodes.Function): - for s in ast.scope: - printAst(s, indent + ' ') - for c in ast.getChildren(): - printAst(c, indent + ' ') - def c3compile(src, diag): print('[0] source:') - #print(src) + print(src) print('[1] parsing') + # Structures: sema = c3.Semantics(diag) p = c3.Parser(sema, diag) tc = c3.TypeChecker(diag) al = c3.Analyzer(diag) cg = c3.CodeGenerator() + ap = c3.AstPrinter() + x86gen = x86.X86CodeGen(diag) p.parseSource(src) ok = len(diag.diags) == 0 if not ok: return + al.analyzePackage(sema.mod) tc.checkPackage(sema.mod) print('{0} errors'.format(len(diag.diags))) @@ -73,6 +70,8 @@ print('ERROR:') ppci.printError(testsrc, d) print('[2] ast:') + ap.printAst(sema.mod) + #printAst(sema.mod) ok = len(diag.diags) == 0