MPC/checker/StaticCheck.py
Nguyễn Anh Khoa d6be336a0c Refactor code
Change mpc.py to auto generate antlr4 files without having to run manualy. Also add a small command line ultility.
Remove commented import in others files, and update README
2018-12-18 01:06:36 +07:00

748 lines
21 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from utils.Visitor import BaseVisitor
from utils.Utils import Utils
from checker.StaticError import (
Function,
Procedure,
Variable,
Parameter,
Identifier,
Undeclared,
Redeclared,
TypeMismatchInExpression,
TypeMismatchInStatement,
FunctionNotReturn,
BreakNotInLoop,
ContinueNotInLoop,
NoEntryPoint,
UnreachableStatement,
Unreachable
)
from utils.AST import (
IntType,
FloatType,
BoolType,
StringType,
ArrayType,
VoidType,
FuncDecl,
CallExpr,
)
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),
str(self.value)
)
# 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 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)
# LHSs are formal parameters and RHSs 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()