pub use crate::lexer::{ByteOffset, Identifier, ModuleName, Operator, Pos};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
pub struct Span {
pub start: ByteOffset,
pub end: ByteOffset,
}
impl Span {
#[must_use]
pub const fn new(start: ByteOffset, end: ByteOffset) -> Self {
Self { start, end }
}
#[must_use]
pub const fn from_usize(start: usize, end: usize) -> Self {
Self {
start: ByteOffset::new(start),
end: ByteOffset::new(end),
}
}
#[must_use]
pub const fn start_usize(self) -> usize {
self.start.get()
}
#[must_use]
pub const fn end_usize(self) -> usize {
self.end.get()
}
#[must_use]
pub const fn is_valid(&self) -> bool {
self.start.get() <= self.end.get()
}
#[must_use]
pub const fn is_empty(&self) -> bool {
self.start.get() == self.end.get()
}
#[must_use]
pub const fn range(&self) -> std::ops::Range<usize> {
self.start.get()..self.end.get()
}
#[must_use]
pub fn get<'a>(&self, source: &'a str) -> Option<&'a str> {
let start = self.start.get();
let end = self.end.get();
if start <= end
&& end <= source.len()
&& source.is_char_boundary(start)
&& source.is_char_boundary(end)
{
Some(&source[start..end])
} else {
None
}
}
#[must_use]
pub const fn contains(&self, other: &Self) -> bool {
self.is_valid()
&& other.is_valid()
&& self.start.get() <= other.start.get()
&& other.end.get() <= self.end.get()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum LitKind {
Int,
Decimal,
Text,
Char,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum ImportStyle {
Qualified,
Unqualified,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum FieldAssign {
Assign {
name: Identifier,
value: Expr,
pos: Pos,
span: Span,
},
Pun {
name: Identifier,
pos: Pos,
span: Span,
},
Wildcard {
pos: Pos,
span: Span,
},
}
impl FieldAssign {
#[must_use]
pub const fn pos(&self) -> Pos {
match self {
Self::Assign { pos, .. } | Self::Pun { pos, .. } | Self::Wildcard { pos, .. } => *pos,
}
}
#[must_use]
pub const fn span(&self) -> Span {
match self {
Self::Assign { span, .. } | Self::Pun { span, .. } | Self::Wildcard { span, .. } => {
*span
}
}
}
#[must_use]
pub const fn name(&self) -> Option<&Identifier> {
match self {
Self::Assign { name, .. } | Self::Pun { name, .. } => Some(name),
Self::Wildcard { .. } => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum GuardQualifier {
Bool {
expr: Expr,
pos: Pos,
span: Span,
},
Pattern {
pat: Pat,
expr: Expr,
pos: Pos,
span: Span,
},
}
impl GuardQualifier {
#[must_use]
pub const fn span(&self) -> Span {
match self {
Self::Bool { span, .. } | Self::Pattern { span, .. } => *span,
}
}
#[must_use]
pub const fn pos(&self) -> Pos {
match self {
Self::Bool { pos, .. } | Self::Pattern { pos, .. } => *pos,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AltBranch {
pub guards: Vec<GuardQualifier>,
pub body: Expr,
pub pos: Pos,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Alt {
pub pat: Pat,
pub body: Expr,
pub branches: Vec<AltBranch>,
pub where_bindings: Vec<Binding>,
pub pos: Pos,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Binding {
pub pat: Pat,
pub params: Vec<Pat>,
pub expr: Expr,
pub pos: Pos,
pub span: Span,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum RecordPatternSyntax {
Braces,
With,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum PatFieldAssign {
Assign {
name: Identifier,
pat: Pat,
pos: Pos,
span: Span,
},
Pun {
name: Identifier,
pos: Pos,
span: Span,
},
Wildcard {
pos: Pos,
span: Span,
},
}
impl PatFieldAssign {
#[must_use]
pub const fn pos(&self) -> Pos {
match self {
Self::Assign { pos, .. } | Self::Pun { pos, .. } | Self::Wildcard { pos, .. } => *pos,
}
}
#[must_use]
pub const fn span(&self) -> Span {
match self {
Self::Assign { span, .. } | Self::Pun { span, .. } | Self::Wildcard { span, .. } => {
*span
}
}
}
#[must_use]
pub const fn name(&self) -> Option<&Identifier> {
match self {
Self::Assign { name, .. } | Self::Pun { name, .. } => Some(name),
Self::Wildcard { .. } => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum Pat {
Var {
name: Identifier,
pos: Pos,
span: Span,
},
Wild {
pos: Pos,
span: Span,
},
Con {
qualifier: Option<ModuleName>,
name: Identifier,
args: Vec<Self>,
pos: Pos,
span: Span,
},
Record {
qualifier: Option<ModuleName>,
name: Identifier,
syntax: RecordPatternSyntax,
fields: Vec<PatFieldAssign>,
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: Identifier,
pat: Box<Self>,
pos: Pos,
span: Span,
},
Other {
raw: String,
pos: Pos,
span: Span,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum Expr {
Var {
qualifier: Option<ModuleName>,
name: Identifier,
pos: Pos,
span: Span,
},
Con {
qualifier: Option<ModuleName>,
name: Identifier,
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: Operator,
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,
},
OperatorRef {
op: Operator,
pos: Pos,
span: Span,
},
LeftSection {
op: Operator,
operand: Box<Self>,
pos: Pos,
span: Span,
},
RightSection {
op: Operator,
operand: Box<Self>,
pos: Pos,
span: Span,
},
Error {
raw: String,
pos: Pos,
span: Span,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[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<ModuleName>,
name: Identifier,
span: Span,
},
App(Box<Self>, Vec<Self>, Span),
List(Box<Self>, Span),
Tuple(Vec<Self>, Span),
Fun(Box<Self>, Box<Self>, Span),
Var(Identifier, Span),
Unit(Span),
Constrained(Box<Self>, Span),
Lit {
kind: LitKind,
text: String,
span: Span,
},
}
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,
(
Self::Lit {
kind: ak, text: at, ..
},
Self::Lit {
kind: bk, text: bt, ..
},
) => ak == bk && at == bt,
_ => false,
}
}
}
impl Eq for Type {}
impl Type {
#[must_use]
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)
| Self::Lit { 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)
| Self::Lit { span: s, .. } => *s = span,
}
self
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum TypeAnnotation {
Absent,
Present(Type),
Malformed { span: Span },
}
impl TypeAnnotation {
#[must_use]
pub const fn as_type(&self) -> Option<&Type> {
match self {
Self::Present(ty) => Some(ty),
Self::Absent | Self::Malformed { .. } => None,
}
}
#[must_use]
pub const fn is_absent(&self) -> bool {
matches!(self, Self::Absent)
}
#[must_use]
pub const fn is_malformed(&self) -> bool {
matches!(self, Self::Malformed { .. })
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FieldDecl {
pub name: Identifier,
pub ty: TypeAnnotation,
pub pos: Pos,
pub span: Span,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum Consuming {
Consuming,
NonConsuming,
PreConsuming,
PostConsuming,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ChoiceDecl {
pub name: Identifier,
pub consuming: Consuming,
pub return_ty: TypeAnnotation,
pub params: Vec<FieldDecl>,
pub controllers: Vec<Expr>,
pub observers: Vec<Expr>,
pub authority_exprs: Vec<Expr>,
pub body: Option<Expr>,
pub pos: Pos,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
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: TypeAnnotation,
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, PartialEq, Eq)]
#[non_exhaustive]
pub enum InterfaceInstanceBodyItem {
View {
expr: Expr,
pos: Pos,
span: Span,
},
Method(Binding),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InterfaceInstanceDecl {
pub interface_name: ModuleName,
pub for_template: Option<ModuleName>,
pub items: Vec<InterfaceInstanceBodyItem>,
pub pos: Pos,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TemplateDecl {
pub name: Identifier,
pub fields: Vec<FieldDecl>,
pub body: Vec<TemplateBodyDecl>,
pub pos: Pos,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InterfaceDecl {
pub name: Identifier,
pub requires: Vec<ModuleName>,
pub viewtype: Option<ModuleName>,
pub methods: Vec<FieldDecl>,
pub choices: Vec<ChoiceDecl>,
pub pos: Pos,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq, Eq)]
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, PartialEq, Eq)]
pub struct FunctionDecl {
pub name: Identifier,
pub ty: TypeAnnotation,
pub equations: Vec<Equation>,
pub pos: Pos,
pub span: Span,
pub sig_span: Option<Span>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum FixityAssoc {
Infix,
InfixL,
InfixR,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum FixityTarget {
Operator(Operator),
Backtick(Identifier),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FixityDecl {
pub assoc: FixityAssoc,
pub precedence: u8,
pub operators: Vec<FixityTarget>,
pub pos: Pos,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ImportPackageLabel {
pub value: String,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ImportDecl {
pub module_name: ModuleName,
pub style: ImportStyle,
pub alias: Option<ModuleName>,
pub package_label: Option<ImportPackageLabel>,
pub pos: Pos,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum Decl {
Template(TemplateDecl),
Interface(InterfaceDecl),
Function(FunctionDecl),
TypeDef {
keyword: String,
name: Identifier,
pos: Pos,
span: Span,
},
Fixity(FixityDecl),
UnsupportedSyntax {
kind: UnsupportedSyntaxKind,
raw: String,
pos: Pos,
span: Span,
},
Unknown {
raw: String,
pos: Pos,
span: Span,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Module {
pub name: ModuleName,
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 {
#[must_use]
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",
}
}
}
impl std::fmt::Display for DiagnosticCategory {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum ParseDiagnosticKind {
Lex(crate::lexer::LexErrorKind),
ExpectedToken(ExpectedToken),
MalformedTypeAnnotation(TypeAnnotationContext),
MalformedSyntax(MalformedSyntaxKind),
SkippedDeclaration(SkippedDeclarationReason),
UnsupportedSyntax(UnsupportedSyntaxKind),
RecursionLimit { limit: u32 },
}
impl ParseDiagnosticKind {
#[must_use]
pub const fn category(&self) -> DiagnosticCategory {
match self {
Self::Lex(_) => DiagnosticCategory::Lex,
Self::ExpectedToken(_)
| Self::MalformedTypeAnnotation(_)
| Self::MalformedSyntax(_) => DiagnosticCategory::Malformed,
Self::SkippedDeclaration(_) => DiagnosticCategory::SkippedDecl,
Self::UnsupportedSyntax(_) => DiagnosticCategory::UnsupportedSyntax,
Self::RecursionLimit { .. } => DiagnosticCategory::RecursionLimit,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum ExpectedToken {
WhereAfterModuleHeader,
ModuleNameAfterImport,
TemplateNameAfterInterfaceInstanceFor,
FieldNameTypePair,
EqualsAfterGuard,
EqualsOrGuardedRightHandSide,
ProjectionFieldAfterDot,
ThenKeyword,
ElseKeyword,
OfKeywordInCaseExpression,
ArrowInGuardedCaseAlternative,
ArrowInCaseAlternative,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum TypeAnnotationContext {
Field,
Key,
Choice,
InterfaceMethod,
Function,
}
impl TypeAnnotationContext {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Field => "field",
Self::Key => "key",
Self::Choice => "choice",
Self::InterfaceMethod => "interface method",
Self::Function => "function",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum MalformedSyntaxKind {
FunctionEquation,
FunctionParameterPattern,
LambdaParameter,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum SkippedDeclarationReason {
TopLevelPatternBinding,
UnrecognizedDeclaration,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum UnsupportedSyntaxKind {
LegacyControllerCan,
PatternSynonym,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParseDiagnostic {
pub kind: ParseDiagnosticKind,
pub message: String,
pub pos: Pos,
pub span: Span,
pub category: DiagnosticCategory,
}
impl ParseDiagnostic {
#[must_use]
pub fn new(
kind: ParseDiagnosticKind,
message: impl Into<String>,
pos: Pos,
span: Span,
) -> Self {
let category = kind.category();
Self {
kind,
message: message.into(),
pos,
span,
category,
}
}
#[must_use]
pub fn message(&self) -> &str {
&self.message
}
#[must_use]
pub const fn kind(&self) -> &ParseDiagnosticKind {
&self.kind
}
#[must_use]
pub const fn category(&self) -> DiagnosticCategory {
self.category
}
}
impl std::fmt::Display for ParseDiagnostic {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.message.fmt(f)
}
}
impl std::error::Error for ParseDiagnostic {}
impl Expr {
#[must_use]
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::OperatorRef { pos, .. }
| Self::LeftSection { pos, .. }
| Self::RightSection { pos, .. }
| Self::Error { pos, .. } => *pos,
}
}
#[must_use]
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::OperatorRef { span, .. }
| Self::LeftSection { span, .. }
| Self::RightSection { span, .. }
| Self::Error { span, .. } => *span,
}
}
#[must_use]
pub fn render(&self) -> String {
match self {
Self::Var {
qualifier, name, ..
}
| Self::Con {
qualifier, name, ..
} => qualifier
.as_ref()
.map_or_else(|| name.to_string(), |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(render_alt).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| match f {
FieldAssign::Assign { name, value, .. } => {
format!("{} = {}", name, value.render())
}
FieldAssign::Pun { name, .. } => name.to_string(),
FieldAssign::Wildcard { .. } => "..".to_string(),
})
.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(render_alt).collect();
format!("try {} catch {}", body.render(), hs.join("; "))
}
Self::OperatorRef { op, .. } => format!("({op})"),
Self::LeftSection { op, operand, .. } => format!("({} {})", operand.render(), op),
Self::RightSection { op, operand, .. } => format!("({} {})", op, operand.render()),
Self::Error { raw, .. } => raw.clone(),
}
}
fn render_atomic(&self) -> String {
match self {
Self::Var { .. }
| Self::Con { .. }
| Self::Lit { .. }
| Self::Tuple { .. }
| Self::List { .. }
| Self::OperatorRef { .. }
| Self::LeftSection { .. }
| Self::RightSection { .. }
| Self::Error { .. } => self.render(),
_ => format!("({})", self.render()),
}
}
#[must_use]
pub fn application_head(&self) -> &Self {
match self {
Self::App { func, .. } => func.application_head(),
_ => self,
}
}
#[must_use]
pub fn application_args(&self) -> &[Self] {
match self {
Self::App { args, .. } => args,
_ => &[],
}
}
}
fn render_guard_qualifier(guard: &GuardQualifier) -> String {
match guard {
GuardQualifier::Bool { expr, .. } => expr.render(),
GuardQualifier::Pattern { pat, expr, .. } => {
format!("{} <- {}", pat.render(), expr.render())
}
}
}
fn render_alt(alt: &Alt) -> String {
let mut rendered = if alt.branches.len() == 1 && alt.branches[0].guards.is_empty() {
format!("{} -> {}", alt.pat.render(), alt.branches[0].body.render())
} else {
let mut parts = vec![alt.pat.render()];
for branch in &alt.branches {
let guards: Vec<String> = branch.guards.iter().map(render_guard_qualifier).collect();
parts.push(format!(
"| {} -> {}",
guards.join(", "),
branch.body.render()
));
}
parts.join(" ")
};
if !alt.where_bindings.is_empty() {
use std::fmt::Write;
let bindings: Vec<String> = alt.where_bindings.iter().map(render_binding).collect();
let _ = write!(rendered, " where {}", bindings.join("; "));
}
rendered
}
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 {
#[must_use]
pub const fn pos(&self) -> Pos {
match self {
Self::Var { pos, .. }
| Self::Wild { pos, .. }
| Self::Con { pos, .. }
| Self::Record { pos, .. }
| Self::Tuple { pos, .. }
| Self::List { pos, .. }
| Self::Lit { pos, .. }
| Self::As { pos, .. }
| Self::Other { pos, .. } => *pos,
}
}
#[must_use]
pub const fn span(&self) -> Span {
match self {
Self::Var { span, .. }
| Self::Wild { span, .. }
| Self::Con { span, .. }
| Self::Record { span, .. }
| Self::Tuple { span, .. }
| Self::List { span, .. }
| Self::Lit { span, .. }
| Self::As { span, .. }
| Self::Other { span, .. } => *span,
}
}
#[must_use]
pub fn render(&self) -> String {
match self {
Self::Var { name, .. } => name.to_string(),
Self::Wild { .. } => "_".to_string(),
Self::Con {
qualifier,
name,
args,
..
} => {
let head = qualifier
.as_ref()
.map_or_else(|| name.to_string(), |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::Record {
qualifier,
name,
syntax,
fields,
..
} => {
let head = qualifier
.as_ref()
.map_or_else(|| name.to_string(), |q| format!("{q}.{name}"));
let fs: Vec<String> = fields
.iter()
.map(|f| match f {
PatFieldAssign::Assign { name, pat, .. } => {
format!("{} = {}", name, pat.render())
}
PatFieldAssign::Pun { name, .. } => name.to_string(),
PatFieldAssign::Wildcard { .. } => "..".to_string(),
})
.collect();
match syntax {
RecordPatternSyntax::Braces => format!("{} {{ {} }}", head, fs.join(", ")),
RecordPatternSyntax::With => format!("{} with {}", head, fs.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 span(start: usize, end: usize) -> Span {
Span::from_usize(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 span_range_and_get_share_source_bytes_safely() {
let source = "foo: Int";
assert_eq!(span(0, 3).range(), 0..3);
assert_eq!(span(0, 3).get(source), Some("foo"));
assert_eq!(span(3, 7).get(source), Some(": In"));
assert!(span(3, 100).get(source).is_none());
}
}