24 Commits

Author SHA1 Message Date
7cddc62aaa chore: add some files in gitignore
Some checks failed
Tests / tests (pull_request) Failing after 1m32s
2026-07-08 16:11:51 +02:00
49e00d9fbc chore: add workflow to run tests 2026-07-08 16:09:53 +02:00
1d2f98419e chore: allow passing commit hash through inputs 2026-07-08 15:48:48 +02:00
2a73dc3fef chore: add ci to compile manual 2026-07-08 15:48:47 +02:00
ede7396f9b chore: update README 2026-07-08 15:48:46 +02:00
6ca778dbfa Merge pull request 'Remove complex type' (#35) from feat/remove-complex-type into main
Reviewed-on: #35
2026-07-08 13:44:58 +00:00
672c9c0fa1 docs: remove complex type from syntax definition 2026-07-08 15:44:07 +02:00
dd2f3d6f6a tests: fix frame ops with filtered groupby columns
see 205d19fb72
2026-07-08 15:37:45 +02:00
ef9dd95844 tests: rewrite test with complex types 2026-07-08 15:35:13 +02:00
725e030374 feat: remove complex and extension types 2026-07-08 15:34:40 +02:00
db986d5242 Merge pull request 'Update syntax definitions' (#34) from feat/update-syntax into main
Reviewed-on: #34
2026-07-08 12:48:14 +00:00
3c97e75db6 feat(parser): allow subscript in type annotations 2026-07-08 14:43:52 +02:00
e0a468a2c2 docs: update annotation syntax definitions 2026-07-08 14:43:10 +02:00
6740344eba docs: update Midas EBNF 2026-07-08 14:08:00 +02:00
4f9099a4c4 chore: update VSCode syntax definition
the TextMate language definition was completely rewritten by Claude from my Sublime Syntax definition
some tests against Midas files show that it seems on par with the other definition

Co-authored-by: Claude <noreply@anthropic.com>
2026-07-08 13:41:47 +02:00
5c66c4b645 docs: update syntax railroad diagrams 2026-07-08 13:32:32 +02:00
c0896d2b9b Merge pull request 'Minor improvements' (#33) from fix/weather-pipeline into main
Reviewed-on: #33
2026-07-08 08:13:08 +00:00
607ff53987 feat(checker): handle single string literal in groupby 2026-07-08 10:12:54 +02:00
3268783cbe chore: improve weather pipeline 2026-07-08 10:04:16 +02:00
a48182a4e3 fix(checker): allow calling methods on TopType and UnknownType 2026-07-08 10:03:51 +02:00
205d19fb72 feat(checker): try to filter groupby columns 2026-07-08 10:03:14 +02:00
10c6ea7dda feat(checker): add context to reports 2026-07-08 09:39:11 +02:00
1acf33f376 chore: fix weather pipeline example 2026-07-08 09:32:39 +02:00
aae481776f fix(checker): check ConstraintType's constraint type 2026-07-08 09:32:13 +02:00
35 changed files with 3073 additions and 2120 deletions

View File

@@ -1,48 +0,0 @@
name: Compile manual
on:
push:
branches:
- main
- master
pull_request:
branches:
- "**"
jobs:
ci:
runs-on: ubuntu-latest
container: catthehacker/ubuntu:act-latest
if: github.event.pull_request.draft == false
steps:
- uses: actions/checkout@v3
- uses: ruby/setup-ruby@v1
with:
ruby-version: "3.2"
- name: Setup Fontist
uses: fontist/setup-fontist@v2
- name: Install Fonts
run: fontist manifest install docs/fonts.yaml
- uses: typst-community/setup-typst@v5
with:
typst-version: "0.15.0"
zip-packages: docs/requirements.json
cache-local-packages: true
token: ""
- run: |
typst compile \
--root . \
--font-path ~/.fontist/fonts \
--input hash=${{ gitea.sha }} \
docs/manual.typ \
docs/manual.pdf
- name: Upload artifact
uses: christopherhx/gitea-upload-artifact@v4
with:
name: manual
path: docs/manual.pdf

View File

@@ -0,0 +1,24 @@
name: Tests
on:
push:
branches:
- main
- master
pull_request:
branches:
- "**"
jobs:
tests:
runs-on: ubuntu-latest
container: catthehacker/ubuntu:act-latest
if: github.event.pull_request.draft == false
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version-file: pyproject.toml
- name: Install uv
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
- run: python3 -m tests

4
.gitignore vendored
View File

@@ -6,4 +6,6 @@ venv
*.pyc
uv.lock
.python-version
/out
/out
/examples/**/build/
/examples/**/*.pyi

View File

@@ -5,7 +5,7 @@ type Celsius = float
type Kelvin = float where _ >= 0
type Hectopascal = float
type Temperature = Celsius where in_range(-30.0, 100.0)
type Temperature = Celsius where in_range(-30.0, 100.0)(_)
type Pressure = Hectopascal where in_range(800.0, 1100.0)(_)
type Humidity = float where is_percentage(_)
type HeatIndex = float
@@ -38,7 +38,7 @@ alias RawData = Frame[
alias Data = Frame[
station_id: StationID,
timestamp: object,
timestamp: Any,
temperature: Temperature,
pressure: Pressure,
humidity: Humidity,
@@ -46,7 +46,7 @@ alias Data = Frame[
alias DataWithHI = Frame[
station_id: StationID,
timestamp: object,
timestamp: Any,
temperature: Temperature,
pressure: Pressure,
humidity: Humidity,
@@ -54,12 +54,12 @@ alias DataWithHI = Frame[
]
alias DailyAverages = Frame[
timestamp: object,
timestamp: Any,
temperature: Mean[Temperature],
pressure: Mean[Pressure],
humidity: Mean[Humidity],
heat_index: Mean[HeatIndex],
]
predicate limit_amplitude(max_amp: float)(ls: list[float]) = max(ls) - min(ls) <= max_amp
type LowAmplitudeWave = list[float where _ >= 1] where limit_amplitude(10)(_)
// predicate limit_amplitude(max_amp: float)(ls: list[float]) = max(ls) - min(ls) <= max_amp
// type LowAmplitudeWave = list[float where _ >= 1] where limit_amplitude(10)(_)

View File

@@ -11,7 +11,7 @@ delta = end_ts - start_ts
min_temp, max_temp = -30.0, 100.0
min_pres, max_pres = 800.0, 1100.0
min_hum, max_hum = 0.0, 1.0
min_hum, max_hum = 0.0, 100.0
N = 3000

View File

@@ -47,7 +47,7 @@ def daily_avg(df: DataWithHI):
DailyAverages,
df.groupby(
by=[
df["station_id"],
"station_id",
df["timestamp"].dt.day.rename("day"),
],
)

View File

@@ -136,15 +136,6 @@ class ConstraintType:
constraint: Expr
class ComplexType:
members: list[MemberStmt]
class ExtensionType:
base: Type
extension: ComplexType
class FunctionType:
params: ParamSpec
returns: Type

View File

@@ -256,12 +256,6 @@ class Type(ABC):
@abstractmethod
def visit_constraint_type(self, type: ConstraintType) -> T: ...
@abstractmethod
def visit_complex_type(self, type: ComplexType) -> T: ...
@abstractmethod
def visit_extension_type(self, type: ExtensionType) -> T: ...
@abstractmethod
def visit_function_type(self, type: FunctionType) -> T: ...
@@ -295,23 +289,6 @@ class ConstraintType(Type):
return visitor.visit_constraint_type(self)
@dataclass(frozen=True)
class ComplexType(Type):
members: list[MemberStmt]
def accept(self, visitor: Type.Visitor[T]) -> T:
return visitor.visit_complex_type(self)
@dataclass(frozen=True)
class ExtensionType(Type):
base: Type
extension: ComplexType
def accept(self, visitor: Type.Visitor[T]) -> T:
return visitor.visit_extension_type(self)
@dataclass(frozen=True)
class FunctionType(Type):
params: ParamSpec

View File

@@ -124,19 +124,6 @@ class MidasPrinter(
res += " where " + type.constraint.accept(self)
return res
def visit_complex_type(self, type: m.ComplexType) -> str:
res: str = "{\n"
self.level += 1
for member in type.members:
res += member.accept(self)
res += "\n"
self.level -= 1
res += self.indented("}")
return res
def visit_extension_type(self, type: m.ExtensionType) -> str:
return f"{type.base.accept(self)} & {type.extension.accept(self)}"
def visit_function_type(self, type: m.FunctionType) -> str:
spec: str = self._visit_param_spec(type.params)
return f"fn {spec} -> {type.returns.accept(self)}"

View File

@@ -177,21 +177,6 @@ class MidasAstPrinter(
with self._child_level(single=True):
type.constraint.accept(self)
def visit_complex_type(self, type: m.ComplexType) -> None:
self._write_line("ComplexType")
with self._child_level():
self._write_sequence("members", type.members, last=True)
def visit_extension_type(self, type: m.ExtensionType) -> None:
self._write_line("ExtensionType")
with self._child_level():
self._write_line("base")
with self._child_level(single=True):
type.base.accept(self)
self._write_line("extension", last=True)
with self._child_level(single=True):
type.extension.accept(self)
def visit_function_type(self, type: m.FunctionType) -> None:
self._write_line("FunctionType")
with self._child_level():

View File

@@ -51,24 +51,25 @@ class FrameGroupByMethodRegistry(MethodRegistry[Call]):
new_columns: list[DataFrameType.Column] = []
for column in call.groupby.frame.columns:
column_groupby: ColumnGroupBy = ColumnGroupBy(column=column.type)
result_type: Type = self.typer.call_method(
location=call.location,
call_expr=call.call_expr,
obj=(call.groupby_expr, column_groupby),
method_name=method,
positional=call.positional,
keywords=call.keywords,
)
if not isinstance(result_type, ColumnType):
result_type = ColumnType(type=UnknownType())
new_columns.append(
DataFrameType.Column(
index=column.index,
name=column.name,
type=result_type,
with self.reporter.with_context(f"in column '{column.name}'"):
column_groupby: ColumnGroupBy = ColumnGroupBy(column=column.type)
result_type: Type = self.typer.call_method(
location=call.location,
call_expr=call.call_expr,
obj=(call.groupby_expr, column_groupby),
method_name=method,
positional=call.positional,
keywords=call.keywords,
)
if not isinstance(result_type, ColumnType):
result_type = ColumnType(type=UnknownType())
new_columns.append(
DataFrameType.Column(
index=column.index,
name=column.name,
type=result_type,
)
)
)
return DataFrameType(columns=new_columns)

View File

@@ -159,7 +159,10 @@ class FrameMethodRegistry(MethodRegistry[Call]):
col_type2 = ColumnType(type=operand[1])
if col_type2 is not None:
col_type = self._get_method_result(call, col_type1, col_type2, method)
with self.reporter.with_context(f"in column '{column.name}'"):
col_type = self._get_method_result(
call, col_type1, col_type2, method
)
new_column = DataFrameType.Column(
index=column.index,
@@ -595,8 +598,62 @@ class FrameMethodRegistry(MethodRegistry[Call]):
)
return result.result
def _filter_groupby_columns(
self, frame: DataFrameType, by: TypedExpr
) -> DataFrameType:
"""Remove columns passed as string literals in groupby's `by` argument
Args:
frame (DataFrameType): the original dataframe
by (TypedExpr): the by argument
Returns:
DataFrameType: the filtered dataframe
"""
by_columns: list[str] = []
by_expr, _ = by
match by_expr:
case p.ListExpr(items=items):
for item in items:
match item:
case p.LiteralExpr(value=str() as name):
by_columns.append(name)
case p.LiteralExpr(value=str() as name):
by_columns.append(name)
if len(by_columns) == 0:
return frame
new_columns: list[DataFrameType.Column] = []
for column in frame.columns:
if column.name in by_columns:
continue
new_columns.append(
DataFrameType.Column(
index=len(new_columns),
name=column.name,
type=column.type,
)
)
return DataFrameType(columns=new_columns)
@method()
def groupby(self, call: Call) -> Type:
new_frame: DataFrameType = call.frame
by: Optional[TypedExpr] = None
if len(call.positional) != 0:
by = call.positional[0]
elif "by" in call.keywords:
by = call.keywords["by"]
if by is not None:
new_frame = self._filter_groupby_columns(call.frame, by)
bool_: Type = self.types.get_type("bool")
function: Function = Function(
params=ParamSpec(
@@ -626,7 +683,7 @@ class FrameMethodRegistry(MethodRegistry[Call]):
)
],
),
returns=FrameGroupBy(frame=call.frame),
returns=FrameGroupBy(frame=new_frame),
)
result: CallResult = self.dispatcher.get_result(

View File

@@ -13,11 +13,9 @@ from midas.checker.registry import TypesRegistry
from midas.checker.reporter import FileReporter, Reporter
from midas.checker.types import (
ColumnType,
ComplexType,
ConstraintType,
DataFrameType,
DerivedType,
ExtensionType,
Function,
GenericType,
ParamSpec,
@@ -407,24 +405,21 @@ class MidasTyper(m.Stmt.Visitor[None], m.Expr.Visitor[Type], m.Type.Visitor[Type
return UnknownType()
def visit_constraint_type(self, type: m.ConstraintType) -> Type:
base_type: Type = type.type.accept(self)
self._predicate_params["_"] = base_type
constraint_type: Type = self.type_of(type.constraint)
self._predicate_params = {}
if not self.types.is_subtype(constraint_type, self._bool):
self.reporter.error(
type.location,
f"Constraint must evaluate to a boolean, got {constraint_type}",
)
return ConstraintType(
type=type.type.accept(self),
type=base_type,
constraint=type.constraint,
)
def visit_complex_type(self, type: m.ComplexType) -> ComplexType:
return ComplexType(
members={
member.name.lexeme: member.type.accept(self) for member in type.members
}
)
def visit_extension_type(self, type: m.ExtensionType) -> Type:
return ExtensionType(
base=type.base.accept(self),
extension=self.visit_complex_type(type.extension),
)
def visit_function_type(self, type: m.FunctionType) -> Type:
return Function(
params=self._visit_param_spec(type.params),

View File

@@ -257,6 +257,9 @@ class PythonTyper(
"""
unfolded: Type = unfold_type(obj[1])
match unfolded:
case TopType() | UnknownType():
return UnknownType()
case DataFrameType():
return self.frame_mgr.call(
method=method_name,

View File

@@ -8,11 +8,9 @@ from midas.checker.types import (
AppliedType,
BaseType,
ColumnType,
ComplexType,
ConstraintType,
DataFrameType,
DerivedType,
ExtensionType,
Function,
GenericType,
OverloadedFunction,
@@ -202,14 +200,6 @@ class TypesRegistry:
case (BaseType(name=name1), BaseType(name=name2)):
return self.is_builtin_subtype(name1, name2)
case (ComplexType(properties=props1), ComplexType(properties=props2)):
for k, t in props2.items():
if k not in props1:
return False
if not self.is_subtype(props1[k], t):
return False
return True
case (DataFrameType(columns=columns1), DataFrameType(columns=columns2)):
# TODO: check order?
by_name1: dict[str, DataFrameType.Column] = {
@@ -506,20 +496,6 @@ class TypesRegistry:
member_type2 = substitute_typevars(member_type2, substitutions)
return member_type2
case ComplexType(members=members):
if member_name in members:
return members[member_name]
self.logger.debug(f"No member '{member_name}' in {type}")
return None
case ExtensionType(base=base, extension=ComplexType(members=members)):
if member_name in members:
return members[member_name]
self.logger.debug(
f"No member '{member_name}' on {type}, looking up in base"
)
return self.lookup_member(base, member_name)
case ConstraintType(type=base):
return self.lookup_member(base, member_name)

View File

@@ -1,5 +1,6 @@
from __future__ import annotations
from contextlib import contextmanager
from typing import Optional
from midas.ast.location import Location
@@ -54,6 +55,7 @@ class FileReporter:
def __init__(self, base_reporter: Reporter, path: Optional[str]) -> None:
self.base_reporter: Reporter = base_reporter
self.path: Optional[str] = path
self._context: list[str] = []
def for_file(self, path: Optional[str]) -> FileReporter:
"""Create a new file reporter for the given path with the same base reporter
@@ -66,6 +68,14 @@ class FileReporter:
"""
return FileReporter(self.base_reporter, path)
@contextmanager
def with_context(self, ctx: str):
self._context.append(ctx)
try:
yield
finally:
self._context.pop()
def report(self, type: DiagnosticType, location: Location, message: str):
"""Report a diagnostic to the base reporter
@@ -74,6 +84,8 @@ class FileReporter:
location (Location): the location of the diagnostic in the file
message (str): the diagnostic's message
"""
for ctx in self._context:
message = message + ", " + ctx
self.base_reporter.report(self.path, type, location, message)
def error(self, location: Location, message: str):

View File

@@ -113,28 +113,6 @@ class OverloadedFunction:
return "<overloaded function>"
@dataclass(frozen=True, kw_only=True)
class ComplexType:
"""A type with inline members"""
members: dict[str, Type]
def __str__(self) -> str:
props: list[str] = [f"{name}: {type}" for name, type in self.members.items()]
return f"{{{', '.join(props)}}}"
@dataclass(frozen=True, kw_only=True)
class ExtensionType:
"""An extension of a type, adding members through a `ComplexType`"""
base: Type
extension: ComplexType
def __str__(self) -> str:
return f"{self.base} & {self.extension}"
class Variance(StrEnum):
"""The variance of a :class:`TypeVar`"""
@@ -323,24 +301,6 @@ def substitute_typevars(type: Type, substitutions: dict[str, Type]) -> Type:
]
)
case ComplexType(members=members):
members2: dict[str, Type] = {
name: substitute_typevars(prop, substitutions)
for name, prop in members.items()
}
return ComplexType(members=members2)
case ExtensionType(base=base, extension=ComplexType(members=members)):
return ExtensionType(
base=substitute_typevars(base, substitutions),
extension=ComplexType(
members={
name: substitute_typevars(prop, substitutions)
for name, prop in members.items()
}
),
)
case AppliedType(name=name, args=args, body=body):
return AppliedType(
name=name,
@@ -468,9 +428,6 @@ def to_annotation(type: Type) -> str:
case OverloadedFunction():
return "Callable"
case ComplexType() | ExtensionType():
raise NotImplementedError
case TypeVar(name=name):
return name
@@ -519,8 +476,6 @@ Type = (
| UnitType
| Function
| OverloadedFunction
| ComplexType
| ExtensionType
| TypeVar
| GenericType
| AppliedType

View File

@@ -338,21 +338,11 @@ class MidasHighlighter(
type.type.accept(self)
type.constraint.accept(self)
def visit_complex_type(self, type: m.ComplexType) -> None:
self.wrap(type, "complex-type")
for member in type.members:
member.accept(self)
def visit_function_type(self, type: m.FunctionType) -> None:
self.wrap(type, "function")
self._visit_param_spec(type.params)
type.returns.accept(self)
def visit_extension_type(self, type: m.ExtensionType) -> None:
self.wrap(type, "extension")
type.base.accept(self)
type.extension.accept(self)
def _visit_param_spec(self, spec: m.ParamSpec) -> None:
for param in spec.pos + spec.mixed + spec.kw:
param.type.accept(self)

View File

@@ -7,8 +7,7 @@ span {
&.named-type,
&.generic-type,
&.constraint-type,
&.complex-type {
&.constraint-type {
--col: 150, 150, 150;
}

View File

@@ -16,11 +16,9 @@ from midas.checker.types import (
BaseType,
ColumnGroupBy,
ColumnType,
ComplexType,
ConstraintType,
DataFrameType,
DerivedType,
ExtensionType,
FrameGroupBy,
Function,
GenericType,
@@ -675,8 +673,6 @@ class Generator(p.Stmt.Visitor[ast.stmt], p.Expr.Visitor[ast.expr]):
case (
Function()
| OverloadedFunction()
| ComplexType()
| ExtensionType()
| GenericType()
| FrameGroupBy()
| ColumnGroupBy()

View File

@@ -8,11 +8,9 @@ from midas.checker.types import (
BaseType,
ColumnGroupBy,
ColumnType,
ComplexType,
ConstraintType,
DataFrameType,
DerivedType,
ExtensionType,
FrameGroupBy,
Function,
GenericType,
@@ -269,14 +267,6 @@ class StubsGenerator:
right=self.dump_type(overloads[-1]),
)
case ComplexType():
name: str = self.new_stub_name()
self.generate_stub(name, type)
return ast.Name(id=name)
case ExtensionType():
raise NotImplementedError
case TypeVar():
return ast.Name(id=type.name)

View File

@@ -5,11 +5,9 @@ from midas.ast.midas import (
AliasStmt,
BinaryExpr,
CallExpr,
ComplexType,
ConstraintType,
Expr,
ExtendStmt,
ExtensionType,
FrameType,
FunctionType,
GenericType,
@@ -187,19 +185,9 @@ class MidasParser(Parser[list[Stmt]]):
Returns:
TypeExpr: the parsed type expression
"""
base: Type
if self.match(TokenType.FUNC):
base = self.function()
else:
base = self.constraint_type()
if self.match(TokenType.AND):
extension: ComplexType = self.complex_type()
return ExtensionType(
location=Location.span(base.location, extension.location),
base=base,
extension=extension,
)
return base
return self.function()
return self.constraint_type()
def constraint_type(self) -> Type:
"""Parse a constraint type expression
@@ -235,9 +223,6 @@ class MidasParser(Parser[list[Stmt]]):
self.consume(TokenType.RIGHT_PAREN, "Unclosed parenthesis")
return type
if self.check(TokenType.LEFT_BRACE):
return self.complex_type()
return self.generic_type()
def generic_type(self) -> Type:
@@ -295,34 +280,6 @@ class MidasParser(Parser[list[Stmt]]):
name=name,
)
def complex_type(self) -> ComplexType:
"""Parse a complex type expression
A complex type consists of zero or more member statements enclosed in
curly braces
Returns:
ComplexType: the parsed complex type expression
"""
left: Token = self.consume(
TokenType.LEFT_BRACE, "Expected '{' to start type body"
)
members: list[MemberStmt] = []
# TODO: add keyword to differentiate properties and methods,
# and allow multiple methods with the same name but not properties
names: set[str] = set()
while not self.check(TokenType.RIGHT_BRACE) and not self.is_at_end():
member: MemberStmt = self.member_stmt()
# if member.name.lexeme in names:
# raise self.error(member.name, "Duplicate property")
# names.add(member.name.lexeme)
members.append(member)
right: Token = self.consume(TokenType.RIGHT_BRACE, "Unclosed type body")
return ComplexType(
location=left.location_to(right),
members=members,
)
def frame_type(self) -> FrameType:
"""Parse a frame type expression
@@ -632,7 +589,7 @@ class MidasParser(Parser[list[Stmt]]):
return WildcardExpr(location=token.get_location(), token=token)
if self.match(TokenType.LEFT_PAREN):
expr: Expr = self.constraint()
expr: Expr = self.expression()
right: Token = self.consume(TokenType.RIGHT_PAREN, "Unclosed parenthesis")
return GroupingExpr(location=token.location_to(right), expr=expr)

View File

@@ -380,7 +380,7 @@ class PythonParser:
for col in cols:
columns.append(self._parse_frame_column(col))
case ast.Slice() | ast.Name():
case ast.Slice() | ast.Name() | ast.Subscript():
columns.append(self._parse_frame_column(schema))
case _:
@@ -391,7 +391,7 @@ class PythonParser:
def _parse_frame_column(self, column: ast.expr) -> FrameColumn:
loc: Location = Location.from_ast(column)
match column:
case ast.Name():
case ast.Name() | ast.Subscript():
return FrameColumn(
location=loc,
name=None,

View File

@@ -1,20 +1,8 @@
identifier ::= '[a-zA-Z][a-zA-Z_]*'
Identifier ::= '[a-zA-Z][a-zA-Z_]*'
integer ::= '\d+'
number ::= integer ["." integer]
boolean ::= "False" | "True"
none ::= "None"
TypeArgs ::= "[" (Type ("," Type)*)? "]"
value ::= number | boolean | none
lambda-value ::= "_" | value
lambda-operator ::= ">" | "<" | ">=" | "<=" | "==" | "!="
lambda ::= lambda-value lambda-operator lambda-value
FrameColumn ::= ((Identifier | "_") ":")? Type
FrameSchema ::= "[" (FrameColumn ("," FrameColumn)*)? "]"
constraint ::= identifier | "(" lambda ")"
base-type ::= identifier
type ::= base-type { "+" constraint }
column-type ::= type | "_"
column-def ::= [ identifier ":" ] column-type
frame-def ::= column-def { "," column-def }
Type ::= "Frame" FrameSchema | Identifier TypeArgs?

View File

@@ -1,64 +1,72 @@
#import "@preview/fervojo:0.1.1": render
#import "@preview/fervojo:0.1.1": default-css, render
#let value = ```
{[`value` <
[`number` 'digit' * ! <!, ["." 'digit' * !]>],
[`boolean` <"False", "True">],
[`none` "None"]
#let extra-css = ```css
svg.railroad .terminal rect {
fill: #F7DCD4;
}
```
#let css = default-css() + bytes(extra-css.text)
#let type-args = ```
{[`type-args` "[" <!, 'type'*","> "]"]}
```
#let frame-schema = ```
{[`frame-schema` "[" <!, [[<'identifier', "_"> ":"]? 'type']*","> "]"]}
```
#let type = ```
{[`type` <
["Frame" 'frame-schema'],
['identifier' <!, 'type-args'>]
>]}
```
#let constraint = ```
{[`constraint` <"_", 'value'> <">", "<", ">=", "<=", "==", "!="> <"_", 'value'>]}
```
#let type-with-constraints = ```
{[`type-with-constraints` 'identifier' <!, ["+" "(" 'constraint' ")"] * !>]}
```
#let column-def = ```
{[`column-def` <!, ['identifier' ":"]> <"_", 'type-with-constraints'>]}
```
#let frame-def = ```
{[`frame-def` 'column-def' * ","]}
```
#let annotation = ```
{[`annotation` 'identifier' <!, ["[" 'frame-def' "]"]>]}
```
#let rules = (
value,
constraint,
type-with-constraints,
column-def,
frame-def,
annotation,
type-args: type-args,
frame-schema: frame-schema,
type: type,
)
#let inline = (
"type-args",
"frame-schema",
)
#set text(font: "Source Sans 3")
= Type annotation syntax
#title[Supported Python annotation syntax]
#for rule in rules {
render(rule)
}
= Outline
/*
#let by-name = (
annotation: annotation,
frame-def: frame-def,
column-def: column-def,
type-with-constraints: type-with-constraints,
constraint: constraint,
value: value,
#box(
columns(
2,
outline(title: none),
),
height: 9cm,
stroke: 1pt,
inset: 1em,
)
= Statements and expressions
#for (name, rule) in rules.pairs().rev() {
[== #name]
render(rule, css: css)
}
#let substitute(base-rule) = {
let new-rule = base-rule
for (key, rule) in by-name.pairs() {
new-rule = new-rule.replace("'" + key + "'", rule.text.slice(1, -1))
for name in inline {
let rule = rules.at(name)
let replacement = rule.text.slice(1, -1).replace(regex("\[`.*?`"), "[")
replacement = "[" + replacement + "#`" + name + "`]"
new-rule = new-rule.replace(
"'" + name + "'",
replacement,
)
}
if new-rule != base-rule {
new-rule = substitute(new-rule)
@@ -66,9 +74,16 @@
return new-rule
}
#let combined = raw(substitute(annotation.text))
#set page(flipped: true)
#render(combined)
*/
= Combined rules
#for (name, rule) in rules.pairs() {
if not name in inline {
[== #name]
let combined = substitute(rule.text)
render(raw(combined), css: css)
//raw(block: true, combined)
}
}

View File

@@ -4,40 +4,85 @@ Identifier ::= [a-zA-Z_] [a-zA-Z_0-9]*
Integer ::= '\d+'
Number ::= "-"? Integer ("." Integer)?
Boolean ::= "False" | "True"
String ::= '(".*?")|(\'.*?\')'
None ::= "None"
Value ::= Number | Boolean | None
Literal ::= Number | Boolean | String | None
UnaryOp ::= "+" | "-" | "!"
FactorOp ::= "*" | "/"
TermOp ::= "+" | "-"
ComparisonOp ::= ">" | "<" | ">=" | "<="
EqualityOp ::= "==" | "!="
Grouping ::= "(" Constraint ")"
Primary ::= "_" | Value | Identifier | Grouping
PosArg ::= Expression
KwArg ::= Identifier "=" Expression
PosArgs ::= PosArg ("," PosArg)*
KwArgs ::= KwArg ("," KwArg)*
Args ::= (
PosArgs
| KwArgs
| PosArgs "," KwArgs
)
Grouping ::= "(" Expression ")"
Primary ::= "_" | Literal | Identifier | Grouping
Reference ::= Primary ("." Identifier)*
Unary ::= "-"? Unary | Reference
Comparison ::= Unary (ComparisonOp Unary)*
CallArgs ::= "(" Args ")"
Call ::= Reference CallArgs*
Unary ::= UnaryOp Unary | Call
Factor ::= Unary (FactorOp Unary)*
Term ::= Factor (TermOp Factor)*
Comparison ::= Term (ComparisonOp Term)*
Equality ::= Comparison (EqualityOp Comparison)*
Constraint ::= Equality ("&" Equality)*
Expression ::= Equality ("&" Equality)*
Constraint ::= Expression
TemplateParam ::= Identifier ("<:" Type)?
Template ::= "[" (TemplateParam ("," TemplateParam)*)? "]"
ParamType ::= Type "?"?
PosParam ::= (Identifier ":")? ParamType
KwParam ::= Identifier ":" ParamType
PosParams ::= (
(PosParam ("," PosParam)* ("," "/")?)
| "/"
)
MixedParams ::= KwParam ("," KwParam)
KwParams ::= (
(("*", ",")? KwParam ("," KwParam)*)
| "*"
)
Params ::= (
PosParams
| MixedParams
| KwParams
| (PosParams "," MixedParams)
| (PosParams "," KwParams)
| (MixedParams "," KwParams)
| (PosParams "," MixedParams "," KwParams)
)
ParamSpec ::= "(" Params? ")"
TypeProperty ::= Identifier ":" Type
ComplexType ::= "{" TypeProperty* "}"
NamedType ::= Identifier
TypeParams ::= "[" (Type ("," Type)*)? "]"
GenericType ::= NamedType TypeParams?
TypeArgs ::= "[" (Type ("," Type)*)? "]"
FrameColumn ::= TOKEN ":" Type
FrameSchema ::= "[" (FrameColumn ("," FrameColumn)*)? "]"
GenericType ::= "Frame" FrameSchema | NamedType TypeArgs?
GroupedType ::= "(" Type ")"
BaseType ::= GroupedType | ComplexType | GenericType
BaseType ::= GroupedType | GenericType
ConstraintType ::= BaseType ("where" Constraint)?
FuncType ::= "fn" ParamSpec "->" Type
Type ::= ConstraintType
OpDefinition ::= "op" Identifier "(" Type ")" "->" Type
ExtendBody ::= "{" OpDefinition* "}"
MemberStatement ::= ("prop" | "def") Identifier ":" Type
ExtendBody ::= "{" MemberStatement* "}"
AliasStatement ::= "alias" Identifier "=" Type
TypeStatement ::= "type" Identifier Template? "=" Type
ExtendStatement ::= "extend" Type ExtendBody
PredicateStatement ::= "predicate" Identifier "(" Identifier ":" Type ")" "=" Constraint
PredicateStatement ::= "predicate" Identifier ParamSpec* "=" Constraint
Statement ::= TypeStatement | ExtendStatement | PredicateStatement
Statement ::= AliasStatement | TypeStatement | ExtendStatement | PredicateStatement

View File

@@ -7,40 +7,61 @@ svg.railroad .terminal rect {
```
#let css = default-css() + bytes(extra-css.text)
#let value = ```
{[`value` <
#let literal = ```
{[`literal` <
[`number` 'digit' * ! <!, ["." 'digit' * !]>],
[`boolean` <"False", "True">],
[`string` <["\"" 'char'*! "\""], ["'" 'char'*! "'"]>],
[`none` "None"]
>]}
```
#let grouping = ```
{[`grouping` "(" 'constraint' ")"]}
{[`grouping` "(" 'expression' ")"]}
```
#let primary = ```
{[`primary` <"_", 'value', 'identifier', 'grouping'>]}
{[`primary` <"_", 'literal', 'identifier', 'grouping'>]}
```
#let reference = ```
{[`reference` 'primary' <!, ["." 'identifier']*!>]}
```
#let call-args = ```
{[`call-args` "(" <!, <'expression', ['identifier' "=" 'expression']>*","#`Same rules as Python`> ")"]}
```
#let call = ```
{[`call` 'reference' <!, 'call-args'*!>]}
```
#let unary = ```
{[`unary` <[<!, "-"> 'unary'], 'reference'>]}
{[`unary` <[<"+", "-", "!"> 'unary'], 'call'>]}
```
#let factor = ```
{[`factor` 'unary'*<"*", "/">]}
```
#let term = ```
{[`term` 'factor'*<"+", "-">]}
```
#let comparison = ```
{[`comparison` 'unary'*<">", "<", ">=", "<=">]}
{[`comparison` 'term'*<">", "<", ">=", "<=">]}
```
#let equality = ```
{[`equality` 'comparison'*<"==", "!=">]}
```
#let expression = ```
{[`expression` 'equality'*"&"]}
```
#let constraint = ```
{[`constraint` 'equality'*"&"]}
{[`constraint` 'expression']}
```
#let template-param = ```
@@ -51,24 +72,20 @@ svg.railroad .terminal rect {
{[`template` "[" <!, 'template-param'*","> "]"]}
```
#let type-property = ```
{[`type-property` 'identifier' ":" 'type']}
```
#let complex-type = ```
{[`complex-type` "{" <!, 'type-property'*!> "}"]}
```
#let named-type = ```
{[`named-type` 'identifier']}
```
#let type-params = ```
{[`type-params` "[" <!, 'type'*","> "]"]}
#let type-args = ```
{[`type-args` "[" <!, 'type'*","> "]"]}
```
#let frame-schema = ```
{[`frame-schema` "[" <!, ['TOKEN' ":" 'type']*","> "]"]}
```
#let generic-type = ```
{[`generic-type` 'named-type' <!, 'type-params'>]}
{[`generic-type` <["Frame" 'frame-schema'], ['named-type' <!, 'type-args'>]>]}
```
#let grouped-type = ```
@@ -76,59 +93,88 @@ svg.railroad .terminal rect {
```
#let base-type = ```
{[`base-type` <'grouped-type', 'complex-type', 'generic-type'>]}
{[`base-type` <'grouped-type', 'generic-type'>]}
```
#let constraint-type = ```
{[`constraint-type` 'base-type' <!, ["where" 'constraint']>]}
```
#let pos-param = ```
{[`pos-param` <!, ['identifier' ":"]> 'type' <!, "?">]}
```
#let kw-param = ```
{[`kw-param` 'identifier' ":" 'type' <!, "?">]}
```
#let param-spec = ```
{[`param-spec` "(" <!, <'pos-param', "/", "*", 'kw-param'>*",">#`Same rules as Python` ")"]}
```
#let func-type = ```
{[`func-type` "fn" 'param-spec' "->" 'type']}
```
#let type = ```
{[`type` 'constraint-type']}
{[`type` <'func-type', 'constraint-type'>]}
```
#let alias-statement = ```
{[`alias-statement` "alias" 'identifier' "=" 'type']}
```
#let type-statement = ```
{[`type-statement` "type" 'identifier' <!, 'template'> "=" 'type']}
```
#let op-definition = ```
{[`op-definition` "op" 'identifier' "(" 'type' ")" "->" 'type']}
#let member-stmt = ```
{[`member-stmt` <"prop", "def"> 'identifier' ":" 'type']}
```
#let extend-statement = ```
{[`extend-statement` "extend" 'type' "{" <!, 'op-definition'*!> "}"]}
{[`extend-statement` "extend" 'type' "{" <!, 'member-stmt'*!> "}"]}
```
#let predicate-statement = ```
{[`predicate-statement` "predicate" 'identifier' "(" 'identifier' ":" 'type' ")" "=" 'constraint']}
{[`predicate-statement` "predicate" 'identifier' <!, 'param-spec'*!> "=" 'constraint']}
```
#let statement = ```
{[`statement` <'type-statement', 'extend-statement', 'predicate-statement'>]}
{[`statement` <'alias-statement', 'type-statement', 'extend-statement', 'predicate-statement'>]}
```
#let rules = (
value: value,
literal: literal,
grouping: grouping,
primary: primary,
reference: reference,
call-args: call-args,
call: call,
unary: unary,
factor: factor,
term: term,
comparison: comparison,
equality: equality,
expression: expression,
constraint: constraint,
template-param: template-param,
template: template,
type-property: type-property,
complex-type: complex-type,
named-type: named-type,
type-params: type-params,
type-args: type-args,
generic-type: generic-type,
grouped-type: grouped-type,
base-type: base-type,
constraint-type: constraint-type,
pos-param: pos-param,
kw-param: kw-param,
param-spec: param-spec,
func-type: func-type,
type: type,
alias-statement: alias-statement,
type-statement: type-statement,
op-definition: op-definition,
member-stmt: member-stmt,
extend-statement: extend-statement,
predicate-statement: predicate-statement,
statement: statement,
@@ -136,18 +182,22 @@ svg.railroad .terminal rect {
#let inline = (
"grouping",
"value",
"literal",
"template-param",
"template",
"type-property",
"complex-type",
"type-params",
"call-args",
"type-args",
"named-type",
"grouped-type",
"generic-type",
"base-type",
"constraint-type",
"op-definition",
"pos-param",
"kw-param",
"func-type",
"member-stmt",
"alias-statement",
"type-statement",
"extend-statement",
"predicate-statement",
@@ -164,7 +214,7 @@ svg.railroad .terminal rect {
2,
outline(title: none),
),
height: 9cm,
height: 15cm,
stroke: 1pt,
inset: 1em,
)

View File

@@ -1,8 +1,8 @@
# type: ignore
# ruff: disable [F821]
df1: Frame[a:int, b:float]
df2: Frame[a:int, b:float]
df1: Frame[i:int, a:int, b:float]
df2: Frame[i:int, a:int, b:float]
_: Any
@@ -38,7 +38,7 @@ _ = df1.sum()
_ = df1.var()
# Groupby
df_gb = df1.groupby(by="a")
df_gb = df1.groupby(by="i")
_ = df_gb.kurt()
_ = df_gb.max()

File diff suppressed because it is too large Load Diff

View File

@@ -9,26 +9,22 @@ type Longitude = float where (-180 <= _ <= 180)
type Difference[T] = T
// Complex custom type, containing two values accessible through properties
type GeoLocation = {
type GeoLocation = object
extend GeoLocation {
prop lat: Latitude
prop lon: Longitude
}
// Define operations on our custom type
extend GeoLocation {
// This type is compatible with the `-` operation with another GeoLocation
// i.e. you can subtract a GeoLocation from another GeoLocation, resulting
// in a Difference of GeoLocations
def __sub__: fn(GeoLocation, /) -> Difference[GeoLocation]
}
type GeoLocationDifference = object
// For complex generics, you need to specify how the genericity the properties
// are handled
type Difference[GeoLocation] = {
extend GeoLocationDifference {
prop lat: Difference[Latitude]
prop lon: Difference[Longitude]
}
// Define operations on our custom type
// Simple operation defined on our custom types
extend Latitude {
def __sub__: fn(Latitude, /) -> Difference[Latitude]
@@ -38,13 +34,23 @@ extend Longitude {
def __sub__: fn(Longitude, /) -> Difference[Longitude]
}
extend GeoLocation {
// This type is compatible with the `-` operation with another GeoLocation
// i.e. you can subtract a GeoLocation from another GeoLocation, resulting
// in a GeoLocationDifference
def __sub__: fn(GeoLocation, /) -> GeoLocationDifference
}
// Predefined custom predicates that can be referenced in other definitions
predicate Positive(v: float) = v >= 0
predicate StrictlyPositive(v: float) = v > 0
predicate Equatorial(loc: GeoLocation) = (-10 <= loc.lat <= 10)
predicate Arctic(loc: GeoLocation) = (loc.lat >= 66)
type Person = {
type Person = object
extend Person {
prop name: str
// Property with an inline constraint

File diff suppressed because it is too large Load Diff

View File

@@ -4,11 +4,9 @@ from midas.ast.midas import (
AliasStmt,
BinaryExpr,
CallExpr,
ComplexType,
ConstraintType,
Expr,
ExtendStmt,
ExtensionType,
FrameType,
FunctionType,
GenericType,
@@ -172,12 +170,6 @@ class MidasAstJsonSerializer(
"constraint": type.constraint.accept(self),
}
def visit_complex_type(self, type: ComplexType) -> dict:
return {
"_type": "ComplexType",
"members": self._serialize_list(type.members),
}
def visit_function_type(self, type: FunctionType) -> dict:
return {
"_type": "FunctionType",
@@ -200,13 +192,6 @@ class MidasAstJsonSerializer(
"required": param.required,
}
def visit_extension_type(self, type: ExtensionType) -> dict:
return {
"_type": "ExtensionType",
"base": type.base.accept(self),
"extension": type.extension.accept(self),
}
def visit_frame_type(self, type: FrameType) -> dict:
return {
"_type": "FrameType",

View File

@@ -1,19 +1,16 @@
{
"brackets": [
["{", "}"],
["[", "]"],
["<", ">"]
["[", "]"]
],
"autoClosingPairs": [
{ "open": "{", "close": "}" },
{ "open": "[", "close": "]" },
{ "open": "(", "close": ")" },
{ "open": "<", "close": ">" }
{ "open": "(", "close": ")" }
],
"surroundingPairs": [
["{", "}"],
["[", "]"],
["(", ")"],
["<", ">"]
["(", ")"]
]
}

View File

@@ -10,7 +10,6 @@
{
"id": "midas",
"extensions": [
".mpy",
".midas"
],
"aliases": [
@@ -23,10 +22,7 @@
{
"language": "midas",
"scopeName": "source.midas",
"path": "./syntaxes/midas.tmLanguage.json",
"embeddedLanguages": {
"meta.embedded.block.python": "python"
}
"path": "./syntaxes/midas.tmLanguage.json"
}
]
}

View File

@@ -2,167 +2,558 @@
"$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json",
"name": "Midas",
"scopeName": "source.midas",
"patterns": [{ "include": "#statement" }],
"fileTypes": [
"midas"
],
"patterns": [
{
"include": "#comments"
},
{
"include": "#alias-stmt"
},
{
"include": "#type-stmt"
},
{
"include": "#extend-stmt"
},
{
"include": "#extend-body"
},
{
"include": "#predicate-stmt"
}
],
"repository": {
"comment": {
"begin": "(//)",
"end": "($)",
"name": "comment.line",
"beginCaptures": {
"1": {
"name": "comment.line.double-dash"
}
}
},
"type-def": {
"begin": "\\b(type)\\s+([a-zA-Z_][a-zA-Z_\\d]*)",
"end": "$",
"beginCaptures": {
"1": {
"name": "keyword.control.type.midas"
},
"2": {
"name" : "variable.name"
}
},
"comments": {
"patterns": [
{ "include": "#type-base" },
{ "include": "#type-body" }
{
"begin": "//",
"beginCaptures": {
"0": {
"name": "punctuation.definition.comment.midas"
}
},
"end": "$",
"name": "comment.line.double-slash.midas"
},
{
"begin": "/\\*",
"beginCaptures": {
"0": {
"name": "punctuation.definition.comment.midas"
}
},
"end": "\\*/",
"endCaptures": {
"0": {
"name": "punctuation.definition.comment.midas"
}
},
"name": "comment.block.midas"
}
]
},
"type-base": {
"begin": "(\\()([a-zA-Z_][a-zA-Z_\\d]*)(\\))",
"end": "$",
"beginCaptures": {
"1": {
"name": "punctuation.definition.base.begin.midas"
},
"2": {
"name": "variable.name"
},
"3": {
"name": "punctuation.definition.base.end.midas"
}
},
"patterns": [
{ "include": "#type-cond" }
]
},
"type-cond": {
"begin": "where",
"end": "$",
"string": {
"begin": "\"",
"beginCaptures": {
"0": {
"name": "keyword.control.where.midas"
"name": "punctuation.definition.string.begin.midas"
}
}
},
"end": "\"",
"endCaptures": {
"0": {
"name": "punctuation.definition.string.end.midas"
}
},
"name": "string.quoted.double.c"
},
"type-body": {
"alias-stmt": {
"begin": "\\b(alias)\\b",
"beginCaptures": {
"1": {
"name": "keyword.declaration.midas"
}
},
"end": "$",
"name": "meta.declaration.alias.midas",
"patterns": [
{
"include": "#comments"
},
{
"match": "[A-Za-z_][A-Za-z0-9_]*",
"name": "entity.name.type.midas"
},
{
"begin": "=",
"beginCaptures": {
"0": {
"name": "keyword.operator.equal.midas"
}
},
"end": "$",
"patterns": [
{
"include": "#type-expr"
}
]
}
]
},
"type-stmt": {
"begin": "\\b(type)\\b",
"beginCaptures": {
"1": {
"name": "keyword.declaration.midas"
}
},
"end": "$",
"name": "meta.declaration.type.midas",
"patterns": [
{
"include": "#comments"
},
{
"match": "[A-Za-z_][A-Za-z0-9_]*",
"name": "entity.name.type.midas"
},
{
"begin": "\\[",
"beginCaptures": {
"0": {
"name": "punctuation.section.brackets.begin.midas"
}
},
"end": "\\]",
"endCaptures": {
"0": {
"name": "punctuation.section.brackets.end.midas"
}
},
"patterns": [
{
"include": "#comments"
},
{
"include": "#type-params"
}
]
},
{
"begin": "=",
"beginCaptures": {
"0": {
"name": "keyword.operator.equal.midas"
}
},
"end": "$",
"patterns": [
{
"include": "#type-expr"
}
]
}
]
},
"type-params": {
"patterns": [
{
"include": "#comments"
},
{
"match": "<:",
"name": "keyword.operator.subtype.midas"
},
{
"match": "[A-Za-z][A-Za-z0-9_]*",
"name": "entity.name.type.midas"
}
]
},
"extend-stmt": {
"begin": "\\b(extend)\\b",
"beginCaptures": {
"1": {
"name": "keyword.declaration.midas"
}
},
"end": "(?=\\{)|$",
"name": "meta.declaration.extend.midas",
"patterns": [
{
"include": "#comments"
},
{
"match": "[A-Za-z_][A-Za-z0-9_]*",
"name": "entity.name.type.midas"
},
{
"begin": "\\[",
"beginCaptures": {
"0": {
"name": "punctuation.section.brackets.begin.midas"
}
},
"end": "\\]",
"endCaptures": {
"0": {
"name": "punctuation.section.brackets.end.midas"
}
},
"patterns": [
{
"include": "#comments"
},
{
"include": "#type-params"
}
]
}
]
},
"extend-body": {
"begin": "\\{",
"end": "\\}",
"beginCaptures": {
"0": {
"name": "punctuation.definition.type-body.begin.midas"
"name": "punctuation.section.block.begin.midas"
}
},
"end": "\\}",
"endCaptures": {
"0": {
"name": "punctuation.definition.type-body.end.midas"
"name": "punctuation.section.block.end.midas"
}
},
"patterns": [
{"include": "#type-prop"},
{"include": "#comment"}
{
"include": "#comments"
},
{
"include": "#member-stmt"
}
]
},
"type-prop": {
"match": "([a-zA-Z_][a-zA-Z_\\d]*)(:)\\s*([a-zA-Z_][a-zA-Z_\\d]*)",
"captures": {
"1": {
"name": "variable.name"
"member-stmt": {
"patterns": [
{
"include": "#comments"
},
"2": {
"name": "punctuation.separator.annotation.midas"
},
"3": {
"name": "meta.type.name"
{
"begin": "\\b(prop|def)\\b",
"beginCaptures": {
"1": {
"name": "keyword.other.midas"
}
},
"end": "$",
"patterns": [
{
"include": "#comments"
},
{
"match": "[A-Za-z_][A-Za-z0-9_]*",
"name": "variable.other.member.midas"
},
{
"begin": ":",
"beginCaptures": {
"0": {
"name": "punctuation.separator.annotation.midas"
}
},
"end": "$",
"patterns": [
{
"include": "#type-expr"
}
]
}
]
}
}
]
},
"extend-def": {
"begin": "\\b(extend)\\s*([a-zA-Z_][a-zA-Z_\\d]*)\\s+(\\{)",
"end": "\\}",
"predicate-stmt": {
"begin": "\\b(predicate)\\b",
"beginCaptures": {
"1": {
"name": "keyword.control.extend.midas"
},
"2": {
"name": "variable.name"
},
"3": {
"name": "punctuation.definition.extend-body.begin.midas"
"name": "keyword.declaration.midas"
}
},
"endCaptures": {
"0": {
"name": "punctuation.definition.extend-body.end.midas"
}
},
"patterns": [
{"include": "#op-def"},
{"include": "#comment"}
]
},
"op-def": {
"match": "\\b(op)\\s+(\\S+)\\s*\\(\\s*([a-zA-Z_][a-zA-Z_\\d]*)\\s*\\)\\s*(->)\\s*([a-zA-Z_][a-zA-Z_\\d]*)",
"captures": {
"1": {
"name": "keyword.control.op.midas"
},
"2": {
"name" : "keyword.operator"
},
"3": {
"name" : "variable.name"
},
"4": {
"name" : "keyword.operator.assignment"
},
"5": {
"name" : "variable.name"
}
}
},
"pred-def": {
"begin": "(predicate)\\s+([a-zA-Z_][a-zA-Z_\\d]*)\\(([a-zA-Z_][a-zA-Z_\\d]*):\\s*([a-zA-Z_][a-zA-Z_\\d]*)\\)\\s*(=)",
"end": "$",
"beginCaptures": {
"1": {
"name": "keyword.control.pred.midas"
},
"2": {
"name": "variable.name"
},
"3": {
"name": "variable.name"
},
"4": {
"name": "variable.name"
},
"5": {
"name": "keyword.operator.assignment"
}
},
"name": "meta.declaration.predicate.midas",
"patterns": [
{ "include": "source.python" }
{
"include": "#comments"
},
{
"match": "[A-Za-z_][A-Za-z0-9_]*",
"name": "entity.name.function.midas"
},
{
"begin": "\\(",
"beginCaptures": {
"0": {
"name": "punctuation.section.group.begin.midas"
}
},
"end": "\\)",
"endCaptures": {
"0": {
"name": "punctuation.section.group.end.midas"
}
},
"patterns": [
{
"include": "#predicate-params"
}
]
},
{
"begin": "=",
"beginCaptures": {
"0": {
"name": "keyword.operator.equal.midas"
}
},
"end": "$",
"patterns": [
{
"include": "#constraint"
}
]
}
]
},
"statement": {
"predicate-params": {
"patterns": [
{ "include": "#comment" },
{ "include": "#type-def" },
{ "include": "#extend-def" },
{ "include": "#pred-def" }
{
"include": "#comments"
},
{
"begin": "([A-Za-z_][A-Za-z0-9_]*)(:)",
"beginCaptures": {
"1": {
"name": "variable.parameter.midas"
},
"2": {
"name": "punctuation.separator.annotation.midas"
}
},
"end": "(?=,)|(?=\\))",
"patterns": [
{
"include": "#type-expr"
}
]
},
{
"match": ",",
"name": "punctuation.separator.midas"
}
]
},
"type-expr": {
"patterns": [
{
"include": "#comments"
},
{
"begin": "\\b(fn)\\s*(\\()",
"beginCaptures": {
"1": {
"name": "keyword.other.midas"
},
"2": {
"name": "punctuation.section.group.begin.midas"
}
},
"end": "\\)",
"endCaptures": {
"0": {
"name": "punctuation.section.group.end.midas"
}
},
"patterns": [
{
"include": "#fn-params"
}
]
},
{
"match": "->",
"name": "keyword.operator.arrow.midas"
},
{
"begin": "\\b(where)\\b",
"beginCaptures": {
"1": {
"name": "keyword.other.midas"
}
},
"end": "$",
"patterns": [
{
"include": "#constraint"
}
]
},
{
"begin": "(\\bFrame\\b)(\\s*)(\\[)",
"beginCaptures": {
"1": {
"name": "entity.name.type.midas"
},
"3": {
"name": "punctuation.section.brackets.begin.midas"
}
},
"end": "\\]",
"endCaptures": {
"0": {
"name": "punctuation.section.brackets.end.midas"
}
},
"patterns": [
{
"include": "#frame-schema"
}
]
},
{
"match": "\\bFrame\\b",
"name": "entity.name.type.midas"
},
{
"match": "[A-Za-z_][A-Za-z0-9_]*",
"name": "entity.name.type.midas"
},
{
"begin": "\\[",
"beginCaptures": {
"0": {
"name": "punctuation.section.brackets.begin.midas"
}
},
"end": "\\]",
"endCaptures": {
"0": {
"name": "punctuation.section.brackets.end.midas"
}
},
"patterns": [
{
"include": "#comments"
},
{
"include": "#type-expr"
},
{
"match": ",",
"name": "punctuation.separator.midas"
}
]
}
]
},
"fn-params": {
"patterns": [
{
"include": "#comments"
},
{
"begin": "([A-Za-z_][A-Za-z0-9_]*)(:)",
"beginCaptures": {
"1": {
"name": "variable.parameter.midas"
},
"2": {
"name": "punctuation.separator.annotation.midas"
}
},
"end": "(?=,)|(?=\\))",
"patterns": [
{
"include": "#type-expr"
},
{
"match": "\\?",
"name": "keyword.operator.qmark.midas"
}
]
},
{
"match": ",",
"name": "punctuation.separator.midas"
},
{
"include": "#type-expr"
}
]
},
"constraint": {
"patterns": [
{
"include": "#comments"
},
{
"match": "\\d+(\\.\\d+)?",
"name": "constant.numeric.midas"
},
{
"match": "\\b(true|false|none)\\b",
"name": "constant.language.midas"
},
{
"include": "#string"
},
{
"match": "(<=|>=|<|>|==|!=|&)",
"name": "keyword.operator.midas"
},
{
"match": "_",
"name": "variable.language.midas"
},
{
"match": "[A-Za-z_][A-Za-z0-9_]*(?=\\s*\\()",
"name": "variable.function.midas"
},
{
"match": "[A-Za-z_][A-Za-z0-9_]*",
"name": "variable.other.readwrite.midas"
}
]
},
"frame-schema": {
"patterns": [
{
"include": "#comments"
},
{
"match": "[A-Za-z_][A-Za-z0-9_]*",
"name": "variable.other.member.midas"
},
{
"begin": ":",
"beginCaptures": {
"0": {
"name": "punctuation.separator.annotation.midas"
}
},
"end": "(?=,)|(?=\\])",
"patterns": [
{
"include": "#type-expr"
}
]
},
{
"match": ",",
"name": "punctuation.separator.midas"
}
]
}
}