CAP/MiniC/TP03/MiniCInterpretVisitor.py

231 lines
7.3 KiB
Python
Raw Permalink Normal View History

2024-09-29 09:58:25 +02:00
# 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()
2024-10-08 08:39:14 +02:00
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
2024-09-29 09:58:25 +02:00
def visitIdList(self, ctx) -> List[str]:
2024-10-08 08:39:14 +02:00
ids = self.visit(ctx.id_l())
return ids + [ctx.ID().getText()]
2024-09-29 09:58:25 +02:00
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:
2024-10-08 08:39:14 +02:00
return self._memory[ctx.getText()]
2024-09-29 09:58:25 +02:00
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:
2024-09-30 14:51:00 +02:00
# TODO : interpret division
2024-10-08 08:39:14 +02:00
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")
2024-09-29 09:58:25 +02:00
elif ctx.myop.type == MiniCParser.MOD:
2024-10-08 08:39:14 +02:00
if rval == 0:
raise MiniCRuntimeError("Division by 0")
if lval < 0:
return -(abs(lval) % abs(rval))
return lval % abs(rval)
2024-09-29 09:58:25 +02:00
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:
2024-10-08 08:39:14 +02:00
v = self.visit(ctx.expr())
self._memory[ctx.ID().getText()] = v
2024-09-29 09:58:25 +02:00
def visitIfStat(self, ctx) -> None:
2024-10-08 08:39:14 +02:00
b = self.visit(ctx.expr())
blocks = ctx.stat_block()
if b != 0:
self.visit(blocks[0])
elif len(blocks) > 1:
self.visit(blocks[1])
2024-09-29 09:58:25 +02:00
def visitWhileStat(self, ctx) -> None:
2024-10-08 08:39:14 +02:00
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
2024-09-29 09:58:25 +02:00
# 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")