nac3-spec/toy-impl/parse_expr.py

285 lines
10 KiB
Python
Raw Permalink Normal View History

2020-12-18 16:40:32 +08:00
import ast
2020-12-21 11:50:20 +08:00
import copy
2020-12-18 16:40:32 +08:00
from helper import *
from type_def import *
from inference import *
# we assume having the following types:
2020-12-22 16:53:33 +08:00
# bool, int32 and float with associated operations
2020-12-18 16:40:32 +08:00
2020-12-21 13:59:11 +08:00
# not handled now: named expression, type guard
2020-12-18 16:40:32 +08:00
def parse_expr(ctx: Context,
sym_table: dict[str, Type],
expr: ast.expr):
if isinstance(expr, ast.Expression):
body = expr.body
else:
body = expr
if isinstance(body, ast.Constant):
2021-01-07 11:57:28 +08:00
result = parse_constant(ctx, sym_table, body)
elif isinstance(body, ast.UnaryOp):
result = parse_unary_ops(ctx, sym_table, body)
elif isinstance(body, ast.BinOp):
result = parse_bin_ops(ctx, sym_table, body)
elif isinstance(body, ast.Name):
result = parse_name(ctx, sym_table, body)
elif isinstance(body, ast.List):
result = parse_list(ctx, sym_table, body)
elif isinstance(body, ast.Tuple):
result = parse_tuple(ctx, sym_table, body)
elif isinstance(body, ast.Attribute):
result = parse_attribute(ctx, sym_table, body)
elif isinstance(body, ast.BoolOp):
result = parse_bool_ops(ctx, sym_table, body)
elif isinstance(body, ast.Compare):
result = parse_compare(ctx, sym_table, body)
elif isinstance(body, ast.Call):
result = parse_call(ctx, sym_table, body)
elif isinstance(body, ast.Subscript):
result = parse_subscript(ctx, sym_table, body)
elif isinstance(body, ast.IfExp):
result = parse_if_expr(ctx, sym_table, body)
elif isinstance(body, ast.ListComp):
result = parse_list_comprehension(ctx, sym_table, body)
else:
raise CustomError(f'{body} is not yet supported', body)
body.type = result
return result
2020-12-18 16:40:32 +08:00
def get_unary_op(op):
if isinstance(op, ast.UAdd):
return '__pos__'
if isinstance(op, ast.USub):
return '__neg__'
if isinstance(op, ast.Invert):
return '__invert__'
raise Exception(f'Unknown {expr}')
def get_bin_ops(op):
if isinstance(op, ast.Div):
return '__truediv__'
if isinstance(op, ast.BitAnd):
return '__and__'
if isinstance(op, ast.BitOr):
return '__or__'
if isinstance(op, ast.BitXor):
return '__xor__'
return f'__{type(op).__name__.lower()}__'
def parse_constant(ctx: Context,
2020-12-21 09:55:38 +08:00
sym_table: dict[str, Type],
node):
2020-12-18 16:40:32 +08:00
v = node.value
2020-12-21 10:08:05 +08:00
if isinstance(v, bool):
2020-12-18 16:40:32 +08:00
return ctx.types['bool']
2020-12-21 10:08:05 +08:00
elif isinstance(v, int):
return ctx.types['int32']
2020-12-22 16:53:33 +08:00
elif isinstance(v, float):
return ctx.types['float']
2020-12-18 16:40:32 +08:00
else:
2020-12-22 16:53:33 +08:00
raise CustomError(f'unknown constant {v}', node)
2020-12-18 16:40:32 +08:00
def parse_name(ctx: Context,
sym_table: dict[str, Type],
node):
if node.id in sym_table:
return sym_table[node.id]
else:
2020-12-22 16:53:33 +08:00
raise CustomError(f'unbounded variable {node.id}', node)
2020-12-18 16:40:32 +08:00
def parse_list(ctx: Context,
sym_table: dict[str, Type],
node):
types = [parse_expr(ctx, sym_table, e) for e in node.elts]
if len(types) == 0:
return ListType(BotType())
for t in types[1:]:
if t != types[0]:
2020-12-22 16:53:33 +08:00
raise CustomError(f'inhomogeneous list is not allowed', node)
2020-12-18 16:40:32 +08:00
return ListType(types[0])
def parse_tuple(ctx: Context,
2020-12-21 09:55:38 +08:00
sym_table: dict[str, Type],
node):
2020-12-18 16:40:32 +08:00
types = [parse_expr(ctx, sym_table, e) for e in node.elts]
return TupleType(types)
def parse_attribute(ctx: Context,
2020-12-21 09:55:38 +08:00
sym_table: dict[str, Type],
node):
2020-12-23 10:34:56 +08:00
obj = parse_expr(ctx, sym_table, node.value)
2020-12-23 11:22:17 +08:00
if isinstance(obj, TypeVariable) and len(obj.constraints) > 0:
if node.attr not in obj.constraints[0].fields:
raise CustomError(f'unknown field {node.attr} in {obj}', node)
ty = obj.constraints[0].fields[node.attr]
for v in obj.constraints[1:]:
if node.attr not in v.fields:
raise CustomError(f'unknown field {node.attr} in {obj}', node)
if v.fields[node.attr] != ty:
raise CustomError(
f'unknown field {node.attr} in {obj} (type mismatch)', node)
return ty
2020-12-18 16:40:32 +08:00
if node.attr in obj.fields:
return obj.fields[node.attr]
2020-12-22 16:53:33 +08:00
raise CustomError(f'unknown field {node.attr} in {obj}', node)
2020-12-18 16:40:32 +08:00
def parse_bool_ops(ctx: Context,
2020-12-21 09:55:38 +08:00
sym_table: dict[str, Type],
node):
2020-12-18 16:40:32 +08:00
assert len(node.values) == 2
left = parse_expr(ctx, sym_table, node.values[0])
right = parse_expr(ctx, sym_table, node.values[1])
b = ctx.types['bool']
if left != b or right != b:
2020-12-22 16:53:33 +08:00
raise CustomError('operands of bool ops must be booleans', node)
2020-12-18 16:40:32 +08:00
return b
def parse_bin_ops(ctx: Context,
2020-12-21 09:55:38 +08:00
sym_table: dict[str, Type],
node):
2020-12-18 16:40:32 +08:00
left = parse_expr(ctx, sym_table, node.left)
right = parse_expr(ctx, sym_table, node.right)
op = get_bin_ops(node.op)
2020-12-22 16:53:33 +08:00
try:
return resolve_call(left, op, [right], {}, ctx)
except CustomError as e:
raise e.at(node)
2020-12-18 16:40:32 +08:00
def parse_unary_ops(ctx: Context,
2020-12-21 09:55:38 +08:00
sym_table: dict[str, Type],
node):
2020-12-22 15:14:34 +08:00
t = parse_expr(ctx, sym_table, node.operand)
2020-12-18 16:40:32 +08:00
if isinstance(node.op, ast.Not):
b = ctx.types['bool']
if t != b:
2020-12-22 16:53:33 +08:00
raise CustomError('operands of bool ops must be booleans', node)
2020-12-18 16:40:32 +08:00
return b
2020-12-22 16:53:33 +08:00
try:
return resolve_call(t, get_unary_op(node.op), [], {}, ctx)
except CustomError as e:
raise e.at(node)
2020-12-18 16:40:32 +08:00
def parse_compare(ctx: Context,
2020-12-21 09:55:38 +08:00
sym_table: dict[str, Type],
node):
2020-12-18 16:40:32 +08:00
items = [parse_expr(ctx, sym_table, v) for v in node.comparators]
items.insert(0, parse_expr(ctx, sym_table, node.left))
boolean = ctx.types['bool']
ops = [get_bin_ops(v) for v in node.ops]
for a, b, op in zip(items[:-1], items[1:], ops):
2020-12-22 16:53:33 +08:00
try:
result = resolve_call(a, op, [b], {}, ctx)
if result != boolean:
raise CustomError(
f'result of comparison must be bool instead of {result}')
except CustomError as e:
raise e.at(node)
2020-12-18 16:40:32 +08:00
return boolean
def parse_call(ctx: Context,
sym_table: dict[str, Type],
node):
if len(node.keywords) > 0:
2020-12-22 16:53:33 +08:00
raise CustomError('keyword arguments are not supported', node)
2020-12-18 16:40:32 +08:00
args = [parse_expr(ctx, sym_table, v) for v in node.args]
obj = None
f = None
if isinstance(node.func, ast.Attribute):
2020-12-21 13:53:59 +08:00
obj = parse_expr(ctx, sym_table, node.func.value)
2020-12-18 16:40:32 +08:00
f = node.func.attr
elif isinstance(node.func, ast.Name):
f = node.func.id
2020-12-22 16:53:33 +08:00
try:
return resolve_call(obj, f, args, {}, ctx)
except CustomError as e:
raise e.at(node)
2020-12-18 16:40:32 +08:00
def parse_subscript(ctx: Context,
2020-12-21 09:55:38 +08:00
sym_table: dict[str, Type],
node):
2020-12-18 16:40:32 +08:00
value = parse_expr(ctx, sym_table, node.value)
if not isinstance(value, ListType):
2020-12-22 16:53:33 +08:00
raise CustomError(f'cannot take index of {value}', node)
2020-12-18 16:40:32 +08:00
i32 = ctx.types['int32']
2020-12-21 09:51:05 +08:00
if isinstance(node.slice, ast.Slice):
if node.slice.lower is not None:
if parse_expr(ctx, sym_table, node.slice.lower) != i32:
2020-12-22 16:53:33 +08:00
raise CustomError(f'slice index must be int32', node.slice.lower)
2020-12-21 09:51:05 +08:00
if node.slice.upper is not None:
if parse_expr(ctx, sym_table, node.slice.upper) != i32:
2020-12-22 16:53:33 +08:00
raise CustomError(f'slice index must be int32', node.slice.upper)
2020-12-21 09:51:05 +08:00
if node.slice.step is not None:
if parse_expr(ctx, sym_table, node.slice.step) != i32:
2020-12-22 16:53:33 +08:00
raise CustomError(f'slice index must be int32', node.slice.step)
2020-12-21 09:51:05 +08:00
return value
2020-12-18 16:40:32 +08:00
else:
2020-12-21 09:51:05 +08:00
s = parse_expr(ctx, sym_table, node.slice)
if s == i32:
return value.params[0]
else:
2020-12-22 16:53:33 +08:00
raise CustomError(f'index of type {s} is not supported', node)
2020-12-18 16:40:32 +08:00
2020-12-21 10:08:05 +08:00
def parse_if_expr(ctx: Context,
sym_table: dict[str, Type],
node):
b = ctx.types['bool']
t = parse_expr(ctx, sym_table, node.test)
if t != b:
2020-12-22 16:53:33 +08:00
raise CustomError(f'type of conditional must be bool instead of {t}', node)
2020-12-21 10:08:05 +08:00
ty1 = parse_expr(ctx, sym_table, node.body)
ty2 = parse_expr(ctx, sym_table, node.orelse)
if ty1 != ty2:
2020-12-22 16:53:33 +08:00
raise CustomError(f'divergent type for if expression: {ty1} != {ty2}', node)
2020-12-21 10:08:05 +08:00
return ty1
2020-12-22 15:14:34 +08:00
def parse_simple_binding(name, ty):
2020-12-21 11:50:20 +08:00
if isinstance(name, ast.Name):
2020-12-21 15:08:55 +08:00
if name.id == '_':
return {}
2021-01-07 11:57:28 +08:00
name.type = ty
2020-12-21 11:50:20 +08:00
return {name.id: ty}
elif isinstance(name, ast.Tuple):
if not isinstance(ty, TupleType):
raise CustomError(f'cannot pattern match over {ty}')
if len(name.elts) != len(ty.params):
raise CustomError(f'pattern matching length mismatch')
result = {}
for x, y in zip(name.elts, ty.params):
2020-12-22 15:14:34 +08:00
binding = parse_simple_binding(x, y)
2020-12-21 15:08:55 +08:00
expected = len(result) + len(binding)
2020-12-22 15:14:34 +08:00
result |= parse_simple_binding(x, y)
2020-12-21 15:08:55 +08:00
if len(result) != expected:
2020-12-22 16:53:33 +08:00
raise CustomError('variable name clash', x)
2020-12-21 11:50:20 +08:00
return result
else:
raise CustomError(f'binding to {name} is not supported')
def parse_list_comprehension(ctx: Context,
sym_table: dict[str, Type],
node):
if len(node.generators) != 1:
2020-12-22 16:53:33 +08:00
raise CustomError(
'list comprehension with more than 1 for loop is not supported', node)
2020-12-21 11:50:20 +08:00
if node.generators[0].is_async:
2020-12-22 16:53:33 +08:00
raise CustomError('async list comprehension is not supported', node)
2020-12-21 11:50:20 +08:00
ty = parse_expr(ctx, sym_table, node.generators[0].iter)
2020-12-23 15:39:39 +08:00
if isinstance(ty, TypeVariable) and \
len(ty.constraints) == 1:
ty = ty.constraints[0]
2020-12-21 11:50:20 +08:00
if not isinstance(ty, ListType):
2020-12-22 16:53:33 +08:00
raise CustomError(f'unable to iterate over {ty}', node)
try:
sym_table2 = sym_table | parse_simple_binding(node.generators[0].target, ty.params[0])
except CustomError as e:
raise e.at(node)
2020-12-21 11:50:20 +08:00
b = ctx.types['bool']
for c in node.generators[0].ifs:
if parse_expr(ctx, sym_table2, c) != b:
2020-12-22 16:53:33 +08:00
raise CustomError(f'condition should be of boolean type', c)
2020-12-21 11:50:20 +08:00
return ListType(parse_expr(ctx, sym_table2, node.elt))