use daml_parser::ast::Type;
use daml_syntax::{Coordinate, SourceFile, TextRange};
use serde::Serialize;
use std::path::{Path, PathBuf};
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
pub struct Span {
pub file: PathBuf,
pub line: usize,
pub column: usize,
}
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
pub struct SourceSpan {
pub file: PathBuf,
pub line: usize,
pub column: usize,
pub start: usize,
pub end: usize,
pub byte_start: usize,
pub byte_end: usize,
}
impl SourceSpan {
fn from_text_range(file: &Path, source_file: &SourceFile, range: TextRange) -> Self {
let byte_start = usize::from(range.start());
let byte_end = usize::from(range.end());
let line_col = source_file.line_index().char_line_col(range.start());
let (start, end) = source_file.line_index().utf16_range(range);
Self {
file: file.to_path_buf(),
line: line_col.line.get(),
column: line_col.column.get(),
start: start.get(),
end: end.get(),
byte_start,
byte_end,
}
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Serialize, PartialEq)]
pub enum TypeNode {
Con {
qualifier: Option<String>,
name: String,
span: SourceSpan,
},
App {
head: Box<Self>,
args: Vec<Self>,
span: SourceSpan,
},
List {
inner: Box<Self>,
span: SourceSpan,
},
Tuple {
items: Vec<Self>,
span: SourceSpan,
},
Fun {
param: Box<Self>,
result: Box<Self>,
span: SourceSpan,
},
Var {
name: String,
span: SourceSpan,
},
Unit {
span: SourceSpan,
},
Constrained {
body: Box<Self>,
span: SourceSpan,
},
Lit {
kind: LiteralKind,
value: String,
span: SourceSpan,
},
}
impl TypeNode {
pub(crate) fn from_type(t: &Type, file: &Path, source_file: &SourceFile) -> Self {
let source_span = || {
SourceSpan::from_text_range(
file,
source_file,
source_file.parser_span_to_text_range(t.span()),
)
};
match t {
Type::Con {
qualifier, name, ..
} => Self::Con {
qualifier: qualifier.to_owned().map(String::from),
name: name.to_string(),
span: source_span(),
},
Type::App(head, args, _) => Self::App {
head: Box::new(Self::from_type(head, file, source_file)),
args: args
.iter()
.map(|arg| Self::from_type(arg, file, source_file))
.collect(),
span: source_span(),
},
Type::List(inner, _) => Self::List {
inner: Box::new(Self::from_type(inner, file, source_file)),
span: source_span(),
},
Type::Tuple(items, _) => Self::Tuple {
items: items
.iter()
.map(|item| Self::from_type(item, file, source_file))
.collect(),
span: source_span(),
},
Type::Fun(param, result, _) => Self::Fun {
param: Box::new(Self::from_type(param, file, source_file)),
result: Box::new(Self::from_type(result, file, source_file)),
span: source_span(),
},
Type::Var(name, _) => Self::Var {
name: name.to_string(),
span: source_span(),
},
Type::Unit(_) => Self::Unit {
span: source_span(),
},
Type::Constrained(body, _) => Self::Constrained {
body: Box::new(Self::from_type(body, file, source_file)),
span: source_span(),
},
Type::Lit { kind, text, .. } => Self::Lit {
kind: match kind {
daml_parser::ast::LitKind::Char => LiteralKind::Char,
daml_parser::ast::LitKind::Int => LiteralKind::Int,
daml_parser::ast::LitKind::Decimal => LiteralKind::Decimal,
_ => LiteralKind::Text,
},
value: text.clone(),
span: source_span(),
},
_ => Self::Con {
qualifier: None,
name: "<unknown>".to_string(),
span: source_span(),
},
}
}
}
#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq)]
pub struct SrcPos {
pub line: usize,
pub column: usize,
}
#[non_exhaustive]
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
pub enum LiteralKind {
Int,
Decimal,
Text,
Char,
}
#[non_exhaustive]
#[derive(Debug, Clone, Serialize, PartialEq)]
pub enum Expr {
Var {
name: String,
qualifier: Option<String>,
span: SrcPos,
},
Con {
name: String,
qualifier: Option<String>,
span: SrcPos,
},
Lit {
kind: LiteralKind,
value: String,
span: SrcPos,
},
App {
func: Box<Self>,
args: Vec<Self>,
span: SrcPos,
},
BinOp {
op: String,
lhs: Box<Self>,
rhs: Box<Self>,
span: SrcPos,
},
Neg {
expr: Box<Self>,
span: SrcPos,
},
Lambda {
params: Vec<String>,
body: Box<Self>,
span: SrcPos,
},
If {
cond: Box<Self>,
then_branch: Box<Self>,
else_branch: Box<Self>,
span: SrcPos,
},
Case {
scrutinee: Box<Self>,
alts: Vec<CaseAlt>,
span: SrcPos,
},
DoBlock {
statements: Vec<Statement>,
span: SrcPos,
},
LetIn {
bindings: Vec<LetBinding>,
body: Box<Self>,
span: SrcPos,
},
Record {
base: Box<Self>,
fields: Vec<RecordField>,
span: SrcPos,
},
Tuple {
items: Vec<Self>,
span: SrcPos,
},
List {
items: Vec<Self>,
span: SrcPos,
},
Unknown {
raw: String,
span: SrcPos,
},
}
#[derive(Debug, Clone, Serialize, PartialEq)]
pub struct CaseAlt {
pub pattern: String,
pub body: Expr,
}
#[derive(Debug, Clone, Serialize, PartialEq)]
pub struct LetBinding {
pub name: String,
pub value: Expr,
}
#[derive(Debug, Clone, Serialize, PartialEq)]
pub struct RecordField {
pub name: String,
pub value: Option<Expr>,
}
#[derive(Debug, Clone, Serialize, PartialEq)]
pub struct Field {
pub name: String,
pub type_: Option<TypeNode>,
pub span: Span,
}
#[derive(Debug, Clone, Serialize)]
pub struct Template {
pub name: String,
pub fields: Vec<Field>,
pub signatory_exprs: Vec<Expr>,
pub observer_exprs: Vec<Expr>,
pub ensure_clause: Option<EnsureClause>,
pub key_expr: Option<Expr>,
pub key_type: Option<TypeNode>,
pub maintainer_exprs: Vec<Expr>,
pub choices: Vec<Choice>,
pub interface_instances: Vec<InterfaceInstance>,
pub span: Span,
}
#[derive(Debug, Clone, Serialize)]
pub struct InterfaceInstance {
pub interface_name: String,
pub methods: Vec<String>,
pub span: Span,
}
#[derive(Debug, Clone, Serialize)]
pub struct EnsureClause {
pub expr: Expr,
pub span: Span,
}
#[derive(Debug, Clone, Serialize)]
pub struct Choice {
pub name: String,
pub consuming: Consuming,
pub controller_exprs: Vec<Expr>,
pub observer_exprs: Vec<Expr>,
pub parameters: Vec<Field>,
pub return_type: Option<TypeNode>,
pub body: Vec<Statement>,
pub span: Span,
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "kebab-case")]
pub enum Consuming {
Consuming,
NonConsuming,
}
impl Consuming {
#[must_use]
pub const fn is_consuming(&self) -> bool {
matches!(self, Self::Consuming)
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Serialize, PartialEq)]
pub enum Statement {
Let {
name: String,
value: Expr,
span: SrcPos,
},
Assert {
condition_expr: Expr,
span: SrcPos,
},
Fetch {
cid: Expr,
binder: Option<String>,
span: SrcPos,
},
Archive {
cid: Expr,
span: SrcPos,
},
Create {
template_name: String,
argument: Expr,
binder: Option<String>,
span: SrcPos,
},
Exercise {
choice_name: String,
cid: Expr,
argument: Option<Expr>,
binder: Option<String>,
span: SrcPos,
},
TryCatch {
try_body: Vec<Self>,
catch_body: Vec<Self>,
span: SrcPos,
},
Branch {
scrutinee: Option<Expr>,
arms: Vec<BranchArm>,
span: SrcPos,
},
Other {
raw: String,
expr: Expr,
binder: Option<String>,
span: SrcPos,
},
}
#[derive(Debug, Clone, Serialize, PartialEq)]
pub struct BranchArm {
pub pattern: Option<String>,
pub body: Vec<Statement>,
}
#[derive(Debug, Clone, Serialize)]
pub struct Function {
pub name: String,
pub type_signature: Option<TypeNode>,
pub body: Vec<Statement>,
pub span: Span,
}
#[derive(Debug, Clone, Serialize)]
pub struct Import {
pub module_name: String,
pub qualified: ImportStyle,
pub alias: Option<String>,
pub span: Span,
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "kebab-case")]
pub enum ImportStyle {
Qualified,
Unqualified,
}
impl ImportStyle {
#[must_use]
pub const fn is_qualified(&self) -> bool {
matches!(self, Self::Qualified)
}
}
#[derive(Debug, Clone, Serialize)]
pub struct InterfaceMethod {
pub name: String,
pub type_: Option<TypeNode>,
pub span: Span,
}
#[derive(Debug, Clone, Serialize)]
pub struct Interface {
pub name: String,
pub requires: Vec<String>,
pub viewtype: Option<String>,
pub methods: Vec<InterfaceMethod>,
pub choices: Vec<Choice>,
pub span: Span,
}
#[derive(Debug, Clone, Serialize)]
pub struct DamlModule {
pub ir_version: u32,
pub name: String,
pub file: PathBuf,
pub source: String,
pub imports: Vec<Import>,
pub templates: Vec<Template>,
pub interfaces: Vec<Interface>,
pub functions: Vec<Function>,
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::Path;
#[test]
fn source_span_line_calculation_tracks_its_source_file() {
let source_a = SourceFile::parse("module A where\nabcde\n");
let source_b = SourceFile::parse("module A where\nx\ncde\n");
let range = TextRange::new(17.into(), 18.into());
let span_a = SourceSpan::from_text_range(Path::new("A.daml"), &source_a, range);
let span_b = SourceSpan::from_text_range(Path::new("B.daml"), &source_b, range);
assert_eq!(span_a.line, 2);
assert_eq!(span_b.line, 3);
assert_ne!(span_a.column, span_b.column);
assert_eq!(span_a.column, 3);
assert_eq!(span_b.column, 1);
}
}