Add dedicated 'Pair' typed and untyped expression

Before this commit, we would parse 'Pair' as a user-defined
  data-types, and thus piggybacking on that whole record system. While
  perhaps handy for some things, it's also semantically wrong and
  induces a lot more complexity in codegen which now needs to
  systematically distinguish every data-type access between pairs, and
  others.

  So it's better to have it as a separate expression, and handle it
  similar to tuples (since it's fundamentally a 2-tuple with a special
  serialization).
This commit is contained in:
KtorZ 2024-04-18 17:18:57 +02:00 committed by Kasey
parent 390bccd406
commit 2cb2c7fa1f
13 changed files with 235 additions and 22 deletions

View File

@ -970,6 +970,7 @@ pub enum Annotation {
location: Span,
elems: Vec<Self>,
},
Pair {
location: Span,
fst: Box<Self>,

View File

@ -15,6 +15,9 @@ use std::{cell::RefCell, collections::HashMap, rc::Rc};
use strum::IntoEnumIterator;
use uplc::builtins::DefaultFunction;
pub const PRELUDE: &str = "aiken";
pub const BUILTIN: &str = "aiken/builtin";
pub const BYTE_ARRAY: &str = "ByteArray";
pub const BOOL: &str = "Bool";
pub const INT: &str = "Int";
@ -37,7 +40,7 @@ pub const FUZZER: &str = "Fuzzer";
/// into a compiler pipeline
pub fn prelude(id_gen: &IdGenerator) -> TypeInfo {
let mut prelude = TypeInfo {
name: "aiken".to_string(),
name: PRELUDE.to_string(),
package: "".to_string(),
kind: ModuleKind::Lib,
types: HashMap::new(),
@ -592,7 +595,7 @@ pub fn prelude(id_gen: &IdGenerator) -> TypeInfo {
pub fn plutus(id_gen: &IdGenerator) -> TypeInfo {
let mut plutus = TypeInfo {
name: "aiken/builtin".to_string(),
name: BUILTIN.to_string(),
package: "".to_string(),
kind: ModuleKind::Lib,
types: HashMap::new(),

View File

@ -161,6 +161,13 @@ pub enum TypedExpr {
elems: Vec<Self>,
},
Pair {
location: Span,
tipo: Rc<Type>,
fst: Box<Self>,
snd: Box<Self>,
},
TupleIndex {
location: Span,
tipo: Rc<Type>,
@ -214,6 +221,7 @@ impl TypedExpr {
| Self::UnOp { tipo, .. }
| Self::BinOp { tipo, .. }
| Self::Tuple { tipo, .. }
| Self::Pair { tipo, .. }
| Self::String { tipo, .. }
| Self::ByteArray { tipo, .. }
| Self::TupleIndex { tipo, .. }
@ -256,6 +264,7 @@ impl TypedExpr {
| TypedExpr::ErrorTerm { .. }
| TypedExpr::BinOp { .. }
| TypedExpr::Tuple { .. }
| TypedExpr::Pair { .. }
| TypedExpr::UnOp { .. }
| TypedExpr::String { .. }
| TypedExpr::Sequence { .. }
@ -299,6 +308,7 @@ impl TypedExpr {
| Self::List { location, .. }
| Self::BinOp { location, .. }
| Self::Tuple { location, .. }
| Self::Pair { location, .. }
| Self::String { location, .. }
| Self::UnOp { location, .. }
| Self::Pipeline { location, .. }
@ -337,6 +347,7 @@ impl TypedExpr {
| Self::List { location, .. }
| Self::BinOp { location, .. }
| Self::Tuple { location, .. }
| Self::Pair { location, .. }
| Self::String { location, .. }
| Self::UnOp { location, .. }
| Self::Sequence { location, .. }
@ -392,6 +403,11 @@ impl TypedExpr {
.find_map(|e| e.find_node(byte_index))
.or(Some(Located::Expression(self))),
TypedExpr::Pair { fst, snd, .. } => [fst, snd]
.iter()
.find_map(|e| e.find_node(byte_index))
.or(Some(Located::Expression(self))),
TypedExpr::List { elements, tail, .. } => elements
.iter()
.find_map(|e| e.find_node(byte_index))
@ -578,6 +594,12 @@ pub enum UntypedExpr {
elems: Vec<Self>,
},
Pair {
location: Span,
fst: Box<Self>,
snd: Box<Self>,
},
TupleIndex {
location: Span,
index: usize,
@ -1272,6 +1294,7 @@ impl UntypedExpr {
| Self::ByteArray { location, .. }
| Self::BinOp { location, .. }
| Self::Tuple { location, .. }
| Self::Pair { location, .. }
| Self::String { location, .. }
| Self::Assignment { location, .. }
| Self::TupleIndex { location, .. }

View File

@ -980,6 +980,14 @@ impl<'comments> Formatter<'comments> {
wrap_args(elems.iter().map(|e| (self.wrap_expr(e), false))).group()
}
UntypedExpr::Pair { fst, snd, .. } => "Pair"
.to_doc()
.append("(")
.append(self.expr(fst, false))
.append(",")
.append(self.expr(snd, false))
.append(")"),
UntypedExpr::TupleIndex { index, tuple, .. } => {
let suffix = Ordinal(*index + 1).suffix().to_doc();
self.expr(tuple, false)

View File

@ -373,7 +373,13 @@ impl<'a> CodeGenerator<'a> {
..
} => {
let data_type = lookup_data_type_by_tipo(&self.data_types, tipo)
.expect("Creating a record with no record definition.");
.unwrap_or_else(||
panic!(
"Creating a record of type {:?} with no record definition. Known definitions: {:?}",
tipo.to_pretty(0),
self.data_types.keys()
)
);
let (constr_index, _) = data_type
.constructors
@ -758,6 +764,12 @@ impl<'a> CodeGenerator<'a> {
}
},
TypedExpr::Pair { tipo, fst, snd, .. } => AirTree::pair(
self.build(fst, module_build_name, &[]),
self.build(snd, module_build_name, &[]),
tipo.clone(),
),
TypedExpr::Tuple { tipo, elems, .. } => AirTree::tuple(
elems
.iter()

View File

@ -1,20 +1,11 @@
use chumsky::prelude::*;
use super::anonymous_function::parser as anonymous_function;
use super::assignment;
use super::block::parser as block;
use super::bytearray::parser as bytearray;
use super::if_else::parser as if_else;
use super::int::parser as int;
use super::list::parser as list;
use super::record::parser as record;
use super::record_update::parser as record_update;
use super::string::parser as string;
use super::tuple::parser as tuple;
use super::var::parser as var;
use super::when::parser as when;
use super::{and_or_chain, anonymous_binop::parser as anonymous_binop};
use super::{
and_or_chain, anonymous_binop::parser as anonymous_binop,
anonymous_function::parser as anonymous_function, assignment, block::parser as block,
bytearray::parser as bytearray, if_else::parser as if_else, int::parser as int,
list::parser as list, pair::parser as pair, record::parser as record,
record_update::parser as record_update, string::parser as string, tuple::parser as tuple,
var::parser as var, when::parser as when,
};
use crate::{
expr::UntypedExpr,
parser::{
@ -23,6 +14,7 @@ use crate::{
token::Token,
},
};
use chumsky::prelude::*;
pub fn parser<'a>(
sequence: Recursive<'a, Token, UntypedExpr, ParseError>,
@ -58,6 +50,7 @@ pub fn chain_start<'a>(
choice((
string(),
int(),
pair(expression.clone()),
record_update(expression.clone()),
record(expression.clone()),
field_access::constructor(),

View File

@ -12,6 +12,7 @@ mod fail_todo_trace;
mod if_else;
mod int;
mod list;
mod pair;
mod record;
mod record_update;
mod sequence;
@ -31,6 +32,7 @@ pub use fail_todo_trace::parser as fail_todo_trace;
pub use if_else::parser as if_else;
pub use int::parser as int;
pub use list::parser as list;
pub use pair::parser as pair;
pub use record::parser as record;
pub use record_update::parser as record_update;
pub use sequence::parser as sequence;

View File

@ -0,0 +1,53 @@
use crate::{
builtins::{PAIR, PRELUDE},
expr::UntypedExpr,
parser::{error::ParseError, token::Token},
};
use chumsky::prelude::*;
pub fn parser(
r: Recursive<'_, Token, UntypedExpr, ParseError>,
) -> impl Parser<Token, UntypedExpr, Error = ParseError> + '_ {
select! {Token::Name { name } if &name == PRELUDE => name}
.then_ignore(just(Token::Dot))
.or_not()
.then_ignore(select! {Token::UpName { name } if &name == PAIR => name})
.ignore_then(
r.clone()
.separated_by(just(Token::Comma))
.exactly(2)
.allow_trailing()
.delimited_by(
choice((just(Token::LeftParen), just(Token::NewLineLeftParen))),
just(Token::RightParen),
)
.map_with_span(|elems, location| UntypedExpr::Pair {
location,
fst: elems
.first()
.expect("Pair should have exactly 2 elements")
.to_owned()
.into(),
snd: elems
.last()
.expect("Pair should have exactly 2 elements")
.to_owned()
.into(),
}),
)
}
#[cfg(test)]
mod tests {
use crate::assert_expr;
#[test]
fn basic_pair() {
assert_expr!(r#"Pair(1, 2)"#);
}
#[test]
fn pair_from_prelude() {
assert_expr!(r#"aiken.Pair(1, 2)"#);
}
}

View File

@ -0,0 +1,21 @@
---
source: crates/aiken-lang/src/parser/expr/pair.rs
description: "Code:\n\nPair(1, 2)"
---
Pair {
location: 4..10,
fst: UInt {
location: 5..6,
value: "1",
base: Decimal {
numeric_underscore: false,
},
},
snd: UInt {
location: 8..9,
value: "2",
base: Decimal {
numeric_underscore: false,
},
},
}

View File

@ -0,0 +1,21 @@
---
source: crates/aiken-lang/src/parser/expr/pair.rs
description: "Code:\n\naiken.Pair(1, 2)"
---
Pair {
location: 10..16,
fst: UInt {
location: 11..12,
value: "1",
base: Decimal {
numeric_underscore: false,
},
},
snd: UInt {
location: 14..15,
value: "2",
base: Decimal {
numeric_underscore: false,
},
},
}

View File

@ -16,7 +16,7 @@ use crate::{
UntypedClauseGuard, UntypedIfBranch, UntypedPattern, UntypedRecordUpdateArg,
},
builtins::{
bool, byte_array, function, g1_element, g2_element, int, list, string, tuple, void,
bool, byte_array, function, g1_element, g2_element, int, list, pair, string, tuple, void,
},
expr::{FnStyle, TypedExpr, UntypedExpr},
format,
@ -226,6 +226,8 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
UntypedExpr::Tuple { location, elems } => self.infer_tuple(elems, location),
UntypedExpr::Pair { location, fst, snd } => self.infer_pair(*fst, *snd, location),
UntypedExpr::String { location, value } => Ok(self.infer_string(value, location)),
UntypedExpr::LogicalOpChain {
@ -2020,6 +2022,26 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
}
}
fn infer_pair(
&mut self,
fst: UntypedExpr,
snd: UntypedExpr,
location: Span,
) -> Result<TypedExpr, Error> {
let typed_fst = self.infer(fst)?;
ensure_serialisable(false, typed_fst.tipo(), location)?;
let typed_snd = self.infer(snd)?;
ensure_serialisable(false, typed_snd.tipo(), location)?;
Ok(TypedExpr::Pair {
location,
tipo: pair(typed_fst.tipo(), typed_snd.tipo()),
fst: typed_fst.into(),
snd: typed_snd.into(),
})
}
fn infer_tuple(&mut self, elems: Vec<UntypedExpr>, location: Span) -> Result<TypedExpr, Error> {
let mut typed_elems = vec![];
@ -2343,6 +2365,7 @@ fn assert_no_assignment(expr: &UntypedExpr) -> Result<(), Error> {
| UntypedExpr::Sequence { .. }
| UntypedExpr::String { .. }
| UntypedExpr::Tuple { .. }
| UntypedExpr::Pair { .. }
| UntypedExpr::TupleIndex { .. }
| UntypedExpr::UnOp { .. }
| UntypedExpr::Var { .. }

View File

@ -9,7 +9,7 @@ Schema {
Var {
tipo: RefCell {
value: Generic {
id: 33,
id: 37,
},
},
alias: None,

View File

@ -0,0 +1,53 @@
---
source: crates/aiken-project/src/export.rs
assertion_line: 154
description: "Code:\n\npub type Foo<a> {\n Empty\n Bar(a, Foo<a>)\n}\n\npub fn add(a: Foo<Int>, b: Foo<Int>) -> Int {\n when (a, b) is {\n (Empty, Empty) -> 0\n (Bar(x, y), Bar(c, d)) -> x + c + add(y, d)\n (Empty, Bar(c, d)) -> c + add(Empty, d)\n (Bar(x, y), Empty) -> x + add(y, Empty)\n }\n}\n"
---
{
"name": "test_module.add",
"parameters": [
{
"title": "a",
"schema": {
"$ref": "#/definitions/test_module~1Foo$Int"
}
},
{
"title": "b",
"schema": {
"$ref": "#/definitions/test_module~1Foo$Int"
}
}
],
"compiledCode": "5901d501000032323232323222323232323253330083002300937540062a666010600460126ea801052000001001132323232533300b3004300c375400c2646464a66601c600e601e6ea80284c8cdc019b80003375a60260026600c0046026602800260206ea8028010c044c048008dd6980800098069baa006001132533300b3005300c375400c2a666016600860186ea801c4c8cdc01bad3010001330034c103d879800030103011001300d375400e00200226466e00dd698078009980118079808000a60103d8798000300c375400a600200244464646464a66601e601260206ea800854ccc03cc024c040dd50018a4000002002264a66601e601060206ea80084c8c8c94ccc048c02cc04cdd500309919b80337000066eb4c05c004ccc02c02c008c05cc060004c050dd5003002180a980b0011bad301400130113754004002264a66601e601260206ea800854ccc03cc020c040dd500189919b80375a6028002666010010980103d8798000301430150013011375400600200226466e00dd698098009998038039809980a000a60103d8798000301037540026022004602060220026601c0046601c00297ae0370e90011b8748000c024008c020c024004cc018008cc0180052f5c0ae6955ceaab9e5740ae855d101",
"hash": "dca86b6e092019b67ef310ba8360682d7bf8284cc728c6b525fb0b0d",
"definitions": {
"Int": {
"dataType": "integer"
},
"test_module/Foo$Int": {
"title": "Foo",
"anyOf": [
{
"title": "Empty",
"dataType": "constructor",
"index": 0,
"fields": []
},
{
"title": "Bar",
"dataType": "constructor",
"index": 1,
"fields": [
{
"$ref": "#/definitions/Int"
},
{
"$ref": "#/definitions/test_module~1Foo$Int"
}
]
}
]
}
}
}