init
This commit is contained in:
778
checker/StaticCheck.py
Normal file
778
checker/StaticCheck.py
Normal file
@ -0,0 +1,778 @@
|
||||
from utils.Visitor import BaseVisitor
|
||||
from utils.Utils import Utils
|
||||
from checker.StaticError import (
|
||||
# Kind,
|
||||
Function,
|
||||
Procedure,
|
||||
Variable,
|
||||
Parameter,
|
||||
Identifier,
|
||||
# StaticError,
|
||||
Undeclared,
|
||||
Redeclared,
|
||||
TypeMismatchInExpression,
|
||||
TypeMismatchInStatement,
|
||||
FunctionNotReturn,
|
||||
BreakNotInLoop,
|
||||
ContinueNotInLoop,
|
||||
NoEntryPoint,
|
||||
UnreachableStatement,
|
||||
Unreachable
|
||||
)
|
||||
from utils.AST import (
|
||||
IntType,
|
||||
FloatType,
|
||||
BoolType,
|
||||
StringType,
|
||||
ArrayType,
|
||||
VoidType,
|
||||
# Program,
|
||||
# Decl,
|
||||
# VarDecl,
|
||||
FuncDecl,
|
||||
# Stmt,
|
||||
# Assign,
|
||||
# If,
|
||||
# While,
|
||||
# For,
|
||||
# Break,
|
||||
# Continue,
|
||||
# Return,
|
||||
# With,
|
||||
# CallStmt,
|
||||
# Expr,
|
||||
# BinaryOp,
|
||||
# UnaryOp,
|
||||
CallExpr,
|
||||
# LHS,
|
||||
# Id,
|
||||
# ArrayCell,
|
||||
# Literal,
|
||||
# IntLiteral,
|
||||
# FloatLiteral,
|
||||
# StringLiteral,
|
||||
# BooleanLiteral
|
||||
)
|
||||
from functools import reduce
|
||||
|
||||
|
||||
class MType:
|
||||
def __init__(self, partype, rettype):
|
||||
self.partype = partype
|
||||
self.rettype = rettype
|
||||
|
||||
def __str__(self):
|
||||
return 'MType([{}],{})'.format(
|
||||
','.join([str(x) for x in self.partype]),
|
||||
str(self.rettype)
|
||||
)
|
||||
|
||||
|
||||
class Symbol:
|
||||
def __init__(self, name, mtype, value=0):
|
||||
self.name = name
|
||||
self.mtype = mtype
|
||||
self.value = value
|
||||
|
||||
def __str__(self):
|
||||
return 'Symbol({},{})'.format(
|
||||
self.name,
|
||||
str(self.mtype)
|
||||
)
|
||||
|
||||
|
||||
# DEBUG = True
|
||||
DEBUG = False
|
||||
|
||||
|
||||
def printEnv(env, stop=False):
|
||||
if not DEBUG:
|
||||
return
|
||||
if stop:
|
||||
try:
|
||||
input(','.join([str(e) for e in env]))
|
||||
except EOFError:
|
||||
print(','.join([str(e) for e in env]))
|
||||
else:
|
||||
print(','.join([str(e) for e in env]))
|
||||
|
||||
|
||||
def printDebug(desc, **kwargs):
|
||||
if not DEBUG:
|
||||
return
|
||||
|
||||
print(desc)
|
||||
|
||||
if 'env' in kwargs:
|
||||
if 'stop' in kwargs:
|
||||
printEnv(kwargs['env'], kwargs['stop'])
|
||||
else:
|
||||
printEnv(kwargs['env'])
|
||||
|
||||
|
||||
class StaticChecker(BaseVisitor, Utils):
|
||||
|
||||
global_envi = [
|
||||
# from specification, section 7: Built-in Functions/Procedures
|
||||
Symbol("getInt", MType([], IntType())),
|
||||
Symbol("putInt", MType([IntType()], VoidType())),
|
||||
Symbol("putIntLn", MType([IntType()], VoidType())),
|
||||
Symbol("getFloat", MType([], FloatType())),
|
||||
Symbol("putFloat", MType([FloatType()], VoidType())),
|
||||
Symbol("putFloatLn", MType([FloatType()], VoidType())),
|
||||
Symbol("putBool", MType([BoolType()], VoidType())),
|
||||
Symbol("putBoolLn", MType([BoolType()], VoidType())),
|
||||
Symbol("putString", MType([StringType()], VoidType())),
|
||||
Symbol("putStringLn", MType([StringType()], VoidType())),
|
||||
Symbol("putLn", MType([], VoidType()))
|
||||
]
|
||||
|
||||
def __init__(self, ast):
|
||||
self.ast = ast
|
||||
|
||||
def check(self):
|
||||
return self.visit(self.ast, StaticChecker.global_envi)
|
||||
|
||||
def checkRedeclared(self, symbol, kind, env):
|
||||
res = self.lookup(symbol.name.lower(), env, lambda e: e.name.lower())
|
||||
if res is not None:
|
||||
raise Redeclared(kind, symbol.name)
|
||||
|
||||
def mergeGlobal2Local(self, local_scope, global_scope):
|
||||
for s in global_scope:
|
||||
res = self.lookup(s.name, local_scope, lambda e: e.name.lower())
|
||||
if res is None:
|
||||
local_scope.append(s)
|
||||
|
||||
def checkTypeCompatibility(self, lhs, rhs, error):
|
||||
# array check
|
||||
if isinstance(lhs, ArrayType):
|
||||
if not isinstance(rhs, ArrayType):
|
||||
raise error
|
||||
if lhs.lower != rhs.lower or \
|
||||
lhs.upper != rhs.upper:
|
||||
raise error
|
||||
# self.checkTypeCompatibility(lhs.eleType, rhs.eleType, error)
|
||||
if not isinstance(lhs.eleType, type(rhs.eleType)):
|
||||
raise error
|
||||
|
||||
# float/int coersion
|
||||
elif isinstance(lhs, FloatType):
|
||||
if not isinstance(rhs, (IntType, FloatType)):
|
||||
raise error
|
||||
|
||||
# else
|
||||
elif not isinstance(lhs, type(rhs)):
|
||||
raise error
|
||||
|
||||
def callBody(self, ast, env):
|
||||
'''
|
||||
ast: CallStmt | CallExpr
|
||||
env: List[Symbol]
|
||||
raise TypeMismatchInStatement | TypeMismatchInExpression
|
||||
=> Type: MType
|
||||
; Used by CallStmt and CallExpr
|
||||
; Both have exact structure difference only on
|
||||
; Raising Error and Kind Expectation
|
||||
'''
|
||||
callParam = [self.visit(x, env) for x in ast.param]
|
||||
mtype = self.visit( # visits Id
|
||||
ast.method,
|
||||
{
|
||||
'env': env,
|
||||
'kind': Function() if isinstance(ast, CallExpr) \
|
||||
else Procedure(),
|
||||
}
|
||||
)
|
||||
rightParam = mtype.partype
|
||||
|
||||
# lazy init of Error
|
||||
error = TypeMismatchInExpression if isinstance(ast, CallExpr) \
|
||||
else TypeMismatchInStatement
|
||||
|
||||
if len(rightParam) != len(callParam):
|
||||
raise error(ast)
|
||||
|
||||
# LHS’s are formal parameters and RHS’s are arguments
|
||||
for pair in zip(rightParam, callParam):
|
||||
lhs = pair[0]
|
||||
rhs = pair[1]
|
||||
|
||||
self.checkTypeCompatibility(lhs, rhs, error(ast))
|
||||
|
||||
return mtype.rettype
|
||||
|
||||
def loopBody(self, stmts, param):
|
||||
'''
|
||||
stmt: List[Statement]
|
||||
param: {
|
||||
'env': List[Symbol],
|
||||
'inloop': Bool,
|
||||
'rettype': Type
|
||||
}
|
||||
'''
|
||||
env = param['env']
|
||||
rettype = param['rettype']
|
||||
|
||||
outFlag = False
|
||||
for stmt in stmts:
|
||||
if outFlag:
|
||||
raise UnreachableStatement(stmt)
|
||||
if self.visit(
|
||||
stmt, {
|
||||
'env': env, 'inloop': True, 'rettype': rettype
|
||||
}
|
||||
):
|
||||
outFlag = True
|
||||
|
||||
return False
|
||||
|
||||
def processStatement(self, stmts, param):
|
||||
returnFlag = False
|
||||
for stmt in stmts:
|
||||
if returnFlag:
|
||||
raise UnreachableStatement(stmt)
|
||||
if self.visit(stmt, param):
|
||||
returnFlag = True
|
||||
return returnFlag
|
||||
|
||||
def visitProgram(self, ast, env):
|
||||
printDebug("======SCAN PROGRAM======")
|
||||
global_scope = reduce(
|
||||
lambda returnList, decl:
|
||||
[self.visit(
|
||||
decl,
|
||||
{'env': returnList, 'scan': False}
|
||||
)] + returnList,
|
||||
ast.decl,
|
||||
env[:]
|
||||
)
|
||||
printDebug("======GLOBAL======", env=global_scope)
|
||||
|
||||
if not any(map(
|
||||
lambda symbol: isinstance(
|
||||
symbol.mtype,
|
||||
MType) and symbol.name.lower() == 'main' and isinstance(
|
||||
symbol.mtype.rettype,
|
||||
VoidType) and len(symbol.mtype.partype) == 0,
|
||||
global_scope)):
|
||||
raise NoEntryPoint()
|
||||
|
||||
funcs = filter(lambda x: isinstance(x, FuncDecl), ast.decl)
|
||||
for func in funcs:
|
||||
self.visit(func, {'env': global_scope, 'scan': True})
|
||||
|
||||
for symbol in global_scope:
|
||||
if not isinstance(symbol.mtype, MType):
|
||||
continue
|
||||
if symbol.name.lower() == 'main' and \
|
||||
isinstance(symbol.mtype.rettype, VoidType):
|
||||
continue
|
||||
if symbol.value == 0:
|
||||
if symbol in env:
|
||||
continue
|
||||
raise Unreachable(
|
||||
Procedure() if isinstance(symbol.mtype.rettype, VoidType)
|
||||
else Function(),
|
||||
symbol.name)
|
||||
|
||||
return global_scope
|
||||
|
||||
def visitFuncDecl(self, ast, param):
|
||||
'''
|
||||
ast: FuncDecl
|
||||
param: {
|
||||
env: List[Symbol], # Global Reference Environment
|
||||
scan: Bool
|
||||
}
|
||||
raise Redeclared(Parameter)
|
||||
raise Redeclared(Variable)
|
||||
raise UnreachableStatement
|
||||
raise FunctionNotReturn
|
||||
=> Symbol if not scan else None
|
||||
'''
|
||||
env = param['env']
|
||||
scan = param['scan']
|
||||
if not scan:
|
||||
printDebug("FUNCDECL", env=env, stop=False)
|
||||
s = Symbol(
|
||||
ast.name.name,
|
||||
MType(
|
||||
[x.varType for x in ast.param],
|
||||
ast.returnType
|
||||
))
|
||||
|
||||
kind = Procedure() if isinstance(ast.returnType, VoidType) \
|
||||
else Function()
|
||||
self.checkRedeclared(s, kind, env)
|
||||
return s
|
||||
else:
|
||||
printDebug("========SCAN FUNC========")
|
||||
printDebug(str(ast))
|
||||
|
||||
try:
|
||||
# visits VarDecl -- throws Redeclared(Variable)
|
||||
parameter = reduce(
|
||||
lambda scope, vardecl:
|
||||
[self.visit(vardecl, {'env': scope})] + scope,
|
||||
ast.param,
|
||||
# env[:] # copy
|
||||
[]
|
||||
)
|
||||
except Redeclared as e:
|
||||
raise Redeclared(Parameter(), e.n)
|
||||
printDebug("PARAM", env=parameter)
|
||||
|
||||
# visits VarDecl -- throws Redeclared(Variable)
|
||||
local_scope = reduce(
|
||||
lambda scope, vardecl:
|
||||
[self.visit(vardecl, {'env': scope})] + scope,
|
||||
ast.local,
|
||||
parameter # for safety reason, copy
|
||||
)
|
||||
printDebug("LOCAL_VAR", env=local_scope)
|
||||
# self.mergeGlobal2Local(local_scope, env)
|
||||
local_scope += env
|
||||
printDebug("LOCAL_ENV", env=local_scope, stop=False)
|
||||
|
||||
# check in body
|
||||
if not self.processStatement(
|
||||
ast.body,
|
||||
{
|
||||
'env': local_scope,
|
||||
'inloop': False,
|
||||
'rettype': ast.returnType
|
||||
}) and not isinstance(ast.returnType, VoidType):
|
||||
raise FunctionNotReturn(ast.name.name)
|
||||
|
||||
def visitVarDecl(self, ast, param):
|
||||
'''
|
||||
ast: VarDecl
|
||||
param: {
|
||||
env: List[Symbol]
|
||||
~~scan: Bool~~ # ignore
|
||||
}
|
||||
=> Symbol
|
||||
'''
|
||||
# print(param, file=sys.stderr)
|
||||
env = param['env']
|
||||
printDebug("VARDECL", env=env, stop=False)
|
||||
|
||||
s = Symbol(
|
||||
ast.variable.name,
|
||||
ast.varType
|
||||
)
|
||||
|
||||
self.checkRedeclared(s, Variable(), env)
|
||||
|
||||
return s
|
||||
|
||||
def visitIntType(self, asttree, param):
|
||||
return None
|
||||
|
||||
def visitFloatType(self, asttree, param):
|
||||
return None
|
||||
|
||||
def visitBoolType(self, asttree, param):
|
||||
return None
|
||||
|
||||
def visitStringType(self, asttree, param):
|
||||
return None
|
||||
|
||||
def visitVoidType(self, asttree, param):
|
||||
return None
|
||||
|
||||
def visitArrayType(self, asttree, param):
|
||||
return None
|
||||
|
||||
def visitBinaryOp(self, ast, param):
|
||||
'''
|
||||
ast: BinaryOp
|
||||
param: list[Symbol]
|
||||
raise TypeMismatchInExpression
|
||||
(/) --> Float/Int:Float/Int => Float
|
||||
(+,-,*) --> Float:Float/Int => Float
|
||||
--> Float/Int:Float => Float
|
||||
--> Int:Int => Int
|
||||
(div,mod) --> Int:Int => Int
|
||||
(<,<=,=,>=,>,<>) --> Float:Float/Int => Bool
|
||||
--> Float/Int:Float => Bool
|
||||
--> Int:Int => Bool
|
||||
(and,or,andthen,orelse) --> Bool:Bool => Bool
|
||||
=> Type
|
||||
'''
|
||||
op = ast.op.lower()
|
||||
# visits (Id, BinaryOp, UnaryOp, CallExpr, ArrayCell)
|
||||
left_type = self.visit(ast.left, param)
|
||||
right_type = self.visit(ast.right, param)
|
||||
|
||||
def deferType(acceptableTypes, returnType=None):
|
||||
if not isinstance(left_type, acceptableTypes):
|
||||
raise TypeMismatchInExpression(ast)
|
||||
if not isinstance(right_type, acceptableTypes):
|
||||
raise TypeMismatchInExpression(ast)
|
||||
|
||||
if returnType is not None:
|
||||
return returnType
|
||||
if isinstance(left_type, FloatType) or \
|
||||
isinstance(right_type, FloatType):
|
||||
return FloatType()
|
||||
if isinstance(left_type, type(right_type)):
|
||||
return left_type
|
||||
|
||||
raise TypeMismatchInExpression(ast)
|
||||
|
||||
if op in ('and', 'or', 'andthen', 'orelse'):
|
||||
return deferType((BoolType), BoolType())
|
||||
|
||||
if op in ('div', 'mod'):
|
||||
return deferType((IntType), IntType())
|
||||
|
||||
if op in ('+', '-', '*'):
|
||||
return deferType((IntType, FloatType))
|
||||
|
||||
if op in ('/'):
|
||||
return deferType((IntType, FloatType), FloatType())
|
||||
|
||||
if op in ('<', '<=', '=', '>=', '>', '<>'):
|
||||
return deferType((IntType, FloatType), BoolType())
|
||||
|
||||
def visitUnaryOp(self, ast, param):
|
||||
'''
|
||||
ast: UnaryOp
|
||||
param: List[Symbol]
|
||||
raise TypeMismatchInExpression
|
||||
not Bool => Bool
|
||||
- Int => Int
|
||||
- Float => Float
|
||||
=> Type
|
||||
'''
|
||||
op = ast.op.lower()
|
||||
expr = self.visit(ast.body, param)
|
||||
|
||||
if op in ('not'):
|
||||
if not isinstance(expr, BoolType):
|
||||
raise TypeMismatchInExpression(ast)
|
||||
return BoolType()
|
||||
|
||||
if op in ('-'):
|
||||
if not isinstance(expr, (IntType, FloatType)):
|
||||
raise TypeMismatchInExpression(ast)
|
||||
return expr
|
||||
|
||||
def visitCallExpr(self, ast, env):
|
||||
'''
|
||||
ast: CallExpr ~ CallStmt
|
||||
env: list[Symbol]
|
||||
raise Undeclared(Function)
|
||||
raise TypeMismatchInExpression
|
||||
wrong param size
|
||||
wrong param type
|
||||
Array[n..m] of X --> Array[n..m] of X
|
||||
Float --> Float/Int
|
||||
X --> X
|
||||
=> Type
|
||||
'''
|
||||
return self.callBody(ast, env)
|
||||
|
||||
def visitId(self, ast, param):
|
||||
'''
|
||||
ast: Id
|
||||
param: List[Symbol] or {
|
||||
'env': List[Symbol]
|
||||
'kind': kind expectation
|
||||
}
|
||||
raise Undeclared
|
||||
=> Type: Symbol.mtype
|
||||
'''
|
||||
env = param['env'] if not isinstance(param, list) else param
|
||||
kind = param['kind'] if not isinstance(param, list) else Identifier()
|
||||
|
||||
# type(res) == Symbol
|
||||
# printDebug("ID", env=env, stop=False)
|
||||
res = self.lookup(ast.name.lower(), env, lambda e: e.name.lower())
|
||||
|
||||
if res is None:
|
||||
raise Undeclared(kind, ast.name)
|
||||
|
||||
if isinstance(kind, Identifier):
|
||||
if isinstance(res.mtype, MType):
|
||||
raise Undeclared(kind, ast.name)
|
||||
return res.mtype
|
||||
|
||||
# param is dict
|
||||
if isinstance(kind, Function) or isinstance(kind, Procedure):
|
||||
# check if mtype -- aka function
|
||||
if not isinstance(res.mtype, MType):
|
||||
raise Undeclared(kind, ast.name)
|
||||
|
||||
if isinstance(kind, Function):
|
||||
if isinstance(res.mtype.rettype, VoidType):
|
||||
raise Undeclared(kind, ast.name)
|
||||
res.value += 1
|
||||
|
||||
elif isinstance(kind, Procedure):
|
||||
if not isinstance(res.mtype.rettype, VoidType):
|
||||
raise Undeclared(kind, ast.name)
|
||||
res.value += 1
|
||||
return res.mtype
|
||||
|
||||
def visitArrayCell(self, ast, param):
|
||||
'''
|
||||
ast: ArrayCell
|
||||
param: List[Symbol]
|
||||
raise TypeMismatchInExpression
|
||||
arr[idx] --> ArrayType[IntType] => ArrayType.eleType
|
||||
=> Type
|
||||
'''
|
||||
arr = self.visit(ast.arr, param)
|
||||
idx = self.visit(ast.idx, param)
|
||||
|
||||
if not isinstance(idx, IntType) or \
|
||||
not isinstance(arr, ArrayType):
|
||||
raise TypeMismatchInExpression(ast)
|
||||
|
||||
return arr.eleType
|
||||
|
||||
'''
|
||||
Statements are passed with a dict() with simple params:
|
||||
env: List[Symbol] # local referencing environment
|
||||
inloop: Bool # in loop flag
|
||||
rettype: Type # return type of function/procedure
|
||||
Simple statements deal only with 'env',
|
||||
Continue/Break statements use 'inloop' to check for non in loop call
|
||||
Return statements use 'rettype' to check for type compatibility when return
|
||||
|
||||
Function/Procedure statements pass all these params,
|
||||
For/While statements pass inloop as True when processing loop statements
|
||||
while preserving 'rettype
|
||||
If statements pass param to then/else statements
|
||||
With statements ...
|
||||
|
||||
All other statements uses param as read only
|
||||
|
||||
Return of statements are the status of the function/procedure whether
|
||||
it has returned or not
|
||||
|
||||
For example:
|
||||
|
||||
procedure main();
|
||||
begin
|
||||
if (n and mask) then
|
||||
return 1;
|
||||
else
|
||||
return 0;
|
||||
end
|
||||
|
||||
the corresponding AST Tree will be:
|
||||
Program([
|
||||
FuncDecl(Id(main),[],VoidType(),[],[
|
||||
If(BinaryOp(and,Id(n),Id(mask)),[
|
||||
Return(Some(IntLiteral(1)))
|
||||
],[
|
||||
Return(Some(IntLiteral(0)))
|
||||
])
|
||||
])
|
||||
])
|
||||
|
||||
The If in AST will return True as both of the branch returns.
|
||||
There is no else if statement, but this algorithm works as
|
||||
expect that in if statement, all branch returns means the function is ended
|
||||
|
||||
The same logic can also be applied to for/while/with statements
|
||||
'''
|
||||
|
||||
def visitAssign(self, ast, param):
|
||||
'''
|
||||
ast: Assign
|
||||
param: {
|
||||
'env': List[Symbol]
|
||||
'inloop': Bool
|
||||
}
|
||||
raise TypeMismatchInStatement
|
||||
Float := Float/Int
|
||||
Int := Int
|
||||
Bool := Bool
|
||||
// no String, Array
|
||||
=> Returned?
|
||||
'''
|
||||
env = param['env']
|
||||
left_type = self.visit(ast.lhs, env)
|
||||
right_type = self.visit(ast.exp, env)
|
||||
|
||||
if isinstance(left_type, (StringType, ArrayType)):
|
||||
raise TypeMismatchInStatement(ast)
|
||||
|
||||
self.checkTypeCompatibility(
|
||||
left_type, right_type, TypeMismatchInStatement(ast))
|
||||
|
||||
return False
|
||||
|
||||
def visitWith(self, ast, param):
|
||||
'''
|
||||
ast: With
|
||||
param: {
|
||||
'env': List[Symbol],
|
||||
'inloop': Bool,
|
||||
'rettype': Type
|
||||
}
|
||||
'''
|
||||
env = param['env']
|
||||
|
||||
with_scope = reduce(
|
||||
lambda with_scope, decl:
|
||||
[self.visit(decl, {'env': with_scope})] + with_scope,
|
||||
ast.decl,
|
||||
[]
|
||||
)
|
||||
with_scope += env
|
||||
|
||||
return self.processStatement(
|
||||
ast.stmt,
|
||||
{
|
||||
'env': with_scope,
|
||||
'inloop': param['inloop'],
|
||||
'rettype': param['rettype']
|
||||
})
|
||||
|
||||
def visitIf(self, ast, param):
|
||||
'''
|
||||
ast: If
|
||||
param: {
|
||||
'env': List[Symbol]
|
||||
'inloop': Bool
|
||||
'rettype': Type
|
||||
}
|
||||
=> Returned?
|
||||
'''
|
||||
expr = self.visit(ast.expr, param['env'])
|
||||
if not isinstance(expr, BoolType):
|
||||
raise TypeMismatchInStatement(ast)
|
||||
|
||||
thenReturnFlag = False
|
||||
for stmt in ast.thenStmt:
|
||||
if thenReturnFlag:
|
||||
raise UnreachableStatement(ast)
|
||||
if self.visit(stmt, param):
|
||||
thenReturnFlag = True
|
||||
|
||||
elseReturnFlag = False
|
||||
for stmt in ast.elseStmt:
|
||||
if elseReturnFlag:
|
||||
raise UnreachableStatement(ast)
|
||||
if self.visit(stmt, param):
|
||||
elseReturnFlag = True
|
||||
|
||||
return thenReturnFlag and elseReturnFlag
|
||||
|
||||
def visitFor(self, ast, param):
|
||||
'''
|
||||
ast: For
|
||||
param: {
|
||||
'env': List[Symbol],
|
||||
'rettype': Type
|
||||
}
|
||||
'''
|
||||
env = param['env']
|
||||
idType = self.visit(ast.id, env)
|
||||
expr1 = self.visit(ast.expr1, env)
|
||||
expr2 = self.visit(ast.expr2, env)
|
||||
|
||||
if not isinstance(idType, IntType) or \
|
||||
not isinstance(expr1, IntType) or \
|
||||
not isinstance(expr2, IntType):
|
||||
raise TypeMismatchInStatement(ast)
|
||||
|
||||
return self.loopBody(ast.loop, param)
|
||||
|
||||
def visitContinue(self, ast, param):
|
||||
'''
|
||||
ast: Continue
|
||||
param: {
|
||||
'inloop': Bool
|
||||
}
|
||||
'''
|
||||
if not param['inloop']:
|
||||
raise ContinueNotInLoop()
|
||||
return True
|
||||
|
||||
def visitBreak(self, ast, param):
|
||||
'''
|
||||
ast: Break
|
||||
param: {
|
||||
'inloop': Bool
|
||||
}
|
||||
'''
|
||||
if not param['inloop']:
|
||||
raise BreakNotInLoop()
|
||||
return True
|
||||
|
||||
def visitReturn(self, ast, param):
|
||||
'''
|
||||
ast: Return
|
||||
param: {
|
||||
'env': List[Symbol]
|
||||
'rettype': Type
|
||||
}
|
||||
'''
|
||||
rettype = param['rettype']
|
||||
env = param['env']
|
||||
if isinstance(rettype, VoidType):
|
||||
if ast.expr is not None:
|
||||
raise TypeMismatchInStatement(ast)
|
||||
else:
|
||||
if ast.expr is None:
|
||||
raise TypeMismatchInStatement(ast)
|
||||
self.checkTypeCompatibility(
|
||||
rettype, self.visit(ast.expr, env),
|
||||
TypeMismatchInStatement(ast)
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
def visitWhile(self, ast, param):
|
||||
'''
|
||||
ast: While
|
||||
param: {
|
||||
'env': List[Symbol],
|
||||
'inloop': Bool,
|
||||
'rettype': Type
|
||||
}
|
||||
'''
|
||||
env = param['env']
|
||||
|
||||
exp = self.visit(ast.exp, env)
|
||||
if not isinstance(exp, BoolType):
|
||||
raise TypeMismatchInStatement(ast)
|
||||
|
||||
return self.loopBody(ast.sl, param)
|
||||
|
||||
def visitCallStmt(self, ast, param):
|
||||
'''
|
||||
ast: CallStmt
|
||||
param: {
|
||||
'env': list[Symbol]
|
||||
}
|
||||
raise Undeclared(Procedure)
|
||||
raise TypeMismatchInStatement
|
||||
wrong param size
|
||||
wrong param type
|
||||
Array[n..m] of X --> Array[n..m] of X
|
||||
Float --> Float/Int
|
||||
X --> X
|
||||
=> Returned?
|
||||
'''
|
||||
self.callBody(ast, param['env']) # skips return
|
||||
return False
|
||||
|
||||
def visitIntLiteral(self, asttree, param):
|
||||
return IntType()
|
||||
|
||||
def visitFloatLiteral(self, asttree, param):
|
||||
return FloatType()
|
||||
|
||||
def visitBooleanLiteral(self, asttree, param):
|
||||
return BoolType()
|
||||
|
||||
def visitStringLiteral(self, asttree, param):
|
||||
return StringType()
|
125
checker/StaticError.py
Normal file
125
checker/StaticError.py
Normal file
@ -0,0 +1,125 @@
|
||||
from abc import ABC
|
||||
|
||||
|
||||
class Kind(ABC):
|
||||
pass
|
||||
|
||||
|
||||
class Function(Kind):
|
||||
def __str__(self):
|
||||
return "Function"
|
||||
|
||||
|
||||
class Procedure(Kind):
|
||||
def __str__(self):
|
||||
return "Procedure"
|
||||
|
||||
|
||||
class Parameter(Kind):
|
||||
def __str__(self):
|
||||
return "Parameter"
|
||||
|
||||
|
||||
class Variable(Kind):
|
||||
def __str__(self):
|
||||
return "Variable"
|
||||
|
||||
|
||||
class Identifier(Kind):
|
||||
def __str__(self):
|
||||
return "Identifier"
|
||||
|
||||
|
||||
class StaticError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class Undeclared(StaticError):
|
||||
"""k: Kind
|
||||
n: string: name of identifier """
|
||||
|
||||
def __init__(self, k, n):
|
||||
self.k = k
|
||||
self.n = n
|
||||
|
||||
def __str__(self):
|
||||
return "Undeclared " + str(self.k) + ": " + self.n
|
||||
|
||||
|
||||
class Redeclared(StaticError):
|
||||
"""k: Kind
|
||||
n: string: name of identifier """
|
||||
|
||||
def __init__(self, k, n):
|
||||
self.k = k
|
||||
self.n = n
|
||||
|
||||
def __str__(self):
|
||||
return "Redeclared " + str(self.k) + ": " + self.n
|
||||
|
||||
|
||||
class TypeMismatchInExpression(StaticError):
|
||||
"""exp: AST.Expr"""
|
||||
|
||||
def __init__(self, exp):
|
||||
self.exp = exp
|
||||
|
||||
def __str__(self):
|
||||
return "Type Mismatch In Expression: " + str(self.exp)
|
||||
|
||||
|
||||
class TypeMismatchInStatement(StaticError):
|
||||
"""stmt:AST.Stmt"""
|
||||
|
||||
def __init__(self, stmt):
|
||||
self.stmt = stmt
|
||||
|
||||
def __str__(self):
|
||||
return "Type Mismatch In Statement: " + str(self.stmt)
|
||||
|
||||
|
||||
class FunctionNotReturn(StaticError):
|
||||
"""m is a string that is the name of the function"""
|
||||
|
||||
def __init__(self, m):
|
||||
self.m = m
|
||||
|
||||
def __str__(self):
|
||||
# return "Function " + self.m + "Not Return "
|
||||
return "Function " + self.m + " Not Return"
|
||||
|
||||
|
||||
class BreakNotInLoop(StaticError):
|
||||
def __str__(self):
|
||||
return "Break Not In Loop"
|
||||
|
||||
|
||||
class ContinueNotInLoop(StaticError):
|
||||
def __str__(self):
|
||||
return "Continue Not In Loop"
|
||||
|
||||
|
||||
class NoEntryPoint(StaticError):
|
||||
def __str__(self):
|
||||
return "No entry point"
|
||||
|
||||
|
||||
class UnreachableStatement(StaticError):
|
||||
"""stmt is AST.Stmt"""
|
||||
|
||||
def __init__(self, stmt):
|
||||
self.stmt = stmt
|
||||
|
||||
def __str__(self):
|
||||
return "Unreachable statement: " + str(self.stmt)
|
||||
|
||||
|
||||
class Unreachable(StaticError):
|
||||
"""m is a string that is the name of the unreachable function/procedure"""
|
||||
|
||||
def __init__(self, k, m):
|
||||
self.k = k
|
||||
self.m = m
|
||||
|
||||
def __str__(self):
|
||||
return "Unreachable " + str(self.k) + ": " + self.m
|
0
checker/__init__.py
Normal file
0
checker/__init__.py
Normal file
Reference in New Issue
Block a user