plum
git clone https://git.pyrossh.dev/plum
A statically typed, imperative programming language inspired by rust, python
plum-core/src/ast.rs
#[derive(Debug, Clone, PartialEq)]
pub struct Source {
pub module: Option<Module>,
pub imports: Vec<Import>,
pub items: Vec<Item>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Module {
pub name: String,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Import {
pub path: String,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Item {
Class(Class),
Trait(Trait),
Enum(Enum),
Fn(Fn),
Const(Const),
}
// ---------- Type definitions ----------
#[derive(Debug, Clone, PartialEq)]
pub struct Class {
pub name: String,
pub implements: Vec<String>,
pub generics: Vec<GenericParam>,
pub fields: Vec<Field>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct GenericParam {
pub name: String,
pub bounds: Vec<String>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Field {
pub name: String,
pub ty: Type,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Trait {
pub name: String,
pub generics: Vec<GenericParam>,
pub methods: Vec<TraitMethod>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct TraitMethod {
pub name: String,
pub params: Vec<Param>,
pub returns: Option<Type>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Enum {
pub name: String,
pub params: Vec<EnumParam>,
pub variants: Vec<EnumVariant>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct EnumParam {
pub name: String,
pub ty: Type,
}
#[derive(Debug, Clone, PartialEq)]
pub struct EnumVariant {
pub name: String,
pub fields: Vec<String>,
pub values: Vec<Expr>,
}
// ---------- Functions ----------
#[derive(Debug, Clone, PartialEq)]
pub struct Fn {
pub name: String,
/// Type parameter for method dispatch, e.g. `<Cat>` in `toStr<Cat>()`
pub type_param: Option<String>,
/// `extern fun foo(...)` — declares a function with no Plum body, backed
/// instead by a host-provided wasm import (see `libs/std/os.plum`'s
/// `printLn`). `plum-checker` requires this to agree with `body` being
/// `FnBody::Extern`, and requires `type_param` to be `None` (no extern methods).
pub is_extern: bool,
pub params: Vec<Param>,
pub returns: Option<Type>,
pub body: FnBody,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Const {
pub name: String,
pub value: Expr,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Param {
pub name: String,
pub ty: ParamType,
pub default: Option<Expr>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ParamType {
Type(Type),
Variadic(Type),
/// `fn(Int, Str) -> Bool` — a function-value type annotation. Positional types
/// only, no param names (types don't need names).
Fn(Vec<Type>, Option<Box<Type>>),
}
#[derive(Debug, Clone, PartialEq)]
pub struct Type {
pub name: String,
pub generics: Vec<Type>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum FnBody {
Expr(Expr),
Block(Block),
/// No body was written at all — valid only paired with `Fn.is_extern`
/// (`plum-checker` rejects any other combination).
Extern,
}
// ---------- Statements ----------
#[derive(Debug, Clone, PartialEq)]
pub struct Block {
pub stmts: Vec<Stmt>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Closure {
pub params: Vec<String>,
pub body: Block,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Stmt {
Assign(Assign),
Break,
Continue,
Assert(Expr),
For(For),
While(While),
If(If),
Match(Match),
Return(Option<Expr>),
Todo,
Expr(Expr),
}
#[derive(Debug, Clone, PartialEq)]
pub enum AssignTarget {
Var(String),
/// `object.field = value` — `object`'s evaluated type must be a class; `field`
/// is that class's field name being written.
Field(Box<Expr>, String),
}
#[derive(Debug, Clone, PartialEq)]
pub struct Assign {
pub targets: Vec<AssignTarget>,
pub values: Vec<Expr>,
/// `true` for `x := 5` (declares `x`, inferring its type from `value`);
/// `false` for `x = 5` (requires `x` already declared, with a type matching
/// `value`'s — a compile error otherwise, like Go's `:=`/`=` distinction).
pub declare: bool,
}
#[derive(Debug, Clone, PartialEq)]
pub struct For {
pub vars: Vec<String>,
pub iter: Expr,
pub body: Block,
}
#[derive(Debug, Clone, PartialEq)]
pub struct While {
pub condition: Expr,
pub body: Block,
}
#[derive(Debug, Clone, PartialEq)]
pub struct If {
pub condition: Expr,
pub body: Block,
pub else_ifs: Vec<ElseIf>,
pub else_: Option<Block>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct ElseIf {
pub condition: Expr,
pub body: Block,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Match {
pub subjects: Vec<Expr>,
pub cases: Vec<Case>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Case {
pub patterns: Vec<CasePattern>,
pub body: Block,
}
#[derive(Debug, Clone, PartialEq)]
pub enum CasePattern {
Class { name: String, fields: Vec<CasePattern> },
String(String),
Int(i64),
Float(f64),
Name(String),
Wildcard,
}
// ---------- Expressions ----------
/// Expressions are the core of the language. Operator precedence is already
/// resolved by the tree-sitter parser (via the PREC table in grammar.js), so
/// the tree structure here directly reflects evaluation order.
#[derive(Debug, Clone, PartialEq)]
pub enum Expr {
/// Arithmetic / bitwise / range: `a + b`, `a .. b`, etc.
Binary(Box<BinaryExpr>),
/// Prefix `+` or `-`
Unary(Box<UnaryExpr>),
/// `&&` / `||`
Bool(Box<BoolExpr>),
/// `!expr`
Not(Box<Expr>),
/// `<`, `<=`, `==`, `!=`, `>=`, `>`, `<>`
Compare(Box<CompareExpr>),
/// `cond ? then : else`
Ternary(Box<TernaryExpr>),
/// `fnName(args…)`
FnCall(FnCall),
/// `TypeName(field: value, …)`
ClassCall(ClassCall),
/// `expr.field` or `expr.method(…)`
Attribute(Box<AttributeExpr>),
/// `{expr}` — grouped/parenthesized expression
Paren(Box<Expr>),
String(StringExpr),
Int(i64),
Float(f64),
Self_,
Var(String),
TypeName(String),
/// `|params| body`
Closure(Box<Closure>),
}
#[derive(Debug, Clone, PartialEq)]
pub struct BinaryExpr {
pub op: BinOp,
pub left: Expr,
pub right: Expr,
}
#[derive(Debug, Clone, PartialEq)]
pub enum BinOp {
Add,
Sub,
Mul,
Div,
Mod,
BitOr,
BitAnd,
Xor,
Shl,
Shr,
}
#[derive(Debug, Clone, PartialEq)]
pub struct UnaryExpr {
pub op: UnOp,
pub operand: Expr,
}
#[derive(Debug, Clone, PartialEq)]
pub enum UnOp {
Pos,
Neg,
}
#[derive(Debug, Clone, PartialEq)]
pub struct BoolExpr {
pub op: BoolOp,
pub left: Expr,
pub right: Expr,
}
#[derive(Debug, Clone, PartialEq)]
pub enum BoolOp {
And,
Or,
}
#[derive(Debug, Clone, PartialEq)]
pub struct CompareExpr {
pub op: CmpOp,
pub left: Expr,
pub right: Expr,
}
#[derive(Debug, Clone, PartialEq)]
pub enum CmpOp {
Lt,
Lte,
Eq,
Neq,
Gte,
Gt,
NotEq2,
}
#[derive(Debug, Clone, PartialEq)]
pub struct TernaryExpr {
pub condition: Expr,
pub then: Expr,
pub else_: Expr,
}
#[derive(Debug, Clone, PartialEq)]
pub struct FnCall {
pub name: String,
pub args: Vec<Arg>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Arg {
Positional(Expr),
Keyword { name: String, value: Expr },
Pair { key: String, value: Expr },
}
#[derive(Debug, Clone, PartialEq)]
pub struct ClassCall {
pub type_name: String,
pub fields: Vec<FieldArg>,
/// Explicit type arguments (`List[Int](...)`) — empty unless the source
/// actually wrote a `[...]` generics clause. Needed whenever a generic
/// class's own field VALUES can't pin down its type params on their own
/// (e.g. constructing an empty `List` with nothing to infer `T` from).
pub generics: Vec<Type>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct FieldArg {
pub name: String,
pub value: Expr,
}
#[derive(Debug, Clone, PartialEq)]
pub struct AttributeExpr {
pub object: Expr,
pub attr: AttrKind,
}
#[derive(Debug, Clone, PartialEq)]
pub enum AttrKind {
Field(String),
Method(FnCall),
}
#[derive(Debug, Clone, PartialEq)]
pub struct StringExpr {
pub parts: Vec<StringPart>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum StringPart {
Text(String),
Interp(Expr),
}