# Visitor to *interpret* MiniC files from typing import ( Dict, List) from MiniCVisitor import MiniCVisitor from MiniCParser import MiniCParser from Lib.Errors import MiniCRuntimeError, MiniCInternalError, MiniCUnsupportedError MINIC_VALUE = int | str | bool | float | List['MINIC_VALUE'] class MiniCInterpretVisitor(MiniCVisitor): _memory: Dict[str, MINIC_VALUE] def __init__(self): self._memory = dict() # store all variable ids and values. self.has_main = False # visitors for variable declarations def visitVarDecl(self, ctx) -> None: # Initialise all variables in self._memory type_str = ctx.typee().getText() default_value = { "float": 0.0, "int": 0, "bool": False, "string": "" }[type_str] names_str = self.visit(ctx.id_l()) for name in names_str: self._memory[name] = default_value def visitIdList(self, ctx) -> List[str]: ids = self.visit(ctx.id_l()) return ids + [ctx.ID().getText()] def visitIdListBase(self, ctx) -> List[str]: return [ctx.ID().getText()] # visitors for atoms --> value def visitParExpr(self, ctx) -> MINIC_VALUE: return self.visit(ctx.expr()) def visitIntAtom(self, ctx) -> int: return int(ctx.getText()) def visitFloatAtom(self, ctx) -> float: return float(ctx.getText()) def visitBooleanAtom(self, ctx) -> bool: return ctx.getText() == "true" def visitIdAtom(self, ctx) -> MINIC_VALUE: return self._memory[ctx.getText()] def visitStringAtom(self, ctx) -> str: return ctx.getText()[1:-1] # Remove the "" # visit expressions def visitAtomExpr(self, ctx) -> MINIC_VALUE: return self.visit(ctx.atom()) def visitOrExpr(self, ctx) -> bool: lval = self.visit(ctx.expr(0)) rval = self.visit(ctx.expr(1)) return lval | rval def visitAndExpr(self, ctx) -> bool: lval = self.visit(ctx.expr(0)) rval = self.visit(ctx.expr(1)) return lval & rval def visitEqualityExpr(self, ctx) -> bool: assert ctx.myop is not None lval = self.visit(ctx.expr(0)) rval = self.visit(ctx.expr(1)) # be careful for float equality if ctx.myop.type == MiniCParser.EQ: return lval == rval else: return lval != rval def visitRelationalExpr(self, ctx) -> bool: assert ctx.myop is not None lval = self.visit(ctx.expr(0)) rval = self.visit(ctx.expr(1)) if ctx.myop.type == MiniCParser.LT: return lval < rval elif ctx.myop.type == MiniCParser.LTEQ: return lval <= rval elif ctx.myop.type == MiniCParser.GT: return lval > rval elif ctx.myop.type == MiniCParser.GTEQ: return lval >= rval else: raise MiniCInternalError( f"Unknown comparison operator '{ctx.myop}'" ) def visitAdditiveExpr(self, ctx) -> MINIC_VALUE: assert ctx.myop is not None lval = self.visit(ctx.expr(0)) rval = self.visit(ctx.expr(1)) if ctx.myop.type == MiniCParser.PLUS: if any(isinstance(x, str) for x in (lval, rval)): return '{}{}'.format(lval, rval) else: return lval + rval elif ctx.myop.type == MiniCParser.MINUS: return lval - rval else: raise MiniCInternalError( f"Unknown additive operator '{ctx.myop}'") def visitMultiplicativeExpr(self, ctx) -> MINIC_VALUE: assert ctx.myop is not None lval = self.visit(ctx.expr(0)) rval = self.visit(ctx.expr(1)) if ctx.myop.type == MiniCParser.MULT: return lval * rval elif ctx.myop.type == MiniCParser.DIV: # TODO : interpret division if rval == 0: raise MiniCRuntimeError("Division by 0") if type(lval) == type(1) and type(rval) == type(1): # Integer division if lval * rval < 0: return -int(abs(lval)/abs(rval)) return int(abs(lval)/abs(rval)) raise MiniCInternalError("Attempting to divide elements of different type") elif ctx.myop.type == MiniCParser.MOD: if rval == 0: raise MiniCRuntimeError("Division by 0") if lval < 0: return -(abs(lval) % abs(rval)) return lval % abs(rval) else: raise MiniCInternalError( f"Unknown multiplicative operator '{ctx.myop}'") def visitNotExpr(self, ctx) -> bool: return not self.visit(ctx.expr()) def visitUnaryMinusExpr(self, ctx) -> MINIC_VALUE: return -self.visit(ctx.expr()) # visit statements def visitPrintlnintStat(self, ctx) -> None: val = self.visit(ctx.expr()) print(val) def visitPrintlnfloatStat(self, ctx) -> None: val = self.visit(ctx.expr()) if isinstance(val, float): val = f"{val:.2f}" print(val) def visitPrintlnboolStat(self, ctx) -> None: val = self.visit(ctx.expr()) print('1' if val else '0') def visitPrintlnstringStat(self, ctx) -> None: val = self.visit(ctx.expr()) print(val) def visitAssignStat(self, ctx) -> None: v = self.visit(ctx.expr()) self._memory[ctx.ID().getText()] = v def visitIfStat(self, ctx) -> None: b = self.visit(ctx.expr()) blocks = ctx.stat_block() if b != 0: self.visit(blocks[0]) elif len(blocks) > 1: self.visit(blocks[1]) def visitWhileStat(self, ctx) -> None: while self.visit(ctx.expr()) != 0: self.visit(ctx.stat_block()) def visitForStat(self, ctx) -> None: from_val = self.visit(ctx.expr(0)) to_val = self.visit(ctx.expr(1)) stride = 1 if len(ctx.expr()) > 2: stride = self.visit(ctx.expr(2)) def cond_fun(stride): if stride > 0: return lambda x,y: x < y return lambda x,y: x > y condition = cond_fun(stride) counter = ctx.ID().getText() self._memory[counter] = from_val while condition(self._memory[counter], to_val): old_counter = self._memory[counter] self.visit(ctx.stat_block()) self._memory[counter] = old_counter + stride # type: ignore # TOPLEVEL def visitProgRule(self, ctx) -> None: self.visitChildren(ctx) if not self.has_main: # A program without a main function is compilable (hence # it's not a typing error per se), but not executable, # hence we consider it a runtime error. raise MiniCRuntimeError("No main function in file") # Visit a function: ignore if non main! def visitFuncDef(self, ctx) -> None: funname = ctx.ID().getText() if funname == "main": self.has_main = True self.visit(ctx.vardecl_l()) self.visit(ctx.block()) else: raise MiniCUnsupportedError("Functions are not supported in evaluation mode") def visitFuncCall(self, ctx) -> None: # pragma: no cover raise MiniCUnsupportedError("Functions are not supported in evaluation mode")