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 StructConstructor {
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 ListConstructor {
580    pub elements: Vec<DataExpr>,
581    pub span: Span,
582}
583
584#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
585pub struct UtxoRef {
586    pub txid: Vec<u8>,
587    pub index: u64,
588    pub span: Span,
589}
590
591#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
592pub struct DataBinaryOp {
593    pub left: Box<DataExpr>,
594    pub operator: BinaryOperator,
595    pub right: Box<DataExpr>,
596    pub span: Span,
597}
598
599#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
600pub enum DataExpr {
601    None,
602    Unit,
603    Number(i64),
604    Bool(bool),
605    String(StringLiteral),
606    HexString(HexStringLiteral),
607    StructConstructor(StructConstructor),
608    ListConstructor(ListConstructor),
609    Identifier(Identifier),
610    PropertyAccess(PropertyAccess),
611    BinaryOp(DataBinaryOp),
612    UtxoRef(UtxoRef),
613}
614
615impl DataExpr {
616    pub fn as_identifier(&self) -> Option<&Identifier> {
617        match self {
618            DataExpr::Identifier(x) => Some(x),
619            _ => None,
620        }
621    }
622}
623
624#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
625pub enum AddressExpr {
626    String(StringLiteral),
627    HexString(HexStringLiteral),
628    Identifier(Identifier),
629}
630
631impl AddressExpr {
632    pub fn as_identifier(&self) -> Option<&Identifier> {
633        match self {
634            AddressExpr::Identifier(x) => Some(x),
635            _ => None,
636        }
637    }
638}
639#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
640pub enum BinaryOperator {
641    Add,
642    Subtract,
643}
644
645#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
646pub enum Type {
647    Undefined,
648    Unit,
649    Int,
650    Bool,
651    Bytes,
652    Address,
653    UtxoRef,
654    AnyAsset,
655    List(Box<Type>),
656    Custom(Identifier),
657}
658
659#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
660pub struct ParamDef {
661    pub name: String,
662    pub r#type: Type,
663}
664
665#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
666pub struct TypeDef {
667    pub name: String,
668    pub cases: Vec<VariantCase>,
669    pub span: Span,
670}
671
672impl TypeDef {
673    pub(crate) fn find_case_index(&self, case: &str) -> Option<usize> {
674        self.cases.iter().position(|x| x.name == case)
675    }
676
677    #[allow(dead_code)]
678    pub(crate) fn find_case(&self, case: &str) -> Option<&VariantCase> {
679        self.cases.iter().find(|x| x.name == case)
680    }
681}
682
683#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
684pub struct VariantCase {
685    pub name: String,
686    pub fields: Vec<RecordField>,
687    pub span: Span,
688}
689
690impl VariantCase {
691    #[allow(dead_code)]
692    pub(crate) fn find_field_index(&self, field: &str) -> Option<usize> {
693        self.fields.iter().position(|x| x.name == field)
694    }
695
696    #[allow(dead_code)]
697    pub(crate) fn find_field(&self, field: &str) -> Option<&RecordField> {
698        self.fields.iter().find(|x| x.name == field)
699    }
700}
701
702#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
703pub struct AssetDef {
704    pub name: String,
705    pub policy: Option<HexStringLiteral>,
706    pub asset_name: Option<String>,
707    pub span: Span,
708}
709
710#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
711pub enum ChainSpecificBlock {
712    Cardano(crate::cardano::CardanoBlock),
713}