tx3_lang/
ast.rs

1//! The Tx3 language abstract syntax tree (AST).
2//!
3//! This module defines the abstract syntax tree (AST) for the Tx3 language.
4//! It provides the structure for representing Tx3 programs, including
5//! transactions, types, assets, and other constructs.
6//!
7//! This module is not intended to be used directly by end-users. See
8//! [`parse_file`](crate::parse_file) and [`parse_string`](crate::parse_string)
9//! for parsing Tx3 source code into an AST.
10
11use serde::{Deserialize, Serialize};
12use std::{collections::HashMap, rc::Rc};
13
14#[derive(Debug, PartialEq, Eq)]
15pub struct Scope {
16    pub(crate) symbols: HashMap<String, Symbol>,
17    pub(crate) parent: Option<Rc<Scope>>,
18}
19
20#[derive(Debug, Clone, PartialEq, Eq)]
21pub enum Symbol {
22    ParamVar(String, Box<Type>),
23    Input(String, Box<Type>),
24    PartyDef(Box<PartyDef>),
25    PolicyDef(Box<PolicyDef>),
26    AssetDef(Box<AssetDef>),
27    TypeDef(Box<TypeDef>),
28    RecordField(Box<RecordField>),
29    VariantCase(Box<VariantCase>),
30    Fees,
31}
32
33#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct Span {
35    dummy: bool,
36    pub start: usize,
37    pub end: usize,
38}
39
40impl Eq for Span {}
41
42impl PartialEq for Span {
43    fn eq(&self, other: &Self) -> bool {
44        if self.dummy || other.dummy {
45            return true;
46        }
47
48        self.start == other.start && self.end == other.end
49    }
50}
51
52impl std::hash::Hash for Span {
53    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
54        self.start.hash(state);
55        self.end.hash(state);
56    }
57}
58
59impl Span {
60    pub const DUMMY: Self = Self {
61        dummy: true,
62        start: 0,
63        end: 0,
64    };
65
66    pub fn new(start: usize, end: usize) -> Self {
67        Self {
68            dummy: false,
69            start,
70            end,
71        }
72    }
73}
74
75impl Symbol {
76    pub fn as_type_def(&self) -> Option<&TypeDef> {
77        match self {
78            Symbol::TypeDef(x) => Some(x.as_ref()),
79            _ => None,
80        }
81    }
82
83    pub fn as_variant_case(&self) -> Option<&VariantCase> {
84        match self {
85            Symbol::VariantCase(x) => Some(x.as_ref()),
86            _ => None,
87        }
88    }
89
90    pub fn as_field_def(&self) -> Option<&RecordField> {
91        match self {
92            Symbol::RecordField(x) => Some(x.as_ref()),
93            _ => None,
94        }
95    }
96
97    pub fn as_policy_def(&self) -> Option<&PolicyDef> {
98        match self {
99            Symbol::PolicyDef(x) => Some(x.as_ref()),
100            _ => None,
101        }
102    }
103
104    pub fn target_type(&self) -> Option<Type> {
105        match self {
106            Symbol::ParamVar(_, ty) => Some(ty.as_ref().clone()),
107            Symbol::RecordField(x) => Some(x.r#type.clone()),
108            Symbol::Input(_, ty) => Some(ty.as_ref().clone()),
109            x => {
110                dbg!(x);
111                None
112            }
113        }
114    }
115}
116
117#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
118pub struct Identifier {
119    pub value: String,
120    pub span: Span,
121
122    // analysis
123    #[serde(skip)]
124    pub(crate) symbol: Option<Symbol>,
125}
126
127impl Identifier {
128    pub fn new(value: impl Into<String>) -> Self {
129        Self {
130            value: value.into(),
131            symbol: None,
132            span: Span::DUMMY,
133        }
134    }
135
136    pub fn try_symbol(&self) -> Result<&Symbol, crate::lowering::Error> {
137        match &self.symbol {
138            Some(symbol) => Ok(symbol),
139            None => Err(crate::lowering::Error::MissingAnalyzePhase),
140        }
141    }
142}
143
144impl AsRef<str> for Identifier {
145    fn as_ref(&self) -> &str {
146        &self.value
147    }
148}
149
150#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
151pub struct Program {
152    pub txs: Vec<TxDef>,
153    pub types: Vec<TypeDef>,
154    pub assets: Vec<AssetDef>,
155    pub parties: Vec<PartyDef>,
156    pub policies: Vec<PolicyDef>,
157    pub span: Span,
158
159    // analysis
160    #[serde(skip)]
161    pub(crate) scope: Option<Rc<Scope>>,
162}
163
164#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
165pub struct ParameterList {
166    pub parameters: Vec<ParamDef>,
167    pub span: Span,
168}
169
170#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
171pub struct TxDef {
172    pub name: String,
173    pub parameters: ParameterList,
174    pub references: Vec<ReferenceBlock>,
175    pub inputs: Vec<InputBlock>,
176    pub outputs: Vec<OutputBlock>,
177    pub burn: Option<BurnBlock>,
178    pub mint: Option<MintBlock>,
179    pub adhoc: Vec<ChainSpecificBlock>,
180    pub span: Span,
181    pub collateral: Vec<CollateralBlock>,
182
183    // analysis
184    #[serde(skip)]
185    pub(crate) scope: Option<Rc<Scope>>,
186}
187
188#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
189pub struct StringLiteral {
190    pub value: String,
191    pub span: Span,
192}
193
194impl StringLiteral {
195    pub fn new(value: impl Into<String>) -> Self {
196        Self {
197            value: value.into(),
198            span: Span::DUMMY,
199        }
200    }
201}
202
203#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
204pub struct HexStringLiteral {
205    pub value: String,
206    pub span: Span,
207}
208
209impl HexStringLiteral {
210    pub fn new(value: impl Into<String>) -> Self {
211        Self {
212            value: value.into(),
213            span: Span::DUMMY,
214        }
215    }
216}
217
218#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
219pub enum CollateralBlockField {
220    From(AddressExpr),
221    MinAmount(AssetExpr),
222    Ref(DataExpr),
223}
224
225impl CollateralBlockField {
226    fn key(&self) -> &str {
227        match self {
228            CollateralBlockField::From(_) => "from",
229            CollateralBlockField::MinAmount(_) => "min_amount",
230            CollateralBlockField::Ref(_) => "ref",
231        }
232    }
233
234    pub fn as_address_expr(&self) -> Option<&AddressExpr> {
235        match self {
236            CollateralBlockField::From(x) => Some(x),
237            _ => None,
238        }
239    }
240
241    pub fn as_asset_expr(&self) -> Option<&AssetExpr> {
242        match self {
243            CollateralBlockField::MinAmount(x) => Some(x),
244            _ => None,
245        }
246    }
247
248    pub fn as_data_expr(&self) -> Option<&DataExpr> {
249        match self {
250            CollateralBlockField::Ref(x) => Some(x),
251            _ => None,
252        }
253    }
254}
255
256#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
257pub struct CollateralBlock {
258    pub fields: Vec<CollateralBlockField>,
259    pub span: Span,
260}
261
262impl CollateralBlock {
263    pub(crate) fn find(&self, key: &str) -> Option<&CollateralBlockField> {
264        self.fields.iter().find(|x| x.key() == key)
265    }
266}
267
268#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
269pub enum InputBlockField {
270    From(AddressExpr),
271    DatumIs(Type),
272    MinAmount(AssetExpr),
273    Redeemer(DataExpr),
274    Ref(DataExpr),
275}
276
277impl InputBlockField {
278    fn key(&self) -> &str {
279        match self {
280            InputBlockField::From(_) => "from",
281            InputBlockField::DatumIs(_) => "datum_is",
282            InputBlockField::MinAmount(_) => "min_amount",
283            InputBlockField::Redeemer(_) => "redeemer",
284            InputBlockField::Ref(_) => "ref",
285        }
286    }
287
288    pub fn as_address_expr(&self) -> Option<&AddressExpr> {
289        match self {
290            InputBlockField::From(x) => Some(x),
291            _ => None,
292        }
293    }
294
295    pub fn as_asset_expr(&self) -> Option<&AssetExpr> {
296        match self {
297            InputBlockField::MinAmount(x) => Some(x),
298            _ => None,
299        }
300    }
301
302    pub fn as_data_expr(&self) -> Option<&DataExpr> {
303        match self {
304            InputBlockField::Redeemer(x) => Some(x),
305            InputBlockField::Ref(x) => Some(x),
306            _ => None,
307        }
308    }
309
310    pub fn as_datum_type(&self) -> Option<&Type> {
311        match self {
312            InputBlockField::DatumIs(x) => Some(x),
313            _ => None,
314        }
315    }
316}
317
318#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
319pub struct ReferenceBlock {
320    pub name: String,
321    pub r#ref: DataExpr,
322    pub span: Span,
323}
324
325#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
326pub struct InputBlock {
327    pub name: String,
328    pub is_many: bool,
329    pub fields: Vec<InputBlockField>,
330    pub span: Span,
331}
332
333impl InputBlock {
334    pub(crate) fn find(&self, key: &str) -> Option<&InputBlockField> {
335        self.fields.iter().find(|x| x.key() == key)
336    }
337
338    pub(crate) fn datum_is(&self) -> Option<&Type> {
339        self.find("datum_is").and_then(|x| x.as_datum_type())
340    }
341}
342
343#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
344pub enum OutputBlockField {
345    To(Box<AddressExpr>),
346    Amount(Box<AssetExpr>),
347    Datum(Box<DataExpr>),
348}
349
350impl OutputBlockField {
351    fn key(&self) -> &str {
352        match self {
353            OutputBlockField::To(_) => "to",
354            OutputBlockField::Amount(_) => "amount",
355            OutputBlockField::Datum(_) => "datum",
356        }
357    }
358}
359
360#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
361pub struct OutputBlock {
362    pub name: Option<String>,
363    pub fields: Vec<OutputBlockField>,
364    pub span: Span,
365}
366
367impl OutputBlock {
368    pub(crate) fn find(&self, key: &str) -> Option<&OutputBlockField> {
369        self.fields.iter().find(|x| x.key() == key)
370    }
371}
372
373#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
374pub enum MintBlockField {
375    Amount(Box<AssetExpr>),
376    Redeemer(Box<DataExpr>),
377}
378
379impl MintBlockField {
380    fn key(&self) -> &str {
381        match self {
382            MintBlockField::Amount(_) => "amount",
383            MintBlockField::Redeemer(_) => "redeemer",
384        }
385    }
386}
387
388#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
389pub struct MintBlock {
390    pub fields: Vec<MintBlockField>,
391    pub span: Span,
392}
393
394impl MintBlock {
395    pub(crate) fn find(&self, key: &str) -> Option<&MintBlockField> {
396        self.fields.iter().find(|x| x.key() == key)
397    }
398}
399
400#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
401pub struct BurnBlock {
402    pub fields: Vec<MintBlockField>,
403    pub span: Span,
404}
405
406#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
407pub struct RecordField {
408    pub name: String,
409    pub r#type: Type,
410    pub span: Span,
411}
412
413impl RecordField {
414    pub fn new(name: &str, r#type: Type) -> Self {
415        Self {
416            name: name.to_string(),
417            r#type,
418            span: Span::DUMMY,
419        }
420    }
421}
422
423#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
424pub struct PartyDef {
425    pub name: String,
426    pub span: Span,
427}
428
429#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
430pub struct PartyField {
431    pub name: String,
432    pub party_type: String,
433}
434
435#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
436pub struct PolicyDef {
437    pub name: String,
438    pub value: PolicyValue,
439    pub span: Span,
440}
441
442#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
443pub enum PolicyField {
444    Hash(DataExpr),
445    Script(DataExpr),
446    Ref(DataExpr),
447}
448
449#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
450pub struct PolicyConstructor {
451    pub fields: Vec<PolicyField>,
452    pub span: Span,
453}
454
455impl PolicyConstructor {
456    pub(crate) fn find_field(&self, field: &str) -> Option<&PolicyField> {
457        self.fields.iter().find(|x| match x {
458            PolicyField::Hash(_) => field == "hash",
459            PolicyField::Script(_) => field == "script",
460            PolicyField::Ref(_) => field == "ref",
461        })
462    }
463}
464
465#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
466pub enum PolicyValue {
467    Constructor(PolicyConstructor),
468    Assign(HexStringLiteral),
469}
470
471#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
472pub struct StaticAssetConstructor {
473    pub r#type: Identifier,
474    pub amount: Box<DataExpr>,
475    pub span: Span,
476}
477
478#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
479pub struct AnyAssetConstructor {
480    pub policy: Box<DataExpr>,
481    pub asset_name: Box<DataExpr>,
482    pub amount: Box<DataExpr>,
483    pub span: Span,
484}
485
486#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
487pub struct AssetBinaryOp {
488    pub left: Box<AssetExpr>,
489    pub operator: BinaryOperator,
490    pub right: Box<AssetExpr>,
491    pub span: Span,
492}
493
494#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
495pub enum AssetExpr {
496    StaticConstructor(StaticAssetConstructor),
497    AnyConstructor(AnyAssetConstructor),
498    BinaryOp(AssetBinaryOp),
499    PropertyAccess(PropertyAccess),
500    Identifier(Identifier),
501}
502
503#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
504pub struct PropertyAccess {
505    pub object: Identifier,
506    pub path: Vec<Identifier>,
507    pub span: Span,
508
509    // analysis
510    #[serde(skip)]
511    pub(crate) scope: Option<Rc<Scope>>,
512}
513
514impl PropertyAccess {
515    pub fn new(object: &str, path: &[&str]) -> Self {
516        Self {
517            object: Identifier::new(object),
518            path: path.iter().map(|x| Identifier::new(*x)).collect(),
519            scope: None,
520            span: Span::DUMMY,
521        }
522    }
523}
524
525impl PropertyAccess {
526    /// Shift the property access to the next property in the path.
527    pub fn shift(mut self) -> Option<Self> {
528        if self.path.is_empty() {
529            return None;
530        }
531
532        let new_object = self.path.remove(0);
533        self.object = new_object;
534
535        Some(self)
536    }
537}
538
539#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
540pub struct RecordConstructorField {
541    pub name: Identifier,
542    pub value: Box<DataExpr>,
543    pub span: Span,
544}
545
546#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
547pub struct DatumConstructor {
548    pub r#type: Identifier,
549    pub case: VariantCaseConstructor,
550    pub span: Span,
551
552    // analysis
553    #[serde(skip)]
554    pub scope: Option<Rc<Scope>>,
555}
556
557#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
558pub struct VariantCaseConstructor {
559    pub name: Identifier,
560    pub fields: Vec<RecordConstructorField>,
561    pub spread: Option<Box<DataExpr>>,
562    pub span: Span,
563
564    // analysis
565    #[serde(skip)]
566    pub scope: Option<Rc<Scope>>,
567}
568
569impl VariantCaseConstructor {
570    pub fn find_field_value(&self, field: &str) -> Option<&DataExpr> {
571        self.fields
572            .iter()
573            .find(|x| x.name.value == field)
574            .map(|x| x.value.as_ref())
575    }
576}
577
578#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
579pub struct UtxoRef {
580    pub txid: Vec<u8>,
581    pub index: u64,
582    pub span: Span,
583}
584
585#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
586pub struct DataBinaryOp {
587    pub left: Box<DataExpr>,
588    pub operator: BinaryOperator,
589    pub right: Box<DataExpr>,
590    pub span: Span,
591}
592
593#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
594pub enum DataExpr {
595    None,
596    Unit,
597    Number(i64),
598    Bool(bool),
599    String(StringLiteral),
600    HexString(HexStringLiteral),
601    Constructor(DatumConstructor),
602    Identifier(Identifier),
603    PropertyAccess(PropertyAccess),
604    BinaryOp(DataBinaryOp),
605    UtxoRef(UtxoRef),
606}
607
608impl DataExpr {
609    pub fn as_identifier(&self) -> Option<&Identifier> {
610        match self {
611            DataExpr::Identifier(x) => Some(x),
612            _ => None,
613        }
614    }
615}
616
617#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
618pub enum AddressExpr {
619    String(StringLiteral),
620    HexString(HexStringLiteral),
621    Identifier(Identifier),
622}
623
624impl AddressExpr {
625    pub fn as_identifier(&self) -> Option<&Identifier> {
626        match self {
627            AddressExpr::Identifier(x) => Some(x),
628            _ => None,
629        }
630    }
631}
632#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
633pub enum BinaryOperator {
634    Add,
635    Subtract,
636}
637
638#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
639pub enum Type {
640    Undefined,
641    Unit,
642    Int,
643    Bool,
644    Bytes,
645    Address,
646    UtxoRef,
647    AnyAsset,
648    Custom(Identifier),
649}
650
651#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
652pub struct ParamDef {
653    pub name: String,
654    pub r#type: Type,
655}
656
657#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
658pub struct TypeDef {
659    pub name: String,
660    pub cases: Vec<VariantCase>,
661    pub span: Span,
662}
663
664impl TypeDef {
665    pub(crate) fn find_case_index(&self, case: &str) -> Option<usize> {
666        self.cases.iter().position(|x| x.name == case)
667    }
668
669    #[allow(dead_code)]
670    pub(crate) fn find_case(&self, case: &str) -> Option<&VariantCase> {
671        self.cases.iter().find(|x| x.name == case)
672    }
673}
674
675#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
676pub struct VariantCase {
677    pub name: String,
678    pub fields: Vec<RecordField>,
679    pub span: Span,
680}
681
682impl VariantCase {
683    #[allow(dead_code)]
684    pub(crate) fn find_field_index(&self, field: &str) -> Option<usize> {
685        self.fields.iter().position(|x| x.name == field)
686    }
687
688    #[allow(dead_code)]
689    pub(crate) fn find_field(&self, field: &str) -> Option<&RecordField> {
690        self.fields.iter().find(|x| x.name == field)
691    }
692}
693
694#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
695pub struct AssetDef {
696    pub name: String,
697    pub policy: Option<HexStringLiteral>,
698    pub asset_name: Option<String>,
699    pub span: Span,
700}
701
702#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
703pub enum ChainSpecificBlock {
704    Cardano(crate::cardano::CardanoBlock),
705}