main script
This commit is contained in:
parent
bd71822719
commit
ee7c1713a5
|
@ -21,7 +21,6 @@ for simplicity reasons:
|
||||||
* method override check modulo variable renaming.
|
* method override check modulo variable renaming.
|
||||||
|
|
||||||
These features are currently not implemented, and would be added in due course:
|
These features are currently not implemented, and would be added in due course:
|
||||||
* Whole script check.
|
|
||||||
* Type guards.
|
* Type guards.
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
I = TypeVar('I', int32, int64)
|
||||||
|
|
||||||
|
class Vec:
|
||||||
|
v: list[int32]
|
||||||
|
def __init__(self, v: list[int32]):
|
||||||
|
self.v = v
|
||||||
|
|
||||||
|
def __add__(self, other: int32) -> Vec:
|
||||||
|
return Vec([v + other for v in self.v])
|
||||||
|
|
||||||
|
|
||||||
|
def addI(a: I, b: I) -> I:
|
||||||
|
return a + b
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
import ast
|
import ast
|
||||||
|
|
||||||
|
def quote_text(text):
|
||||||
|
return '\n'.join([f' {t}' for t in text.split('\n')])
|
||||||
|
|
||||||
class CustomError(Exception):
|
class CustomError(Exception):
|
||||||
def __init__(self, msg, node = None):
|
def __init__(self, msg, node = None):
|
||||||
self.msg = msg
|
self.msg = msg
|
||||||
|
@ -9,7 +12,7 @@ class CustomError(Exception):
|
||||||
def at(self, node):
|
def at(self, node):
|
||||||
self.msg = f'Error at {node.lineno}:{node.col_offset+1}-' \
|
self.msg = f'Error at {node.lineno}:{node.col_offset+1}-' \
|
||||||
f'{node.end_lineno}:{node.end_col_offset+1} ' \
|
f'{node.end_lineno}:{node.end_col_offset+1} ' \
|
||||||
f'"{ast.unparse(node)}"\n{self.msg}'
|
f'\n{quote_text(ast.unparse(node))}\n{self.msg}'
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def stringify_subst(subst):
|
def stringify_subst(subst):
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
import ast
|
||||||
|
import sys
|
||||||
|
from helper import CustomError
|
||||||
|
from type_def import SelfType
|
||||||
|
from parse_stmt import parse_stmts
|
||||||
|
from primitives import simplest_ctx
|
||||||
|
from top_level import parse_top_level
|
||||||
|
|
||||||
|
if len(sys.argv) != 2:
|
||||||
|
print('please pass the python script name as argument')
|
||||||
|
exit()
|
||||||
|
|
||||||
|
with open(sys.argv[1], 'r') as f:
|
||||||
|
source = f.read()
|
||||||
|
|
||||||
|
tree = ast.parse(source, filename=sys.argv[1])
|
||||||
|
|
||||||
|
try:
|
||||||
|
ctx, fns = parse_top_level(simplest_ctx, tree)
|
||||||
|
for c, name, fn in fns:
|
||||||
|
if c is None:
|
||||||
|
params, result, _ = ctx.functions[name]
|
||||||
|
else:
|
||||||
|
params, result, _ = ctx.types[c].methods[name]
|
||||||
|
# check if fully annotated all params
|
||||||
|
sym_table = {}
|
||||||
|
for n, ty in zip(fn.args.args, params):
|
||||||
|
if ty is None:
|
||||||
|
raise CustomError(
|
||||||
|
'Function parameters must be annotated',
|
||||||
|
fn)
|
||||||
|
if isinstance(ty, SelfType):
|
||||||
|
ty = ctx.types[c]
|
||||||
|
sym_table[n.arg] = ty
|
||||||
|
_, _, returned = parse_stmts(ctx, sym_table, sym_table, result, fn.body)
|
||||||
|
if result is not None and not returned:
|
||||||
|
raise CustomError('Function may have no return value', fn)
|
||||||
|
except CustomError as e:
|
||||||
|
print('Error while type checking:')
|
||||||
|
print(e.msg)
|
||||||
|
|
|
@ -106,7 +106,7 @@ def parse_tuple(ctx: Context,
|
||||||
def parse_attribute(ctx: Context,
|
def parse_attribute(ctx: Context,
|
||||||
sym_table: dict[str, Type],
|
sym_table: dict[str, Type],
|
||||||
node):
|
node):
|
||||||
obj = parse_expr(node.value)
|
obj = parse_expr(ctx, sym_table, node.value)
|
||||||
if node.attr in obj.fields:
|
if node.attr in obj.fields:
|
||||||
return obj.fields[node.attr]
|
return obj.fields[node.attr]
|
||||||
raise CustomError(f'unknown field {node.attr} in {obj}', node)
|
raise CustomError(f'unknown field {node.attr} in {obj}', node)
|
||||||
|
|
|
@ -105,8 +105,44 @@ def parse_class(ctx, c: ast.ClassDef):
|
||||||
return functions
|
return functions
|
||||||
|
|
||||||
|
|
||||||
|
def parse_type_var(ctx: Context, node):
|
||||||
|
if isinstance(node, ast.Assign):
|
||||||
|
if not isinstance(node.value, ast.Call) or\
|
||||||
|
not isinstance(node.value.func, ast.Name) or\
|
||||||
|
node.value.func.id != 'TypeVar':
|
||||||
|
return
|
||||||
|
if not len(node.targets) == 1 or\
|
||||||
|
not isinstance(node.targets[0], ast.Name):
|
||||||
|
raise CustomError(
|
||||||
|
'complicated variable assignment is not allowed',
|
||||||
|
node)
|
||||||
|
if len(node.value.args) == 0 or\
|
||||||
|
not isinstance(node.value.args[0], ast.Constant) or\
|
||||||
|
not isinstance(node.value.args[0].value, str):
|
||||||
|
raise CustomError('call to TypeVar must at least have a name',
|
||||||
|
node.value)
|
||||||
|
name = node.value.args[0]
|
||||||
|
if name in ctx.variables:
|
||||||
|
raise CustomError('redefining type variable is not allowed', node)
|
||||||
|
constraints = []
|
||||||
|
for v in node.value.args[1:]:
|
||||||
|
if isinstance(v, ast.Constant):
|
||||||
|
value = v.value
|
||||||
|
elif isinstance(v, ast.Name):
|
||||||
|
value = v.id
|
||||||
|
else:
|
||||||
|
raise CustomError(
|
||||||
|
'TypeVar constraints must be either string or type name',
|
||||||
|
node)
|
||||||
|
if value not in ctx.types:
|
||||||
|
raise CustomError(f'unbounded type {value}', node)
|
||||||
|
constraints.append(ctx.types[value])
|
||||||
|
ctx.variables[node.targets[0].id] = TypeVariable(name, constraints)
|
||||||
|
|
||||||
|
|
||||||
def parse_top_level(ctx: Context, module: ast.Module):
|
def parse_top_level(ctx: Context, module: ast.Module):
|
||||||
to_be_processed = []
|
to_be_processed = []
|
||||||
|
assignments = []
|
||||||
# first pass, obtain all type names
|
# first pass, obtain all type names
|
||||||
for element in module.body:
|
for element in module.body:
|
||||||
if isinstance(element, ast.ClassDef):
|
if isinstance(element, ast.ClassDef):
|
||||||
|
@ -117,8 +153,14 @@ def parse_top_level(ctx: Context, module: ast.Module):
|
||||||
to_be_processed.append(element)
|
to_be_processed.append(element)
|
||||||
elif isinstance(element, ast.FunctionDef):
|
elif isinstance(element, ast.FunctionDef):
|
||||||
to_be_processed.append(element)
|
to_be_processed.append(element)
|
||||||
|
elif isinstance(element, ast.Assign):
|
||||||
|
assignments.append(element)
|
||||||
|
|
||||||
# second pass, obtain all function types
|
# second pass, obtain all type variables
|
||||||
|
for node in assignments:
|
||||||
|
parse_type_var(ctx, node)
|
||||||
|
|
||||||
|
# third pass, obtain all function types
|
||||||
function_stmts = []
|
function_stmts = []
|
||||||
for element in to_be_processed:
|
for element in to_be_processed:
|
||||||
if isinstance(element, ast.ClassDef):
|
if isinstance(element, ast.ClassDef):
|
||||||
|
@ -131,7 +173,7 @@ def parse_top_level(ctx: Context, module: ast.Module):
|
||||||
raise CustomError(f"Function name {name} clashed with type name")
|
raise CustomError(f"Function name {name} clashed with type name")
|
||||||
args, result, var = parse_function(ctx, None, element)
|
args, result, var = parse_function(ctx, None, element)
|
||||||
ctx.functions[name] = (args, result, var)
|
ctx.functions[name] = (args, result, var)
|
||||||
function_stmts.append(element)
|
function_stmts.append((None, name, element))
|
||||||
|
|
||||||
return ctx, function_stmts
|
return ctx, function_stmts
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue