Merge pull request #202 from aiken-lang/tuple-indexes

Tuple indexes
This commit is contained in:
Matthias Benkort 2022-12-22 09:35:08 +01:00 committed by GitHub
commit 18acd0e65f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 299 additions and 42 deletions

20
Cargo.lock generated
View File

@ -79,6 +79,7 @@ dependencies = [
"indoc", "indoc",
"itertools", "itertools",
"miette", "miette",
"ordinal",
"pretty_assertions", "pretty_assertions",
"strum", "strum",
"thiserror", "thiserror",
@ -1125,6 +1126,16 @@ dependencies = [
"version_check", "version_check",
] ]
[[package]]
name = "num-integer"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
dependencies = [
"autocfg",
"num-traits",
]
[[package]] [[package]]
name = "num-traits" name = "num-traits"
version = "0.2.15" version = "0.2.15"
@ -1204,6 +1215,15 @@ dependencies = [
"vcpkg", "vcpkg",
] ]
[[package]]
name = "ordinal"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c80c1530f46e9d8985706d7deb80b83172b250538902f607dea6cd6028851083"
dependencies = [
"num-integer",
]
[[package]] [[package]]
name = "os_str_bytes" name = "os_str_bytes"
version = "6.4.1" version = "6.4.1"

View File

@ -15,6 +15,7 @@ chumsky = "0.8.0"
indexmap = "1.9.1" indexmap = "1.9.1"
itertools = "0.10.5" itertools = "0.10.5"
miette = "5.2.0" miette = "5.2.0"
ordinal = "0.3.2"
strum = "0.24.1" strum = "0.24.1"
thiserror = "1.0.37" thiserror = "1.0.37"
uplc = { path = '../uplc', version = "0.0.25" } uplc = { path = '../uplc', version = "0.0.25" }

View File

@ -191,11 +191,10 @@ pub enum Air {
}, },
// TupleIndex { // TupleIndex {
// scope: Vec<u64>, // scope: Vec<u64>,
// // tipo: Arc<Type>,
// tipo: Arc<Type>, // index: u64,
// index: u64, // tuple: Box<Self>,
// tuple: Box<Self>,
// }, // },
Todo { Todo {
scope: Vec<u64>, scope: Vec<u64>,

View File

@ -135,12 +135,13 @@ pub enum TypedExpr {
elems: Vec<Self>, elems: Vec<Self>,
}, },
// TupleIndex { TupleIndex {
// location: Span, location: Span,
// tipo: Arc<Type>, tipo: Arc<Type>,
// index: u64, index: usize,
// tuple: Box<Self>, tuple: Box<Self>,
// }, },
Todo { Todo {
location: Span, location: Span,
label: Option<String>, label: Option<String>,
@ -165,7 +166,7 @@ impl TypedExpr {
match self { match self {
Self::Negate { .. } => bool(), Self::Negate { .. } => bool(),
Self::Var { constructor, .. } => constructor.tipo.clone(), Self::Var { constructor, .. } => constructor.tipo.clone(),
Self::Trace {then, ..} => then.tipo(), Self::Trace { then, .. } => then.tipo(),
Self::Fn { tipo, .. } Self::Fn { tipo, .. }
| Self::Int { tipo, .. } | Self::Int { tipo, .. }
| Self::Todo { tipo, .. } | Self::Todo { tipo, .. }
@ -177,7 +178,7 @@ impl TypedExpr {
| Self::Tuple { tipo, .. } | Self::Tuple { tipo, .. }
| Self::String { tipo, .. } | Self::String { tipo, .. }
| Self::ByteArray { tipo, .. } | Self::ByteArray { tipo, .. }
// | Self::TupleIndex { tipo, .. } | Self::TupleIndex { tipo, .. }
| Self::Assignment { tipo, .. } | Self::Assignment { tipo, .. }
| Self::ModuleSelect { tipo, .. } | Self::ModuleSelect { tipo, .. }
| Self::RecordAccess { tipo, .. } | Self::RecordAccess { tipo, .. }
@ -221,9 +222,9 @@ impl TypedExpr {
| TypedExpr::Pipeline { .. } | TypedExpr::Pipeline { .. }
| TypedExpr::ByteArray { .. } | TypedExpr::ByteArray { .. }
| TypedExpr::Assignment { .. } | TypedExpr::Assignment { .. }
// | TypedExpr::TupleIndex { .. } | TypedExpr::TupleIndex { .. }
| TypedExpr::RecordAccess { .. } => None, | TypedExpr::RecordAccess { .. } => None,
| TypedExpr::If { .. } => None, TypedExpr::If { .. } => None,
// TODO: test // TODO: test
// TODO: definition // TODO: definition
@ -261,15 +262,12 @@ impl TypedExpr {
| Self::Pipeline { location, .. } | Self::Pipeline { location, .. }
| Self::ByteArray { location, .. } | Self::ByteArray { location, .. }
| Self::Assignment { location, .. } | Self::Assignment { location, .. }
// | Self::TupleIndex { location, .. } | Self::TupleIndex { location, .. }
| Self::ModuleSelect { location, .. } | Self::ModuleSelect { location, .. }
| Self::RecordAccess { location, .. } | Self::RecordAccess { location, .. }
| Self::RecordUpdate { location, .. } => *location, | Self::RecordUpdate { location, .. } => *location,
Self::If { Self::If { branches, .. } => branches.first().body.type_defining_location(),
branches,
..
} => branches.first().body.type_defining_location(),
Self::Sequence { Self::Sequence {
expressions, expressions,
@ -301,7 +299,7 @@ impl TypedExpr {
| Self::Pipeline { location, .. } | Self::Pipeline { location, .. }
| Self::ByteArray { location, .. } | Self::ByteArray { location, .. }
| Self::Assignment { location, .. } | Self::Assignment { location, .. }
// | Self::TupleIndex { location, .. } | Self::TupleIndex { location, .. }
| Self::ModuleSelect { location, .. } | Self::ModuleSelect { location, .. }
| Self::RecordAccess { location, .. } | Self::RecordAccess { location, .. }
| Self::RecordUpdate { location, .. } => *location, | Self::RecordUpdate { location, .. } => *location,
@ -401,11 +399,13 @@ pub enum UntypedExpr {
location: Span, location: Span,
elems: Vec<Self>, elems: Vec<Self>,
}, },
// TupleIndex {
// location: Span, TupleIndex {
// index: u64, location: Span,
// tuple: Box<Self>, index: usize,
// }, tuple: Box<Self>,
},
Todo { Todo {
kind: TodoKind, kind: TodoKind,
location: Span, location: Span,
@ -490,7 +490,7 @@ impl UntypedExpr {
| Self::Tuple { location, .. } | Self::Tuple { location, .. }
| Self::String { location, .. } | Self::String { location, .. }
| Self::Assignment { location, .. } | Self::Assignment { location, .. }
// | Self::TupleIndex { location, .. } | Self::TupleIndex { location, .. }
| Self::FieldAccess { location, .. } | Self::FieldAccess { location, .. }
| Self::RecordUpdate { location, .. } | Self::RecordUpdate { location, .. }
| Self::Negate { location, .. } | Self::Negate { location, .. }

View File

@ -1,4 +1,5 @@
use itertools::Itertools; use itertools::Itertools;
use ordinal::Ordinal;
use std::sync::Arc; use std::sync::Arc;
use vec1::Vec1; use vec1::Vec1;
@ -796,6 +797,14 @@ impl<'comments> Formatter<'comments> {
elems.iter().map(|e| (self.wrap_expr(e), false)), elems.iter().map(|e| (self.wrap_expr(e), false)),
)) ))
.group(), .group(),
UntypedExpr::TupleIndex { index, tuple, .. } => {
let suffix = Ordinal(*index + 1).suffix().to_doc();
self.expr(tuple)
.append(".".to_doc())
.append((index + 1).to_doc())
.append(suffix)
}
}; };
commented(document, comments) commented(document, comments)
} }

View File

@ -1089,6 +1089,7 @@ pub fn expr_parser(
enum Chain { enum Chain {
Call(Vec<ParserArg>, Span), Call(Vec<ParserArg>, Span),
FieldAccess(String, Span), FieldAccess(String, Span),
TupleIndex(usize, Span),
} }
let field_access_parser = just(Token::Dot) let field_access_parser = just(Token::Dot)
@ -1097,6 +1098,19 @@ pub fn expr_parser(
}) })
.map_with_span(Chain::FieldAccess); .map_with_span(Chain::FieldAccess);
let tuple_index_parser = just(Token::Dot)
.ignore_then(select! {
Token::Ordinal { index } => index,
})
.validate(|index, span, emit| {
if index < 1 {
emit(ParseError::invalid_tuple_index(span, index, None));
Chain::TupleIndex(0, span)
} else {
Chain::TupleIndex(index as usize - 1, span)
}
});
let call_parser = choice(( let call_parser = choice((
select! { Token::Name { name } => name } select! { Token::Name { name } => name }
.then_ignore(just(Token::Colon)) .then_ignore(just(Token::Colon))
@ -1123,11 +1137,11 @@ pub fn expr_parser(
.delimited_by(just(Token::LeftParen), just(Token::RightParen)) .delimited_by(just(Token::LeftParen), just(Token::RightParen))
.map_with_span(Chain::Call); .map_with_span(Chain::Call);
let chain = choice((field_access_parser, call_parser)); let chain = choice((tuple_index_parser, field_access_parser, call_parser));
let chained = expr_unit_parser let chained = expr_unit_parser
.then(chain.repeated()) .then(chain.repeated())
.foldl(|e, chain| match chain { .foldl(|expr, chain| match chain {
Chain::Call(args, span) => { Chain::Call(args, span) => {
let mut holes = Vec::new(); let mut holes = Vec::new();
@ -1161,7 +1175,7 @@ pub fn expr_parser(
let call = expr::UntypedExpr::Call { let call = expr::UntypedExpr::Call {
location: span, location: span,
fun: Box::new(e), fun: Box::new(expr),
arguments: args, arguments: args,
}; };
@ -1179,9 +1193,15 @@ pub fn expr_parser(
} }
Chain::FieldAccess(label, span) => expr::UntypedExpr::FieldAccess { Chain::FieldAccess(label, span) => expr::UntypedExpr::FieldAccess {
location: e.location().union(span), location: expr.location().union(span),
label, label,
container: Box::new(e), container: Box::new(expr),
},
Chain::TupleIndex(index, span) => expr::UntypedExpr::TupleIndex {
location: expr.location().union(span),
index,
tuple: Box::new(expr),
}, },
}); });

View File

@ -24,6 +24,17 @@ impl ParseError {
} }
self self
} }
pub fn invalid_tuple_index(span: Span, index: u32, suffix: Option<String>) -> Self {
let hint = suffix.map(|suffix| format!("{index}{suffix}"));
Self {
kind: ErrorKind::InvalidTupleIndex { hint },
span,
while_parsing: None,
expected: HashSet::new(),
label: None,
}
}
} }
impl PartialEq for ParseError { impl PartialEq for ParseError {
@ -69,20 +80,22 @@ impl<T: Into<Pattern>> chumsky::Error<T> for ParseError {
#[derive(Debug, PartialEq, Eq, Diagnostic, thiserror::Error)] #[derive(Debug, PartialEq, Eq, Diagnostic, thiserror::Error)]
pub enum ErrorKind { pub enum ErrorKind {
#[error("unexpected end")] #[error("Unexpected end")]
UnexpectedEnd, UnexpectedEnd,
#[error("{0}")] #[error("{0}")]
#[diagnostic(help("{}", .0.help().unwrap_or_else(|| Box::new(""))))] #[diagnostic(help("{}", .0.help().unwrap_or_else(|| Box::new(""))))]
Unexpected(Pattern), Unexpected(Pattern),
#[error("unclosed {start}")] #[error("Unclosed {start}")]
Unclosed { Unclosed {
start: Pattern, start: Pattern,
#[label] #[label]
before_span: Span, before_span: Span,
before: Option<Pattern>, before: Option<Pattern>,
}, },
#[error("no end branch")] #[error("No end branch")]
NoEndBranch, NoEndBranch,
#[error("Invalid tuple index{}", hint.as_ref().map(|s| format!("; did you mean '{s}' ?")).unwrap_or_default())]
InvalidTupleIndex { hint: Option<String> },
} }
#[derive(Debug, PartialEq, Eq, Hash, Diagnostic, thiserror::Error)] #[derive(Debug, PartialEq, Eq, Hash, Diagnostic, thiserror::Error)]

View File

@ -2,6 +2,8 @@ use chumsky::prelude::*;
use crate::ast::Span; use crate::ast::Span;
use ordinal::Ordinal;
use super::{error::ParseError, token::Token}; use super::{error::ParseError, token::Token};
pub fn lexer() -> impl Parser<char, Vec<(Token, Span)>, Error = ParseError> { pub fn lexer() -> impl Parser<char, Vec<(Token, Span)>, Error = ParseError> {
@ -17,6 +19,25 @@ pub fn lexer() -> impl Parser<char, Vec<(Token, Span)>, Error = ParseError> {
)) ))
.map(|value| Token::Int { value }); .map(|value| Token::Int { value });
let ordinal = text::int(10)
.from_str()
.unwrapped()
.then_with(|index: u32| {
choice((just("st"), just("nd"), just("rd"), just("th")))
.map(move |suffix| (index, suffix))
})
.validate(|(index, suffix), span, emit| {
let expected_suffix = Ordinal(index).suffix();
if expected_suffix != suffix {
emit(ParseError::invalid_tuple_index(
span,
index,
Some(expected_suffix.to_string()),
))
}
Token::Ordinal { index }
});
let op = choice(( let op = choice((
just("==").to(Token::EqualEqual), just("==").to(Token::EqualEqual),
just('=').to(Token::Equal), just('=').to(Token::Equal),
@ -132,7 +153,7 @@ pub fn lexer() -> impl Parser<char, Vec<(Token, Span)>, Error = ParseError> {
module_comments, module_comments,
doc_comments, doc_comments,
comments, comments,
choice((keyword, int, op, grouping, string)) choice((ordinal, keyword, int, op, grouping, string))
.or(any().map(Token::Error).validate(|t, span, emit| { .or(any().map(Token::Error).validate(|t, span, emit| {
emit(ParseError::expected_input_found( emit(ParseError::expected_input_found(
span, span,

View File

@ -4,6 +4,7 @@ use std::fmt;
pub enum Token { pub enum Token {
Error(char), Error(char),
Name { name: String }, Name { name: String },
Ordinal { index: u32 },
UpName { name: String }, UpName { name: String },
DiscardName { name: String }, DiscardName { name: String },
Int { value: String }, Int { value: String },
@ -78,12 +79,17 @@ pub enum Token {
impl fmt::Display for Token { impl fmt::Display for Token {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let index_str;
let s = match self { let s = match self {
Token::Error(c) => { Token::Error(c) => {
write!(f, "\"{}\"", c)?; write!(f, "\"{}\"", c)?;
return Ok(()); return Ok(());
} }
Token::Name { name } => name, Token::Name { name } => name,
Token::Ordinal { index } => {
index_str = index.to_string();
&index_str[..]
}
Token::UpName { name } => name, Token::UpName { name } => name,
Token::DiscardName { name } => name, Token::DiscardName { name } => name,
Token::Int { value } => value, Token::Int { value } => value,

View File

@ -1436,3 +1436,106 @@ fn record_create_unlabeled() {
}), }),
) )
} }
#[test]
fn parse_tuple() {
let code = indoc! {r#"
fn foo() {
let tuple = #(1, 2, 3, 4)
tuple.1st + tuple.2nd + tuple.3rd + tuple.4th
}
"#};
assert_definition(
code,
ast::UntypedDefinition::Fn(Function {
arguments: vec![],
body: expr::UntypedExpr::Sequence {
location: Span::new((), 13..86),
expressions: vec![
expr::UntypedExpr::Assignment {
location: Span::new((), 13..38),
value: Box::new(expr::UntypedExpr::Tuple {
location: Span::new((), 25..38),
elems: vec![
expr::UntypedExpr::Int {
location: Span::new((), 27..28),
value: "1".to_string(),
},
expr::UntypedExpr::Int {
location: Span::new((), 30..31),
value: "2".to_string(),
},
expr::UntypedExpr::Int {
location: Span::new((), 33..34),
value: "3".to_string(),
},
expr::UntypedExpr::Int {
location: Span::new((), 36..37),
value: "4".to_string(),
},
],
}),
pattern: ast::Pattern::Var {
location: Span::new((), 17..22),
name: "tuple".to_string(),
},
kind: ast::AssignmentKind::Let,
annotation: None,
},
expr::UntypedExpr::BinOp {
location: Span::new((), 41..86),
name: ast::BinOp::AddInt,
left: Box::new(expr::UntypedExpr::BinOp {
location: Span::new((), 41..74),
name: ast::BinOp::AddInt,
left: Box::new(expr::UntypedExpr::BinOp {
location: Span::new((), 41..62),
name: ast::BinOp::AddInt,
left: Box::new(expr::UntypedExpr::TupleIndex {
location: Span::new((), 41..50),
index: 0,
tuple: Box::new(expr::UntypedExpr::Var {
location: Span::new((), 41..46),
name: "tuple".to_string(),
}),
}),
right: Box::new(expr::UntypedExpr::TupleIndex {
location: Span::new((), 53..62),
index: 1,
tuple: Box::new(expr::UntypedExpr::Var {
location: Span::new((), 53..58),
name: "tuple".to_string(),
}),
}),
}),
right: Box::new(expr::UntypedExpr::TupleIndex {
location: Span::new((), 65..74),
index: 2,
tuple: Box::new(expr::UntypedExpr::Var {
location: Span::new((), 65..70),
name: "tuple".to_string(),
}),
}),
}),
right: Box::new(expr::UntypedExpr::TupleIndex {
location: Span::new((), 77..86),
index: 3,
tuple: Box::new(expr::UntypedExpr::Var {
location: Span::new((), 77..82),
name: "tuple".to_string(),
}),
}),
},
],
},
doc: None,
location: Span::new((), 0..8),
name: "foo".to_string(),
public: false,
return_annotation: None,
return_type: (),
end_position: 87,
}),
)
}

View File

@ -1,5 +1,7 @@
use std::{collections::HashMap, sync::Arc}; use std::{collections::HashMap, sync::Arc};
use ordinal::Ordinal;
use miette::Diagnostic; use miette::Diagnostic;
use crate::ast::{BinOp, Span, TodoKind}; use crate::ast::{BinOp, Span, TodoKind};
@ -283,6 +285,20 @@ pub enum Error {
#[label] #[label]
location: Span, location: Span,
}, },
#[error("Trying to access tuple elements on something else than a tuple\n")]
NotATuple {
#[label]
location: Span,
},
#[error("Trying to access the {} element of a {}-tuple\n", Ordinal(*index + 1).to_string(), size)]
TupleIndexOutOfBound {
#[label]
location: Span,
index: usize,
size: usize,
},
} }
impl Error { impl Error {

View File

@ -321,12 +321,13 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
.. ..
} => self.infer_field_access(*container, label, location), } => self.infer_field_access(*container, label, location),
// UntypedExpr::TupleIndex { UntypedExpr::TupleIndex {
// location, location,
// index, index,
// tuple, tuple,
// .. ..
// } => self.infer_tuple_index(*tuple, index, location), } => self.infer_tuple_index(*tuple, index, location),
UntypedExpr::ByteArray { location, bytes } => { UntypedExpr::ByteArray { location, bytes } => {
Ok(self.infer_byte_array(bytes, location)) Ok(self.infer_byte_array(bytes, location))
} }
@ -1701,6 +1702,38 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
}) })
} }
fn infer_tuple_index(
&mut self,
tuple: UntypedExpr,
index: usize,
location: Span,
) -> Result<TypedExpr, Error> {
let tuple = self.infer(tuple)?;
let tipo = match *tuple.tipo() {
Type::Tuple { ref elems, .. } => {
let size = elems.len();
if index >= size {
Err(Error::TupleIndexOutOfBound {
location,
index,
size,
})
} else {
Ok(elems[index].clone())
}
}
_ => Err(Error::NotATuple { location }),
}?;
Ok(TypedExpr::TupleIndex {
location,
tipo,
index,
tuple: Box::new(tuple),
})
}
fn infer_todo(&mut self, location: Span, kind: TodoKind, label: Option<String>) -> TypedExpr { fn infer_todo(&mut self, location: Span, kind: TodoKind, label: Option<String>) -> TypedExpr {
let tipo = self.new_unbound_var(); let tipo = self.new_unbound_var();

View File

@ -532,6 +532,10 @@ impl<'a> CodeGenerator<'a> {
self.build_ir(then, ir_stack, scope); self.build_ir(then, ir_stack, scope);
} }
TypedExpr::TupleIndex { .. } => {
todo!("Tuple indexing not implementing yet");
}
} }
} }

View File

@ -0,0 +1,5 @@
# This file was generated by Aiken
# You typically do not need to edit this file
requirements = []
packages = []

View File

@ -0,0 +1,2 @@
name = "aiken-lang/acceptance_test_033"
version = "0.0.0"

View File

@ -0,0 +1 @@
packages = []

View File

@ -0,0 +1,4 @@
test tuple_1() {
let coordinates = #(14, 42)
coordinates.1st == 14 && coordinates.2nd == 42
}