feat(report): add python ast nodes

This commit is contained in:
2026-07-19 12:55:24 +02:00
parent 7ac1d880d7
commit 66610fdac9
5 changed files with 231 additions and 41 deletions

View File

@@ -1,5 +1,5 @@
#import "@preview/acrostiche:0.7.0": acr
#import "@local/codly:1.3.1": codly-disable, codly-enable
#import "utils.typ": format-ast-nodes
#let token-types = (
"Punctuation": (
@@ -188,47 +188,14 @@
```),
)
#let format-ast-nodes(name, base-cls, code) = {
let nodes = code.text.split(regex(`\n\nclass`.text)).map(n => if n.starts-with("class") {n} else {"class" + n})
let cells = nodes.map(n => raw(n, block: true, lang: "python"))
if calc.rem(cells.len(), 3) == 2 {
let last-two = cells.slice(-2)
cells = cells.slice(0, -2)
let last = grid.cell(
colspan: 3,
stack(
dir: ltr,
spacing: 1fr,
none,
..last-two,
none
)
)
cells.push(last)
}
align(center)[*#name*]
if base-cls != none [Base class: #base-cls]
codly-disable()
grid(
columns: 3,
column-gutter: 1fr,
row-gutter: 2em,
align: left + top,
..cells
)
codly-enable()
}
#let midas-ast-nodes = stack(
dir: ttb,
spacing: 2em,
#let midas-ast-nodes = table(
inset: .5em,
row-gutter: 2em,
stroke: gray,
..ast-nodes.map(nodes => format-ast-nodes(..nodes))
)
#show figure: set block(breakable: true)
#figure(
grid(
columns: 4,

View File

@@ -0,0 +1,172 @@
#import "@preview/acrostiche:0.7.0": acr
#import "utils.typ": format-ast-nodes
#let ast-nodes = (
("Other classes", none, ```python
class ParamSpec:
pos: list[Function.Parameter]
mixed: list[Function.Parameter]
kw: list[Function.Parameter]
@property
def all(self) -> list[Function.Parameter]:
return self.pos + self.mixed + self.kw
class ImportAlias:
location: Location
name: str
alias: Optional[str] = None
@property
def imported_name(self) -> str:
return (
self.alias
if self.alias is not None else
self.name
)
```),
("Type annotations", `MidasType`, ```python
class BaseType:
base: str
args: tuple[MidasType,...]
class FrameColumn:
name: Optional[str]
type: Optional[MidasType]
class FrameType:
columns: list[FrameColumn]
```),
("Statements", `Stmt`, ```python
class ExpressionStmt:
expr: Expr
class Function:
name: str
params: ParamSpec
returns: Optional[MidasType]
body: list[Stmt]
class Parameter:
location: Optional[Location] = None
name: str
type: Optional[MidasType]
default: Optional[Expr]
class TypeAssign:
name: str
type: MidasType
class AssignStmt:
targets: list[Expr]
value: Expr
class ReturnStmt:
value: Optional[Expr]
class IfStmt:
test: Expr
body: list[Stmt]
orelse: list[Stmt]
class Pass: ...
class ForStmt:
target: Expr
iterator: Expr
body: list[Stmt]
class ImportStmt:
imports: list[ImportAlias]
class FromImportStmt:
module: Optional[str]
imports: list[ImportAlias]
level: int
class RawStmt:
stmt: ast.stmt
```),
("Expressions", `Expr`, ```python
class BinaryExpr:
left: Expr
operator: ast.operator
right: Expr
class CompareExpr:
left: Expr
operator: ast.cmpop
right: Expr
class UnaryExpr:
operator: ast.unaryop
right: Expr
class CallExpr:
callee: Expr
arguments: list[Expr]
keywords: dict[str, Expr]
class GetExpr:
object: Expr
name: str
class LiteralExpr:
value: Any
class VariableExpr:
name: str
class LogicalExpr:
left: Expr
operator: ast.boolop
right: Expr
class CastExpr:
type: MidasType
expr: Expr
unsafe: bool
class TernaryExpr:
test: Expr
if_true: Expr
if_false: Expr
class ListExpr:
items: list[Expr]
class DictExpr:
keys: list[Optional[Expr]]
values: list[Expr]
class SubscriptExpr:
object: Expr
index: Expr
class SliceExpr:
lower: Optional[Expr]
upper: Optional[Expr]
step: Optional[Expr]
class TupleExpr:
items: tuple[Expr, ...]
class RawExpr:
expr: ast.expr
```),
)
#let python-ast-nodes = table(
inset: .5em,
stroke: gray,
row-gutter: 2em,
..ast-nodes.map(nodes => format-ast-nodes(..nodes))
)
#show figure: set block(breakable: true)
#figure(
python-ast-nodes,
caption: [Python #acr("AST") Nodes],
) <tab:python-ast-nodes>

View File

@@ -0,0 +1,37 @@
#import "@preview/codly:1.3.0": codly-disable, codly-enable
#import "../requirements.typ": isc-hei-bthesis
#let format-ast-nodes(name, base-cls, code) = {
let nodes = code.text.split(regex(`\n\nclass`.text)).map(n => if n.starts-with("class") {n} else {"class" + n})
let cells = nodes.map(n => raw(n, block: true, lang: "python"))
if calc.rem(cells.len(), 3) == 2 {
let last-two = cells.slice(-2)
cells = cells.slice(0, -2)
let last = grid.cell(
colspan: 3,
stack(
dir: ltr,
spacing: 1fr,
none,
..last-two,
none
)
)
cells.push(last)
}
align(center)[*#name*]
if base-cls != none [Base class: #base-cls]
codly-disable()
grid(
columns: 3,
column-gutter: 1fr,
row-gutter: 2em,
align: left + top,
..cells
)
codly-enable()
}

View File

@@ -38,7 +38,7 @@ Concretely, the lexer scans the source code character by character and produces
caption: [`Token` and `Position` classes]
) <fig:token-class>
`MidasLexer` has a simple constructor, taking in a source code string and optional file path, and a concise `process` method returning a list of tokens, as shown in @fig:lexer-signatures.
`MidasLexer` has a simple constructor taking in a source code string and optional file path, and a concise `process` method returning a list of tokens, as shown in @fig:lexer-signatures.
#figure(
```python

View File

@@ -1,12 +1,26 @@
#import "../../requirements.typ": isc-hei-bthesis
#import isc-hei-bthesis: todo
#import "@preview/acrostiche:0.7.0": acr
#import "../../utils.typ": code-ref
#import "@preview/codly:1.3.0": codly
= Python Type Checking <sec:impl-python>
We have now built a type definition language and registry, but it is of no use if we cannot type-check our Python code. In a similar fashion to `MidasTyper`, we must now implement a `PythonTyper`. Its role is to take in some Python source code and the types registry, walk through each statement and expression, infer the type of each term and verify the program's soundness. We will need several tools to concretize this process, such as a resolver to keep track of which value each variable references, and an environment to store the inferred types of local variables.
== Parsing Python <sec:python-parsing>
#todo[]
For consistency and resource sharing, we will mirror a number of structures and methods used for the Midas language. However, we do not need to implement a lexer and parser for Python as it already provides an `ast` module just for this purpose. Nonetheless, we will implement our own #acr("AST") nodes similar to those discussed in @sec:midas-lexing-parsing. This allows having the same structure as the Midas #acr("AST") and explicitly only implement supported Python constructs. Additionally, it will make some node namings and internal structures clearer.
The same generation script is used to create dataclasses for each node type (see @sec:midas-lexing-parsing).
For Python, we will also use three kinds of nodes:
- Statements (`Stmt`): top-level constructs introduced which can have side-effects in the environment
- Expressions (`Expr`): composable building blocks with an inferrable type
- Types (`MidasType`): type expressions used in annotations
The full list of #acr("AST") nodes used for Python is available in @tab:python-ast-nodes and their concrete implementation in the repository in #code-ref(<python-ast>, "midas/ast/python.py").
== Resolving References <sec:python-resolver>
#todo[]