Compare commits
3 Commits
822a74acce
...
73b21789d5
| Author | SHA1 | Date | |
|---|---|---|---|
|
73b21789d5
|
|||
|
5d7c724bc8
|
|||
|
74b297c89c
|
@@ -2,10 +2,6 @@
|
|||||||
# ruff: disable[F821]
|
# ruff: disable[F821]
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
# Prototype of custom type import to use valid Python syntax
|
|
||||||
import midas
|
|
||||||
midas.using("02_custom_types.midas")
|
|
||||||
|
|
||||||
# A data-frame using a custom type
|
# A data-frame using a custom type
|
||||||
df: Frame[
|
df: Frame[
|
||||||
location: GeoLocation
|
location: GeoLocation
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
# type: ignore
|
# type: ignore
|
||||||
# ruff: disable [F821]
|
# ruff: disable [F821]
|
||||||
|
|
||||||
midas.using("02_simple_types.midas")
|
|
||||||
|
|
||||||
distance: Meter = cast(Meter, 123.45)
|
distance: Meter = cast(Meter, 123.45)
|
||||||
time: Second = cast(Second, 6.7)
|
time: Second = cast(Second, 6.7)
|
||||||
speed = distance / time
|
speed = distance / time
|
||||||
|
|||||||
@@ -34,9 +34,15 @@ class Checker(
|
|||||||
):
|
):
|
||||||
"""A type checker which can use custom type definitions"""
|
"""A type checker which can use custom type definitions"""
|
||||||
|
|
||||||
def __init__(self, locals: dict[p.Expr, int], file_path: Path):
|
def __init__(
|
||||||
|
self,
|
||||||
|
locals: dict[p.Expr, int],
|
||||||
|
source_path: Path,
|
||||||
|
types_paths: list[Path],
|
||||||
|
):
|
||||||
self.logger: logging.Logger = logging.getLogger("Checker")
|
self.logger: logging.Logger = logging.getLogger("Checker")
|
||||||
self.file_path: Path = file_path
|
self.source_path: Path = source_path
|
||||||
|
self.types_paths: list[Path] = types_paths
|
||||||
self.ctx: MidasResolver = MidasResolver()
|
self.ctx: MidasResolver = MidasResolver()
|
||||||
self.global_env: Environment = Environment()
|
self.global_env: Environment = Environment()
|
||||||
self.env: Environment = self.global_env
|
self.env: Environment = self.global_env
|
||||||
@@ -46,7 +52,7 @@ class Checker(
|
|||||||
def diagnostic(self, type: DiagnosticType, location: Location, message: str):
|
def diagnostic(self, type: DiagnosticType, location: Location, message: str):
|
||||||
self.diagnostics.append(
|
self.diagnostics.append(
|
||||||
Diagnostic(
|
Diagnostic(
|
||||||
file_path=self.file_path,
|
file_path=self.source_path,
|
||||||
location=location,
|
location=location,
|
||||||
type=type,
|
type=type,
|
||||||
message=message,
|
message=message,
|
||||||
@@ -119,6 +125,12 @@ class Checker(
|
|||||||
list[Diagnostic]: the list of diagnostics (errors, warning, etc.)
|
list[Diagnostic]: the list of diagnostics (errors, warning, etc.)
|
||||||
"""
|
"""
|
||||||
self.diagnostics = []
|
self.diagnostics = []
|
||||||
|
|
||||||
|
for path in self.types_paths:
|
||||||
|
self.import_midas(path)
|
||||||
|
self.logger.debug(f"Midas types: {self.ctx._types}")
|
||||||
|
self.logger.debug(f"Midas operations: {self.ctx._operations}")
|
||||||
|
|
||||||
for stmt in statements:
|
for stmt in statements:
|
||||||
stmt.accept(self)
|
stmt.accept(self)
|
||||||
|
|
||||||
@@ -140,30 +152,6 @@ class Checker(
|
|||||||
return self.env.get_at(distance, name)
|
return self.env.get_at(distance, name)
|
||||||
return self.global_env.get(name)
|
return self.global_env.get(name)
|
||||||
|
|
||||||
def parse_midas_import(self, expr: p.CallExpr) -> Optional[Path]:
|
|
||||||
"""Parse a Midas import statement
|
|
||||||
|
|
||||||
The statement should be written as `midas.using("path/to/types.midas")`
|
|
||||||
|
|
||||||
Args:
|
|
||||||
expr (p.CallExpr): the import call expression
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Optional[Path]: the path to the imported file, or None if the expression is malformed
|
|
||||||
"""
|
|
||||||
match expr:
|
|
||||||
case p.CallExpr(
|
|
||||||
callee=p.GetExpr(
|
|
||||||
object=p.VariableExpr(name="midas"),
|
|
||||||
name="using",
|
|
||||||
),
|
|
||||||
arguments=[
|
|
||||||
p.LiteralExpr(value=path),
|
|
||||||
],
|
|
||||||
):
|
|
||||||
return Path(path)
|
|
||||||
return None
|
|
||||||
|
|
||||||
def import_midas(self, path: Path) -> None:
|
def import_midas(self, path: Path) -> None:
|
||||||
"""Import Midas definitions from a path
|
"""Import Midas definitions from a path
|
||||||
|
|
||||||
@@ -171,14 +159,11 @@ class Checker(
|
|||||||
path (Path): the import path
|
path (Path): the import path
|
||||||
"""
|
"""
|
||||||
self.logger.debug(f"Importing type definitions from {path}")
|
self.logger.debug(f"Importing type definitions from {path}")
|
||||||
path = (self.file_path.parent / path).resolve()
|
|
||||||
lexer: MidasLexer = MidasLexer(path.read_text())
|
lexer: MidasLexer = MidasLexer(path.read_text())
|
||||||
tokens: list[Token] = lexer.process()
|
tokens: list[Token] = lexer.process()
|
||||||
parser: MidasParser = MidasParser(tokens)
|
parser: MidasParser = MidasParser(tokens)
|
||||||
stmts: list[m.Stmt] = parser.parse()
|
stmts: list[m.Stmt] = parser.parse()
|
||||||
self.ctx.resolve(stmts)
|
self.ctx.resolve(stmts)
|
||||||
self.logger.debug(f"Midas types: {self.ctx._types}")
|
|
||||||
self.logger.debug(f"Midas operations: {self.ctx._operations}")
|
|
||||||
|
|
||||||
def visit_expression_stmt(self, stmt: p.ExpressionStmt) -> None:
|
def visit_expression_stmt(self, stmt: p.ExpressionStmt) -> None:
|
||||||
self.type_of(stmt.expr)
|
self.type_of(stmt.expr)
|
||||||
@@ -362,9 +347,6 @@ class Checker(
|
|||||||
def visit_unary_expr(self, expr: p.UnaryExpr) -> Type: ...
|
def visit_unary_expr(self, expr: p.UnaryExpr) -> Type: ...
|
||||||
|
|
||||||
def visit_call_expr(self, expr: p.CallExpr) -> Type:
|
def visit_call_expr(self, expr: p.CallExpr) -> Type:
|
||||||
if path := self.parse_midas_import(expr):
|
|
||||||
self.import_midas(path)
|
|
||||||
return UnknownType()
|
|
||||||
callee: Type = self.type_of(expr.callee)
|
callee: Type = self.type_of(expr.callee)
|
||||||
if not isinstance(callee, Function):
|
if not isinstance(callee, Function):
|
||||||
self.error(expr.callee.location, "Callee is not a function")
|
self.error(expr.callee.location, "Callee is not a function")
|
||||||
|
|||||||
@@ -34,8 +34,9 @@ def midas():
|
|||||||
|
|
||||||
@midas.command()
|
@midas.command()
|
||||||
@click.option("-l", "--highlight", type=click.File("w"))
|
@click.option("-l", "--highlight", type=click.File("w"))
|
||||||
|
@click.option("-t", "--types", type=click.File("r"), multiple=True)
|
||||||
@click.argument("file", type=click.File("r"))
|
@click.argument("file", type=click.File("r"))
|
||||||
def compile(highlight: Optional[TextIO], file: TextIO):
|
def compile(highlight: Optional[TextIO], file: TextIO, types: tuple[TextIO]):
|
||||||
logging.basicConfig(level=logging.DEBUG)
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
source: str = file.read()
|
source: str = file.read()
|
||||||
tree: ast.Module = ast.parse(source, filename=file.name)
|
tree: ast.Module = ast.parse(source, filename=file.name)
|
||||||
@@ -43,7 +44,12 @@ def compile(highlight: Optional[TextIO], file: TextIO):
|
|||||||
stmts: list[p.Stmt] = parser.parse_module(tree)
|
stmts: list[p.Stmt] = parser.parse_module(tree)
|
||||||
resolver = Resolver()
|
resolver = Resolver()
|
||||||
resolver.resolve(*stmts)
|
resolver.resolve(*stmts)
|
||||||
checker = Checker(resolver.locals, file_path=Path(file.name).resolve())
|
types_paths: list[Path] = [Path(t.name).resolve() for t in types]
|
||||||
|
checker = Checker(
|
||||||
|
resolver.locals,
|
||||||
|
source_path=Path(file.name).resolve(),
|
||||||
|
types_paths=types_paths,
|
||||||
|
)
|
||||||
diagnostics: list[Diagnostic] = checker.check(stmts)
|
diagnostics: list[Diagnostic] = checker.check(stmts)
|
||||||
for diagnostic in diagnostics:
|
for diagnostic in diagnostics:
|
||||||
print(diagnostic)
|
print(diagnostic)
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
# type: ignore
|
# type: ignore
|
||||||
# ruff: disable [F821]
|
# ruff: disable [F821]
|
||||||
|
|
||||||
midas.using("04_custom_types.midas")
|
|
||||||
|
|
||||||
distance: Meter = cast(Meter, 123.45)
|
distance: Meter = cast(Meter, 123.45)
|
||||||
time: Second = cast(Second, 6.7)
|
time: Second = cast(Second, 6.7)
|
||||||
speed = distance / time
|
speed = distance / time
|
||||||
|
|||||||
@@ -2,10 +2,6 @@
|
|||||||
# ruff: disable[F821]
|
# ruff: disable[F821]
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import midas
|
|
||||||
|
|
||||||
midas.using("02_custom_types.midas")
|
|
||||||
|
|
||||||
df: Frame[
|
df: Frame[
|
||||||
location: GeoLocation
|
location: GeoLocation
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,26 +1,5 @@
|
|||||||
{
|
{
|
||||||
"stmts": [
|
"stmts": [
|
||||||
{
|
|
||||||
"_type": "ExpressionStmt",
|
|
||||||
"expr": {
|
|
||||||
"_type": "CallExpr",
|
|
||||||
"callee": {
|
|
||||||
"_type": "GetExpr",
|
|
||||||
"object": {
|
|
||||||
"_type": "VariableExpr",
|
|
||||||
"name": "midas"
|
|
||||||
},
|
|
||||||
"name": "using"
|
|
||||||
},
|
|
||||||
"arguments": [
|
|
||||||
{
|
|
||||||
"_type": "LiteralExpr",
|
|
||||||
"value": "02_custom_types.midas"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"keywords": {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"_type": "TypeAssign",
|
"_type": "TypeAssign",
|
||||||
"name": "df",
|
"name": "df",
|
||||||
|
|||||||
@@ -33,6 +33,10 @@ class CheckerTester(Tester):
|
|||||||
if not path.is_file():
|
if not path.is_file():
|
||||||
raise TypeError(f"Test '{path}' is not a file")
|
raise TypeError(f"Test '{path}' is not a file")
|
||||||
|
|
||||||
|
types_paths: list[Path] = []
|
||||||
|
types_path: Path = path.with_suffix(".midas")
|
||||||
|
if types_path.exists():
|
||||||
|
types_paths.append(types_path)
|
||||||
source: str = path.read_text()
|
source: str = path.read_text()
|
||||||
tree: ast.Module = ast.parse(source, filename=path)
|
tree: ast.Module = ast.parse(source, filename=path)
|
||||||
parser = PythonParser()
|
parser = PythonParser()
|
||||||
@@ -40,7 +44,11 @@ class CheckerTester(Tester):
|
|||||||
resolver = Resolver()
|
resolver = Resolver()
|
||||||
resolver.resolve(*stmts)
|
resolver.resolve(*stmts)
|
||||||
result: CaseResult = CaseResult()
|
result: CaseResult = CaseResult()
|
||||||
checker = Checker(resolver.locals, file_path=path)
|
checker = Checker(
|
||||||
|
resolver.locals,
|
||||||
|
source_path=path,
|
||||||
|
types_paths=types_paths,
|
||||||
|
)
|
||||||
diagnostics: list[Diagnostic] = checker.check(stmts)
|
diagnostics: list[Diagnostic] = checker.check(stmts)
|
||||||
for diagnostic in diagnostics:
|
for diagnostic in diagnostics:
|
||||||
result.diagnostics.append(
|
result.diagnostics.append(
|
||||||
|
|||||||
Reference in New Issue
Block a user