d6be336a0c
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
748 lines
21 KiB
Python
748 lines
21 KiB
Python
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)
|
||
|
||
# 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()
|