pub use crate::lexer::Pos;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Span {
pub start: usize,
pub end: usize,
}
impl Span {
pub const fn new(start: usize, end: usize) -> Self {
Self { start, end }
}
pub const fn is_valid(&self) -> bool {
self.start <= self.end
}
pub const fn is_empty(&self) -> bool {
self.start == self.end
}
pub const fn contains(&self, other: &Self) -> bool {
self.is_valid() && other.is_valid() && self.start <= other.start && other.end <= self.end
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum LitKind {
Int,
Decimal,
Text,
Char,
}
#[derive(Debug, Clone)]
pub struct FieldAssign {
pub name: String,
pub value: Option<Expr>,
pub pos: Pos,
pub span: Span,
}
#[derive(Debug, Clone)]
pub struct Alt {
pub pat: Pat,
pub body: Expr,
pub pos: Pos,
pub span: Span,
}
#[derive(Debug, Clone)]
pub struct Binding {
pub pat: Pat,
pub params: Vec<Pat>,
pub expr: Expr,
pub pos: Pos,
pub span: Span,
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum Pat {
Var {
name: String,
pos: Pos,
span: Span,
},
Wild {
pos: Pos,
span: Span,
},
Con {
qualifier: Option<String>,
name: String,
args: Vec<Self>,
pos: Pos,
span: Span,
},
Tuple {
items: Vec<Self>,
pos: Pos,
span: Span,
},
List {
items: Vec<Self>,
pos: Pos,
span: Span,
},
Lit {
kind: LitKind,
text: String,
pos: Pos,
span: Span,
},
As {
name: String,
pat: Box<Self>,
pos: Pos,
span: Span,
},
Other {
raw: String,
pos: Pos,
span: Span,
},
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum Expr {
Var {
qualifier: Option<String>,
name: String,
pos: Pos,
span: Span,
},
Con {
qualifier: Option<String>,
name: String,
pos: Pos,
span: Span,
},
Lit {
kind: LitKind,
text: String,
pos: Pos,
span: Span,
},
App {
func: Box<Self>,
args: Vec<Self>,
pos: Pos,
span: Span,
},
BinOp {
op: String,
lhs: Box<Self>,
rhs: Box<Self>,
pos: Pos,
span: Span,
},
Neg {
expr: Box<Self>,
pos: Pos,
span: Span,
},
Lambda {
params: Vec<Pat>,
body: Box<Self>,
pos: Pos,
span: Span,
},
If {
cond: Box<Self>,
then_branch: Box<Self>,
else_branch: Box<Self>,
pos: Pos,
span: Span,
},
Case {
scrutinee: Box<Self>,
alts: Vec<Alt>,
pos: Pos,
span: Span,
},
Do {
stmts: Vec<DoStmt>,
pos: Pos,
span: Span,
},
LetIn {
bindings: Vec<Binding>,
body: Box<Self>,
pos: Pos,
span: Span,
},
Record {
base: Box<Self>,
fields: Vec<FieldAssign>,
pos: Pos,
span: Span,
},
Tuple {
items: Vec<Self>,
pos: Pos,
span: Span,
},
List {
items: Vec<Self>,
pos: Pos,
span: Span,
},
Try {
body: Box<Self>,
handlers: Vec<Alt>,
pos: Pos,
span: Span,
},
Section {
op: String,
operand: Option<Box<Self>>,
left: bool,
pos: Pos,
span: Span,
},
Error { raw: String, pos: Pos, span: Span },
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum DoStmt {
Bind {
pat: Pat,
expr: Expr,
pos: Pos,
span: Span,
},
Let {
bindings: Vec<Binding>,
pos: Pos,
span: Span,
},
Expr { expr: Expr, pos: Pos, span: Span },
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum Type {
Con {
qualifier: Option<String>,
name: String,
span: Span,
},
App(Box<Self>, Vec<Self>, Span),
List(Box<Self>, Span),
Tuple(Vec<Self>, Span),
Fun(Box<Self>, Box<Self>, Span),
Var(String, Span),
Unit(Span),
Constrained(Box<Self>, Span),
}
impl Type {
pub const fn span(&self) -> Span {
match self {
Self::Con { span, .. }
| Self::App(_, _, span)
| Self::List(_, span)
| Self::Tuple(_, span)
| Self::Fun(_, _, span)
| Self::Var(_, span)
| Self::Unit(span)
| Self::Constrained(_, span) => *span,
}
}
pub(crate) const fn with_span(mut self, span: Span) -> Self {
match &mut self {
Self::Con { span: s, .. }
| Self::App(_, _, s)
| Self::List(_, s)
| Self::Tuple(_, s)
| Self::Fun(_, _, s)
| Self::Var(_, s)
| Self::Unit(s)
| Self::Constrained(_, s) => *s = span,
}
self
}
}
impl PartialEq for Type {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(
Self::Con {
qualifier: aq,
name: an,
..
},
Self::Con {
qualifier: bq,
name: bn,
..
},
) => aq == bq && an == bn,
(Self::App(ah, aa, _), Self::App(bh, ba, _)) => ah == bh && aa == ba,
(Self::List(a, _), Self::List(b, _))
| (Self::Constrained(a, _), Self::Constrained(b, _)) => a == b,
(Self::Tuple(a, _), Self::Tuple(b, _)) => a == b,
(Self::Fun(al, ar, _), Self::Fun(bl, br, _)) => al == bl && ar == br,
(Self::Var(a, _), Self::Var(b, _)) => a == b,
(Self::Unit(_), Self::Unit(_)) => true,
_ => false,
}
}
}
impl Eq for Type {}
#[derive(Debug, Clone)]
pub struct FieldDecl {
pub name: String,
pub ty: Option<Type>,
pub pos: Pos,
pub span: Span,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Consuming {
Consuming,
NonConsuming,
PreConsuming,
PostConsuming,
}
#[derive(Debug, Clone)]
pub struct ChoiceDecl {
pub name: String,
pub consuming: Consuming,
pub return_ty: Option<Type>,
pub params: Vec<FieldDecl>,
pub controllers: Vec<Expr>,
pub observers: Vec<Expr>,
pub body: Option<Expr>,
pub pos: Pos,
pub span: Span,
}
#[derive(Debug, Clone)]
pub enum TemplateBodyDecl {
Signatory {
parties: Vec<Expr>,
pos: Pos,
span: Span,
},
Observer {
parties: Vec<Expr>,
pos: Pos,
span: Span,
},
Ensure {
expr: Expr,
pos: Pos,
span: Span,
},
Key {
expr: Expr,
ty: Option<Type>,
pos: Pos,
span: Span,
},
Maintainer {
expr: Expr,
pos: Pos,
span: Span,
},
Choice(ChoiceDecl),
InterfaceInstance(InterfaceInstanceDecl),
Other {
raw: String,
pos: Pos,
span: Span,
},
}
#[derive(Debug, Clone)]
pub struct InterfaceInstanceDecl {
pub interface_name: String,
pub for_template: String,
pub methods: Vec<Binding>,
pub pos: Pos,
pub span: Span,
}
#[derive(Debug, Clone)]
pub struct TemplateDecl {
pub name: String,
pub fields: Vec<FieldDecl>,
pub body: Vec<TemplateBodyDecl>,
pub pos: Pos,
pub span: Span,
}
#[derive(Debug, Clone)]
pub struct InterfaceDecl {
pub name: String,
pub requires: Vec<String>,
pub viewtype: Option<String>,
pub methods: Vec<FieldDecl>,
pub choices: Vec<ChoiceDecl>,
pub pos: Pos,
pub span: Span,
}
#[derive(Debug, Clone)]
pub struct Equation {
pub params: Vec<Pat>,
pub body: Expr,
pub guards: Vec<(Expr, Expr)>,
pub where_bindings: Vec<Binding>,
pub pos: Pos,
pub span: Span,
}
#[derive(Debug, Clone)]
pub struct FunctionDecl {
pub name: String,
pub ty: Option<Type>,
pub equations: Vec<Equation>,
pub pos: Pos,
pub span: Span,
pub sig_span: Option<Span>,
}
#[derive(Debug, Clone)]
pub struct ImportDecl {
pub module_name: String,
pub qualified: bool,
pub alias: Option<String>,
pub pos: Pos,
pub span: Span,
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum Decl {
Template(TemplateDecl),
Interface(InterfaceDecl),
Function(FunctionDecl),
TypeDef {
keyword: String,
name: String,
pos: Pos,
span: Span,
},
Unknown {
raw: String,
pos: Pos,
span: Span,
},
}
#[derive(Debug, Clone)]
pub struct Module {
pub name: String,
pub pos: Pos,
pub span: Span,
pub header: Span,
pub imports: Vec<ImportDecl>,
pub decls: Vec<Decl>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum DiagnosticCategory {
SkippedDecl,
Malformed,
UnsupportedSyntax,
RecursionLimit,
Lex,
}
impl DiagnosticCategory {
pub const fn as_str(self) -> &'static str {
match self {
Self::SkippedDecl => "skipped-declaration",
Self::Malformed => "malformed",
Self::UnsupportedSyntax => "unsupported-syntax",
Self::RecursionLimit => "recursion-limit",
Self::Lex => "lexical-error",
}
}
}
#[derive(Debug, Clone)]
pub struct ParseDiagnostic {
pub message: String,
pub pos: Pos,
pub span: Span,
pub category: DiagnosticCategory,
}
impl Expr {
pub const fn pos(&self) -> Pos {
match self {
Self::Var { pos, .. }
| Self::Con { pos, .. }
| Self::Lit { pos, .. }
| Self::App { pos, .. }
| Self::BinOp { pos, .. }
| Self::Neg { pos, .. }
| Self::Lambda { pos, .. }
| Self::If { pos, .. }
| Self::Case { pos, .. }
| Self::Do { pos, .. }
| Self::LetIn { pos, .. }
| Self::Record { pos, .. }
| Self::Tuple { pos, .. }
| Self::List { pos, .. }
| Self::Try { pos, .. }
| Self::Section { pos, .. }
| Self::Error { pos, .. } => *pos,
}
}
pub const fn span(&self) -> Span {
match self {
Self::Var { span, .. }
| Self::Con { span, .. }
| Self::Lit { span, .. }
| Self::App { span, .. }
| Self::BinOp { span, .. }
| Self::Neg { span, .. }
| Self::Lambda { span, .. }
| Self::If { span, .. }
| Self::Case { span, .. }
| Self::Do { span, .. }
| Self::LetIn { span, .. }
| Self::Record { span, .. }
| Self::Tuple { span, .. }
| Self::List { span, .. }
| Self::Try { span, .. }
| Self::Section { span, .. }
| Self::Error { span, .. } => *span,
}
}
pub fn render(&self) -> String {
match self {
Self::Var {
qualifier, name, ..
}
| Self::Con {
qualifier, name, ..
} => qualifier
.as_ref()
.map_or_else(|| name.clone(), |q| format!("{q}.{name}")),
Self::Lit { kind, text, .. } => match kind {
LitKind::Text => format!("{text:?}"),
LitKind::Char => format!("'{text}'"),
_ => text.clone(),
},
Self::App { func, args, .. } => {
let mut s = func.render_atomic();
for a in args {
s.push(' ');
s.push_str(&a.render_atomic());
}
s
}
Self::BinOp { op, lhs, rhs, .. } => {
if op == "." {
format!("{}.{}", lhs.render_atomic(), rhs.render_atomic())
} else {
format!("{} {} {}", lhs.render_atomic(), op, rhs.render_atomic())
}
}
Self::Neg { expr, .. } => format!("-{}", expr.render_atomic()),
Self::Lambda { params, body, .. } => {
let ps: Vec<String> = params.iter().map(|p| p.render()).collect();
format!("\\{} -> {}", ps.join(" "), body.render())
}
Self::If {
cond,
then_branch,
else_branch,
..
} => format!(
"if {} then {} else {}",
cond.render(),
then_branch.render(),
else_branch.render()
),
Self::Case {
scrutinee, alts, ..
} => {
let arms: Vec<String> = alts
.iter()
.map(|a| format!("{} -> {}", a.pat.render(), a.body.render()))
.collect();
format!("case {} of {}", scrutinee.render(), arms.join("; "))
}
Self::Do { stmts, .. } => {
let body: Vec<String> = stmts.iter().map(render_do_stmt).collect();
format!("do {}", body.join("; "))
}
Self::LetIn { bindings, body, .. } => {
let bs: Vec<String> = bindings.iter().map(render_binding).collect();
format!("let {} in {}", bs.join("; "), body.render())
}
Self::Record { base, fields, .. } => {
let fs: Vec<String> = fields
.iter()
.map(|f| {
f.value.as_ref().map_or_else(
|| f.name.clone(),
|v| format!("{} = {}", f.name, v.render()),
)
})
.collect();
format!("{} with {}", base.render_atomic(), fs.join("; "))
}
Self::Tuple { items, .. } => {
let xs: Vec<String> = items.iter().map(|e| e.render()).collect();
format!("({})", xs.join(", "))
}
Self::List { items, .. } => {
let xs: Vec<String> = items.iter().map(|e| e.render()).collect();
format!("[{}]", xs.join(", "))
}
Self::Try { body, handlers, .. } => {
let hs: Vec<String> = handlers
.iter()
.map(|a| format!("{} -> {}", a.pat.render(), a.body.render()))
.collect();
format!("try {} catch {}", body.render(), hs.join("; "))
}
Self::Section {
op, operand, left, ..
} => match (operand, left) {
(Some(e), true) => format!("({} {})", e.render(), op),
(Some(e), false) => format!("({} {})", op, e.render()),
(None, _) => format!("({op})"),
},
Self::Error { raw, .. } => raw.clone(),
}
}
fn render_atomic(&self) -> String {
match self {
Self::Var { .. }
| Self::Con { .. }
| Self::Lit { .. }
| Self::Tuple { .. }
| Self::List { .. }
| Self::Section { .. }
| Self::Error { .. } => self.render(),
_ => format!("({})", self.render()),
}
}
pub fn application_head(&self) -> &Self {
match self {
Self::App { func, .. } => func.application_head(),
_ => self,
}
}
pub fn application_args(&self) -> &[Self] {
match self {
Self::App { args, .. } => args,
_ => &[],
}
}
}
fn render_do_stmt(s: &DoStmt) -> String {
match s {
DoStmt::Bind { pat, expr, .. } => format!("{} <- {}", pat.render(), expr.render()),
DoStmt::Let { bindings, .. } => {
let bs: Vec<String> = bindings.iter().map(render_binding).collect();
format!("let {}", bs.join("; "))
}
DoStmt::Expr { expr, .. } => expr.render(),
}
}
fn render_binding(b: &Binding) -> String {
let mut s = b.pat.render();
for p in &b.params {
s.push(' ');
s.push_str(&p.render());
}
format!("{} = {}", s, b.expr.render())
}
impl Pat {
pub const fn pos(&self) -> Pos {
match self {
Self::Var { pos, .. }
| Self::Wild { pos, .. }
| Self::Con { pos, .. }
| Self::Tuple { pos, .. }
| Self::List { pos, .. }
| Self::Lit { pos, .. }
| Self::As { pos, .. }
| Self::Other { pos, .. } => *pos,
}
}
pub const fn span(&self) -> Span {
match self {
Self::Var { span, .. }
| Self::Wild { span, .. }
| Self::Con { span, .. }
| Self::Tuple { span, .. }
| Self::List { span, .. }
| Self::Lit { span, .. }
| Self::As { span, .. }
| Self::Other { span, .. } => *span,
}
}
pub fn render(&self) -> String {
match self {
Self::Var { name, .. } => name.clone(),
Self::Wild { .. } => "_".to_string(),
Self::Con {
qualifier,
name,
args,
..
} => {
let head = qualifier
.as_ref()
.map_or_else(|| name.clone(), |q| format!("{q}.{name}"));
if args.is_empty() {
head
} else {
let parts: Vec<String> = args.iter().map(|p| p.render()).collect();
format!("({} {})", head, parts.join(" "))
}
}
Self::Tuple { items, .. } => {
let xs: Vec<String> = items.iter().map(|p| p.render()).collect();
format!("({})", xs.join(", "))
}
Self::List { items, .. } => {
let xs: Vec<String> = items.iter().map(|p| p.render()).collect();
format!("[{}]", xs.join(", "))
}
Self::Lit { kind, text, .. } => match kind {
LitKind::Text => format!("{text:?}"),
LitKind::Char => format!("'{text}'"),
_ => text.clone(),
},
Self::As { name, pat, .. } => format!("{}@{}", name, pat.render()),
Self::Other { raw, .. } => raw.clone(),
}
}
}
impl std::fmt::Display for Expr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.render())
}
}
impl std::fmt::Display for Pat {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.render())
}
}
#[cfg(test)]
mod tests {
use super::*;
fn pos() -> Pos {
Pos { line: 1, column: 1 }
}
fn span(start: usize, end: usize) -> Span {
Span::new(start, end)
}
#[test]
fn span_distinguishes_empty_from_invalid() {
assert!(span(3, 3).is_valid());
assert!(span(3, 3).is_empty());
assert!(!span(4, 3).is_valid());
assert!(!span(4, 3).is_empty());
}
#[test]
fn contains_rejects_invalid_spans() {
let parent = span(1, 10);
assert!(parent.contains(&span(3, 7)));
assert!(!parent.contains(&span(7, 3)));
assert!(!span(10, 1).contains(&span(3, 7)));
}
#[test]
fn expr_render_keeps_normalized_application_and_projection_shape() {
let projection = Expr::BinOp {
op: ".".to_string(),
lhs: Box::new(Expr::Var {
qualifier: None,
name: "this".to_string(),
pos: pos(),
span: span(0, 4),
}),
rhs: Box::new(Expr::Var {
qualifier: None,
name: "note".to_string(),
pos: pos(),
span: span(5, 9),
}),
pos: pos(),
span: span(0, 9),
};
let expr = Expr::App {
func: Box::new(Expr::Var {
qualifier: None,
name: "length".to_string(),
pos: pos(),
span: span(0, 6),
}),
args: vec![projection],
pos: pos(),
span: span(0, 16),
};
assert_eq!(expr.render(), "length (this.note)");
}
#[test]
fn pat_render_preserves_collection_shape() {
let pat = Pat::Tuple {
items: vec![
Pat::Var {
name: "owner".to_string(),
pos: pos(),
span: span(1, 6),
},
Pat::List {
items: Vec::new(),
pos: pos(),
span: span(8, 10),
},
],
pos: pos(),
span: span(0, 11),
};
assert_eq!(pat.render(), "(owner, [])");
}
}