plum

#treesitter#compiler#wasm

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),
}