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 inputs: Vec<InputBlock>,
175    pub outputs: Vec<OutputBlock>,
176    pub burn: Option<BurnBlock>,
177    pub mint: Option<MintBlock>,
178    pub adhoc: Vec<ChainSpecificBlock>,
179    pub span: Span,
180
181    // analysis
182    #[serde(skip)]
183    pub(crate) scope: Option<Rc<Scope>>,
184}
185
186#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
187pub struct StringLiteral {
188    pub value: String,
189    pub span: Span,
190}
191
192impl StringLiteral {
193    pub fn new(value: impl Into<String>) -> Self {
194        Self {
195            value: value.into(),
196            span: Span::DUMMY,
197        }
198    }
199}
200
201#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
202pub struct HexStringLiteral {
203    pub value: String,
204    pub span: Span,
205}
206
207impl HexStringLiteral {
208    pub fn new(value: impl Into<String>) -> Self {
209        Self {
210            value: value.into(),
211            span: Span::DUMMY,
212        }
213    }
214}
215
216#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
217pub enum InputBlockField {
218    From(AddressExpr),
219    DatumIs(Type),
220    MinAmount(AssetExpr),
221    Redeemer(DataExpr),
222    Ref(DataExpr),
223}
224
225impl InputBlockField {
226    fn key(&self) -> &str {
227        match self {
228            InputBlockField::From(_) => "from",
229            InputBlockField::DatumIs(_) => "datum_is",
230            InputBlockField::MinAmount(_) => "min_amount",
231            InputBlockField::Redeemer(_) => "redeemer",
232            InputBlockField::Ref(_) => "ref",
233        }
234    }
235
236    pub fn as_address_expr(&self) -> Option<&AddressExpr> {
237        match self {
238            InputBlockField::From(x) => Some(x),
239            _ => None,
240        }
241    }
242
243    pub fn as_asset_expr(&self) -> Option<&AssetExpr> {
244        match self {
245            InputBlockField::MinAmount(x) => Some(x),
246            _ => None,
247        }
248    }
249
250    pub fn as_data_expr(&self) -> Option<&DataExpr> {
251        match self {
252            InputBlockField::Redeemer(x) => Some(x),
253            InputBlockField::Ref(x) => Some(x),
254            _ => None,
255        }
256    }
257
258    pub fn as_datum_type(&self) -> Option<&Type> {
259        match self {
260            InputBlockField::DatumIs(x) => Some(x),
261            _ => None,
262        }
263    }
264}
265
266#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
267pub struct InputBlock {
268    pub name: String,
269    pub is_many: bool,
270    pub fields: Vec<InputBlockField>,
271    pub span: Span,
272}
273
274impl InputBlock {
275    pub(crate) fn find(&self, key: &str) -> Option<&InputBlockField> {
276        self.fields.iter().find(|x| x.key() == key)
277    }
278
279    pub(crate) fn datum_is(&self) -> Type {
280        self.find("datum_is")
281            .and_then(|x| x.as_datum_type())
282            .cloned()
283            .unwrap_or(Type::Unit)
284    }
285}
286
287#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
288pub enum OutputBlockField {
289    To(Box<AddressExpr>),
290    Amount(Box<AssetExpr>),
291    Datum(Box<DataExpr>),
292}
293
294impl OutputBlockField {
295    fn key(&self) -> &str {
296        match self {
297            OutputBlockField::To(_) => "to",
298            OutputBlockField::Amount(_) => "amount",
299            OutputBlockField::Datum(_) => "datum",
300        }
301    }
302}
303
304#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
305pub struct OutputBlock {
306    pub name: Option<String>,
307    pub fields: Vec<OutputBlockField>,
308    pub span: Span,
309}
310
311impl OutputBlock {
312    pub(crate) fn find(&self, key: &str) -> Option<&OutputBlockField> {
313        self.fields.iter().find(|x| x.key() == key)
314    }
315}
316
317#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
318pub enum MintBlockField {
319    Amount(Box<AssetExpr>),
320    Redeemer(Box<DataExpr>),
321}
322
323impl MintBlockField {
324    fn key(&self) -> &str {
325        match self {
326            MintBlockField::Amount(_) => "amount",
327            MintBlockField::Redeemer(_) => "redeemer",
328        }
329    }
330}
331
332#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
333pub struct MintBlock {
334    pub fields: Vec<MintBlockField>,
335    pub span: Span,
336}
337
338impl MintBlock {
339    pub(crate) fn find(&self, key: &str) -> Option<&MintBlockField> {
340        self.fields.iter().find(|x| x.key() == key)
341    }
342}
343
344#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
345pub struct BurnBlock {
346    pub fields: Vec<MintBlockField>,
347    pub span: Span,
348}
349
350#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
351pub struct RecordField {
352    pub name: String,
353    pub r#type: Type,
354    pub span: Span,
355}
356
357impl RecordField {
358    pub fn new(name: &str, r#type: Type) -> Self {
359        Self {
360            name: name.to_string(),
361            r#type,
362            span: Span::DUMMY,
363        }
364    }
365}
366
367#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
368pub struct PartyDef {
369    pub name: String,
370    pub span: Span,
371}
372
373#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
374pub struct PartyField {
375    pub name: String,
376    pub party_type: String,
377}
378
379#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
380pub struct PolicyDef {
381    pub name: String,
382    pub value: PolicyValue,
383    pub span: Span,
384}
385
386#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
387pub enum PolicyField {
388    Hash(DataExpr),
389    Script(DataExpr),
390    Ref(DataExpr),
391}
392
393#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
394pub struct PolicyConstructor {
395    pub fields: Vec<PolicyField>,
396    pub span: Span,
397}
398
399impl PolicyConstructor {
400    pub(crate) fn find_field(&self, field: &str) -> Option<&PolicyField> {
401        self.fields.iter().find(|x| match x {
402            PolicyField::Hash(_) => field == "hash",
403            PolicyField::Script(_) => field == "script",
404            PolicyField::Ref(_) => field == "ref",
405        })
406    }
407}
408
409#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
410pub enum PolicyValue {
411    Constructor(PolicyConstructor),
412    Assign(HexStringLiteral),
413}
414
415#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
416pub struct AssetConstructor {
417    pub r#type: Identifier,
418    pub amount: Box<DataExpr>,
419    pub span: Span,
420}
421
422#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
423pub struct AssetBinaryOp {
424    pub left: Box<AssetExpr>,
425    pub operator: BinaryOperator,
426    pub right: Box<AssetExpr>,
427    pub span: Span,
428}
429
430#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
431pub enum AssetExpr {
432    Constructor(AssetConstructor),
433    BinaryOp(AssetBinaryOp),
434    PropertyAccess(PropertyAccess),
435    Identifier(Identifier),
436}
437
438#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
439pub struct PropertyAccess {
440    pub object: Identifier,
441    pub path: Vec<Identifier>,
442    pub span: Span,
443
444    // analysis
445    #[serde(skip)]
446    pub(crate) scope: Option<Rc<Scope>>,
447}
448
449impl PropertyAccess {
450    pub fn new(object: &str, path: &[&str]) -> Self {
451        Self {
452            object: Identifier::new(object),
453            path: path.iter().map(|x| Identifier::new(*x)).collect(),
454            scope: None,
455            span: Span::DUMMY,
456        }
457    }
458}
459
460impl PropertyAccess {
461    /// Shift the property access to the next property in the path.
462    pub fn shift(mut self) -> Option<Self> {
463        if self.path.is_empty() {
464            return None;
465        }
466
467        let new_object = self.path.remove(0);
468        self.object = new_object;
469
470        Some(self)
471    }
472}
473
474#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
475pub struct RecordConstructorField {
476    pub name: Identifier,
477    pub value: Box<DataExpr>,
478    pub span: Span,
479}
480
481#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
482pub struct DatumConstructor {
483    pub r#type: Identifier,
484    pub case: VariantCaseConstructor,
485    pub span: Span,
486
487    // analysis
488    #[serde(skip)]
489    pub scope: Option<Rc<Scope>>,
490}
491
492#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
493pub struct VariantCaseConstructor {
494    pub name: Identifier,
495    pub fields: Vec<RecordConstructorField>,
496    pub spread: Option<Box<DataExpr>>,
497    pub span: Span,
498
499    // analysis
500    #[serde(skip)]
501    pub scope: Option<Rc<Scope>>,
502}
503
504impl VariantCaseConstructor {
505    pub fn find_field_value(&self, field: &str) -> Option<&DataExpr> {
506        self.fields
507            .iter()
508            .find(|x| x.name.value == field)
509            .map(|x| x.value.as_ref())
510    }
511}
512
513#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
514pub struct DataBinaryOp {
515    pub left: Box<DataExpr>,
516    pub operator: BinaryOperator,
517    pub right: Box<DataExpr>,
518    pub span: Span,
519}
520
521#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
522pub enum DataExpr {
523    None,
524    Unit,
525    Number(i64),
526    Bool(bool),
527    String(StringLiteral),
528    HexString(HexStringLiteral),
529    Constructor(DatumConstructor),
530    Identifier(Identifier),
531    PropertyAccess(PropertyAccess),
532    BinaryOp(DataBinaryOp),
533}
534
535impl DataExpr {
536    pub fn as_identifier(&self) -> Option<&Identifier> {
537        match self {
538            DataExpr::Identifier(x) => Some(x),
539            _ => None,
540        }
541    }
542}
543
544#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
545pub enum AddressExpr {
546    String(StringLiteral),
547    HexString(HexStringLiteral),
548    Identifier(Identifier),
549}
550
551impl AddressExpr {
552    pub fn as_identifier(&self) -> Option<&Identifier> {
553        match self {
554            AddressExpr::Identifier(x) => Some(x),
555            _ => None,
556        }
557    }
558}
559#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
560pub enum BinaryOperator {
561    Add,
562    Subtract,
563}
564
565#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
566pub enum Type {
567    Unit,
568    Int,
569    Bool,
570    Bytes,
571    Address,
572    UtxoRef,
573    AnyAsset,
574    Custom(Identifier),
575}
576
577#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
578pub struct ParamDef {
579    pub name: String,
580    pub r#type: Type,
581}
582
583#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
584pub struct TypeDef {
585    pub name: String,
586    pub cases: Vec<VariantCase>,
587    pub span: Span,
588}
589
590impl TypeDef {
591    pub(crate) fn find_case_index(&self, case: &str) -> Option<usize> {
592        self.cases.iter().position(|x| x.name == case)
593    }
594
595    #[allow(dead_code)]
596    pub(crate) fn find_case(&self, case: &str) -> Option<&VariantCase> {
597        self.cases.iter().find(|x| x.name == case)
598    }
599}
600
601#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
602pub struct VariantCase {
603    pub name: String,
604    pub fields: Vec<RecordField>,
605    pub span: Span,
606}
607
608impl VariantCase {
609    #[allow(dead_code)]
610    pub(crate) fn find_field_index(&self, field: &str) -> Option<usize> {
611        self.fields.iter().position(|x| x.name == field)
612    }
613
614    #[allow(dead_code)]
615    pub(crate) fn find_field(&self, field: &str) -> Option<&RecordField> {
616        self.fields.iter().find(|x| x.name == field)
617    }
618}
619
620#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
621pub struct AssetDef {
622    pub name: String,
623    pub policy: HexStringLiteral,
624    pub asset_name: String,
625    pub span: Span,
626}
627
628#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
629pub enum ChainSpecificBlock {
630    Cardano(crate::cardano::CardanoBlock),
631}