diff --git a/crates/tx3-lang/src/analyzing.rs b/crates/tx3-lang/src/analyzing.rs index ec7abed..2ccd88b 100644 --- a/crates/tx3-lang/src/analyzing.rs +++ b/crates/tx3-lang/src/analyzing.rs @@ -750,6 +750,16 @@ impl Analyzable for ListConstructor { } } +impl Analyzable for TupleConstructor { + fn analyze(&mut self, parent: Option>) -> AnalyzeReport { + self.elements.analyze(parent) + } + + fn is_resolved(&self) -> bool { + self.elements.is_resolved() + } +} + impl Analyzable for MapField { fn analyze(&mut self, parent: Option>) -> AnalyzeReport { self.key.analyze(parent.clone()) + self.value.analyze(parent.clone()) @@ -776,6 +786,7 @@ impl Analyzable for DataExpr { DataExpr::StructConstructor(x) => x.analyze(parent), DataExpr::ListConstructor(x) => x.analyze(parent), DataExpr::MapConstructor(x) => x.analyze(parent), + DataExpr::TupleConstructor(x) => x.analyze(parent), DataExpr::Identifier(x) => x.analyze(parent), DataExpr::AddOp(x) => x.analyze(parent), DataExpr::SubOp(x) => x.analyze(parent), @@ -795,6 +806,7 @@ impl Analyzable for DataExpr { DataExpr::StructConstructor(x) => x.is_resolved(), DataExpr::ListConstructor(x) => x.is_resolved(), DataExpr::MapConstructor(x) => x.is_resolved(), + DataExpr::TupleConstructor(x) => x.is_resolved(), DataExpr::Identifier(x) => x.is_resolved(), DataExpr::AddOp(x) => x.is_resolved(), DataExpr::SubOp(x) => x.is_resolved(), @@ -942,6 +954,7 @@ impl Analyzable for Type { Type::Map(key_type, value_type) => { key_type.analyze(parent.clone()) + value_type.analyze(parent) } + Type::Tuple(elements) => elements.analyze(parent), _ => AnalyzeReport::default(), } } @@ -951,6 +964,7 @@ impl Analyzable for Type { Type::Custom(x) => x.is_resolved(), Type::List(x) => x.is_resolved(), Type::Map(key_type, value_type) => key_type.is_resolved() && value_type.is_resolved(), + Type::Tuple(elements) => elements.iter().all(|t| t.is_resolved()), _ => true, } } diff --git a/crates/tx3-lang/src/ast.rs b/crates/tx3-lang/src/ast.rs index a9c300d..5b2faef 100644 --- a/crates/tx3-lang/src/ast.rs +++ b/crates/tx3-lang/src/ast.rs @@ -626,6 +626,25 @@ impl ListConstructor { } } +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct TupleConstructor { + pub elements: Vec, + pub span: Span, +} + +impl TupleConstructor { + pub fn target_type(&self) -> Option { + // A tuple's type is known only when every element's type is known. + let elements = self + .elements + .iter() + .map(|x| x.target_type()) + .collect::>>()?; + + Some(Type::Tuple(elements)) + } +} + #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct MapField { pub key: DataExpr, @@ -689,6 +708,14 @@ pub struct PropertyOp { impl PropertyOp { pub fn target_type(&self) -> Option { + // Positional tuple access (`t.0`) resolves to the element type at that + // index; for every other operand the property carries its own type. + if let (Some(Type::Tuple(elements)), DataExpr::Number(index)) = + (self.operand.target_type(), self.property.as_ref()) + { + return elements.get(*index as usize).cloned(); + } + self.property.target_type() } } @@ -785,6 +812,7 @@ pub enum DataExpr { StructConstructor(StructConstructor), ListConstructor(ListConstructor), MapConstructor(MapConstructor), + TupleConstructor(TupleConstructor), AnyAssetConstructor(AnyAssetConstructor), Identifier(Identifier), AddOp(AddOp), @@ -821,6 +849,7 @@ impl DataExpr { Some(inner) => Some(Type::List(Box::new(inner))), None => None, }, + DataExpr::TupleConstructor(x) => x.target_type(), DataExpr::AddOp(x) => x.target_type(), DataExpr::SubOp(x) => x.target_type(), DataExpr::MulOp(x) => x.target_type(), @@ -864,6 +893,7 @@ pub enum Type { AnyAsset, List(Box), Map(Box, Box), + Tuple(Vec), Custom(Identifier), } @@ -881,6 +911,14 @@ impl std::fmt::Display for Type { Type::Utxo => write!(f, "Utxo"), Type::Map(key, value) => write!(f, "Map<{}, {}>", key, value), Type::List(inner) => write!(f, "List<{inner}>"), + Type::Tuple(elements) => { + let inner = elements + .iter() + .map(|t| t.to_string()) + .collect::>() + .join(", "); + write!(f, "Tuple<{inner}>") + } Type::Custom(id) => write!(f, "{}", id.value), } } @@ -932,6 +970,14 @@ impl Type { .target_type() .filter(|ty| *ty == Type::Int) .map(|_| property), + // Positional tuple access (`t.0`): the property is a literal index, + // valid only when it falls within the tuple's arity. + Type::Tuple(elements) => match property { + DataExpr::Number(index) if (0..elements.len() as i64).contains(&index) => { + Some(DataExpr::Number(index)) + } + _ => None, + }, _ => None, } } diff --git a/crates/tx3-lang/src/facade.rs b/crates/tx3-lang/src/facade.rs index 39f507d..0e78c8c 100644 --- a/crates/tx3-lang/src/facade.rs +++ b/crates/tx3-lang/src/facade.rs @@ -165,4 +165,45 @@ pub mod tests { workspace.analyze().unwrap(); workspace.lower().unwrap(); } + + // End-to-end: a `Tuple<..>` param type, a `(..)` tuple literal, and `[i]` + // positional access all flow through parse -> analyze -> lower. + #[test] + fn tuple_end_to_end() { + use tx3_tir::model::v1beta0::Expression; + + let src = r#" + party Sender; + party Receiver; + + tx swap( + pair: Tuple + ) { + input source { + from: Sender, + min_amount: Ada(pair[0]), + } + + output { + to: Receiver, + amount: Ada(pair[0]), + datum: (pair[0], pair[1]), + } + } + "#; + + let mut workspace = Workspace::from_string(src.to_string()); + workspace.parse().unwrap(); + workspace.analyze().unwrap(); + workspace.lower().unwrap(); + + let tir = workspace.tir("swap").unwrap(); + + // The output datum lowered to an N-element tuple expression. + let datum = &tir.outputs[0].datum; + assert!( + matches!(datum, Expression::Tuple(elements) if elements.len() == 2), + "expected a 2-element tuple datum, got {datum:?}" + ); + } } diff --git a/crates/tx3-lang/src/lowering.rs b/crates/tx3-lang/src/lowering.rs index 48d3cbe..0ebe9ab 100644 --- a/crates/tx3-lang/src/lowering.rs +++ b/crates/tx3-lang/src/lowering.rs @@ -412,6 +412,7 @@ impl IntoLower for ast::Type { ast::Type::AnyAsset => Ok(Type::AnyAsset), ast::Type::List(_) => Ok(Type::List), ast::Type::Map(_, _) => Ok(Type::Map), + ast::Type::Tuple(_) => Ok(Type::Tuple), ast::Type::Custom(x) => Ok(Type::Custom(x.value.clone())), } } @@ -611,6 +612,20 @@ impl IntoLower for ast::ListConstructor { } } +impl IntoLower for ast::TupleConstructor { + type Output = ir::Expression; + + fn into_lower(&self, ctx: &Context) -> Result { + let elements = self + .elements + .iter() + .map(|x| x.into_lower(ctx)) + .collect::, _>>()?; + + Ok(ir::Expression::Tuple(elements)) + } +} + impl IntoLower for ast::MapConstructor { type Output = ir::Expression; @@ -642,6 +657,7 @@ impl IntoLower for ast::DataExpr { ast::DataExpr::StructConstructor(x) => ir::Expression::Struct(x.into_lower(ctx)?), ast::DataExpr::ListConstructor(x) => ir::Expression::List(x.into_lower(ctx)?), ast::DataExpr::MapConstructor(x) => x.into_lower(ctx)?, + ast::DataExpr::TupleConstructor(x) => x.into_lower(ctx)?, ast::DataExpr::AnyAssetConstructor(x) => x.into_lower(ctx)?, ast::DataExpr::Unit => ir::Expression::Struct(ir::StructExpr::unit()), ast::DataExpr::Identifier(x) => x.into_lower(ctx)?, @@ -1129,6 +1145,8 @@ mod tests { test_lowering!(lang_tour); + test_lowering!(tuples); + test_lowering!(transfer); test_lowering!(swap); diff --git a/crates/tx3-lang/src/parsing.rs b/crates/tx3-lang/src/parsing.rs index 58b2836..45ad16f 100644 --- a/crates/tx3-lang/src/parsing.rs +++ b/crates/tx3-lang/src/parsing.rs @@ -1232,6 +1232,23 @@ impl AstNode for MapConstructor { } } +impl AstNode for TupleConstructor { + const RULE: Rule = Rule::tuple_constructor; + + fn parse(pair: Pair) -> Result { + let span = pair.as_span().into(); + let inner = pair.into_inner(); + + let elements = inner.map(DataExpr::parse).collect::, _>>()?; + + Ok(TupleConstructor { elements, span }) + } + + fn span(&self) -> &Span { + &self.span + } +} + impl DataExpr { fn number_parse(pair: Pair) -> Result { Ok(DataExpr::Number(pair.as_str().parse().unwrap())) @@ -1257,6 +1274,10 @@ impl DataExpr { Ok(DataExpr::MapConstructor(MapConstructor::parse(pair)?)) } + fn tuple_constructor_parse(pair: Pair) -> Result { + Ok(DataExpr::TupleConstructor(TupleConstructor::parse(pair)?)) + } + fn utxo_ref_parse(pair: Pair) -> Result { Ok(DataExpr::UtxoRef(UtxoRef::parse(pair)?)) } @@ -1372,6 +1393,7 @@ impl AstNode for DataExpr { Rule::struct_constructor => DataExpr::struct_constructor_parse(x), Rule::list_constructor => DataExpr::list_constructor_parse(x), Rule::map_constructor => DataExpr::map_constructor_parse(x), + Rule::tuple_constructor => DataExpr::tuple_constructor_parse(x), Rule::unit => Ok(DataExpr::Unit), Rule::identifier => DataExpr::identifier_parse(x), Rule::utxo_ref => DataExpr::utxo_ref_parse(x), @@ -1411,6 +1433,7 @@ impl AstNode for DataExpr { DataExpr::StructConstructor(x) => x.span(), DataExpr::ListConstructor(x) => x.span(), DataExpr::MapConstructor(x) => x.span(), + DataExpr::TupleConstructor(x) => x.span(), DataExpr::AnyAssetConstructor(x) => x.span(), DataExpr::Identifier(x) => x.span(), DataExpr::AddOp(x) => &x.span, @@ -1452,6 +1475,13 @@ impl AstNode for Type { let value_type = Type::parse(inner.next().unwrap())?; Ok(Type::Map(Box::new(key_type), Box::new(value_type))) } + Rule::tuple_type => { + let elements = inner + .into_inner() + .map(Type::parse) + .collect::, _>>()?; + Ok(Type::Tuple(elements)) + } Rule::custom_type => Ok(Type::Custom(Identifier::new(inner.as_str().to_owned()))), x => unreachable!("Unexpected rule in type: {:?}", x), } @@ -1753,6 +1783,34 @@ mod tests { Type::List(Box::new(Type::List(Box::new(Type::Int)))) ); + input_to_ast_check!( + Type, + "tuple", + "Tuple", + Type::Tuple(vec![Type::Int, Type::Bytes]) + ); + + input_to_ast_check!( + Type, + "tuple_three_with_nested", + "Tuple>", + Type::Tuple(vec![ + Type::Int, + Type::Bytes, + Type::List(Box::new(Type::Bool)), + ]) + ); + + input_to_ast_check!( + Type, + "tuple_nested", + "Tuple, Bytes>", + Type::Tuple(vec![ + Type::Tuple(vec![Type::Int, Type::Int]), + Type::Bytes, + ]) + ); + input_to_ast_check!( TypeDef, "type_def_record", @@ -2015,6 +2073,49 @@ mod tests { } ); + // A two-or-more element parenthesized list is a tuple literal. + input_to_ast_check!( + DataExpr, + "tuple_literal", + "(1, 0xFF, true)", + DataExpr::TupleConstructor(TupleConstructor { + elements: vec![ + DataExpr::Number(1), + DataExpr::HexString(HexStringLiteral::new("FF".to_string())), + DataExpr::Bool(true), + ], + span: Span::DUMMY, + }) + ); + + // Trailing comma is permitted in a tuple literal. + input_to_ast_check!( + DataExpr, + "tuple_literal_trailing_comma", + "(1, 2,)", + DataExpr::TupleConstructor(TupleConstructor { + elements: vec![DataExpr::Number(1), DataExpr::Number(2)], + span: Span::DUMMY, + }) + ); + + // A single parenthesized expression is grouping, NOT a one-tuple. + input_to_ast_check!(DataExpr, "grouping_not_tuple", "(42)", DataExpr::Number(42)); + + // Positional tuple access uses bracket indexing with a literal index; it + // lowers through the same PropertyOp path as struct/list access. + input_to_ast_check!( + DataExpr, + "tuple_index_access", + "my_tuple[0]", + DataExpr::PropertyOp(PropertyOp { + operand: Box::new(DataExpr::Identifier(Identifier::new("my_tuple"))), + property: Box::new(DataExpr::Number(0)), + span: Span::DUMMY, + scope: None, + }) + ); + input_to_ast_check!(DataExpr, "literal_bool_true", "true", DataExpr::Bool(true)); input_to_ast_check!( @@ -3124,6 +3225,8 @@ mod tests { test_parsing!(lang_tour); + test_parsing!(tuples); + test_parsing!(transfer); test_parsing!(swap); diff --git a/crates/tx3-lang/src/tx3.pest b/crates/tx3-lang/src/tx3.pest index da0ee9e..6ece057 100644 --- a/crates/tx3-lang/src/tx3.pest +++ b/crates/tx3-lang/src/tx3.pest @@ -30,11 +30,14 @@ primitive_type = { custom_type = { identifier } list_type = { "List<" ~ type ~ ">" } map_type = { "Map<" ~ type ~ "," ~ type ~ ">"} +// At least two element types; trailing comma optional. +tuple_type = { "Tuple<" ~ type ~ ("," ~ type)+ ~ ","? ~ ">" } type = { primitive_type | list_type | map_type | + tuple_type | custom_type } @@ -165,6 +168,7 @@ data_expr = { data_prefix* ~ data_primary ~ data_postfix* ~ (data_infix ~ data_p any_asset_constructor | fn_call | identifier | + tuple_constructor | "(" ~ data_expr ~ ")" } @@ -201,6 +205,12 @@ list_constructor = { "[" ~ (data_expr ~ ",")* ~ data_expr? ~ "]" } +// At least two elements distinguishes a tuple from a parenthesized expression; +// `()` is the unit literal and `(x)` is grouping. Trailing comma optional. +tuple_constructor = { + "(" ~ data_expr ~ ("," ~ data_expr)+ ~ ","? ~ ")" +} + map_field = { data_expr ~ ":" ~ data_expr } diff --git a/examples/lang_tour.ast b/examples/lang_tour.ast index cfe0e4c..38d8b1d 100644 --- a/examples/lang_tour.ast +++ b/examples/lang_tour.ast @@ -52,8 +52,8 @@ "value": "my_tx", "span": { "dummy": false, - "start": 540, - "end": 545 + "start": 571, + "end": 576 } }, "parameters": { @@ -63,8 +63,8 @@ "value": "quantity", "span": { "dummy": false, - "start": 551, - "end": 559 + "start": 582, + "end": 590 } }, "type": "Int" @@ -74,8 +74,8 @@ "value": "validUntil", "span": { "dummy": false, - "start": 570, - "end": 580 + "start": 601, + "end": 611 } }, "type": "Int" @@ -85,8 +85,8 @@ "value": "metadata", "span": { "dummy": false, - "start": 591, - "end": 599 + "start": 622, + "end": 630 } }, "type": "Bytes" @@ -94,8 +94,8 @@ ], "span": { "dummy": false, - "start": 545, - "end": 609 + "start": 576, + "end": 640 } }, "locals": { @@ -105,8 +105,8 @@ "value": "local_var", "span": { "dummy": false, - "start": 1914, - "end": 1923 + "start": 1985, + "end": 1994 } }, "value": { @@ -116,8 +116,8 @@ "value": "Lang", "span": { "dummy": false, - "start": 1932, - "end": 1938 + "start": 2003, + "end": 2009 } } }, @@ -126,29 +126,85 @@ "value": "Tour", "span": { "dummy": false, - "start": 1940, - "end": 1946 + "start": 2011, + "end": 2017 } } }, "span": { "dummy": false, - "start": 1925, - "end": 1947 + "start": 1996, + "end": 2018 } } }, "span": { "dummy": false, - "start": 1914, - "end": 1947 + "start": 1985, + "end": 2018 + } + }, + { + "name": { + "value": "tuple_first", + "span": { + "dummy": false, + "start": 2028, + "end": 2039 + } + }, + "value": { + "PropertyOp": { + "operand": { + "PropertyOp": { + "operand": { + "Identifier": { + "value": "source", + "span": { + "dummy": false, + "start": 2041, + "end": 2047 + } + } + }, + "property": { + "Identifier": { + "value": "field6", + "span": { + "dummy": false, + "start": 2048, + "end": 2054 + } + } + }, + "span": { + "dummy": false, + "start": 2047, + "end": 2054 + } + } + }, + "property": { + "Number": 0 + }, + "span": { + "dummy": false, + "start": 2054, + "end": 2057 + } + } + }, + "span": { + "dummy": false, + "start": 2028, + "end": 2057 } } ], "span": { "dummy": false, - "start": 1897, - "end": 1954 + "start": 1968, + "end": 2064 } }, "references": [ @@ -164,15 +220,16 @@ "index": 2, "span": { "dummy": false, - "start": 1220, - "end": 1230 + "start": 1251, + "end": 1261 } } }, + "datum_is": null, "span": { "dummy": false, - "start": 1185, - "end": 1237 + "start": 1216, + "end": 1268 } } ], @@ -187,8 +244,8 @@ "value": "MyParty", "span": { "dummy": false, - "start": 645, - "end": 652 + "start": 676, + "end": 683 } } } @@ -214,8 +271,8 @@ "value": "Ada", "span": { "dummy": false, - "start": 702, - "end": 705 + "start": 733, + "end": 736 } }, "args": [ @@ -224,16 +281,16 @@ "value": "quantity", "span": { "dummy": false, - "start": 706, - "end": 714 + "start": 737, + "end": 745 } } } ], "span": { "dummy": false, - "start": 702, - "end": 715 + "start": 733, + "end": 746 } } }, @@ -243,8 +300,8 @@ "value": "min_utxo", "span": { "dummy": false, - "start": 718, - "end": 726 + "start": 749, + "end": 757 } }, "args": [ @@ -253,23 +310,23 @@ "value": "named_output", "span": { "dummy": false, - "start": 727, - "end": 739 + "start": 758, + "end": 770 } } } ], "span": { "dummy": false, - "start": 718, - "end": 740 + "start": 749, + "end": 771 } } }, "span": { "dummy": false, - "start": 716, - "end": 717 + "start": 747, + "end": 748 } } } @@ -281,8 +338,8 @@ "value": "MyVariant", "span": { "dummy": false, - "start": 760, - "end": 769 + "start": 791, + "end": 800 } }, "case": { @@ -290,8 +347,8 @@ "value": "Case1", "span": { "dummy": false, - "start": 771, - "end": 776 + "start": 802, + "end": 807 } }, "fields": [ @@ -300,8 +357,8 @@ "value": "field1", "span": { "dummy": false, - "start": 791, - "end": 797 + "start": 822, + "end": 828 } }, "value": { @@ -309,15 +366,15 @@ "value": "field_a", "span": { "dummy": false, - "start": 799, - "end": 806 + "start": 830, + "end": 837 } } }, "span": { "dummy": false, - "start": 791, - "end": 806 + "start": 822, + "end": 837 } }, { @@ -325,8 +382,8 @@ "value": "field2", "span": { "dummy": false, - "start": 820, - "end": 826 + "start": 851, + "end": 857 } }, "value": { @@ -334,15 +391,15 @@ "value": "AFAFAF", "span": { "dummy": false, - "start": 828, - "end": 836 + "start": 859, + "end": 867 } } }, "span": { "dummy": false, - "start": 820, - "end": 836 + "start": 851, + "end": 867 } }, { @@ -350,8 +407,8 @@ "value": "field3", "span": { "dummy": false, - "start": 850, - "end": 856 + "start": 881, + "end": 887 } }, "value": { @@ -359,29 +416,29 @@ "value": "quantity", "span": { "dummy": false, - "start": 858, - "end": 866 + "start": 889, + "end": 897 } } }, "span": { "dummy": false, - "start": 850, - "end": 866 + "start": 881, + "end": 897 } } ], "spread": null, "span": { "dummy": false, - "start": 769, - "end": 877 + "start": 800, + "end": 908 } }, "span": { "dummy": false, - "start": 760, - "end": 877 + "start": 791, + "end": 908 } } } @@ -389,8 +446,8 @@ ], "span": { "dummy": false, - "start": 616, - "end": 884 + "start": 647, + "end": 915 } } ], @@ -400,8 +457,8 @@ "value": "named_output", "span": { "dummy": false, - "start": 1250, - "end": 1262 + "start": 1281, + "end": 1293 } }, "optional": false, @@ -412,8 +469,8 @@ "value": "MyParty", "span": { "dummy": false, - "start": 1277, - "end": 1284 + "start": 1308, + "end": 1315 } } } @@ -425,8 +482,8 @@ "value": "MyRecord", "span": { "dummy": false, - "start": 1301, - "end": 1309 + "start": 1332, + "end": 1340 } }, "case": { @@ -444,8 +501,8 @@ "value": "field1", "span": { "dummy": false, - "start": 1324, - "end": 1330 + "start": 1355, + "end": 1361 } }, "value": { @@ -453,15 +510,15 @@ "value": "quantity", "span": { "dummy": false, - "start": 1332, - "end": 1340 + "start": 1363, + "end": 1371 } } }, "span": { "dummy": false, - "start": 1324, - "end": 1340 + "start": 1355, + "end": 1371 } }, { @@ -469,8 +526,8 @@ "value": "field2", "span": { "dummy": false, - "start": 1354, - "end": 1360 + "start": 1385, + "end": 1391 } }, "value": { @@ -485,8 +542,8 @@ }, "span": { "dummy": false, - "start": 1366, - "end": 1367 + "start": 1397, + "end": 1398 } } }, @@ -500,22 +557,22 @@ }, "span": { "dummy": false, - "start": 1377, - "end": 1378 + "start": 1408, + "end": 1409 } } }, "span": { "dummy": false, - "start": 1372, - "end": 1373 + "start": 1403, + "end": 1404 } } }, "span": { "dummy": false, - "start": 1354, - "end": 1381 + "start": 1385, + "end": 1412 } }, { @@ -523,8 +580,8 @@ "value": "field4", "span": { "dummy": false, - "start": 1395, - "end": 1401 + "start": 1426, + "end": 1432 } }, "value": { @@ -546,8 +603,8 @@ "value": "source", "span": { "dummy": false, - "start": 1413, - "end": 1419 + "start": 1444, + "end": 1450 } } }, @@ -556,30 +613,30 @@ "value": "field1", "span": { "dummy": false, - "start": 1420, - "end": 1426 + "start": 1451, + "end": 1457 } } }, "span": { "dummy": false, - "start": 1419, - "end": 1426 + "start": 1450, + "end": 1457 } } } ], "span": { "dummy": false, - "start": 1403, - "end": 1427 + "start": 1434, + "end": 1458 } } }, "span": { "dummy": false, - "start": 1395, - "end": 1427 + "start": 1426, + "end": 1458 } }, { @@ -587,8 +644,8 @@ "value": "field5", "span": { "dummy": false, - "start": 1441, - "end": 1447 + "start": 1472, + "end": 1478 } }, "value": { @@ -603,15 +660,15 @@ "value": "Value1", "span": { "dummy": false, - "start": 1453, - "end": 1461 + "start": 1484, + "end": 1492 } } }, "span": { "dummy": false, - "start": 1450, - "end": 1461 + "start": 1481, + "end": 1492 } }, { @@ -623,29 +680,75 @@ "value": "Value2", "span": { "dummy": false, - "start": 1466, - "end": 1474 + "start": 1497, + "end": 1505 } } }, "span": { "dummy": false, - "start": 1463, - "end": 1474 + "start": 1494, + "end": 1505 } } ], "span": { "dummy": false, - "start": 1449, - "end": 1476 + "start": 1480, + "end": 1507 } } }, "span": { "dummy": false, - "start": 1441, - "end": 1476 + "start": 1472, + "end": 1507 + } + }, + { + "name": { + "value": "field6", + "span": { + "dummy": false, + "start": 1521, + "end": 1527 + } + }, + "value": { + "TupleConstructor": { + "elements": [ + { + "Identifier": { + "value": "quantity", + "span": { + "dummy": false, + "start": 1530, + "end": 1538 + } + } + }, + { + "HexString": { + "value": "AABB", + "span": { + "dummy": false, + "start": 1540, + "end": 1546 + } + } + } + ], + "span": { + "dummy": false, + "start": 1529, + "end": 1547 + } + } + }, + "span": { + "dummy": false, + "start": 1521, + "end": 1547 } } ], @@ -654,21 +757,21 @@ "value": "source", "span": { "dummy": false, - "start": 1493, - "end": 1499 + "start": 1564, + "end": 1570 } } }, "span": { "dummy": false, - "start": 1310, - "end": 1509 + "start": 1341, + "end": 1580 } }, "span": { "dummy": false, - "start": 1301, - "end": 1509 + "start": 1332, + "end": 1580 } } } @@ -687,8 +790,8 @@ "value": "source", "span": { "dummy": false, - "start": 1536, - "end": 1542 + "start": 1607, + "end": 1613 } } }, @@ -697,15 +800,15 @@ "value": "field3", "span": { "dummy": false, - "start": 1543, - "end": 1549 + "start": 1614, + "end": 1620 } } }, "span": { "dummy": false, - "start": 1542, - "end": 1549 + "start": 1613, + "end": 1620 } } }, @@ -716,8 +819,8 @@ "value": "source", "span": { "dummy": false, - "start": 1551, - "end": 1557 + "start": 1622, + "end": 1628 } } }, @@ -726,15 +829,15 @@ "value": "field2", "span": { "dummy": false, - "start": 1558, - "end": 1564 + "start": 1629, + "end": 1635 } } }, "span": { "dummy": false, - "start": 1557, - "end": 1564 + "start": 1628, + "end": 1635 } } }, @@ -745,8 +848,8 @@ "value": "source", "span": { "dummy": false, - "start": 1566, - "end": 1572 + "start": 1637, + "end": 1643 } } }, @@ -755,22 +858,22 @@ "value": "field1", "span": { "dummy": false, - "start": 1573, - "end": 1579 + "start": 1644, + "end": 1650 } } }, "span": { "dummy": false, - "start": 1572, - "end": 1579 + "start": 1643, + "end": 1650 } } }, "span": { "dummy": false, - "start": 1527, - "end": 1580 + "start": 1598, + "end": 1651 } } }, @@ -780,8 +883,8 @@ "value": "Ada", "span": { "dummy": false, - "start": 1583, - "end": 1586 + "start": 1654, + "end": 1657 } }, "args": [ @@ -791,15 +894,15 @@ ], "span": { "dummy": false, - "start": 1583, - "end": 1590 + "start": 1654, + "end": 1661 } } }, "span": { "dummy": false, - "start": 1581, - "end": 1582 + "start": 1652, + "end": 1653 } } }, @@ -809,8 +912,8 @@ "value": "min_utxo", "span": { "dummy": false, - "start": 1593, - "end": 1601 + "start": 1664, + "end": 1672 } }, "args": [ @@ -819,23 +922,23 @@ "value": "named_output", "span": { "dummy": false, - "start": 1602, - "end": 1614 + "start": 1673, + "end": 1685 } } } ], "span": { "dummy": false, - "start": 1593, - "end": 1615 + "start": 1664, + "end": 1686 } } }, "span": { "dummy": false, - "start": 1591, - "end": 1592 + "start": 1662, + "end": 1663 } } } @@ -843,8 +946,8 @@ ], "span": { "dummy": false, - "start": 1243, - "end": 1622 + "start": 1274, + "end": 1693 } } ], @@ -857,15 +960,15 @@ "value": "tip_slot", "span": { "dummy": false, - "start": 1765, - "end": 1773 + "start": 1836, + "end": 1844 } }, "args": [], "span": { "dummy": false, - "start": 1765, - "end": 1775 + "start": 1836, + "end": 1846 } } } @@ -876,8 +979,8 @@ "value": "validUntil", "span": { "dummy": false, - "start": 1797, - "end": 1807 + "start": 1868, + "end": 1878 } } } @@ -885,8 +988,8 @@ ], "span": { "dummy": false, - "start": 1734, - "end": 1814 + "start": 1805, + "end": 1885 } }, "mints": [ @@ -899,8 +1002,8 @@ "value": "StaticAsset", "span": { "dummy": false, - "start": 913, - "end": 924 + "start": 944, + "end": 955 } }, "args": [ @@ -910,8 +1013,8 @@ ], "span": { "dummy": false, - "start": 913, - "end": 929 + "start": 944, + "end": 960 } } } @@ -922,8 +1025,8 @@ ], "span": { "dummy": false, - "start": 890, - "end": 958 + "start": 921, + "end": 989 } }, { @@ -936,8 +1039,8 @@ "value": "AB11223344", "span": { "dummy": false, - "start": 996, - "end": 1008 + "start": 1027, + "end": 1039 } } }, @@ -946,8 +1049,8 @@ "value": "OTHER_TOKEN", "span": { "dummy": false, - "start": 1010, - "end": 1023 + "start": 1041, + "end": 1054 } } }, @@ -956,8 +1059,8 @@ }, "span": { "dummy": false, - "start": 987, - "end": 1028 + "start": 1018, + "end": 1059 } } } @@ -968,8 +1071,8 @@ ], "span": { "dummy": false, - "start": 964, - "end": 1057 + "start": 995, + "end": 1088 } } ], @@ -983,8 +1086,8 @@ "value": "StaticAsset", "span": { "dummy": false, - "start": 1086, - "end": 1097 + "start": 1117, + "end": 1128 } }, "args": [ @@ -994,8 +1097,8 @@ ], "span": { "dummy": false, - "start": 1086, - "end": 1101 + "start": 1117, + "end": 1132 } } } @@ -1006,8 +1109,8 @@ ], "span": { "dummy": false, - "start": 1063, - "end": 1130 + "start": 1094, + "end": 1161 } } ], @@ -1018,8 +1121,8 @@ "value": "MyParty", "span": { "dummy": false, - "start": 1646, - "end": 1653 + "start": 1717, + "end": 1724 } } }, @@ -1028,16 +1131,16 @@ "value": "0F5B22E57FEEB5B4FD1D501B007A427C56A76884D4978FAFEF979D9C", "span": { "dummy": false, - "start": 1663, - "end": 1721 + "start": 1734, + "end": 1792 } } } ], "span": { "dummy": false, - "start": 1628, - "end": 1728 + "start": 1699, + "end": 1799 } }, "adhoc": [ @@ -1049,8 +1152,8 @@ "value": "12345678", "span": { "dummy": false, - "start": 2013, - "end": 2023 + "start": 2123, + "end": 2133 } } }, @@ -1059,15 +1162,15 @@ "value": "87654321", "span": { "dummy": false, - "start": 2040, - "end": 2050 + "start": 2150, + "end": 2160 } } }, "span": { "dummy": false, - "start": 1969, - "end": 2057 + "start": 2079, + "end": 2167 } } } @@ -1082,8 +1185,8 @@ "value": "MyParty", "span": { "dummy": false, - "start": 2099, - "end": 2106 + "start": 2209, + "end": 2216 } } } @@ -1099,8 +1202,8 @@ ], "span": { "dummy": false, - "start": 2072, - "end": 2156 + "start": 2182, + "end": 2266 } } } @@ -1116,8 +1219,8 @@ }, { "dummy": false, - "start": 2196, - "end": 2206 + "start": 2306, + "end": 2316 } ] }, @@ -1128,23 +1231,23 @@ "value": "ABCDEF1234", "span": { "dummy": false, - "start": 2224, - "end": 2236 + "start": 2334, + "end": 2346 } } }, { "dummy": false, - "start": 2216, - "end": 2236 + "start": 2326, + "end": 2346 } ] } ], "span": { "dummy": false, - "start": 2171, - "end": 2243 + "start": 2281, + "end": 2353 } } } @@ -1160,23 +1263,23 @@ "value": "12345678", "span": { "dummy": false, - "start": 2291, - "end": 2301 + "start": 2401, + "end": 2411 } } }, { "dummy": false, - "start": 2283, - "end": 2301 + "start": 2393, + "end": 2411 } ] } ], "span": { "dummy": false, - "start": 2258, - "end": 2308 + "start": 2368, + "end": 2418 } } } @@ -1189,8 +1292,8 @@ }, "span": { "dummy": false, - "start": 2323, - "end": 2367 + "start": 2433, + "end": 2477 } } } @@ -1198,8 +1301,8 @@ ], "span": { "dummy": false, - "start": 537, - "end": 2369 + "start": 568, + "end": 2479 }, "collateral": [ { @@ -1215,8 +1318,8 @@ "index": 1, "span": { "dummy": false, - "start": 1162, - "end": 1172 + "start": 1193, + "end": 1203 } } } @@ -1224,8 +1327,8 @@ ], "span": { "dummy": false, - "start": 1136, - "end": 1179 + "start": 1167, + "end": 1210 } } ], @@ -1240,15 +1343,15 @@ "value": "metadata", "span": { "dummy": false, - "start": 1842, - "end": 1850 + "start": 1913, + "end": 1921 } } }, "span": { "dummy": false, - "start": 1839, - "end": 1850 + "start": 1910, + "end": 1921 } }, { @@ -1260,22 +1363,22 @@ "value": "Additional Metadata", "span": { "dummy": false, - "start": 1863, - "end": 1884 + "start": 1934, + "end": 1955 } } }, "span": { "dummy": false, - "start": 1860, - "end": 1884 + "start": 1931, + "end": 1955 } } ], "span": { "dummy": false, - "start": 1820, - "end": 1891 + "start": 1891, + "end": 1962 } } } @@ -1387,19 +1490,40 @@ "start": 206, "end": 229 } + }, + { + "name": { + "value": "field6", + "span": { + "dummy": false, + "start": 235, + "end": 241 + } + }, + "type": { + "Tuple": [ + "Int", + "Bytes" + ] + }, + "span": { + "dummy": false, + "start": 235, + "end": 260 + } } ], "span": { "dummy": false, "start": 108, - "end": 232 + "end": 263 } } ], "span": { "dummy": false, "start": 108, - "end": 232 + "end": 263 } }, { @@ -1407,8 +1531,8 @@ "value": "MyVariant", "span": { "dummy": false, - "start": 239, - "end": 248 + "start": 270, + "end": 279 } }, "cases": [ @@ -1417,8 +1541,8 @@ "value": "Case1", "span": { "dummy": false, - "start": 255, - "end": 260 + "start": 286, + "end": 291 } }, "fields": [ @@ -1427,15 +1551,15 @@ "value": "field1", "span": { "dummy": false, - "start": 271, - "end": 277 + "start": 302, + "end": 308 } }, "type": "Int", "span": { "dummy": false, - "start": 271, - "end": 282 + "start": 302, + "end": 313 } }, { @@ -1443,15 +1567,15 @@ "value": "field2", "span": { "dummy": false, - "start": 292, - "end": 298 + "start": 323, + "end": 329 } }, "type": "Bytes", "span": { "dummy": false, - "start": 292, - "end": 305 + "start": 323, + "end": 336 } }, { @@ -1459,22 +1583,22 @@ "value": "field3", "span": { "dummy": false, - "start": 315, - "end": 321 + "start": 346, + "end": 352 } }, "type": "Int", "span": { "dummy": false, - "start": 315, - "end": 326 + "start": 346, + "end": 357 } } ], "span": { "dummy": false, - "start": 255, - "end": 333 + "start": 286, + "end": 364 } }, { @@ -1482,22 +1606,22 @@ "value": "Case2", "span": { "dummy": false, - "start": 339, - "end": 344 + "start": 370, + "end": 375 } }, "fields": [], "span": { "dummy": false, - "start": 339, - "end": 344 + "start": 370, + "end": 375 } } ], "span": { "dummy": false, - "start": 234, - "end": 347 + "start": 265, + "end": 378 } } ], @@ -1508,8 +1632,8 @@ "value": "StaticAsset", "span": { "dummy": false, - "start": 394, - "end": 405 + "start": 425, + "end": 436 } }, "policy": { @@ -1517,8 +1641,8 @@ "value": "ABCDEF1234", "span": { "dummy": false, - "start": 408, - "end": 420 + "start": 439, + "end": 451 } } }, @@ -1527,15 +1651,15 @@ "value": "MYTOKEN", "span": { "dummy": false, - "start": 421, - "end": 430 + "start": 452, + "end": 461 } } }, "span": { "dummy": false, - "start": 388, - "end": 431 + "start": 419, + "end": 462 } } ], @@ -1562,8 +1686,8 @@ "value": "OnlyHashPolicy", "span": { "dummy": false, - "start": 356, - "end": 370 + "start": 387, + "end": 401 } }, "value": { @@ -1571,15 +1695,15 @@ "value": "ABCDEF1234", "span": { "dummy": false, - "start": 373, - "end": 385 + "start": 404, + "end": 416 } } }, "span": { "dummy": false, - "start": 349, - "end": 386 + "start": 380, + "end": 417 } }, { @@ -1587,8 +1711,8 @@ "value": "FullyDefinedPolicy", "span": { "dummy": false, - "start": 440, - "end": 458 + "start": 471, + "end": 489 } }, "value": { @@ -1600,8 +1724,8 @@ "value": "ABCDEF1234", "span": { "dummy": false, - "start": 471, - "end": 483 + "start": 502, + "end": 514 } } } @@ -1612,8 +1736,8 @@ "value": "ABCDEF1234", "span": { "dummy": false, - "start": 497, - "end": 509 + "start": 528, + "end": 540 } } } @@ -1624,8 +1748,8 @@ "value": "ABCDEF1234", "span": { "dummy": false, - "start": 520, - "end": 532 + "start": 551, + "end": 563 } } } @@ -1633,21 +1757,22 @@ ], "span": { "dummy": false, - "start": 459, - "end": 535 + "start": 490, + "end": 566 } } }, "span": { "dummy": false, - "start": 433, - "end": 535 + "start": 464, + "end": 566 } } ], + "functions": [], "span": { "dummy": false, "start": 0, - "end": 2370 + "end": 2480 } } \ No newline at end of file diff --git a/examples/lang_tour.my_tx.tir b/examples/lang_tour.my_tx.tir index 525dcb7..7b66c51 100644 --- a/examples/lang_tour.my_tx.tir +++ b/examples/lang_tour.my_tx.tir @@ -308,6 +308,24 @@ } ] ] + }, + { + "Tuple": [ + { + "EvalParam": { + "ExpectValue": [ + "quantity", + "Int" + ] + } + }, + { + "Bytes": [ + 170, + 187 + ] + } + ] } ] } @@ -645,14 +663,6 @@ { "name": "vote_delegation_certificate", "data": { - "stake": { - "Bytes": [ - 135, - 101, - 67, - 33 - ] - }, "drep": { "Bytes": [ 18, @@ -660,15 +670,20 @@ 86, 120 ] + }, + "stake": { + "Bytes": [ + 135, + 101, + 67, + 33 + ] } } }, { "name": "withdrawal", "data": { - "amount": { - "Number": 100 - }, "credential": { "EvalParam": { "ExpectValue": [ @@ -677,6 +692,9 @@ ] } }, + "amount": { + "Number": 100 + }, "redeemer": { "Struct": { "constructor": 0, @@ -688,6 +706,9 @@ { "name": "plutus_witness", "data": { + "version": { + "Number": 2 + }, "script": { "Bytes": [ 171, @@ -696,9 +717,6 @@ 18, 52 ] - }, - "version": { - "Number": 2 } } }, diff --git a/examples/lang_tour.tx3 b/examples/lang_tour.tx3 index 9af2be7..a22c0e5 100644 --- a/examples/lang_tour.tx3 +++ b/examples/lang_tour.tx3 @@ -13,6 +13,7 @@ type MyRecord { field3: Bytes, field4: List, field5: Map, + field6: Tuple, } type MyVariant { @@ -80,6 +81,7 @@ tx my_tx( field2: (54 + 10) - (8 + 2), field4: [1, 2, 3, source.field1], field5: {1: "Value1", 2: "Value2",}, + field6: (quantity, 0xAABB), ...source }, amount: AnyAsset(source.field3, source.field2, source.field1) + Ada(40) + min_utxo(named_output), @@ -102,6 +104,7 @@ tx my_tx( locals { local_var: concat("Lang", "Tour"), + tuple_first: source.field6[0], } cardano::vote_delegation_certificate { diff --git a/examples/tuples.ast b/examples/tuples.ast new file mode 100644 index 0000000..0e6931e --- /dev/null +++ b/examples/tuples.ast @@ -0,0 +1,403 @@ +{ + "env": null, + "txs": [ + { + "name": { + "value": "relist", + "span": { + "dummy": false, + "start": 144, + "end": 150 + } + }, + "parameters": { + "parameters": [ + { + "name": { + "value": "terms", + "span": { + "dummy": false, + "start": 220, + "end": 225 + } + }, + "type": { + "Tuple": [ + "Int", + "Int" + ] + } + } + ], + "span": { + "dummy": false, + "start": 150, + "end": 245 + } + }, + "locals": null, + "references": [], + "inputs": [ + { + "name": "source", + "many": false, + "fields": [ + { + "From": { + "Identifier": { + "value": "Seller", + "span": { + "dummy": false, + "start": 281, + "end": 287 + } + } + } + }, + { + "DatumIs": { + "Custom": { + "value": "Listing", + "span": { + "dummy": true, + "start": 0, + "end": 0 + } + } + } + } + ], + "span": { + "dummy": false, + "start": 252, + "end": 321 + } + } + ], + "outputs": [ + { + "name": null, + "optional": false, + "fields": [ + { + "To": { + "Identifier": { + "value": "Buyer", + "span": { + "dummy": false, + "start": 348, + "end": 353 + } + } + } + }, + { + "Amount": { + "SubOp": { + "lhs": { + "Identifier": { + "value": "source", + "span": { + "dummy": false, + "start": 371, + "end": 377 + } + } + }, + "rhs": { + "Identifier": { + "value": "fees", + "span": { + "dummy": false, + "start": 380, + "end": 384 + } + } + }, + "span": { + "dummy": false, + "start": 378, + "end": 379 + } + } + } + }, + { + "Datum": { + "StructConstructor": { + "type": { + "value": "Listing", + "span": { + "dummy": false, + "start": 401, + "end": 408 + } + }, + "case": { + "name": { + "value": "Default", + "span": { + "dummy": true, + "start": 0, + "end": 0 + } + }, + "fields": [ + { + "name": { + "value": "offer", + "span": { + "dummy": false, + "start": 613, + "end": 618 + } + }, + "value": { + "TupleConstructor": { + "elements": [ + { + "PropertyOp": { + "operand": { + "Identifier": { + "value": "terms", + "span": { + "dummy": false, + "start": 621, + "end": 626 + } + } + }, + "property": { + "Number": 0 + }, + "span": { + "dummy": false, + "start": 626, + "end": 629 + } + } + }, + { + "PropertyOp": { + "operand": { + "PropertyOp": { + "operand": { + "Identifier": { + "value": "source", + "span": { + "dummy": false, + "start": 631, + "end": 637 + } + } + }, + "property": { + "Identifier": { + "value": "offer", + "span": { + "dummy": false, + "start": 638, + "end": 643 + } + } + }, + "span": { + "dummy": false, + "start": 637, + "end": 643 + } + } + }, + "property": { + "Number": 1 + }, + "span": { + "dummy": false, + "start": 643, + "end": 646 + } + } + } + ], + "span": { + "dummy": false, + "start": 620, + "end": 647 + } + } + }, + "span": { + "dummy": false, + "start": 613, + "end": 647 + } + } + ], + "spread": null, + "span": { + "dummy": false, + "start": 409, + "end": 658 + } + }, + "span": { + "dummy": false, + "start": 401, + "end": 658 + } + } + } + } + ], + "span": { + "dummy": false, + "start": 327, + "end": 665 + } + } + ], + "validity": { + "fields": [ + { + "UntilSlot": { + "PropertyOp": { + "operand": { + "Identifier": { + "value": "terms", + "span": { + "dummy": false, + "start": 702, + "end": 707 + } + } + }, + "property": { + "Number": 1 + }, + "span": { + "dummy": false, + "start": 707, + "end": 710 + } + } + } + } + ], + "span": { + "dummy": false, + "start": 671, + "end": 717 + } + }, + "mints": [], + "burns": [], + "signers": null, + "adhoc": [], + "span": { + "dummy": false, + "start": 141, + "end": 719 + }, + "collateral": [], + "metadata": null + } + ], + "types": [ + { + "name": { + "value": "Listing", + "span": { + "dummy": false, + "start": 98, + "end": 105 + } + }, + "cases": [ + { + "name": { + "value": "Default", + "span": { + "dummy": true, + "start": 0, + "end": 0 + } + }, + "fields": [ + { + "name": { + "value": "offer", + "span": { + "dummy": false, + "start": 112, + "end": 117 + } + }, + "type": { + "Tuple": [ + "Int", + "Bytes" + ] + }, + "span": { + "dummy": false, + "start": 112, + "end": 136 + } + } + ], + "span": { + "dummy": false, + "start": 93, + "end": 139 + } + } + ], + "span": { + "dummy": false, + "start": 93, + "end": 139 + } + } + ], + "aliases": [], + "assets": [], + "parties": [ + { + "name": { + "value": "Seller", + "span": { + "dummy": false, + "start": 6, + "end": 12 + } + }, + "span": { + "dummy": false, + "start": 0, + "end": 13 + } + }, + { + "name": { + "value": "Buyer", + "span": { + "dummy": false, + "start": 20, + "end": 25 + } + }, + "span": { + "dummy": false, + "start": 14, + "end": 26 + } + } + ], + "policies": [], + "functions": [], + "span": { + "dummy": false, + "start": 0, + "end": 720 + } +} \ No newline at end of file diff --git a/examples/tuples.relist.tir b/examples/tuples.relist.tir new file mode 100644 index 0000000..f6f7262 --- /dev/null +++ b/examples/tuples.relist.tir @@ -0,0 +1,177 @@ +{ + "fees": { + "EvalParam": "ExpectFees" + }, + "references": [], + "inputs": [ + { + "name": "source", + "utxos": { + "EvalParam": { + "ExpectInput": [ + "source", + { + "address": { + "EvalParam": { + "ExpectValue": [ + "seller", + "Address" + ] + } + }, + "min_amount": "None", + "ref": "None", + "many": false, + "collateral": false + } + ] + } + }, + "redeemer": "None" + } + ], + "outputs": [ + { + "address": { + "EvalParam": { + "ExpectValue": [ + "buyer", + "Address" + ] + } + }, + "datum": { + "Struct": { + "constructor": 0, + "fields": [ + { + "Tuple": [ + { + "EvalBuiltIn": { + "Property": [ + { + "EvalParam": { + "ExpectValue": [ + "terms", + "Tuple" + ] + } + }, + { + "Number": 0 + } + ] + } + }, + { + "EvalBuiltIn": { + "Property": [ + { + "EvalBuiltIn": { + "Property": [ + { + "EvalCoerce": { + "IntoDatum": { + "EvalParam": { + "ExpectInput": [ + "source", + { + "address": { + "EvalParam": { + "ExpectValue": [ + "seller", + "Address" + ] + } + }, + "min_amount": "None", + "ref": "None", + "many": false, + "collateral": false + } + ] + } + } + } + }, + { + "Number": 0 + } + ] + } + }, + { + "Number": 1 + } + ] + } + } + ] + } + ] + } + }, + "amount": { + "EvalBuiltIn": { + "Sub": [ + { + "EvalCoerce": { + "IntoAssets": { + "EvalParam": { + "ExpectInput": [ + "source", + { + "address": { + "EvalParam": { + "ExpectValue": [ + "seller", + "Address" + ] + } + }, + "min_amount": "None", + "ref": "None", + "many": false, + "collateral": false + } + ] + } + } + } + }, + { + "EvalParam": "ExpectFees" + } + ] + } + }, + "optional": false + } + ], + "validity": { + "since": "None", + "until": { + "EvalBuiltIn": { + "Property": [ + { + "EvalParam": { + "ExpectValue": [ + "terms", + "Tuple" + ] + } + }, + { + "Number": 1 + } + ] + } + } + }, + "mints": [], + "burns": [], + "adhoc": [], + "collateral": [], + "signers": null, + "metadata": [] +} \ No newline at end of file diff --git a/examples/tuples.tx3 b/examples/tuples.tx3 new file mode 100644 index 0000000..01ba2f4 --- /dev/null +++ b/examples/tuples.tx3 @@ -0,0 +1,32 @@ +party Seller; +party Buyer; + +// A datum carrying a tuple field: an (price, token_name) offer. +type Listing { + offer: Tuple, +} + +tx relist( + // A tuple parameter: the new (price, deadline_slot) terms. + terms: Tuple, +) { + input source { + from: Seller, + datum_is: Listing, + } + + output { + to: Buyer, + amount: source - fees, + datum: Listing { + // Build a tuple literal, reading elements back by position: + // the new price from the param, the original token name from + // the consumed datum's tuple. + offer: (terms[0], source.offer[1]), + }, + } + + validity { + until_slot: terms[1], + } +} diff --git a/specs/v1beta0/04-syntactic-grammar.md b/specs/v1beta0/04-syntactic-grammar.md index 1396c21..e2790e9 100644 --- a/specs/v1beta0/04-syntactic-grammar.md +++ b/specs/v1beta0/04-syntactic-grammar.md @@ -147,14 +147,18 @@ A *transaction definition* declares a parameterised template. The ## 4.3 Types ``` -type ::= primitive_type | list_type | map_type | custom_type +type ::= primitive_type | list_type | map_type | tuple_type | custom_type primitive_type ::= "Int" | "Bool" | "Bytes" | "AnyAsset" | "Address" | "UtxoRef" -list_type ::= "List" "<" type ">" -map_type ::= "Map" "<" type "," type ">" +list_type ::= "List" "<" type ">" +map_type ::= "Map" "<" type "," type ">" +tuple_type ::= "Tuple" "<" type ("," type)+ ","? ">" custom_type ::= identifier ``` +A `tuple_type` has **two or more** element types (the `("," type)+` +repetition); `Tuple` is not a valid type. + A `custom_type` is an identifier that MUST resolve to a `record_def`, `variant_def`, or `alias_def` (§6). @@ -183,6 +187,7 @@ data_primary ::= | any_asset_constructor | fn_call | identifier + | tuple_constructor | "(" data_expr ")" struct_constructor ::= identifier variant_case_constructor @@ -200,6 +205,8 @@ spread_expression ::= "..." data_expr list_constructor ::= "[" (data_expr ",")* data_expr? "]" +tuple_constructor ::= "(" data_expr ("," data_expr)+ ","? ")" + map_constructor ::= "{" (map_field ",")+ "}" map_field ::= data_expr ":" data_expr @@ -209,6 +216,25 @@ any_asset_constructor ::= "AnyAsset" "(" data_expr "," data_expr "," data_expr " fn_call ::= identifier "(" (data_expr ("," data_expr)* ","?)? ")" ``` +### 4.4.0 Disambiguation of `( … )` + +A parenthesized form in expression position is parsed by element count: + +1. `()` — the `unit_literal`. +2. `(e)` — grouping; the parenthesized expression `e`, with no change of + type. This is **not** a one-element tuple. +3. `(e1, e2, ...)` with two or more comma-separated expressions — a + `tuple_constructor`. A trailing comma is permitted only when at least two + elements are present (`(e,)` is not a tuple). + +Positional access on a tuple value reuses the `"[" data_expr "]"` +(`data_postfix`) index form, where the index MUST be an integer literal in +range (§5.2). Tuples do not introduce a new access operator; `t.0`-style +syntax is **not** part of `v1beta0` — the `"." identifier` postfix is +reserved for named record/asset fields, and a `"." integer` form would be +ambiguous with the `policy "." asset_name` separator of `asset_def` +(§4.2.2). + ### 4.4.1 Disambiguation of `{ … }` A `{ … }` literal in expression position is parsed as: diff --git a/specs/v1beta0/05-type-system.md b/specs/v1beta0/05-type-system.md index 3fc9a8f..db53bc9 100644 --- a/specs/v1beta0/05-type-system.md +++ b/specs/v1beta0/05-type-system.md @@ -27,19 +27,20 @@ In addition, the type `Unit` exists as the type of the unit literal `()` ## 5.2 Compound types ``` -List :: list of values, each of type T -Map :: finite mapping from keys of type K to values of type V +List :: list of values, each of type T +Map :: finite mapping from keys of type K to values of type V +Tuple :: fixed-arity, positionally-typed product (n >= 2) ``` -Both `List` and `Map` are *parametric type constructors* applied at the +`List`, `Map`, and `Tuple` are *parametric type constructors* applied at the syntactic level. They do not introduce parametric polymorphism into the -expression language: each occurrence of `List` or `Map` is a -distinct concrete type. +expression language: each occurrence of `List`, `Map`, or +`Tuple` is a distinct concrete type. Constraints: -- `T`, `K`, and `V` MAY be any type, including other compound types and - user-defined types. +- `T`, `K`, `V`, and each `Ti` MAY be any type, including other compound + types and user-defined types. - A `List` constructed from a `list_constructor` (§4.4) takes its element type from the type of its first element; an empty `list_constructor` MUST appear in a context that supplies the element @@ -48,6 +49,17 @@ Constraints: - A `Map` constructed from a `map_constructor` takes `K` and `V` from the first entry; subsequent entries' key and value types MUST match (§6.3). +- A `Tuple` has **arity `n >= 2`**: there is no zero- or + one-element tuple. A tuple is constructed from a `tuple_constructor` + (§4.4) — a parenthesized list of two or more expressions, `(e1, ..., en)` + — whose element types become `T1, ..., Tn` positionally. A parenthesized + single expression `(e)` is grouping, and `()` is the unit literal; neither + is a tuple. +- A tuple element is read by **positional index**: `t[i]`, where `i` is an + integer literal `0 <= i < n`. The result has type `Ti`. The index MUST be + a literal — a tuple cannot be indexed by a runtime value, because each + position may have a different type. An out-of-range index is a static + error. ## 5.3 User-defined types @@ -287,9 +299,12 @@ results in a duplicate-definition error (§6.2). Two types `A` and `B` are *type-equivalent* if they are the same primitive, the same `List` (with equivalent `T`), the same `Map` (with -equivalent `K` and `V`), refer to the same user-defined type definition -(after alias chasing), or denote the same internal `Unit`/`Undefined` -placeholder. +equivalent `K` and `V`), the same `Tuple` (same arity `n`, with +each `Ti` equivalent positionally), refer to the same user-defined type +definition (after alias chasing), or denote the same internal +`Unit`/`Undefined` placeholder. Tuple equivalence is **structural and +arity-sensitive**: `Tuple` and `Tuple` are +distinct types, and tuple order is significant. A value of type `A` is *assignable* to a position expecting type `B` exactly when `A` and `B` are type-equivalent. There are no implicit