Compare commits
17 Commits
main
...
d0f1178c17
| Author | SHA1 | Date | |
|---|---|---|---|
|
d0f1178c17
|
|||
|
0eca23b894
|
|||
|
f664fb4a4f
|
|||
|
32330243c6
|
|||
|
96e76065cf
|
|||
|
7b7d87e59a
|
|||
|
1eb90164e6
|
|||
|
35ec0d0db8
|
|||
|
48fcb499a1
|
|||
|
bdc1b265a6
|
|||
|
1fb4b6f8c6
|
|||
|
48c1ecc1c8
|
|||
|
04853eac70
|
|||
|
020824d1f8
|
|||
|
ad86446a2d
|
|||
|
94d84ab170
|
|||
|
8381f4f31d
|
23
gen/midas.py
23
gen/midas.py
@@ -26,6 +26,14 @@ class MemberKind(Enum):
|
|||||||
METHOD = auto()
|
METHOD = auto()
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True, kw_only=True)
|
||||||
|
class ParamSpec:
|
||||||
|
l_paren: Token
|
||||||
|
pos: list[FunctionType.Argument]
|
||||||
|
mixed: list[FunctionType.Argument]
|
||||||
|
kw: list[FunctionType.Argument]
|
||||||
|
|
||||||
|
|
||||||
###<
|
###<
|
||||||
|
|
||||||
|
|
||||||
@@ -50,9 +58,8 @@ class ExtendStmt:
|
|||||||
|
|
||||||
class PredicateStmt:
|
class PredicateStmt:
|
||||||
name: Token
|
name: Token
|
||||||
subject: Token
|
params: list[ParamSpec]
|
||||||
type: Type
|
body: Expr
|
||||||
condition: Expr
|
|
||||||
|
|
||||||
|
|
||||||
###<
|
###<
|
||||||
@@ -78,6 +85,12 @@ class UnaryExpr:
|
|||||||
right: Expr
|
right: Expr
|
||||||
|
|
||||||
|
|
||||||
|
class CallExpr:
|
||||||
|
callee: Expr
|
||||||
|
arguments: list[Expr]
|
||||||
|
keywords: dict[str, Expr]
|
||||||
|
|
||||||
|
|
||||||
class GetExpr:
|
class GetExpr:
|
||||||
expr: Expr
|
expr: Expr
|
||||||
name: Token
|
name: Token
|
||||||
@@ -128,9 +141,7 @@ class ExtensionType:
|
|||||||
|
|
||||||
|
|
||||||
class FunctionType:
|
class FunctionType:
|
||||||
pos_args: list[Argument]
|
params: ParamSpec
|
||||||
args: list[Argument]
|
|
||||||
kw_args: list[Argument]
|
|
||||||
returns: Type
|
returns: Type
|
||||||
|
|
||||||
@dataclass(frozen=True, kw_only=True)
|
@dataclass(frozen=True, kw_only=True)
|
||||||
|
|||||||
@@ -27,6 +27,14 @@ class MemberKind(Enum):
|
|||||||
METHOD = auto()
|
METHOD = auto()
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True, kw_only=True)
|
||||||
|
class ParamSpec:
|
||||||
|
l_paren: Token
|
||||||
|
pos: list[FunctionType.Argument]
|
||||||
|
mixed: list[FunctionType.Argument]
|
||||||
|
kw: list[FunctionType.Argument]
|
||||||
|
|
||||||
|
|
||||||
##############
|
##############
|
||||||
# Statements #
|
# Statements #
|
||||||
##############
|
##############
|
||||||
@@ -86,9 +94,8 @@ class ExtendStmt(Stmt):
|
|||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class PredicateStmt(Stmt):
|
class PredicateStmt(Stmt):
|
||||||
name: Token
|
name: Token
|
||||||
subject: Token
|
params: list[ParamSpec]
|
||||||
type: Type
|
body: Expr
|
||||||
condition: Expr
|
|
||||||
|
|
||||||
def accept(self, visitor: Stmt.Visitor[T]) -> T:
|
def accept(self, visitor: Stmt.Visitor[T]) -> T:
|
||||||
return visitor.visit_predicate_stmt(self)
|
return visitor.visit_predicate_stmt(self)
|
||||||
@@ -116,6 +123,9 @@ class Expr(ABC):
|
|||||||
@abstractmethod
|
@abstractmethod
|
||||||
def visit_unary_expr(self, expr: UnaryExpr) -> T: ...
|
def visit_unary_expr(self, expr: UnaryExpr) -> T: ...
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def visit_call_expr(self, expr: CallExpr) -> T: ...
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def visit_get_expr(self, expr: GetExpr) -> T: ...
|
def visit_get_expr(self, expr: GetExpr) -> T: ...
|
||||||
|
|
||||||
@@ -161,6 +171,16 @@ class UnaryExpr(Expr):
|
|||||||
return visitor.visit_unary_expr(self)
|
return visitor.visit_unary_expr(self)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class CallExpr(Expr):
|
||||||
|
callee: Expr
|
||||||
|
arguments: list[Expr]
|
||||||
|
keywords: dict[str, Expr]
|
||||||
|
|
||||||
|
def accept(self, visitor: Expr.Visitor[T]) -> T:
|
||||||
|
return visitor.visit_call_expr(self)
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class GetExpr(Expr):
|
class GetExpr(Expr):
|
||||||
expr: Expr
|
expr: Expr
|
||||||
@@ -279,9 +299,7 @@ class ExtensionType(Type):
|
|||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class FunctionType(Type):
|
class FunctionType(Type):
|
||||||
pos_args: list[Argument]
|
params: ParamSpec
|
||||||
args: list[Argument]
|
|
||||||
kw_args: list[Argument]
|
|
||||||
returns: Type
|
returns: Type
|
||||||
|
|
||||||
@dataclass(frozen=True, kw_only=True)
|
@dataclass(frozen=True, kw_only=True)
|
||||||
|
|||||||
@@ -150,13 +150,17 @@ class MidasAstPrinter(
|
|||||||
self._write_line("PredicateStmt")
|
self._write_line("PredicateStmt")
|
||||||
with self._child_level():
|
with self._child_level():
|
||||||
self._write_line(f'name: "{stmt.name.lexeme}"')
|
self._write_line(f'name: "{stmt.name.lexeme}"')
|
||||||
self._write_line(f'subject: "{stmt.subject.lexeme}"')
|
self._write_line("params")
|
||||||
self._write_line("type")
|
with self._child_level():
|
||||||
|
for i, spec in enumerate(stmt.params):
|
||||||
|
self._idx = i
|
||||||
|
if i == len(stmt.params) - 1:
|
||||||
|
self._mark_last()
|
||||||
|
self._visit_param_spec(spec)
|
||||||
|
|
||||||
|
self._write_line("body", last=True)
|
||||||
with self._child_level(single=True):
|
with self._child_level(single=True):
|
||||||
stmt.type.accept(self)
|
stmt.body.accept(self)
|
||||||
self._write_line("condition", last=True)
|
|
||||||
with self._child_level(single=True):
|
|
||||||
stmt.condition.accept(self)
|
|
||||||
|
|
||||||
# Expressions
|
# Expressions
|
||||||
|
|
||||||
@@ -195,6 +199,29 @@ class MidasAstPrinter(
|
|||||||
with self._child_level(single=True):
|
with self._child_level(single=True):
|
||||||
expr.right.accept(self)
|
expr.right.accept(self)
|
||||||
|
|
||||||
|
def visit_call_expr(self, expr: m.CallExpr) -> None:
|
||||||
|
self._write_line("CallExpr")
|
||||||
|
with self._child_level():
|
||||||
|
self._write_line("callee")
|
||||||
|
with self._child_level(single=True):
|
||||||
|
expr.callee.accept(self)
|
||||||
|
self._write_line("arguments")
|
||||||
|
with self._child_level():
|
||||||
|
for i, arg in enumerate(expr.arguments):
|
||||||
|
self._idx = i
|
||||||
|
if i == len(expr.arguments) - 1:
|
||||||
|
self._mark_last()
|
||||||
|
arg.accept(self)
|
||||||
|
self._write_line("keywords", last=True)
|
||||||
|
with self._child_level():
|
||||||
|
for i, (name, arg) in enumerate(expr.keywords.items()):
|
||||||
|
self._idx = i
|
||||||
|
if i == len(expr.keywords) - 1:
|
||||||
|
self._mark_last()
|
||||||
|
self._write_line(name)
|
||||||
|
with self._child_level(single=True):
|
||||||
|
arg.accept(self)
|
||||||
|
|
||||||
def visit_get_expr(self, expr: m.GetExpr):
|
def visit_get_expr(self, expr: m.GetExpr):
|
||||||
self._write_line("GetExpr")
|
self._write_line("GetExpr")
|
||||||
with self._child_level():
|
with self._child_level():
|
||||||
@@ -276,34 +303,41 @@ class MidasAstPrinter(
|
|||||||
def visit_function_type(self, type: m.FunctionType) -> None:
|
def visit_function_type(self, type: m.FunctionType) -> None:
|
||||||
self._write_line("FunctionType")
|
self._write_line("FunctionType")
|
||||||
with self._child_level():
|
with self._child_level():
|
||||||
self._write_line("pos_args")
|
self._write_line("params")
|
||||||
with self._child_level():
|
with self._child_level(single=True):
|
||||||
for i, arg in enumerate(type.pos_args):
|
self._visit_param_spec(type.params)
|
||||||
self._idx = i
|
|
||||||
if i == len(type.pos_args) - 1:
|
|
||||||
self._mark_last()
|
|
||||||
self._print_function_arg(arg)
|
|
||||||
|
|
||||||
self._write_line("args")
|
|
||||||
with self._child_level():
|
|
||||||
for i, arg in enumerate(type.args):
|
|
||||||
self._idx = i
|
|
||||||
if i == len(type.args) - 1:
|
|
||||||
self._mark_last()
|
|
||||||
self._print_function_arg(arg)
|
|
||||||
|
|
||||||
self._write_line("kw_args")
|
|
||||||
with self._child_level():
|
|
||||||
for i, arg in enumerate(type.kw_args):
|
|
||||||
self._idx = i
|
|
||||||
if i == len(type.kw_args) - 1:
|
|
||||||
self._mark_last()
|
|
||||||
self._print_function_arg(arg)
|
|
||||||
|
|
||||||
self._write_line("returns", last=True)
|
self._write_line("returns", last=True)
|
||||||
with self._child_level(single=True):
|
with self._child_level(single=True):
|
||||||
type.returns.accept(self)
|
type.returns.accept(self)
|
||||||
|
|
||||||
|
def _visit_param_spec(self, spec: m.ParamSpec) -> None:
|
||||||
|
self._write_line("ParamSpec")
|
||||||
|
with self._child_level():
|
||||||
|
self._write_line("pos")
|
||||||
|
with self._child_level():
|
||||||
|
for i, arg in enumerate(spec.pos):
|
||||||
|
self._idx = i
|
||||||
|
if i == len(spec.pos) - 1:
|
||||||
|
self._mark_last()
|
||||||
|
self._print_function_arg(arg)
|
||||||
|
|
||||||
|
self._write_line("mixed")
|
||||||
|
with self._child_level():
|
||||||
|
for i, arg in enumerate(spec.mixed):
|
||||||
|
self._idx = i
|
||||||
|
if i == len(spec.mixed) - 1:
|
||||||
|
self._mark_last()
|
||||||
|
self._print_function_arg(arg)
|
||||||
|
|
||||||
|
self._write_line("kw", last=True)
|
||||||
|
with self._child_level():
|
||||||
|
for i, arg in enumerate(spec.kw):
|
||||||
|
self._idx = i
|
||||||
|
if i == len(spec.kw) - 1:
|
||||||
|
self._mark_last()
|
||||||
|
self._print_function_arg(arg)
|
||||||
|
|
||||||
def _print_function_arg(self, arg: m.FunctionType.Argument) -> None:
|
def _print_function_arg(self, arg: m.FunctionType.Argument) -> None:
|
||||||
self._write_line("Argument")
|
self._write_line("Argument")
|
||||||
with self._child_level():
|
with self._child_level():
|
||||||
@@ -367,10 +401,9 @@ class MidasPrinter(m.Expr.Visitor[str], m.Stmt.Visitor[str], m.Type.Visitor[str]
|
|||||||
|
|
||||||
def visit_predicate_stmt(self, stmt: m.PredicateStmt):
|
def visit_predicate_stmt(self, stmt: m.PredicateStmt):
|
||||||
name: str = stmt.name.lexeme
|
name: str = stmt.name.lexeme
|
||||||
subject: str = stmt.subject.lexeme
|
sig: str = "".join(self._visit_param_spec(spec) for spec in stmt.params)
|
||||||
type: str = stmt.type.accept(self)
|
body: str = stmt.body.accept(self)
|
||||||
condition: str = stmt.condition.accept(self)
|
return self.indented(f"predicate {name}{sig} = {body}")
|
||||||
return self.indented(f"predicate {name}({subject}: {type}) = {condition}")
|
|
||||||
|
|
||||||
def visit_logical_expr(self, expr: m.LogicalExpr):
|
def visit_logical_expr(self, expr: m.LogicalExpr):
|
||||||
left: str = expr.left.accept(self)
|
left: str = expr.left.accept(self)
|
||||||
@@ -389,6 +422,12 @@ class MidasPrinter(m.Expr.Visitor[str], m.Stmt.Visitor[str], m.Type.Visitor[str]
|
|||||||
right: str = expr.right.accept(self)
|
right: str = expr.right.accept(self)
|
||||||
return f"{operator}{right}"
|
return f"{operator}{right}"
|
||||||
|
|
||||||
|
def visit_call_expr(self, expr: m.CallExpr) -> str:
|
||||||
|
args: list[str] = [arg.accept(self) for arg in expr.arguments] + [
|
||||||
|
f"{name}={arg.accept(self)}" for name, arg in expr.keywords.items()
|
||||||
|
]
|
||||||
|
return f"{expr.callee.accept(self)}({', '.join(args)})"
|
||||||
|
|
||||||
def visit_get_expr(self, expr: m.GetExpr):
|
def visit_get_expr(self, expr: m.GetExpr):
|
||||||
expr_: str = expr.expr.accept(self)
|
expr_: str = expr.expr.accept(self)
|
||||||
name: str = expr.name.lexeme
|
name: str = expr.name.lexeme
|
||||||
@@ -436,9 +475,13 @@ class MidasPrinter(m.Expr.Visitor[str], m.Stmt.Visitor[str], m.Type.Visitor[str]
|
|||||||
return f"{type.base.accept(self)} & {type.extension.accept(self)}"
|
return f"{type.base.accept(self)} & {type.extension.accept(self)}"
|
||||||
|
|
||||||
def visit_function_type(self, type: m.FunctionType) -> str:
|
def visit_function_type(self, type: m.FunctionType) -> str:
|
||||||
pos_args: list[str] = [self._print_arg(arg) for arg in type.pos_args]
|
spec: str = self._visit_param_spec(type.params)
|
||||||
mixed_args: list[str] = [self._print_arg(arg) for arg in type.args]
|
return f"fn {spec} -> {type.returns.accept(self)}"
|
||||||
kw_args: list[str] = [self._print_arg(arg) for arg in type.kw_args]
|
|
||||||
|
def _visit_param_spec(self, spec: m.ParamSpec) -> str:
|
||||||
|
pos_args: list[str] = [self._print_arg(arg) for arg in spec.pos]
|
||||||
|
mixed_args: list[str] = [self._print_arg(arg) for arg in spec.mixed]
|
||||||
|
kw_args: list[str] = [self._print_arg(arg) for arg in spec.kw]
|
||||||
args: list[str] = pos_args
|
args: list[str] = pos_args
|
||||||
|
|
||||||
if len(pos_args) != 0:
|
if len(pos_args) != 0:
|
||||||
@@ -447,8 +490,7 @@ class MidasPrinter(m.Expr.Visitor[str], m.Stmt.Visitor[str], m.Type.Visitor[str]
|
|||||||
if len(kw_args) != 0:
|
if len(kw_args) != 0:
|
||||||
args.append("*")
|
args.append("*")
|
||||||
args += kw_args
|
args += kw_args
|
||||||
|
return f"({', '.join(args)})"
|
||||||
return f"fn ({', '.join(args)}) -> {type.returns.accept(self)}"
|
|
||||||
|
|
||||||
def _print_arg(self, arg: m.FunctionType.Argument) -> str:
|
def _print_arg(self, arg: m.FunctionType.Argument) -> str:
|
||||||
res: str = ""
|
res: str = ""
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import logging
|
import logging
|
||||||
|
from dataclasses import dataclass
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
@@ -9,9 +10,11 @@ from midas.checker.reporter import FileReporter, Reporter
|
|||||||
from midas.checker.types import (
|
from midas.checker.types import (
|
||||||
AliasType,
|
AliasType,
|
||||||
ComplexType,
|
ComplexType,
|
||||||
|
ConstraintType,
|
||||||
ExtensionType,
|
ExtensionType,
|
||||||
Function,
|
Function,
|
||||||
GenericType,
|
GenericType,
|
||||||
|
Predicate,
|
||||||
Type,
|
Type,
|
||||||
TypeVar,
|
TypeVar,
|
||||||
UnknownType,
|
UnknownType,
|
||||||
@@ -21,7 +24,14 @@ from midas.lexer.token import Token
|
|||||||
from midas.parser.midas import MidasParser
|
from midas.parser.midas import MidasParser
|
||||||
|
|
||||||
|
|
||||||
class MidasTyper(m.Stmt.Visitor[None], m.Expr.Visitor[None], m.Type.Visitor[Type]):
|
@dataclass(frozen=True, kw_only=True)
|
||||||
|
class TypedParamSpec:
|
||||||
|
pos: list[Function.Argument]
|
||||||
|
mixed: list[Function.Argument]
|
||||||
|
kw: list[Function.Argument]
|
||||||
|
|
||||||
|
|
||||||
|
class MidasTyper(m.Stmt.Visitor[None], m.Expr.Visitor[Type], m.Type.Visitor[Type]):
|
||||||
"""A resolver which evaluates Midas type definitions and build a registry"""
|
"""A resolver which evaluates Midas type definitions and build a registry"""
|
||||||
|
|
||||||
def __init__(self, types: TypesRegistry, reporter: Reporter) -> None:
|
def __init__(self, types: TypesRegistry, reporter: Reporter) -> None:
|
||||||
@@ -31,12 +41,16 @@ class MidasTyper(m.Stmt.Visitor[None], m.Expr.Visitor[None], m.Type.Visitor[Type
|
|||||||
self.types: TypesRegistry = types
|
self.types: TypesRegistry = types
|
||||||
self._local_variables: dict[str, TypeVar] = {}
|
self._local_variables: dict[str, TypeVar] = {}
|
||||||
|
|
||||||
|
self._predicate_params: dict[str, Type] = {}
|
||||||
|
|
||||||
self._current_name: Optional[str] = None
|
self._current_name: Optional[str] = None
|
||||||
|
|
||||||
define_builtins(self.types)
|
define_builtins(self.types)
|
||||||
builtins_path: Path = (Path(__file__).parent / "builtins.midas").resolve()
|
builtins_path: Path = (Path(__file__).parent / "builtins.midas").resolve()
|
||||||
self.process(builtins_path.read_text(), str(builtins_path))
|
self.process(builtins_path.read_text(), str(builtins_path))
|
||||||
|
|
||||||
|
self._bool: Type = self.get_type("bool")
|
||||||
|
|
||||||
def process(self, source: str, path: Optional[str]):
|
def process(self, source: str, path: Optional[str]):
|
||||||
self.reporter = self.reporter.for_file(path)
|
self.reporter = self.reporter.for_file(path)
|
||||||
lexer: MidasLexer = MidasLexer(source)
|
lexer: MidasLexer = MidasLexer(source)
|
||||||
@@ -47,6 +61,10 @@ class MidasTyper(m.Stmt.Visitor[None], m.Expr.Visitor[None], m.Type.Visitor[Type
|
|||||||
self.reporter.error(error.token.get_location(), error.message)
|
self.reporter.error(error.token.get_location(), error.message)
|
||||||
self.resolve(stmts)
|
self.resolve(stmts)
|
||||||
|
|
||||||
|
def type_of(self, expr: m.Expr) -> Type:
|
||||||
|
type: Type = expr.accept(self)
|
||||||
|
return type
|
||||||
|
|
||||||
def get_type(self, name: str) -> Type:
|
def get_type(self, name: str) -> Type:
|
||||||
"""Get a type from its name
|
"""Get a type from its name
|
||||||
|
|
||||||
@@ -63,6 +81,14 @@ class MidasTyper(m.Stmt.Visitor[None], m.Expr.Visitor[None], m.Type.Visitor[Type
|
|||||||
return self._local_variables[name]
|
return self._local_variables[name]
|
||||||
return self.types.get_type(name)
|
return self.types.get_type(name)
|
||||||
|
|
||||||
|
def get_variable(self, name: str) -> Type:
|
||||||
|
if name in self._predicate_params:
|
||||||
|
return self._predicate_params[name]
|
||||||
|
predicate: Optional[Predicate] = self.types.lookup_predicate(name)
|
||||||
|
if predicate is None:
|
||||||
|
raise NameError(f"Unknown variable '{name}'")
|
||||||
|
return predicate.type
|
||||||
|
|
||||||
def resolve(self, stmts: list[m.Stmt]):
|
def resolve(self, stmts: list[m.Stmt]):
|
||||||
"""Process a sequence of statements
|
"""Process a sequence of statements
|
||||||
|
|
||||||
@@ -72,6 +98,12 @@ class MidasTyper(m.Stmt.Visitor[None], m.Expr.Visitor[None], m.Type.Visitor[Type
|
|||||||
for stmt in stmts:
|
for stmt in stmts:
|
||||||
stmt.accept(self)
|
stmt.accept(self)
|
||||||
|
|
||||||
|
def assert_bool(self, expr: m.Expr):
|
||||||
|
type: Type = self.type_of(expr)
|
||||||
|
if not self.types.is_subtype(type, self._bool):
|
||||||
|
# TODO: change back to error when operations are type checked
|
||||||
|
self.reporter.warning(expr.location, f"Must be a boolean but is {type}")
|
||||||
|
|
||||||
def visit_type_stmt(self, stmt: m.TypeStmt) -> None:
|
def visit_type_stmt(self, stmt: m.TypeStmt) -> None:
|
||||||
name: str = stmt.name.lexeme
|
name: str = stmt.name.lexeme
|
||||||
self._current_name = name
|
self._current_name = name
|
||||||
@@ -106,31 +138,125 @@ class MidasTyper(m.Stmt.Visitor[None], m.Expr.Visitor[None], m.Type.Visitor[Type
|
|||||||
)
|
)
|
||||||
|
|
||||||
def visit_predicate_stmt(self, stmt: m.PredicateStmt) -> None:
|
def visit_predicate_stmt(self, stmt: m.PredicateStmt) -> None:
|
||||||
self.reporter.warning(stmt.location, "PredicateStmt not yet supported")
|
for spec in stmt.params:
|
||||||
|
for param in spec.mixed:
|
||||||
|
assert param.name is not None
|
||||||
|
self._predicate_params[param.name.lexeme] = param.type.accept(self)
|
||||||
|
|
||||||
def visit_logical_expr(self, expr: m.LogicalExpr) -> None:
|
type: Type = self.type_of(stmt.body)
|
||||||
self.reporter.warning(expr.location, "LogicalExpr not yet supported")
|
params: list[TypedParamSpec] = [
|
||||||
|
self._visit_param_spec(spec) for spec in stmt.params
|
||||||
|
]
|
||||||
|
|
||||||
def visit_binary_expr(self, expr: m.BinaryExpr) -> None:
|
if not self._is_valid_predicate(type):
|
||||||
|
# TODO: change back to error when operations are type checked
|
||||||
|
self.reporter.warning(
|
||||||
|
stmt.body.location,
|
||||||
|
f"Predicate function body must evaluate to a boolean, got {type}",
|
||||||
|
)
|
||||||
|
if len(params) != 0:
|
||||||
|
type = self._bool
|
||||||
|
for spec in reversed(params):
|
||||||
|
type = Function(
|
||||||
|
pos_args=spec.pos,
|
||||||
|
args=spec.mixed,
|
||||||
|
kw_args=spec.kw,
|
||||||
|
returns=type,
|
||||||
|
)
|
||||||
|
self._predicate_params = {}
|
||||||
|
self.types.define_predicate(
|
||||||
|
stmt.name.lexeme,
|
||||||
|
Predicate(
|
||||||
|
type=type,
|
||||||
|
body=stmt.body,
|
||||||
|
alias=len(params) == 0,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
def _is_valid_predicate(self, body: Type) -> bool:
|
||||||
|
match body:
|
||||||
|
case Function(returns=returns):
|
||||||
|
return self._is_valid_predicate(returns)
|
||||||
|
case _ if self.types.is_subtype(body, self._bool):
|
||||||
|
return True
|
||||||
|
case _:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def visit_logical_expr(self, expr: m.LogicalExpr) -> Type:
|
||||||
|
self.assert_bool(expr.left)
|
||||||
|
self.assert_bool(expr.right)
|
||||||
|
return self._bool
|
||||||
|
|
||||||
|
def visit_binary_expr(self, expr: m.BinaryExpr) -> Type:
|
||||||
|
# TODO
|
||||||
self.reporter.warning(expr.location, "BinaryExpr not yet supported")
|
self.reporter.warning(expr.location, "BinaryExpr not yet supported")
|
||||||
|
return UnknownType()
|
||||||
|
|
||||||
def visit_unary_expr(self, expr: m.UnaryExpr) -> None:
|
def visit_unary_expr(self, expr: m.UnaryExpr) -> Type:
|
||||||
|
# TODO
|
||||||
self.reporter.warning(expr.location, "UnaryExpr not yet supported")
|
self.reporter.warning(expr.location, "UnaryExpr not yet supported")
|
||||||
|
return UnknownType()
|
||||||
|
|
||||||
def visit_get_expr(self, expr: m.GetExpr) -> None:
|
def visit_call_expr(self, expr: m.CallExpr) -> Type:
|
||||||
self.reporter.warning(expr.location, "GetExpr not yet supported")
|
callee: Type = expr.callee.accept(self)
|
||||||
|
if not isinstance(callee, Function):
|
||||||
|
self.reporter.error(expr.location, f"Cannot call {callee}")
|
||||||
|
return UnknownType()
|
||||||
|
args: list[Type] = [arg.accept(self) for arg in expr.arguments]
|
||||||
|
|
||||||
def visit_variable_expr(self, expr: m.VariableExpr) -> None:
|
n_args: int = len(args)
|
||||||
self.reporter.warning(expr.location, "VariableExpr not yet supported")
|
n_params: int = len(callee.args)
|
||||||
|
if n_args != n_params:
|
||||||
|
self.reporter.error(
|
||||||
|
expr.location,
|
||||||
|
f"Wrong number of argument, expected {n_params}, got {n_args}",
|
||||||
|
)
|
||||||
|
return UnknownType()
|
||||||
|
|
||||||
def visit_grouping_expr(self, expr: m.GroupingExpr) -> None:
|
valid: bool = True
|
||||||
|
for arg, param in zip(args, callee.args):
|
||||||
|
if not self.types.is_subtype(arg, param.type):
|
||||||
|
self.reporter.error(
|
||||||
|
expr.location,
|
||||||
|
f"Invalid argument type at pos {param.pos}, expected {param.type}, got {arg}",
|
||||||
|
)
|
||||||
|
valid = False
|
||||||
|
if not valid:
|
||||||
|
return UnknownType()
|
||||||
|
return callee.returns
|
||||||
|
|
||||||
|
def visit_get_expr(self, expr: m.GetExpr) -> Type:
|
||||||
|
object: Type = expr.expr.accept(self)
|
||||||
|
member: Optional[Type] = self.types.lookup_member(object, expr.name.lexeme)
|
||||||
|
if member is None:
|
||||||
|
self.reporter.error(
|
||||||
|
expr.location, f"Unknown member '{expr.name}' of {object}"
|
||||||
|
)
|
||||||
|
return UnknownType()
|
||||||
|
return member
|
||||||
|
|
||||||
|
def visit_variable_expr(self, expr: m.VariableExpr) -> Type:
|
||||||
|
return self.get_variable(expr.name.lexeme)
|
||||||
|
|
||||||
|
def visit_grouping_expr(self, expr: m.GroupingExpr) -> Type:
|
||||||
return expr.expr.accept(self)
|
return expr.expr.accept(self)
|
||||||
|
|
||||||
def visit_literal_expr(self, expr: m.LiteralExpr) -> None:
|
def visit_literal_expr(self, expr: m.LiteralExpr) -> Type:
|
||||||
self.reporter.warning(expr.location, "LiteralExpr not yet supported")
|
match expr.value:
|
||||||
|
case bool(): # Must be before int
|
||||||
|
return self.types.get_type("bool")
|
||||||
|
case int():
|
||||||
|
return self.types.get_type("int")
|
||||||
|
case float():
|
||||||
|
return self.types.get_type("float")
|
||||||
|
case str():
|
||||||
|
return self.types.get_type("str")
|
||||||
|
case _:
|
||||||
|
self.reporter.warning(expr.location, f"Unknown literal {expr}")
|
||||||
|
return UnknownType()
|
||||||
|
|
||||||
def visit_wildcard_expr(self, expr: m.WildcardExpr) -> None:
|
def visit_wildcard_expr(self, expr: m.WildcardExpr) -> Type:
|
||||||
self.reporter.warning(expr.location, "WildcardExpr not yet supported")
|
return self.get_variable("_")
|
||||||
|
|
||||||
def visit_named_type(self, type: m.NamedType) -> Type:
|
def visit_named_type(self, type: m.NamedType) -> Type:
|
||||||
name: str = type.name.lexeme
|
name: str = type.name.lexeme
|
||||||
@@ -153,10 +279,10 @@ class MidasTyper(m.Stmt.Visitor[None], m.Expr.Visitor[None], m.Type.Visitor[Type
|
|||||||
return UnknownType()
|
return UnknownType()
|
||||||
|
|
||||||
def visit_constraint_type(self, type: m.ConstraintType) -> Type:
|
def visit_constraint_type(self, type: m.ConstraintType) -> Type:
|
||||||
type_: Type = type.type.accept(self)
|
return ConstraintType(
|
||||||
type.constraint.accept(self)
|
type=type.type.accept(self),
|
||||||
# TODO
|
constraint=type.constraint,
|
||||||
return UnknownType()
|
)
|
||||||
|
|
||||||
def visit_complex_type(self, type: m.ComplexType) -> ComplexType:
|
def visit_complex_type(self, type: m.ComplexType) -> ComplexType:
|
||||||
return ComplexType(
|
return ComplexType(
|
||||||
@@ -172,8 +298,17 @@ class MidasTyper(m.Stmt.Visitor[None], m.Expr.Visitor[None], m.Type.Visitor[Type
|
|||||||
)
|
)
|
||||||
|
|
||||||
def visit_function_type(self, type: m.FunctionType) -> Type:
|
def visit_function_type(self, type: m.FunctionType) -> Type:
|
||||||
n_pos_args: int = len(type.pos_args)
|
params: TypedParamSpec = self._visit_param_spec(type.params)
|
||||||
n_args: int = len(type.args)
|
return Function(
|
||||||
|
pos_args=params.pos,
|
||||||
|
args=params.mixed,
|
||||||
|
kw_args=params.kw,
|
||||||
|
returns=type.returns.accept(self),
|
||||||
|
)
|
||||||
|
|
||||||
|
def _visit_param_spec(self, spec: m.ParamSpec) -> TypedParamSpec:
|
||||||
|
n_pos: int = len(spec.pos)
|
||||||
|
n_mixed: int = len(spec.mixed)
|
||||||
|
|
||||||
def process_arg(arg: m.FunctionType.Argument, i: int) -> Function.Argument:
|
def process_arg(arg: m.FunctionType.Argument, i: int) -> Function.Argument:
|
||||||
return Function.Argument(
|
return Function.Argument(
|
||||||
@@ -183,14 +318,10 @@ class MidasTyper(m.Stmt.Visitor[None], m.Expr.Visitor[None], m.Type.Visitor[Type
|
|||||||
required=arg.required,
|
required=arg.required,
|
||||||
)
|
)
|
||||||
|
|
||||||
return Function(
|
return TypedParamSpec(
|
||||||
pos_args=[process_arg(arg, i) for i, arg in enumerate(type.pos_args)],
|
pos=[process_arg(arg, i) for i, arg in enumerate(spec.pos)],
|
||||||
args=[process_arg(arg, i + n_pos_args) for i, arg in enumerate(type.args)],
|
mixed=[process_arg(arg, i + n_pos) for i, arg in enumerate(spec.mixed)],
|
||||||
kw_args=[
|
kw=[process_arg(arg, i + n_pos + n_mixed) for i, arg in enumerate(spec.kw)],
|
||||||
process_arg(arg, i + n_pos_args + n_args)
|
|
||||||
for i, arg in enumerate(type.kw_args)
|
|
||||||
],
|
|
||||||
returns=type.returns.accept(self),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def _resolve_type_params(self, params: list[m.TypeParam]):
|
def _resolve_type_params(self, params: list[m.TypeParam]):
|
||||||
|
|||||||
@@ -652,7 +652,7 @@ class PythonTyper(
|
|||||||
If the function has overloads, the function will try to resolve the
|
If the function has overloads, the function will try to resolve the
|
||||||
appropriate signature.
|
appropriate signature.
|
||||||
Argument types are matched to the defined parameters.
|
Argument types are matched to the defined parameters.
|
||||||
The function doesn't take the raw expression as a parameter to accomodate
|
The function doesn't take the raw expression as a parameter to accommodate
|
||||||
for desugared calls such as for operators.
|
for desugared calls such as for operators.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|||||||
@@ -7,10 +7,12 @@ from midas.checker.types import (
|
|||||||
AppliedType,
|
AppliedType,
|
||||||
BaseType,
|
BaseType,
|
||||||
ComplexType,
|
ComplexType,
|
||||||
|
ConstraintType,
|
||||||
ExtensionType,
|
ExtensionType,
|
||||||
Function,
|
Function,
|
||||||
GenericType,
|
GenericType,
|
||||||
OverloadedFunction,
|
OverloadedFunction,
|
||||||
|
Predicate,
|
||||||
TopType,
|
TopType,
|
||||||
Type,
|
Type,
|
||||||
TypeVar,
|
TypeVar,
|
||||||
@@ -24,6 +26,7 @@ class TypesRegistry:
|
|||||||
self.logger: logging.Logger = logging.getLogger("TypesRegistry")
|
self.logger: logging.Logger = logging.getLogger("TypesRegistry")
|
||||||
self._types: dict[str, Type] = {}
|
self._types: dict[str, Type] = {}
|
||||||
self._members: dict[str, dict[str, Type]] = {}
|
self._members: dict[str, dict[str, Type]] = {}
|
||||||
|
self._predicates: dict[str, Predicate] = {}
|
||||||
|
|
||||||
def get_type(self, name: str) -> Type:
|
def get_type(self, name: str) -> Type:
|
||||||
"""Get a type from its name
|
"""Get a type from its name
|
||||||
@@ -81,6 +84,11 @@ class TypesRegistry:
|
|||||||
else:
|
else:
|
||||||
members[member_name] = member_type
|
members[member_name] = member_type
|
||||||
|
|
||||||
|
def define_predicate(self, name: str, predicate: Predicate):
|
||||||
|
if name in self._predicates:
|
||||||
|
raise ValueError(f"Predicate {name} already defined")
|
||||||
|
self._predicates[name] = predicate
|
||||||
|
|
||||||
def is_subtype(self, type1: Type, type2: Type) -> bool:
|
def is_subtype(self, type1: Type, type2: Type) -> bool:
|
||||||
"""Check whether `type1` is a subtype of `type2`
|
"""Check whether `type1` is a subtype of `type2`
|
||||||
|
|
||||||
@@ -123,6 +131,9 @@ class TypesRegistry:
|
|||||||
return False
|
return False
|
||||||
return self.is_subtype(bound, type2)
|
return self.is_subtype(bound, type2)
|
||||||
|
|
||||||
|
case (ConstraintType(type=base1), _):
|
||||||
|
return self.is_subtype(base1, type2)
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# TODO: verify the logic in here
|
# TODO: verify the logic in here
|
||||||
@@ -345,3 +356,6 @@ class TypesRegistry:
|
|||||||
case _:
|
case _:
|
||||||
self.logger.debug(f"Can't get member on {type}")
|
self.logger.debug(f"Can't get member on {type}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def lookup_predicate(self, name: str) -> Optional[Predicate]:
|
||||||
|
return self._predicates.get(name)
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from typing import Optional
|
from typing import Optional, assert_never
|
||||||
|
|
||||||
|
import midas.ast.midas as m
|
||||||
|
from midas.ast.printer import MidasPrinter
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True, kw_only=True)
|
@dataclass(frozen=True, kw_only=True)
|
||||||
@@ -130,6 +133,16 @@ class AppliedType:
|
|||||||
return f"{self.name}[{', '.join(map(str, self.args))}]"
|
return f"{self.name}[{', '.join(map(str, self.args))}]"
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True, kw_only=True)
|
||||||
|
class ConstraintType:
|
||||||
|
type: Type
|
||||||
|
constraint: m.Expr
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
printer = MidasPrinter()
|
||||||
|
return f"{self.type} where {printer.print(self.constraint)}"
|
||||||
|
|
||||||
|
|
||||||
def substitute_typevars(type: Type, substitutions: dict[str, Type]) -> Type:
|
def substitute_typevars(type: Type, substitutions: dict[str, Type]) -> Type:
|
||||||
def sub_argument(arg: Function.Argument):
|
def sub_argument(arg: Function.Argument):
|
||||||
return Function.Argument(
|
return Function.Argument(
|
||||||
@@ -195,6 +208,12 @@ def substitute_typevars(type: Type, substitutions: dict[str, Type]) -> Type:
|
|||||||
body=substitute_typevars(body, substitutions),
|
body=substitute_typevars(body, substitutions),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
case ConstraintType():
|
||||||
|
return ConstraintType(
|
||||||
|
type=substitute_typevars(type.type, substitutions),
|
||||||
|
constraint=type.constraint,
|
||||||
|
)
|
||||||
|
|
||||||
case TypeVar(name=name):
|
case TypeVar(name=name):
|
||||||
if name in substitutions:
|
if name in substitutions:
|
||||||
return substitutions[name]
|
return substitutions[name]
|
||||||
@@ -203,9 +222,13 @@ def substitute_typevars(type: Type, substitutions: dict[str, Type]) -> Type:
|
|||||||
case UnknownType() | UnitType():
|
case UnknownType() | UnitType():
|
||||||
return type
|
return type
|
||||||
|
|
||||||
case _:
|
case TopType() | GenericType():
|
||||||
raise NotImplementedError(f"Unsupported type {type}")
|
raise NotImplementedError(f"Unsupported type {type}")
|
||||||
|
|
||||||
|
# Ensure exhaustiveness
|
||||||
|
case _:
|
||||||
|
assert_never(type)
|
||||||
|
|
||||||
|
|
||||||
def unfold_type(type: Type) -> Type:
|
def unfold_type(type: Type) -> Type:
|
||||||
match type:
|
match type:
|
||||||
@@ -215,6 +238,65 @@ def unfold_type(type: Type) -> Type:
|
|||||||
return type
|
return type
|
||||||
|
|
||||||
|
|
||||||
|
def to_annotation(type: Type) -> str:
|
||||||
|
def _args_annotation(func: Function) -> str:
|
||||||
|
if len(func.kw_args) != 0:
|
||||||
|
return "..."
|
||||||
|
|
||||||
|
args: str = ", ".join(
|
||||||
|
to_annotation(arg.type) for arg in func.pos_args + func.args
|
||||||
|
)
|
||||||
|
return f"[{args}]"
|
||||||
|
|
||||||
|
match type:
|
||||||
|
case TopType():
|
||||||
|
return "Any"
|
||||||
|
|
||||||
|
case BaseType(name=name):
|
||||||
|
return name
|
||||||
|
|
||||||
|
case AliasType(name=name):
|
||||||
|
return name
|
||||||
|
|
||||||
|
case UnknownType():
|
||||||
|
return "Any"
|
||||||
|
|
||||||
|
case UnitType():
|
||||||
|
return "None"
|
||||||
|
|
||||||
|
case Function(returns=returns):
|
||||||
|
params_annot: str = _args_annotation(type)
|
||||||
|
return f"Callable[{params_annot}, {to_annotation(returns)}]"
|
||||||
|
|
||||||
|
case OverloadedFunction():
|
||||||
|
return "Callable"
|
||||||
|
|
||||||
|
case ComplexType() | ExtensionType():
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
case TypeVar(name=name):
|
||||||
|
return name
|
||||||
|
|
||||||
|
case GenericType(name=name, params=params):
|
||||||
|
return f"{name}[{', '.join(map(to_annotation, params))}]"
|
||||||
|
|
||||||
|
case AppliedType(name=name, args=args):
|
||||||
|
return f"{name}[{', '.join(map(to_annotation, args))}]"
|
||||||
|
|
||||||
|
case ConstraintType():
|
||||||
|
return str(type)
|
||||||
|
|
||||||
|
case _:
|
||||||
|
assert_never(type)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True, kw_only=True)
|
||||||
|
class Predicate:
|
||||||
|
type: Type
|
||||||
|
body: m.Expr
|
||||||
|
alias: bool
|
||||||
|
|
||||||
|
|
||||||
Type = (
|
Type = (
|
||||||
TopType
|
TopType
|
||||||
| BaseType
|
| BaseType
|
||||||
@@ -228,4 +310,5 @@ Type = (
|
|||||||
| TypeVar
|
| TypeVar
|
||||||
| GenericType
|
| GenericType
|
||||||
| AppliedType
|
| AppliedType
|
||||||
|
| ConstraintType
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -38,5 +38,5 @@ def compile(
|
|||||||
if any(map(lambda d: d.type == DiagnosticType.ERROR, diagnostics)):
|
if any(map(lambda d: d.type == DiagnosticType.ERROR, diagnostics)):
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
generator = Generator(workdir=source_path.parent)
|
generator = Generator(workdir=source_path.parent, types=checker.types)
|
||||||
generator.generate(typed_ast, source_path)
|
generator.generate(typed_ast, source_path)
|
||||||
|
|||||||
224
midas/generator/constraints.py
Normal file
224
midas/generator/constraints.py
Normal file
@@ -0,0 +1,224 @@
|
|||||||
|
import ast
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
import midas.ast.midas as m
|
||||||
|
from midas.checker.registry import TypesRegistry
|
||||||
|
from midas.checker.types import (
|
||||||
|
Function,
|
||||||
|
Predicate,
|
||||||
|
Type,
|
||||||
|
to_annotation,
|
||||||
|
)
|
||||||
|
from midas.lexer.token import TokenType
|
||||||
|
|
||||||
|
LOGICAL_OPERATORS: dict[TokenType, type[ast.boolop]] = {
|
||||||
|
TokenType.AND: ast.And,
|
||||||
|
# TokenType.OR: ast.Or,
|
||||||
|
}
|
||||||
|
|
||||||
|
BINARY_OPERATORS: dict[TokenType, type[ast.operator]] = {
|
||||||
|
# TokenType.PLUS: ast.Add,
|
||||||
|
TokenType.MINUS: ast.Sub,
|
||||||
|
TokenType.STAR: ast.Mult,
|
||||||
|
TokenType.SLASH: ast.Div,
|
||||||
|
}
|
||||||
|
|
||||||
|
UNARY_OPERATORS: dict[TokenType, type[ast.unaryop]] = {
|
||||||
|
# TokenType.PLUS: ast.UAdd,
|
||||||
|
TokenType.MINUS: ast.USub,
|
||||||
|
}
|
||||||
|
|
||||||
|
COMPARISON_OPERATORS: dict[TokenType, type[ast.cmpop]] = {
|
||||||
|
TokenType.GREATER: ast.Gt,
|
||||||
|
TokenType.GREATER_EQUAL: ast.GtE,
|
||||||
|
TokenType.LESS: ast.Lt,
|
||||||
|
TokenType.LESS_EQUAL: ast.LtE,
|
||||||
|
TokenType.EQUAL_EQUAL: ast.Eq,
|
||||||
|
TokenType.BANG_EQUAL: ast.NotEq,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ConstraintGenerator(m.Expr.Visitor[ast.expr]):
|
||||||
|
def __init__(self, types: TypesRegistry):
|
||||||
|
self.types: TypesRegistry = types
|
||||||
|
self._id: int = 0
|
||||||
|
self._definitions: list[ast.stmt] = []
|
||||||
|
self._aliases: dict[str, str] = {}
|
||||||
|
|
||||||
|
def get_definitions(self) -> list[ast.stmt]:
|
||||||
|
return self._definitions
|
||||||
|
|
||||||
|
def generate(self, expr: m.Expr) -> ast.expr:
|
||||||
|
match expr:
|
||||||
|
case m.VariableExpr():
|
||||||
|
return expr.accept(self)
|
||||||
|
case _:
|
||||||
|
func = Function(
|
||||||
|
pos_args=[],
|
||||||
|
args=[
|
||||||
|
Function.Argument(
|
||||||
|
pos=0,
|
||||||
|
name="_",
|
||||||
|
type=self.types.get_type("Any"),
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
kw_args=[],
|
||||||
|
returns=self.types.get_type("bool"),
|
||||||
|
)
|
||||||
|
alias: str = self.make_alias(None)
|
||||||
|
definition: ast.stmt = self.make_definition(
|
||||||
|
alias, Predicate(type=func, body=expr, alias=False)
|
||||||
|
)
|
||||||
|
self._definitions.append(definition)
|
||||||
|
return ast.Name(id=alias)
|
||||||
|
|
||||||
|
def make_alias(self, name: Optional[str]) -> str:
|
||||||
|
suffix: str
|
||||||
|
if name is None:
|
||||||
|
suffix = f"p{self._id}"
|
||||||
|
self._id += 1
|
||||||
|
else:
|
||||||
|
suffix = name
|
||||||
|
alias: str = f"__midas_{suffix}__"
|
||||||
|
return alias
|
||||||
|
|
||||||
|
def make_definition(self, name: str, predicate: Predicate) -> ast.stmt:
|
||||||
|
body: ast.expr = predicate.body.accept(self)
|
||||||
|
if predicate.alias:
|
||||||
|
return ast.Assign(
|
||||||
|
targets=[
|
||||||
|
ast.Name(id=name),
|
||||||
|
],
|
||||||
|
value=body,
|
||||||
|
)
|
||||||
|
return self.make_func(name, [ast.Return(value=body)], predicate.type)
|
||||||
|
|
||||||
|
def make_args(self, func: Function) -> ast.arguments:
|
||||||
|
return ast.arguments(
|
||||||
|
posonlyargs=[
|
||||||
|
ast.arg(
|
||||||
|
arg=arg.name,
|
||||||
|
annotation=ast.Constant(value=to_annotation(arg.type)),
|
||||||
|
)
|
||||||
|
for arg in func.pos_args
|
||||||
|
],
|
||||||
|
args=[
|
||||||
|
ast.arg(
|
||||||
|
arg=arg.name,
|
||||||
|
annotation=ast.Constant(value=to_annotation(arg.type)),
|
||||||
|
)
|
||||||
|
for arg in func.args
|
||||||
|
],
|
||||||
|
kwonlyargs=[
|
||||||
|
ast.arg(
|
||||||
|
arg=arg.name,
|
||||||
|
annotation=ast.Constant(value=to_annotation(arg.type)),
|
||||||
|
)
|
||||||
|
for arg in func.kw_args
|
||||||
|
],
|
||||||
|
defaults=[],
|
||||||
|
kw_defaults=[],
|
||||||
|
)
|
||||||
|
|
||||||
|
def make_func(
|
||||||
|
self, name: str, inner_body: list[ast.stmt], type: Type, level: int = 0
|
||||||
|
) -> ast.stmt:
|
||||||
|
match type:
|
||||||
|
case Function(returns=Function()):
|
||||||
|
inner_name: str = f"inner{level}"
|
||||||
|
return ast.FunctionDef(
|
||||||
|
name=name,
|
||||||
|
args=self.make_args(type),
|
||||||
|
body=[
|
||||||
|
self.make_func(inner_name, inner_body, type.returns, level + 1),
|
||||||
|
ast.Return(value=ast.Name(id=inner_name)),
|
||||||
|
],
|
||||||
|
returns=ast.Constant(value=to_annotation(type.returns)),
|
||||||
|
decorator_list=[],
|
||||||
|
)
|
||||||
|
|
||||||
|
case Function():
|
||||||
|
return ast.FunctionDef(
|
||||||
|
name=name,
|
||||||
|
args=self.make_args(type),
|
||||||
|
body=inner_body,
|
||||||
|
returns=ast.Constant(value=to_annotation(type.returns)),
|
||||||
|
decorator_list=[],
|
||||||
|
)
|
||||||
|
|
||||||
|
case _:
|
||||||
|
raise ValueError(f"Expected function, got {type!r}")
|
||||||
|
|
||||||
|
def get_predicate(self, name: str) -> Optional[ast.expr]:
|
||||||
|
if name not in self._aliases:
|
||||||
|
predicate: Optional[Predicate] = self.types.lookup_predicate(name)
|
||||||
|
if predicate is None:
|
||||||
|
return None
|
||||||
|
alias: str = self.make_alias(name)
|
||||||
|
self._aliases[name] = alias
|
||||||
|
self._definitions.append(self.make_definition(alias, predicate))
|
||||||
|
|
||||||
|
return ast.Name(id=self._aliases[name])
|
||||||
|
|
||||||
|
def visit_logical_expr(self, expr: m.LogicalExpr) -> ast.expr:
|
||||||
|
return ast.BoolOp(
|
||||||
|
op=LOGICAL_OPERATORS[expr.operator.type](),
|
||||||
|
values=[
|
||||||
|
expr.left.accept(self),
|
||||||
|
expr.right.accept(self),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
def visit_binary_expr(self, expr: m.BinaryExpr) -> ast.expr:
|
||||||
|
op: TokenType = expr.operator.type
|
||||||
|
if op in BINARY_OPERATORS:
|
||||||
|
return ast.BinOp(
|
||||||
|
left=expr.left.accept(self),
|
||||||
|
op=BINARY_OPERATORS[op](),
|
||||||
|
right=expr.right.accept(self),
|
||||||
|
)
|
||||||
|
if op in COMPARISON_OPERATORS:
|
||||||
|
return ast.Compare(
|
||||||
|
left=expr.left.accept(self),
|
||||||
|
ops=[COMPARISON_OPERATORS[op]()],
|
||||||
|
comparators=[expr.right.accept(self)],
|
||||||
|
)
|
||||||
|
raise ValueError(f"Unexpected binary operator {op}")
|
||||||
|
|
||||||
|
def visit_unary_expr(self, expr: m.UnaryExpr) -> ast.expr:
|
||||||
|
return ast.UnaryOp(
|
||||||
|
op=UNARY_OPERATORS[expr.operator.type](),
|
||||||
|
operand=expr.right.accept(self),
|
||||||
|
)
|
||||||
|
|
||||||
|
def visit_call_expr(self, expr: m.CallExpr) -> ast.expr:
|
||||||
|
return ast.Call(
|
||||||
|
func=expr.callee.accept(self),
|
||||||
|
args=[arg.accept(self) for arg in expr.arguments],
|
||||||
|
keywords=[
|
||||||
|
ast.keyword(arg=name, value=arg.accept(self))
|
||||||
|
for name, arg in expr.keywords.items()
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
def visit_get_expr(self, expr: m.GetExpr) -> ast.expr:
|
||||||
|
return ast.Attribute(
|
||||||
|
value=expr.expr.accept(self),
|
||||||
|
attr=expr.name.lexeme,
|
||||||
|
)
|
||||||
|
|
||||||
|
def visit_variable_expr(self, expr: m.VariableExpr) -> ast.expr:
|
||||||
|
name: str = expr.name.lexeme
|
||||||
|
if (p := self.get_predicate(name)) is not None:
|
||||||
|
return p
|
||||||
|
return ast.Name(id=name)
|
||||||
|
|
||||||
|
def visit_grouping_expr(self, expr: m.GroupingExpr) -> ast.expr:
|
||||||
|
return expr.accept(self)
|
||||||
|
|
||||||
|
def visit_literal_expr(self, expr: m.LiteralExpr) -> ast.expr:
|
||||||
|
return ast.Constant(value=expr.value)
|
||||||
|
|
||||||
|
def visit_wildcard_expr(self, expr: m.WildcardExpr) -> ast.expr:
|
||||||
|
return ast.Name(id="_")
|
||||||
@@ -2,15 +2,19 @@ import ast
|
|||||||
import shutil
|
import shutil
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional
|
from typing import Optional, assert_never
|
||||||
|
|
||||||
|
import midas.ast.midas as m
|
||||||
import midas.ast.python as p
|
import midas.ast.python as p
|
||||||
from midas.ast.location import Location
|
from midas.ast.location import Location
|
||||||
|
from midas.ast.printer import MidasPrinter
|
||||||
|
from midas.checker.registry import TypesRegistry
|
||||||
from midas.checker.types import (
|
from midas.checker.types import (
|
||||||
AliasType,
|
AliasType,
|
||||||
AppliedType,
|
AppliedType,
|
||||||
BaseType,
|
BaseType,
|
||||||
ComplexType,
|
ComplexType,
|
||||||
|
ConstraintType,
|
||||||
ExtensionType,
|
ExtensionType,
|
||||||
Function,
|
Function,
|
||||||
GenericType,
|
GenericType,
|
||||||
@@ -19,7 +23,9 @@ from midas.checker.types import (
|
|||||||
Type,
|
Type,
|
||||||
TypeVar,
|
TypeVar,
|
||||||
UnitType,
|
UnitType,
|
||||||
|
UnknownType,
|
||||||
)
|
)
|
||||||
|
from midas.generator.constraints import ConstraintGenerator
|
||||||
from midas.utils import TypedAST
|
from midas.utils import TypedAST
|
||||||
|
|
||||||
|
|
||||||
@@ -30,7 +36,7 @@ class Scope:
|
|||||||
|
|
||||||
|
|
||||||
class Generator(p.Stmt.Visitor[ast.stmt], p.Expr.Visitor[ast.expr]):
|
class Generator(p.Stmt.Visitor[ast.stmt], p.Expr.Visitor[ast.expr]):
|
||||||
def __init__(self, workdir: Path) -> None:
|
def __init__(self, workdir: Path, types: TypesRegistry) -> None:
|
||||||
self.workdir: Path = workdir.resolve()
|
self.workdir: Path = workdir.resolve()
|
||||||
self.build_dir: Path = self.workdir / "build" / "midas"
|
self.build_dir: Path = self.workdir / "build" / "midas"
|
||||||
if self.build_dir.exists():
|
if self.build_dir.exists():
|
||||||
@@ -43,13 +49,18 @@ class Generator(p.Stmt.Visitor[ast.stmt], p.Expr.Visitor[ast.expr]):
|
|||||||
judgements=[],
|
judgements=[],
|
||||||
)
|
)
|
||||||
self._alias_count: int = 0
|
self._alias_count: int = 0
|
||||||
|
self._predicate_count: int = 0
|
||||||
self._scopes: list[Scope] = []
|
self._scopes: list[Scope] = []
|
||||||
|
|
||||||
|
self._constraint_generator: ConstraintGenerator = ConstraintGenerator(types)
|
||||||
|
self._constraints: list[tuple[m.Expr, ast.expr]] = []
|
||||||
|
|
||||||
def generate_ast(self, typed_ast: TypedAST, src_path: Path) -> ast.AST:
|
def generate_ast(self, typed_ast: TypedAST, src_path: Path) -> ast.AST:
|
||||||
self.rel_src_path = src_path.relative_to(self.workdir)
|
self.rel_src_path = src_path.relative_to(self.workdir)
|
||||||
self._typed_ast = typed_ast
|
self._typed_ast = typed_ast
|
||||||
body: list[ast.stmt] = self._visit_body(typed_ast.stmts)
|
body: list[ast.stmt] = self._visit_body(typed_ast.stmts)
|
||||||
module = ast.Module(body=body, type_ignores=[])
|
predicates: list[ast.stmt] = self._constraint_generator.get_definitions()
|
||||||
|
module = ast.Module(body=predicates + body, type_ignores=[])
|
||||||
module = ast.fix_missing_locations(module)
|
module = ast.fix_missing_locations(module)
|
||||||
return module
|
return module
|
||||||
|
|
||||||
@@ -246,7 +257,7 @@ class Generator(p.Stmt.Visitor[ast.stmt], p.Expr.Visitor[ast.expr]):
|
|||||||
return generated
|
return generated
|
||||||
|
|
||||||
def _make_alias(self, expr: ast.expr) -> ast.expr:
|
def _make_alias(self, expr: ast.expr) -> ast.expr:
|
||||||
name: str = f"__midas_alias_{self._alias_count}__"
|
name: str = f"__midas_a{self._alias_count}__"
|
||||||
alias = ast.Name(id=name)
|
alias = ast.Name(id=name)
|
||||||
self._alias_count += 1
|
self._alias_count += 1
|
||||||
self._scopes[-1].aliases.append(name)
|
self._scopes[-1].aliases.append(name)
|
||||||
@@ -276,6 +287,9 @@ class Generator(p.Stmt.Visitor[ast.stmt], p.Expr.Visitor[ast.expr]):
|
|||||||
|
|
||||||
def _make_cast_asserts(self, src_location: Location, expr: ast.expr, type: Type):
|
def _make_cast_asserts(self, src_location: Location, expr: ast.expr, type: Type):
|
||||||
match type:
|
match type:
|
||||||
|
case UnknownType():
|
||||||
|
pass
|
||||||
|
|
||||||
case BaseType(name=name):
|
case BaseType(name=name):
|
||||||
self._add_assert(
|
self._add_assert(
|
||||||
ast.Call(
|
ast.Call(
|
||||||
@@ -301,8 +315,15 @@ class Generator(p.Stmt.Visitor[ast.stmt], p.Expr.Visitor[ast.expr]):
|
|||||||
self._make_cast_assert_message(src_location, expr, type),
|
self._make_cast_assert_message(src_location, expr, type),
|
||||||
)
|
)
|
||||||
|
|
||||||
case AppliedType():
|
case AppliedType(body=body):
|
||||||
self._make_cast_asserts(src_location, expr, type.body)
|
self._make_cast_asserts(src_location, expr, body)
|
||||||
|
|
||||||
|
case ConstraintType(type=base, constraint=constraint):
|
||||||
|
self._make_cast_asserts(src_location, expr, base)
|
||||||
|
self._make_constraint_assert(src_location, expr, constraint)
|
||||||
|
|
||||||
|
case TypeVar():
|
||||||
|
raise RuntimeError("Unexpected TypeVar")
|
||||||
|
|
||||||
case (
|
case (
|
||||||
TopType()
|
TopType()
|
||||||
@@ -314,8 +335,9 @@ class Generator(p.Stmt.Visitor[ast.stmt], p.Expr.Visitor[ast.expr]):
|
|||||||
):
|
):
|
||||||
raise NotImplementedError(f"Can't make assertion for type {type}")
|
raise NotImplementedError(f"Can't make assertion for type {type}")
|
||||||
|
|
||||||
case TypeVar():
|
# Ensure exhaustiveness
|
||||||
raise RuntimeError("Unexpected TypeVar")
|
case _:
|
||||||
|
assert_never(type)
|
||||||
|
|
||||||
def _make_cast_assert_message(
|
def _make_cast_assert_message(
|
||||||
self, location: Location, expr: ast.expr, type: Type
|
self, location: Location, expr: ast.expr, type: Type
|
||||||
@@ -339,3 +361,36 @@ class Generator(p.Stmt.Visitor[ast.stmt], p.Expr.Visitor[ast.expr]):
|
|||||||
ast.Constant(f" to {type}"),
|
ast.Constant(f" to {type}"),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _make_constraint_assert(
|
||||||
|
self, src_location: Location, expr: ast.expr, constraint: m.Expr
|
||||||
|
):
|
||||||
|
test_func: ast.expr = self._get_constraint(constraint)
|
||||||
|
self._add_assert(
|
||||||
|
ast.Call(
|
||||||
|
func=test_func,
|
||||||
|
args=[expr],
|
||||||
|
keywords=[],
|
||||||
|
),
|
||||||
|
self._make_constraint_assert_message(src_location, expr, constraint),
|
||||||
|
)
|
||||||
|
|
||||||
|
def _make_constraint_assert_message(
|
||||||
|
self, location: Location, expr: ast.expr, constraint: m.Expr
|
||||||
|
) -> ast.expr:
|
||||||
|
printer = MidasPrinter()
|
||||||
|
constraint_str: str = printer.print(constraint)
|
||||||
|
loc_str: str = f"{self.rel_src_path}:L{location.lineno}:{location.col_offset+1}"
|
||||||
|
# f"file.py:L1:1: ConstraintError: Value does not fit constraint 'v > 0'"
|
||||||
|
return ast.Constant(
|
||||||
|
f"{loc_str}: ConstraintError: Value does not fit constraint '{constraint_str}'"
|
||||||
|
)
|
||||||
|
|
||||||
|
def _get_constraint(self, expr: m.Expr) -> ast.expr:
|
||||||
|
for expr2, constraint in self._constraints:
|
||||||
|
if expr2 == expr:
|
||||||
|
return constraint
|
||||||
|
|
||||||
|
constraint: ast.expr = self._constraint_generator.generate(expr)
|
||||||
|
self._constraints.append((expr, constraint))
|
||||||
|
return constraint
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ from typing import Optional
|
|||||||
from midas.ast.location import Location
|
from midas.ast.location import Location
|
||||||
from midas.ast.midas import (
|
from midas.ast.midas import (
|
||||||
BinaryExpr,
|
BinaryExpr,
|
||||||
|
CallExpr,
|
||||||
ComplexType,
|
ComplexType,
|
||||||
ConstraintType,
|
ConstraintType,
|
||||||
Expr,
|
Expr,
|
||||||
@@ -17,6 +18,7 @@ from midas.ast.midas import (
|
|||||||
MemberKind,
|
MemberKind,
|
||||||
MemberStmt,
|
MemberStmt,
|
||||||
NamedType,
|
NamedType,
|
||||||
|
ParamSpec,
|
||||||
PredicateStmt,
|
PredicateStmt,
|
||||||
Stmt,
|
Stmt,
|
||||||
Type,
|
Type,
|
||||||
@@ -265,6 +267,9 @@ class MidasParser(Parser):
|
|||||||
Returns:
|
Returns:
|
||||||
Expr: the parsed constraint expression
|
Expr: the parsed constraint expression
|
||||||
"""
|
"""
|
||||||
|
return self.expression()
|
||||||
|
|
||||||
|
def expression(self) -> Expr:
|
||||||
return self.and_()
|
return self.and_()
|
||||||
|
|
||||||
def and_(self) -> Expr:
|
def and_(self) -> Expr:
|
||||||
@@ -331,7 +336,55 @@ class MidasParser(Parser):
|
|||||||
right: Expr = self.unary()
|
right: Expr = self.unary()
|
||||||
location: Location = Location.span(operator.get_location(), right.location)
|
location: Location = Location.span(operator.get_location(), right.location)
|
||||||
return UnaryExpr(location=location, operator=operator, right=right)
|
return UnaryExpr(location=location, operator=operator, right=right)
|
||||||
return self.reference()
|
return self.call()
|
||||||
|
|
||||||
|
def call(self) -> Expr:
|
||||||
|
expr: Expr = self.reference()
|
||||||
|
while self.match(TokenType.LEFT_PAREN):
|
||||||
|
expr = self.finish_call(expr)
|
||||||
|
return expr
|
||||||
|
|
||||||
|
def finish_call(self, callee: Expr) -> Expr:
|
||||||
|
l_paren: Token = self.previous()
|
||||||
|
pos_args: list[Expr] = []
|
||||||
|
kw_args: dict[str, Expr] = {}
|
||||||
|
keywords: bool = False
|
||||||
|
while not self.match(TokenType.RIGHT_PAREN):
|
||||||
|
if self.check_identifier() and self.check_next(TokenType.EQUAL):
|
||||||
|
keywords = True
|
||||||
|
keyword: Token = self.advance()
|
||||||
|
value: Expr = self.expression()
|
||||||
|
name: str = keyword.lexeme
|
||||||
|
if name in kw_args:
|
||||||
|
self.error(
|
||||||
|
self.peek(),
|
||||||
|
f"Multiple values passed for '{name}', only the last occurrence will be used",
|
||||||
|
)
|
||||||
|
kw_args[name] = value
|
||||||
|
else:
|
||||||
|
value = self.expression()
|
||||||
|
if self.check(TokenType.EQUAL):
|
||||||
|
if keywords:
|
||||||
|
raise self.error(self.peek(), "Invalid keyword argument name")
|
||||||
|
else:
|
||||||
|
raise self.error(
|
||||||
|
self.peek(),
|
||||||
|
"Cannot pass positional arguments after a keyword argument",
|
||||||
|
)
|
||||||
|
pos_args.append(value)
|
||||||
|
|
||||||
|
if not self.match(TokenType.COMMA):
|
||||||
|
break
|
||||||
|
|
||||||
|
r_paren: Token = self.consume(
|
||||||
|
TokenType.RIGHT_PAREN, "Expected ')' after arguments."
|
||||||
|
)
|
||||||
|
return CallExpr(
|
||||||
|
location=Location.span(callee.location, r_paren.get_location()),
|
||||||
|
callee=callee,
|
||||||
|
arguments=pos_args,
|
||||||
|
keywords=kw_args,
|
||||||
|
)
|
||||||
|
|
||||||
def reference(self) -> Expr:
|
def reference(self) -> Expr:
|
||||||
"""Parse an attribute access expression or a simpler expression
|
"""Parse an attribute access expression or a simpler expression
|
||||||
@@ -453,23 +506,35 @@ class MidasParser(Parser):
|
|||||||
PredicateStmt: the parsed predicate declaration statement
|
PredicateStmt: the parsed predicate declaration statement
|
||||||
"""
|
"""
|
||||||
keyword: Token = self.previous()
|
keyword: Token = self.previous()
|
||||||
|
|
||||||
name: Token = self.consume_identifier("Expected predicate name")
|
name: Token = self.consume_identifier("Expected predicate name")
|
||||||
self.consume(TokenType.LEFT_PAREN, "Expected '(' before predicate subject")
|
|
||||||
subject: Token = self.consume_identifier("Expected subject name")
|
params: list[ParamSpec] = []
|
||||||
self.consume(TokenType.COLON, "Expected ':' after subject name")
|
while self.check(TokenType.LEFT_PAREN):
|
||||||
type: Type = self.type_expr()
|
params.append(self.function_args())
|
||||||
self.consume(TokenType.RIGHT_PAREN, "Expected ')' after predicate subject")
|
|
||||||
self.consume(TokenType.EQUAL, "Expected '=' after predicate subject")
|
self.consume(TokenType.EQUAL, "Expected '=' after predicate subject")
|
||||||
condition: Expr = self.constraint()
|
body: Expr = self.constraint()
|
||||||
return PredicateStmt(
|
return PredicateStmt(
|
||||||
location=keyword.location_to(self.previous()),
|
location=keyword.location_to(self.previous()),
|
||||||
name=name,
|
name=name,
|
||||||
subject=subject,
|
params=params,
|
||||||
type=type,
|
body=body,
|
||||||
condition=condition,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def function(self) -> FunctionType:
|
def function(self) -> FunctionType:
|
||||||
|
params: ParamSpec = self.function_args()
|
||||||
|
|
||||||
|
self.consume(TokenType.ARROW, "Expected '->' before result type")
|
||||||
|
result: Type = self.type_expr()
|
||||||
|
|
||||||
|
return FunctionType(
|
||||||
|
location=params.l_paren.location_to(self.previous()),
|
||||||
|
params=params,
|
||||||
|
returns=result,
|
||||||
|
)
|
||||||
|
|
||||||
|
def function_args(self) -> ParamSpec:
|
||||||
l_paren: Token = self.consume(
|
l_paren: Token = self.consume(
|
||||||
TokenType.LEFT_PAREN, "Expected '(' before function parameters"
|
TokenType.LEFT_PAREN, "Expected '(' before function parameters"
|
||||||
)
|
)
|
||||||
@@ -526,14 +591,4 @@ class MidasParser(Parser):
|
|||||||
self.error(token, "Unnamed mixed argument")
|
self.error(token, "Unnamed mixed argument")
|
||||||
|
|
||||||
self.consume(TokenType.RIGHT_PAREN, "Expected ')' after function parameters")
|
self.consume(TokenType.RIGHT_PAREN, "Expected ')' after function parameters")
|
||||||
|
return ParamSpec(l_paren=l_paren, pos=pos_args, mixed=args, kw=kw_args)
|
||||||
self.consume(TokenType.ARROW, "Expected '->' before result type")
|
|
||||||
result: Type = self.type_expr()
|
|
||||||
|
|
||||||
return FunctionType(
|
|
||||||
location=l_paren.location_to(self.previous()),
|
|
||||||
pos_args=pos_args,
|
|
||||||
args=args,
|
|
||||||
kw_args=kw_args,
|
|
||||||
returns=result,
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -9,13 +9,13 @@ Module(
|
|||||||
level=0),
|
level=0),
|
||||||
Assign(
|
Assign(
|
||||||
targets=[
|
targets=[
|
||||||
Name(id='__midas_alias_0__')],
|
Name(id='__midas_a0__')],
|
||||||
value=Constant(value=123.45)),
|
value=Constant(value=123.45)),
|
||||||
Assert(
|
Assert(
|
||||||
test=Call(
|
test=Call(
|
||||||
func=Name(id='isinstance'),
|
func=Name(id='isinstance'),
|
||||||
args=[
|
args=[
|
||||||
Name(id='__midas_alias_0__'),
|
Name(id='__midas_a0__'),
|
||||||
Name(id='float')],
|
Name(id='float')],
|
||||||
keywords=[]),
|
keywords=[]),
|
||||||
msg=JoinedStr(
|
msg=JoinedStr(
|
||||||
@@ -26,7 +26,7 @@ Module(
|
|||||||
value=Call(
|
value=Call(
|
||||||
func=Name(id='type'),
|
func=Name(id='type'),
|
||||||
args=[
|
args=[
|
||||||
Name(id='__midas_alias_0__')],
|
Name(id='__midas_a0__')],
|
||||||
keywords=[]),
|
keywords=[]),
|
||||||
attr='__name__'),
|
attr='__name__'),
|
||||||
conversion=-1),
|
conversion=-1),
|
||||||
@@ -34,19 +34,19 @@ Module(
|
|||||||
Assign(
|
Assign(
|
||||||
targets=[
|
targets=[
|
||||||
Name(id='distance')],
|
Name(id='distance')],
|
||||||
value=Name(id='__midas_alias_0__')),
|
value=Name(id='__midas_a0__')),
|
||||||
Delete(
|
Delete(
|
||||||
targets=[
|
targets=[
|
||||||
Name(id='__midas_alias_0__')]),
|
Name(id='__midas_a0__')]),
|
||||||
Assign(
|
Assign(
|
||||||
targets=[
|
targets=[
|
||||||
Name(id='__midas_alias_1__')],
|
Name(id='__midas_a1__')],
|
||||||
value=Constant(value=6.7)),
|
value=Constant(value=6.7)),
|
||||||
Assert(
|
Assert(
|
||||||
test=Call(
|
test=Call(
|
||||||
func=Name(id='isinstance'),
|
func=Name(id='isinstance'),
|
||||||
args=[
|
args=[
|
||||||
Name(id='__midas_alias_1__'),
|
Name(id='__midas_a1__'),
|
||||||
Name(id='float')],
|
Name(id='float')],
|
||||||
keywords=[]),
|
keywords=[]),
|
||||||
msg=JoinedStr(
|
msg=JoinedStr(
|
||||||
@@ -57,7 +57,7 @@ Module(
|
|||||||
value=Call(
|
value=Call(
|
||||||
func=Name(id='type'),
|
func=Name(id='type'),
|
||||||
args=[
|
args=[
|
||||||
Name(id='__midas_alias_1__')],
|
Name(id='__midas_a1__')],
|
||||||
keywords=[]),
|
keywords=[]),
|
||||||
attr='__name__'),
|
attr='__name__'),
|
||||||
conversion=-1),
|
conversion=-1),
|
||||||
@@ -65,10 +65,10 @@ Module(
|
|||||||
Assign(
|
Assign(
|
||||||
targets=[
|
targets=[
|
||||||
Name(id='time')],
|
Name(id='time')],
|
||||||
value=Name(id='__midas_alias_1__')),
|
value=Name(id='__midas_a1__')),
|
||||||
Delete(
|
Delete(
|
||||||
targets=[
|
targets=[
|
||||||
Name(id='__midas_alias_1__')]),
|
Name(id='__midas_a1__')]),
|
||||||
Assign(
|
Assign(
|
||||||
targets=[
|
targets=[
|
||||||
Name(id='speed')],
|
Name(id='speed')],
|
||||||
|
|||||||
@@ -2582,18 +2582,21 @@
|
|||||||
"name": "__sub__",
|
"name": "__sub__",
|
||||||
"type": {
|
"type": {
|
||||||
"_type": "FunctionType",
|
"_type": "FunctionType",
|
||||||
"pos_args": [
|
"params": {
|
||||||
{
|
"_type": "ParamSpec",
|
||||||
"name": null,
|
"pos": [
|
||||||
"type": {
|
{
|
||||||
"_type": "NamedType",
|
"name": null,
|
||||||
"name": "GeoLocation"
|
"type": {
|
||||||
},
|
"_type": "NamedType",
|
||||||
"required": true
|
"name": "GeoLocation"
|
||||||
}
|
},
|
||||||
],
|
"required": true
|
||||||
"args": [],
|
}
|
||||||
"kw_args": [],
|
],
|
||||||
|
"mixed": [],
|
||||||
|
"kw": []
|
||||||
|
},
|
||||||
"returns": {
|
"returns": {
|
||||||
"_type": "GenericType",
|
"_type": "GenericType",
|
||||||
"type": {
|
"type": {
|
||||||
@@ -2673,18 +2676,21 @@
|
|||||||
"name": "__sub__",
|
"name": "__sub__",
|
||||||
"type": {
|
"type": {
|
||||||
"_type": "FunctionType",
|
"_type": "FunctionType",
|
||||||
"pos_args": [
|
"params": {
|
||||||
{
|
"_type": "ParamSpec",
|
||||||
"name": null,
|
"pos": [
|
||||||
"type": {
|
{
|
||||||
"_type": "NamedType",
|
"name": null,
|
||||||
"name": "Latitude"
|
"type": {
|
||||||
},
|
"_type": "NamedType",
|
||||||
"required": true
|
"name": "Latitude"
|
||||||
}
|
},
|
||||||
],
|
"required": true
|
||||||
"args": [],
|
}
|
||||||
"kw_args": [],
|
],
|
||||||
|
"mixed": [],
|
||||||
|
"kw": []
|
||||||
|
},
|
||||||
"returns": {
|
"returns": {
|
||||||
"_type": "GenericType",
|
"_type": "GenericType",
|
||||||
"type": {
|
"type": {
|
||||||
@@ -2713,18 +2719,21 @@
|
|||||||
"name": "__sub__",
|
"name": "__sub__",
|
||||||
"type": {
|
"type": {
|
||||||
"_type": "FunctionType",
|
"_type": "FunctionType",
|
||||||
"pos_args": [
|
"params": {
|
||||||
{
|
"_type": "ParamSpec",
|
||||||
"name": null,
|
"pos": [
|
||||||
"type": {
|
{
|
||||||
"_type": "NamedType",
|
"name": null,
|
||||||
"name": "Longitude"
|
"type": {
|
||||||
},
|
"_type": "NamedType",
|
||||||
"required": true
|
"name": "Longitude"
|
||||||
}
|
},
|
||||||
],
|
"required": true
|
||||||
"args": [],
|
}
|
||||||
"kw_args": [],
|
],
|
||||||
|
"mixed": [],
|
||||||
|
"kw": []
|
||||||
|
},
|
||||||
"returns": {
|
"returns": {
|
||||||
"_type": "GenericType",
|
"_type": "GenericType",
|
||||||
"type": {
|
"type": {
|
||||||
@@ -2745,12 +2754,24 @@
|
|||||||
{
|
{
|
||||||
"_type": "PredicateStmt",
|
"_type": "PredicateStmt",
|
||||||
"name": "Positive",
|
"name": "Positive",
|
||||||
"subject": "v",
|
"params": [
|
||||||
"type": {
|
{
|
||||||
"_type": "NamedType",
|
"_type": "ParamSpec",
|
||||||
"name": "float"
|
"pos": [],
|
||||||
},
|
"mixed": [
|
||||||
"condition": {
|
{
|
||||||
|
"name": "v",
|
||||||
|
"type": {
|
||||||
|
"_type": "NamedType",
|
||||||
|
"name": "float"
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"kw": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"body": {
|
||||||
"_type": "BinaryExpr",
|
"_type": "BinaryExpr",
|
||||||
"left": {
|
"left": {
|
||||||
"_type": "VariableExpr",
|
"_type": "VariableExpr",
|
||||||
@@ -2766,12 +2787,24 @@
|
|||||||
{
|
{
|
||||||
"_type": "PredicateStmt",
|
"_type": "PredicateStmt",
|
||||||
"name": "StrictlyPositive",
|
"name": "StrictlyPositive",
|
||||||
"subject": "v",
|
"params": [
|
||||||
"type": {
|
{
|
||||||
"_type": "NamedType",
|
"_type": "ParamSpec",
|
||||||
"name": "float"
|
"pos": [],
|
||||||
},
|
"mixed": [
|
||||||
"condition": {
|
{
|
||||||
|
"name": "v",
|
||||||
|
"type": {
|
||||||
|
"_type": "NamedType",
|
||||||
|
"name": "float"
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"kw": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"body": {
|
||||||
"_type": "BinaryExpr",
|
"_type": "BinaryExpr",
|
||||||
"left": {
|
"left": {
|
||||||
"_type": "VariableExpr",
|
"_type": "VariableExpr",
|
||||||
@@ -2787,12 +2820,24 @@
|
|||||||
{
|
{
|
||||||
"_type": "PredicateStmt",
|
"_type": "PredicateStmt",
|
||||||
"name": "Equatorial",
|
"name": "Equatorial",
|
||||||
"subject": "loc",
|
"params": [
|
||||||
"type": {
|
{
|
||||||
"_type": "NamedType",
|
"_type": "ParamSpec",
|
||||||
"name": "GeoLocation"
|
"pos": [],
|
||||||
},
|
"mixed": [
|
||||||
"condition": {
|
{
|
||||||
|
"name": "loc",
|
||||||
|
"type": {
|
||||||
|
"_type": "NamedType",
|
||||||
|
"name": "GeoLocation"
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"kw": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"body": {
|
||||||
"_type": "GroupingExpr",
|
"_type": "GroupingExpr",
|
||||||
"expr": {
|
"expr": {
|
||||||
"_type": "BinaryExpr",
|
"_type": "BinaryExpr",
|
||||||
@@ -2827,12 +2872,24 @@
|
|||||||
{
|
{
|
||||||
"_type": "PredicateStmt",
|
"_type": "PredicateStmt",
|
||||||
"name": "Arctic",
|
"name": "Arctic",
|
||||||
"subject": "loc",
|
"params": [
|
||||||
"type": {
|
{
|
||||||
"_type": "NamedType",
|
"_type": "ParamSpec",
|
||||||
"name": "GeoLocation"
|
"pos": [],
|
||||||
},
|
"mixed": [
|
||||||
"condition": {
|
{
|
||||||
|
"name": "loc",
|
||||||
|
"type": {
|
||||||
|
"_type": "NamedType",
|
||||||
|
"name": "GeoLocation"
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"kw": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"body": {
|
||||||
"_type": "GroupingExpr",
|
"_type": "GroupingExpr",
|
||||||
"expr": {
|
"expr": {
|
||||||
"_type": "BinaryExpr",
|
"_type": "BinaryExpr",
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ class GeneratorTester(Tester):
|
|||||||
typed_ast: TypedAST = checker.type_check(path)
|
typed_ast: TypedAST = checker.type_check(path)
|
||||||
|
|
||||||
if not any(d.type == DiagnosticType.ERROR for d in checker.diagnostics):
|
if not any(d.type == DiagnosticType.ERROR for d in checker.diagnostics):
|
||||||
generator = Generator(workdir=path.parent)
|
generator = Generator(workdir=path.parent, types=checker.types)
|
||||||
result.compiled_ast = generator.generate_ast(typed_ast, path)
|
result.compiled_ast = generator.generate_ast(typed_ast, path)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ from typing import Optional, Sequence
|
|||||||
|
|
||||||
from midas.ast.midas import (
|
from midas.ast.midas import (
|
||||||
BinaryExpr,
|
BinaryExpr,
|
||||||
|
CallExpr,
|
||||||
ComplexType,
|
ComplexType,
|
||||||
ConstraintType,
|
ConstraintType,
|
||||||
Expr,
|
Expr,
|
||||||
@@ -15,6 +16,7 @@ from midas.ast.midas import (
|
|||||||
LogicalExpr,
|
LogicalExpr,
|
||||||
MemberStmt,
|
MemberStmt,
|
||||||
NamedType,
|
NamedType,
|
||||||
|
ParamSpec,
|
||||||
PredicateStmt,
|
PredicateStmt,
|
||||||
Stmt,
|
Stmt,
|
||||||
Type,
|
Type,
|
||||||
@@ -78,9 +80,8 @@ class MidasAstJsonSerializer(
|
|||||||
return {
|
return {
|
||||||
"_type": "PredicateStmt",
|
"_type": "PredicateStmt",
|
||||||
"name": stmt.name.lexeme,
|
"name": stmt.name.lexeme,
|
||||||
"subject": stmt.subject.lexeme,
|
"params": [self._serialize_param_spec(spec) for spec in stmt.params],
|
||||||
"type": stmt.type.accept(self),
|
"body": stmt.body.accept(self),
|
||||||
"condition": stmt.condition.accept(self),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def visit_logical_expr(self, expr: LogicalExpr) -> dict:
|
def visit_logical_expr(self, expr: LogicalExpr) -> dict:
|
||||||
@@ -106,6 +107,14 @@ class MidasAstJsonSerializer(
|
|||||||
"right": expr.right.accept(self),
|
"right": expr.right.accept(self),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def visit_call_expr(self, expr: CallExpr) -> dict:
|
||||||
|
return {
|
||||||
|
"_type": "CallExpr",
|
||||||
|
"callee": expr.callee.accept(self),
|
||||||
|
"arguments": self._serialize_list(expr.arguments),
|
||||||
|
"keywords": {name: arg.accept(self) for name, arg in expr.keywords.items()},
|
||||||
|
}
|
||||||
|
|
||||||
def visit_get_expr(self, expr: GetExpr) -> dict:
|
def visit_get_expr(self, expr: GetExpr) -> dict:
|
||||||
return {
|
return {
|
||||||
"_type": "GetExpr",
|
"_type": "GetExpr",
|
||||||
@@ -163,15 +172,21 @@ class MidasAstJsonSerializer(
|
|||||||
def visit_function_type(self, type: FunctionType) -> dict:
|
def visit_function_type(self, type: FunctionType) -> dict:
|
||||||
return {
|
return {
|
||||||
"_type": "FunctionType",
|
"_type": "FunctionType",
|
||||||
"pos_args": [self._serialize_func_arg(arg) for arg in type.pos_args],
|
"params": self._serialize_param_spec(type.params),
|
||||||
"args": [self._serialize_func_arg(arg) for arg in type.args],
|
|
||||||
"kw_args": [self._serialize_func_arg(arg) for arg in type.kw_args],
|
|
||||||
"returns": type.returns.accept(self),
|
"returns": type.returns.accept(self),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def _serialize_param_spec(self, spec: ParamSpec) -> dict:
|
||||||
|
return {
|
||||||
|
"_type": "ParamSpec",
|
||||||
|
"pos": [self._serialize_func_arg(arg) for arg in spec.pos],
|
||||||
|
"mixed": [self._serialize_func_arg(arg) for arg in spec.mixed],
|
||||||
|
"kw": [self._serialize_func_arg(arg) for arg in spec.kw],
|
||||||
|
}
|
||||||
|
|
||||||
def _serialize_func_arg(self, arg: FunctionType.Argument) -> dict:
|
def _serialize_func_arg(self, arg: FunctionType.Argument) -> dict:
|
||||||
return {
|
return {
|
||||||
"name": arg.name,
|
"name": arg.name.lexeme if arg.name is not None else None,
|
||||||
"type": arg.type.accept(self),
|
"type": arg.type.accept(self),
|
||||||
"required": arg.required,
|
"required": arg.required,
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user