use crate::span::Span;
pub type Ast = Vec<Item>;
#[derive(Debug, Clone, PartialEq)]
pub enum Item {
Fn(FnDecl),
Struct(StructDecl),
Enum(EnumDecl),
Interface(InterfaceDecl),
}
impl Item {
pub fn span(&self) -> Span {
match self {
Item::Fn(d) => d.span,
Item::Struct(d) => d.span,
Item::Enum(d) => d.span,
Item::Interface(d) => d.span,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct FnDecl {
pub type_name: Option<String>,
pub name: String,
pub params: Vec<Param>,
pub ret_ty: Option<TypeExpr>,
pub effect: Option<Effect>,
pub body: Block,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Param {
pub is_self: bool,
pub name: String,
pub ty: Option<TypeExpr>,
pub default: Option<Expr>,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Effect {
Pure,
Io,
Alloc,
Panic,
}
#[derive(Debug, Clone, PartialEq)]
pub struct StructDecl {
pub name: String,
pub fields: Vec<Field>,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Field {
pub name: String,
pub ty: TypeExpr,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub struct EnumDecl {
pub name: String,
pub variants: Vec<Variant>,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Variant {
pub name: String,
pub fields: Vec<TypeExpr>,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub struct InterfaceDecl {
pub name: String,
pub methods: Vec<MethodSig>,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub struct MethodSig {
pub name: String,
pub params: Vec<Param>,
pub ret_ty: Option<TypeExpr>,
pub effect: Option<Effect>,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Block {
pub stmts: Vec<Stmt>,
pub value: Option<Box<Expr>>,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Stmt {
Let {
is_mut: bool,
name: String,
ty: Option<TypeExpr>,
init: Expr,
span: Span,
},
If {
cond: Expr,
then_block: Block,
else_branch: Option<ElseBranch>,
span: Span,
},
While { cond: Expr, body: Block, span: Span },
For {
var: String,
iter: Expr,
body: Block,
span: Span,
},
Return { value: Option<Expr>, span: Span },
Break { span: Span },
Continue { span: Span },
Defer { expr: Expr, span: Span },
Expr { expr: Expr, span: Span },
}
impl Stmt {
pub fn span(&self) -> Span {
match self {
Stmt::Let { span, .. }
| Stmt::If { span, .. }
| Stmt::While { span, .. }
| Stmt::For { span, .. }
| Stmt::Return { span, .. }
| Stmt::Break { span }
| Stmt::Continue { span }
| Stmt::Defer { span, .. }
| Stmt::Expr { span, .. } => *span,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum ElseBranch {
Block(Block),
If(Box<Stmt>),
}
#[derive(Debug, Clone, PartialEq)]
pub enum Expr {
Int { value: i64, span: Span },
Float { value: f64, span: Span },
Byte { value: u8, span: Span },
Str { value: String, span: Span },
Bool { value: bool, span: Span },
Ident { name: String, span: Span },
Paren { inner: Box<Expr>, span: Span },
Tuple { elems: Vec<Expr>, span: Span },
ArrayLit { elems: Vec<Expr>, span: Span },
ArrayRepeat {
value: Box<Expr>,
count: Box<Expr>,
span: Span,
},
StructLit {
name: String,
fields: Vec<FieldInit>,
span: Span,
},
FieldAccess {
obj: Box<Expr>,
name: String,
span: Span,
},
MethodCall {
receiver: Box<Expr>,
name: String,
args: Vec<Expr>,
span: Span,
},
Call {
callee: Box<Expr>,
args: Vec<Expr>,
span: Span,
},
Index {
obj: Box<Expr>,
index: Box<Expr>,
span: Span,
},
Try { expr: Box<Expr>, span: Span },
Unary {
op: UnaryOp,
operand: Box<Expr>,
span: Span,
},
Binary {
op: BinOp,
lhs: Box<Expr>,
rhs: Box<Expr>,
span: Span,
},
Range {
start: Option<Box<Expr>>,
end: Option<Box<Expr>>,
inclusive: bool,
span: Span,
},
Pipeline {
lhs: Box<Expr>,
call: Box<Expr>,
span: Span,
},
Comptime { body: Box<Expr>, span: Span },
Block { block: Block, span: Span },
Match {
scrutinee: Box<Expr>,
arms: Vec<MatchArm>,
span: Span,
},
OrElse {
expr: Box<Expr>,
fallback: Box<Expr>,
span: Span,
},
Interpolation { parts: Vec<InterpPart>, span: Span },
}
impl Expr {
pub fn span(&self) -> Span {
match self {
Expr::Int { span, .. }
| Expr::Float { span, .. }
| Expr::Byte { span, .. }
| Expr::Str { span, .. }
| Expr::Bool { span, .. }
| Expr::Ident { span, .. }
| Expr::Paren { span, .. }
| Expr::Tuple { span, .. }
| Expr::ArrayLit { span, .. }
| Expr::ArrayRepeat { span, .. }
| Expr::StructLit { span, .. }
| Expr::FieldAccess { span, .. }
| Expr::MethodCall { span, .. }
| Expr::Call { span, .. }
| Expr::Index { span, .. }
| Expr::Try { span, .. }
| Expr::Unary { span, .. }
| Expr::Binary { span, .. }
| Expr::Range { span, .. }
| Expr::Pipeline { span, .. }
| Expr::Comptime { span, .. }
| Expr::Block { span, .. }
| Expr::Match { span, .. }
| Expr::OrElse { span, .. }
| Expr::Interpolation { span, .. } => *span,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct FieldInit {
pub name: String,
pub value: Expr,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub enum UnaryOp {
Not,
Neg,
}
#[derive(Debug, Clone, PartialEq)]
pub enum BinOp {
Add,
Sub,
Mul,
Div,
Rem,
Eq,
Ne,
Lt,
Le,
Gt,
Ge,
And,
Or,
}
#[derive(Debug, Clone, PartialEq)]
pub enum InterpPart {
Literal(String),
Expr(Expr),
}
#[derive(Debug, Clone, PartialEq)]
pub enum Pattern {
Variant {
name: String,
sub: Vec<Pattern>,
span: Span,
},
Wildcard { span: Span },
Binding { name: String, span: Span },
Int { value: i64, span: Span },
Float { value: f64, span: Span },
Byte { value: u8, span: Span },
Str { value: String, span: Span },
Bool { value: bool, span: Span },
}
impl Pattern {
pub fn span(&self) -> Span {
match self {
Pattern::Variant { span, .. }
| Pattern::Wildcard { span }
| Pattern::Binding { span, .. }
| Pattern::Int { span, .. }
| Pattern::Float { span, .. }
| Pattern::Byte { span, .. }
| Pattern::Str { span, .. }
| Pattern::Bool { span, .. } => *span,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct MatchArm {
pub pattern: Pattern,
pub guard: Option<Expr>,
pub body: MatchArmBody,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub enum MatchArmBody {
Expr(Box<Expr>),
Block(Block),
}
#[derive(Debug, Clone, PartialEq)]
pub enum TypeExpr {
Primitive { kind: PrimType, span: Span },
Named { name: String, span: Span },
Array {
elem: Box<TypeExpr>,
size: u64,
span: Span,
},
DynArray { elem: Box<TypeExpr>, span: Span },
Tuple { elems: Vec<TypeExpr>, span: Span },
Fn {
params: Vec<TypeExpr>,
ret: Box<TypeExpr>,
span: Span,
},
Generic {
name: String,
args: Vec<TypeExpr>,
span: Span,
},
}
impl TypeExpr {
pub fn span(&self) -> Span {
match self {
TypeExpr::Primitive { span, .. }
| TypeExpr::Named { span, .. }
| TypeExpr::Array { span, .. }
| TypeExpr::DynArray { span, .. }
| TypeExpr::Tuple { span, .. }
| TypeExpr::Fn { span, .. }
| TypeExpr::Generic { span, .. } => *span,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum PrimType {
I64,
F64,
Bool,
Str,
Byte,
Void,
}
#[cfg(test)]
mod tests {
use super::*;
fn span(n: usize) -> Span {
Span::new(n, n + 1)
}
#[test]
fn nodes_are_debug_clone_and_partial_eq() {
let a = Expr::Binary {
op: BinOp::Add,
lhs: Box::new(Expr::Int {
value: 1,
span: span(0),
}),
rhs: Box::new(Expr::Int {
value: 2,
span: span(2),
}),
span: span(0),
};
let b = a.clone();
assert_eq!(a, b);
let _ = format!("{a:?}");
let c = Expr::Binary {
op: BinOp::Mul, lhs: Box::new(Expr::Int {
value: 1,
span: span(0),
}),
rhs: Box::new(Expr::Int {
value: 2,
span: span(2),
}),
span: span(0),
};
assert_ne!(a, c);
let f = Expr::Float {
value: 3.14,
span: span(5),
};
assert_eq!(f.clone(), f);
}
#[test]
fn expr_span_returns_the_stored_span_for_a_sample_of_variants() {
let cases: Vec<(Expr, Span)> = vec![
(
Expr::Int {
value: 0,
span: span(1),
},
span(1),
),
(
Expr::Float {
value: 0.0,
span: span(2),
},
span(2),
),
(
Expr::Byte {
value: 0,
span: span(3),
},
span(3),
),
(
Expr::Str {
value: String::new(),
span: span(4),
},
span(4),
),
(
Expr::Bool {
value: true,
span: span(5),
},
span(5),
),
(
Expr::Ident {
name: "x".to_string(),
span: span(6),
},
span(6),
),
(
Expr::Unary {
op: UnaryOp::Neg,
operand: Box::new(Expr::Int {
value: 1,
span: span(8),
}),
span: span(7),
},
span(7),
),
(
Expr::MethodCall {
receiver: Box::new(Expr::Ident {
name: "f".to_string(),
span: span(10),
}),
name: "read_all".to_string(),
args: vec![],
span: span(9),
},
span(9),
),
(
Expr::Pipeline {
lhs: Box::new(Expr::Int {
value: 5,
span: span(12),
}),
call: Box::new(Expr::Ident {
name: "double".to_string(),
span: span(14),
}),
span: span(11),
},
span(11),
),
(
Expr::OrElse {
expr: Box::new(Expr::Ident {
name: "a".to_string(),
span: span(16),
}),
fallback: Box::new(Expr::Str {
value: "no data".to_string(),
span: span(18),
}),
span: span(15),
},
span(15),
),
(
Expr::Interpolation {
parts: vec![
InterpPart::Literal("fib(".to_string()),
InterpPart::Expr(Expr::Ident {
name: "i".to_string(),
span: span(20),
}),
InterpPart::Literal(")".to_string()),
],
span: span(19),
},
span(19),
),
];
for (expr, expected) in cases {
assert_eq!(expr.span(), expected, "span() mismatch for {expr:?}");
}
}
#[test]
fn stmt_span_returns_the_stored_span_for_a_sample_of_variants() {
let cases: Vec<(Stmt, Span)> = vec![
(
Stmt::Let {
is_mut: false,
name: "x".to_string(),
ty: None,
init: Expr::Int {
value: 1,
span: span(1),
},
span: span(0),
},
span(0),
),
(Stmt::Break { span: span(2) }, span(2)),
(Stmt::Continue { span: span(3) }, span(3)),
(
Stmt::Return {
value: None,
span: span(4),
},
span(4),
),
(
Stmt::For {
var: "i".to_string(),
iter: Expr::Range {
start: Some(Box::new(Expr::Int {
value: 0,
span: span(6),
})),
end: Some(Box::new(Expr::Int {
value: 15,
span: span(8),
})),
inclusive: false,
span: span(6),
},
body: Block {
stmts: vec![],
value: None,
span: span(9),
},
span: span(5),
},
span(5),
),
];
for (stmt, expected) in cases {
assert_eq!(stmt.span(), expected, "span() mismatch for {stmt:?}");
}
}
#[test]
fn item_span_delegates_to_the_wrapped_decl() {
let f = Item::Fn(FnDecl {
type_name: None,
name: "main".to_string(),
params: vec![],
ret_ty: None,
effect: Some(Effect::Io),
body: Block {
stmts: vec![],
value: None,
span: span(1),
},
span: span(0),
});
assert_eq!(f.span(), span(0));
let s = Item::Struct(StructDecl {
name: "S".to_string(),
fields: vec![],
span: span(2),
});
assert_eq!(s.span(), span(2));
let e = Item::Enum(EnumDecl {
name: "E".to_string(),
variants: vec![],
span: span(3),
});
assert_eq!(e.span(), span(3));
let i = Item::Interface(InterfaceDecl {
name: "I".to_string(),
methods: vec![],
span: span(4),
});
assert_eq!(i.span(), span(4));
}
#[test]
fn pattern_span_returns_the_stored_span_for_every_variant() {
let cases: Vec<(Pattern, Span)> = vec![
(
Pattern::Variant {
name: "Circle".to_string(),
sub: vec![Pattern::Binding {
name: "r".to_string(),
span: span(1),
}],
span: span(0),
},
span(0),
),
(Pattern::Wildcard { span: span(2) }, span(2)),
(
Pattern::Binding {
name: "v".to_string(),
span: span(3),
},
span(3),
),
(
Pattern::Int {
value: 0,
span: span(4),
},
span(4),
),
(
Pattern::Float {
value: 0.0,
span: span(5),
},
span(5),
),
(
Pattern::Byte {
value: 0,
span: span(6),
},
span(6),
),
(
Pattern::Str {
value: String::new(),
span: span(7),
},
span(7),
),
(
Pattern::Bool {
value: false,
span: span(8),
},
span(8),
),
];
for (pat, expected) in cases {
assert_eq!(pat.span(), expected, "span() mismatch for {pat:?}");
}
}
#[test]
fn type_expr_span_returns_the_stored_span_for_every_variant() {
let cases: Vec<(TypeExpr, Span)> = vec![
(
TypeExpr::Primitive {
kind: PrimType::I64,
span: span(1),
},
span(1),
),
(
TypeExpr::Named {
name: "Shape".to_string(),
span: span(2),
},
span(2),
),
(
TypeExpr::Array {
elem: Box::new(TypeExpr::Primitive {
kind: PrimType::I64,
span: span(4),
}),
size: 5,
span: span(3),
},
span(3),
),
(
TypeExpr::DynArray {
elem: Box::new(TypeExpr::Primitive {
kind: PrimType::I64,
span: span(6),
}),
span: span(5),
},
span(5),
),
(
TypeExpr::Tuple {
elems: vec![
TypeExpr::Primitive {
kind: PrimType::I64,
span: span(8),
},
TypeExpr::Primitive {
kind: PrimType::Bool,
span: span(9),
},
],
span: span(7),
},
span(7),
),
(
TypeExpr::Fn {
params: vec![TypeExpr::Primitive {
kind: PrimType::I64,
span: span(11),
}],
ret: Box::new(TypeExpr::Primitive {
kind: PrimType::I64,
span: span(12),
}),
span: span(10),
},
span(10),
),
(
TypeExpr::Generic {
name: "Result".to_string(),
args: vec![
TypeExpr::Primitive {
kind: PrimType::Str,
span: span(14),
},
TypeExpr::Primitive {
kind: PrimType::Str,
span: span(15),
},
],
span: span(13),
},
span(13),
),
];
for (ty, expected) in cases {
assert_eq!(ty.span(), expected, "span() mismatch for {ty:?}");
}
}
#[test]
fn a_match_arm_holds_a_pattern_an_optional_guard_and_a_body() {
let arm = MatchArm {
pattern: Pattern::Binding {
name: "v".to_string(),
span: span(0),
},
guard: Some(Expr::Binary {
op: BinOp::Gt,
lhs: Box::new(Expr::Ident {
name: "v".to_string(),
span: span(2),
}),
rhs: Box::new(Expr::Int {
value: 0,
span: span(4),
}),
span: span(2),
}),
body: MatchArmBody::Expr(Box::new(Expr::Str {
value: "positive".to_string(),
span: span(6),
})),
span: span(0),
};
assert!(arm.guard.is_some());
let arm2 = MatchArm {
pattern: Pattern::Wildcard { span: span(10) },
guard: None,
body: MatchArmBody::Block(Block {
stmts: vec![],
value: None,
span: span(12),
}),
span: span(10),
};
assert!(arm2.guard.is_none());
assert_ne!(arm, arm2);
}
#[test]
fn a_block_separates_statements_from_an_optional_trailing_value() {
let block = Block {
stmts: vec![Stmt::Let {
is_mut: false,
name: "x".to_string(),
ty: None,
init: Expr::Int {
value: 1,
span: span(1),
},
span: span(0),
}],
value: Some(Box::new(Expr::Ident {
name: "x".to_string(),
span: span(3),
})),
span: span(0),
};
assert_eq!(block.stmts.len(), 1);
assert!(block.value.is_some());
let empty = Block {
stmts: vec![],
value: None,
span: span(5),
};
assert!(empty.stmts.is_empty());
assert!(empty.value.is_none());
}
}