tests: add python parser tester
This commit is contained in:
46
tests/python.py
Normal file
46
tests/python.py
Normal file
@@ -0,0 +1,46 @@
|
||||
import ast
|
||||
import json
|
||||
from dataclasses import asdict, dataclass
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
from midas.ast.python import Stmt
|
||||
from midas.parser.python import PythonParser
|
||||
from tests.base import Tester
|
||||
from tests.serializer.python import PythonAstJsonSerializer
|
||||
|
||||
|
||||
@dataclass
|
||||
class CaseResult:
|
||||
stmts: Optional[list[dict]] = None
|
||||
|
||||
def dumps(self) -> str:
|
||||
return json.dumps(asdict(self), indent=2)
|
||||
|
||||
|
||||
class PythonTester(Tester):
|
||||
@property
|
||||
def namespace(self) -> str:
|
||||
return "python-parser"
|
||||
|
||||
def _list_tests(self) -> list[Path]:
|
||||
return list(self.base_dir.rglob("*.py"))
|
||||
|
||||
def _exec_case(self, path: Path) -> CaseResult:
|
||||
if not path.exists():
|
||||
raise FileNotFoundError(f"Could not find test '{path}'")
|
||||
if not path.is_file():
|
||||
raise TypeError(f"Test '{path}' is not a file")
|
||||
|
||||
result: CaseResult = CaseResult()
|
||||
content: str = path.read_text()
|
||||
tree: ast.Module = ast.parse(content)
|
||||
|
||||
parser: PythonParser = PythonParser()
|
||||
stmts: list[Stmt] = parser.parse_module(tree)
|
||||
result.stmts = PythonAstJsonSerializer().serialize(stmts)
|
||||
return result
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
PythonTester.main()
|
||||
230
tests/serializer/python.py
Normal file
230
tests/serializer/python.py
Normal file
@@ -0,0 +1,230 @@
|
||||
import ast
|
||||
from typing import Optional, Sequence, Type
|
||||
|
||||
from midas.ast.python import (
|
||||
AssignStmt,
|
||||
BaseType,
|
||||
BinaryExpr,
|
||||
CallExpr,
|
||||
CompareExpr,
|
||||
ConstraintType,
|
||||
Expr,
|
||||
ExpressionStmt,
|
||||
FrameColumn,
|
||||
FrameType,
|
||||
Function,
|
||||
GetExpr,
|
||||
LiteralExpr,
|
||||
LogicalExpr,
|
||||
MidasType,
|
||||
ReturnStmt,
|
||||
SetExpr,
|
||||
Stmt,
|
||||
TypeAssign,
|
||||
UnaryExpr,
|
||||
VariableExpr,
|
||||
)
|
||||
|
||||
unary_ops: dict[Type[ast.unaryop], str] = {
|
||||
ast.Invert: "~",
|
||||
ast.Not: "not",
|
||||
ast.UAdd: "+",
|
||||
ast.USub: "-",
|
||||
}
|
||||
binary_ops: dict[Type[ast.operator], str] = {
|
||||
ast.Add: "+",
|
||||
ast.Sub: "-",
|
||||
ast.Mult: "*",
|
||||
ast.MatMult: "@",
|
||||
ast.Div: "/",
|
||||
ast.Mod: "%",
|
||||
ast.LShift: "<<",
|
||||
ast.RShift: ">>",
|
||||
ast.BitOr: "|",
|
||||
ast.BitXor: "^",
|
||||
ast.BitAnd: "&",
|
||||
ast.FloorDiv: "//",
|
||||
ast.Pow: "**",
|
||||
}
|
||||
compare_ops: dict[Type[ast.cmpop], str] = {
|
||||
ast.Eq: "==",
|
||||
ast.NotEq: "!=",
|
||||
ast.Lt: "<",
|
||||
ast.LtE: "<=",
|
||||
ast.Gt: ">",
|
||||
ast.GtE: ">=",
|
||||
ast.Is: "is",
|
||||
ast.IsNot: "is not",
|
||||
ast.In: "in",
|
||||
ast.NotIn: "not in",
|
||||
}
|
||||
boolean_ops: dict[Type[ast.boolop], str] = {
|
||||
ast.And: "and",
|
||||
ast.Or: "or",
|
||||
}
|
||||
|
||||
|
||||
class PythonAstJsonSerializer(
|
||||
Stmt.Visitor[dict], Expr.Visitor[dict], MidasType.Visitor[dict]
|
||||
):
|
||||
"""An AST serializer which produces a JSON-compatible structure"""
|
||||
|
||||
def serialize(self, stmts: list[Stmt]) -> list[dict]:
|
||||
return [stmt.accept(self) for stmt in stmts]
|
||||
|
||||
def _serialize_optional(
|
||||
self, element: Optional[Stmt | Expr | MidasType]
|
||||
) -> Optional[dict]:
|
||||
if element is None:
|
||||
return None
|
||||
return element.accept(self)
|
||||
|
||||
def _serialize_list(
|
||||
self, elements: Sequence[Stmt | Expr | MidasType]
|
||||
) -> list[dict]:
|
||||
return [element.accept(self) for element in elements]
|
||||
|
||||
def visit_base_type(self, node: BaseType) -> dict:
|
||||
return {
|
||||
"_type": "BaseType",
|
||||
"base": node.base,
|
||||
"param": self._serialize_optional(node.param),
|
||||
}
|
||||
|
||||
def visit_constraint_type(self, node: ConstraintType) -> dict:
|
||||
return {
|
||||
"_type": "ConstraintType",
|
||||
"type": node.type.accept(self),
|
||||
"constraint": ast.unparse(node.constraint),
|
||||
}
|
||||
|
||||
def visit_frame_column(self, node: FrameColumn) -> dict:
|
||||
return {
|
||||
"_type": "FrameColumn",
|
||||
"name": node.name,
|
||||
"type": self._serialize_optional(node.type),
|
||||
}
|
||||
|
||||
def visit_frame_type(self, node: FrameType) -> dict:
|
||||
return {
|
||||
"_type": "FrameType",
|
||||
"columns": self._serialize_list(node.columns),
|
||||
}
|
||||
|
||||
def visit_expression_stmt(self, stmt: ExpressionStmt) -> dict:
|
||||
return {
|
||||
"_type": "ExpressionStmt",
|
||||
"expr": stmt.expr.accept(self),
|
||||
}
|
||||
|
||||
def _serialize_argument(self, arg: Function.Argument) -> dict:
|
||||
return {
|
||||
"name": arg.name,
|
||||
"type": self._serialize_optional(arg.type),
|
||||
"default": self._serialize_optional(arg.default),
|
||||
}
|
||||
|
||||
def visit_function(self, stmt: Function) -> dict:
|
||||
return {
|
||||
"_type": "Function",
|
||||
"name": stmt.name,
|
||||
"posonlyargs": [self._serialize_argument(arg) for arg in stmt.posonlyargs],
|
||||
"args": [self._serialize_argument(arg) for arg in stmt.args],
|
||||
"sink": (
|
||||
self._serialize_argument(stmt.sink) if stmt.sink is not None else None
|
||||
),
|
||||
"kwonlyargs": [self._serialize_argument(arg) for arg in stmt.kwonlyargs],
|
||||
"kw_sink": (
|
||||
self._serialize_argument(stmt.kw_sink)
|
||||
if stmt.kw_sink is not None
|
||||
else None
|
||||
),
|
||||
"returns": self._serialize_optional(stmt.returns),
|
||||
"body": self._serialize_list(stmt.body),
|
||||
}
|
||||
|
||||
def visit_type_assign(self, stmt: TypeAssign) -> dict:
|
||||
return {
|
||||
"_type": "TypeAssign",
|
||||
"name": stmt.name,
|
||||
"type": stmt.type.accept(self),
|
||||
}
|
||||
|
||||
def visit_assign_stmt(self, stmt: AssignStmt) -> dict:
|
||||
return {
|
||||
"_type": "AssignStmt",
|
||||
"targets": self._serialize_list(stmt.targets),
|
||||
"value": stmt.value.accept(self),
|
||||
}
|
||||
|
||||
def visit_return_stmt(self, stmt: ReturnStmt) -> dict:
|
||||
return {
|
||||
"_type": "ReturnStmt",
|
||||
"value": self._serialize_optional(stmt.value),
|
||||
}
|
||||
|
||||
def visit_binary_expr(self, expr: BinaryExpr) -> dict:
|
||||
return {
|
||||
"_type": "BinaryExpr",
|
||||
"left": expr.left.accept(self),
|
||||
"operator": binary_ops[expr.operator.__class__],
|
||||
"right": expr.right.accept(self),
|
||||
}
|
||||
|
||||
def visit_compare_expr(self, expr: CompareExpr) -> dict:
|
||||
return {
|
||||
"_type": "CompareExpr",
|
||||
"left": expr.left.accept(self),
|
||||
"operator": compare_ops[expr.operator.__class__],
|
||||
"right": expr.right.accept(self),
|
||||
}
|
||||
|
||||
def visit_unary_expr(self, expr: UnaryExpr) -> dict:
|
||||
return {
|
||||
"_type": "UnaryExpr",
|
||||
"operator": unary_ops[expr.operator.__class__],
|
||||
"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:
|
||||
return {
|
||||
"_type": "GetExpr",
|
||||
"object": expr.object.accept(self),
|
||||
"name": expr.name,
|
||||
}
|
||||
|
||||
def visit_literal_expr(self, expr: LiteralExpr) -> dict:
|
||||
return {
|
||||
"_type": "LiteralExpr",
|
||||
"value": expr.value,
|
||||
}
|
||||
|
||||
def visit_variable_expr(self, expr: VariableExpr) -> dict:
|
||||
return {
|
||||
"_type": "VariableExpr",
|
||||
"name": expr.name,
|
||||
}
|
||||
|
||||
def visit_logical_expr(self, expr: LogicalExpr) -> dict:
|
||||
return {
|
||||
"_type": "LogicalExpr",
|
||||
"left": expr.left.accept(self),
|
||||
"operator": boolean_ops[expr.operator.__class__],
|
||||
"right": expr.right.accept(self),
|
||||
}
|
||||
|
||||
def visit_set_expr(self, expr: SetExpr) -> dict:
|
||||
return {
|
||||
"_type": "SetExpr",
|
||||
"object": expr.object.accept(self),
|
||||
"name": expr.name,
|
||||
"value": expr.value.accept(self),
|
||||
}
|
||||
Reference in New Issue
Block a user