From 3e78dfeadacaf036c10f4f1682ae72f46768bbcf Mon Sep 17 00:00:00 2001 From: Mohammad Fawaz Date: Thu, 27 Jul 2023 16:55:42 -0400 Subject: [PATCH] Update tuple syntax and allow named tuples --- specs/src/lang/language_primitives.md | 52 +++++++-- yurtc/src/ast.rs | 10 +- yurtc/src/parser.rs | 126 +++++++++++--------- yurtc/src/parser/tests.rs | 160 +++++++++++++++++++------- yurtc/tests/basic_tests/tuples.yrt | 26 +++++ 5 files changed, 264 insertions(+), 110 deletions(-) create mode 100644 yurtc/tests/basic_tests/tuples.yrt diff --git a/specs/src/lang/language_primitives.md b/specs/src/lang/language_primitives.md index 7ec368a69..c8f14effe 100644 --- a/specs/src/lang/language_primitives.md +++ b/specs/src/lang/language_primitives.md @@ -149,7 +149,7 @@ TODO Yurt provides 4 scalar types built-in: Booleans, integers, reals and strings. Yurt also provides tuples as a compound built-int type. -The syntax for a types is as follows: +The syntax for types is as follows: ```ebnf ::= "bool" @@ -158,10 +158,14 @@ The syntax for a types is as follows: | "string" | - ::= "(" ( "," ... ) ")" + ::= "{" ( [ ":" ] "," ... ) "}" ``` -For example, in `let t: (int, real, string) = (5, 3.0, "foo")`, `(int, real, string)` is a tuple type. +For example, in `let t: { int, real, string } = { 5, 3.0, "foo" }`, `{ int, real, string }` is a tuple type. `{x: int, y: real, string }` is also a tuple tuple type where some of the fields are named. + +Names of tuple fields modify the type of the tuple. That is, `{ x: int }` and `{ y: int }` are different types. However they both coerce to `{ int }`. + +Note that the grammar disallows empty tuple types `{ }`. ## Expressions @@ -180,7 +184,7 @@ Expressions represent values and have the following syntax: | | | - | + | | | | @@ -281,23 +285,49 @@ let string = "first line\ third line"; ``` -#### Tuple Expressions and Tuple Indexing Expressions +#### Tuple Expressions and Tuple Field Access Expressions Tuple Expressions are written as: ```ebnf - ::= "(" ( "," ... ) ")" + ::= "{" ( [ ":" ] "," ... ) "}" ``` -For example: `let t = (5, 3, "foo");`. +For example: `let t = { x: 5, 3, "foo" };`. The type of this tuple can be inferred by the compiler to be `{ x: int, int, string }`. + +The following is another example: + +```rust +let t: { x: int, real } = { 6, 5.0 } +``` + +where the type of the tuple is indicated by the type annotation and has a named field `x`, but that named field is not actually used in the tuple expression. This is allowed because `{ x: int, real }` and `{ int, real }` coerce into each other. + +Tuple fields can be initialized out of order only if all the fields have names and their names are used in the tuple expression. For example, the following is allowed: + +```rust +let t: { x: int, y: real } = { y: 5.0, x: 6 }; +``` + +while the following are not: + +```rust +let t: { x: int, real } = { 5.0, x: 6 }; +let t: { x: int, y: real } = { 5.0, x: 6 }; +let t: { x: int, y: real } = { 5.0, 6 }; // This is a type mismatch! +``` + +Tuple expressions that contain a single _unnamed_ field require the trailing `,` as in `let t = { 4.0, };`. Otherwise, the expression becomes a code block that simply evaluates to its contained expression. Tuple expressions that contain a single _named_ field do not require the trailing `,`. + +Note that the grammar disallows empty tuple expressions `{ }`. -Tuple indexing expressions are written as: +Tuple field access expressions are written as: ```ebnf - ::= "." [0-9]+ + ::= "." ( [0-9]+ | ) ``` -For example: `let second = t.1;` which extracts the second element of tuple `t` and stores it into `second`. +For example: `let second = t.1;` which extracts the second element of tuple `t` and stores it into `second`. Named field can be accessed using their names or their index. For example, if `x` is the third field of tuple `t`, then `t.2` and `t.x` are equivalent. #### "If" Expressions @@ -349,7 +379,7 @@ The precedence of Yurt operators and expressions is ordered as follows, going fr | Operator | Associativity | | -------------------------------- | -------------------- | -| Tuple index expressions | left to right | +| Tuple field access expressions | left to right | | Unary `-`, Unary `+`, `!` | | | `*`, `/`, `%` | left to right | | Binary `+`, Binary `-` | left to right | diff --git a/yurtc/src/ast.rs b/yurtc/src/ast.rs index 70fe89a66..7f9d2432d 100644 --- a/yurtc/src/ast.rs +++ b/yurtc/src/ast.rs @@ -1,3 +1,5 @@ +use itertools::Either; + #[derive(Clone, Debug, PartialEq)] pub(super) enum UseTree { Glob, @@ -46,7 +48,7 @@ pub(super) enum Type { Int, Bool, String, - Tuple(Vec), + Tuple(Vec<(Option, Type)>), } #[derive(Clone, Debug, PartialEq)] @@ -69,10 +71,10 @@ pub(super) enum Expr { Block(Block), If(IfExpr), Cond(CondExpr), - Tuple(Vec), - TupleIndex { + Tuple(Vec<(Option, Expr)>), + TupleFieldAccess { tuple: Box, - index: usize, + field: Either, }, } diff --git a/yurtc/src/parser.rs b/yurtc/src/parser.rs index 8315d2420..9aa04c3c8 100644 --- a/yurtc/src/parser.rs +++ b/yurtc/src/parser.rs @@ -4,6 +4,7 @@ use crate::{ lexer::{self, Token, KEYWORDS}, }; use chumsky::{prelude::*, Stream}; +use itertools::Either; use regex::Regex; use std::{fs::read_to_string, path::Path}; @@ -278,12 +279,19 @@ fn expr<'sc>() -> impl Parser, ast::Expr, Error = ParseError<'sc>> + .then(args.clone()) .map(|(name, args)| ast::Expr::Call { name, args }); - let tuple = args - .validate(|args, span, emit| { - if args.is_empty() { + let tuple_fields = (ident().then_ignore(just(Token::Colon))) + .or_not() + .then(expr.clone()) + .separated_by(just(Token::Comma)) + .allow_trailing() + .delimited_by(just(Token::BraceOpen), just(Token::BraceClose)); + + let tuple = tuple_fields + .validate(|tuple_fields, span, emit| { + if tuple_fields.is_empty() { emit(ParseError::EmptyTupleExpr { span }) } - args + tuple_fields }) .map(ast::Expr::Tuple); @@ -304,72 +312,78 @@ fn expr<'sc>() -> impl Parser, ast::Expr, Error = ParseError<'sc>> + and_or_op( Token::DoubleAmpersand, ast::BinaryOp::LogicalAnd, - comparison_op(additive_op(multiplicative_op(tuple_index(atom)))), + comparison_op(additive_op(multiplicative_op(tuple_field_access(atom)))), ), ) }) } -fn tuple_index<'sc, P>( +fn tuple_field_access<'sc, P>( parser: P, ) -> impl Parser, ast::Expr, Error = ParseError<'sc>> + Clone where P: Parser, ast::Expr, Error = ParseError<'sc>> + Clone, { - let indices = - filter_map(|span, token| match &token { - Token::IntLiteral(num_str) => num_str - .parse::() - .map(|index| vec![index]) - .map_err(|_| ParseError::InvalidIntegerTupleIndex { - span, - index: num_str, - }), - - // If the next token is of the form `.` which, to the lexer, looks like a real, - // break it apart manually. - Token::RealLiteral(num_str) => { - match Regex::new(r"[0-9]+\.[0-9]+") - .expect("valid regex") - .captures(num_str) - { - Some(_) => { - // Collect the spans for the two integers - let dot_index = num_str - .chars() - .position(|c| c == '.') - .expect("guaranteed by regex"); - let spans = [ - span.start..span.start + dot_index, - span.start + dot_index + 1..span.end, - ]; - - // Split at `.` then collect the two indices as `usize`. Report errors as - // needed - num_str - .split('.') - .zip(spans.iter()) - .map(|(index, span)| { - index.parse::().map_err(|_| { - ParseError::InvalidIntegerTupleIndex { - span: span.clone(), - index, - } + let indices = filter_map(|span, token| match &token { + // Field access with an identifier + Token::Ident(ident) => Ok(vec![Either::Right(ast::Ident( + ident.to_owned().to_string(), + ))]), + + // Field access with an integer + Token::IntLiteral(num_str) => num_str + .parse::() + .map(|index| vec![Either::Left(index)]) + .map_err(|_| ParseError::InvalidIntegerTupleIndex { + span, + index: num_str, + }), + + // If the next token is of the form `.` which, to the lexer, looks like a real, + // break it apart manually. + Token::RealLiteral(num_str) => { + match Regex::new(r"[0-9]+\.[0-9]+") + .expect("valid regex") + .captures(num_str) + { + Some(_) => { + // Collect the spans for the two integers + let dot_index = num_str + .chars() + .position(|c| c == '.') + .expect("guaranteed by regex"); + let spans = [ + span.start..span.start + dot_index, + span.start + dot_index + 1..span.end, + ]; + + // Split at `.` then collect the two indices as `usize`. Report errors as + // needed + num_str + .split('.') + .zip(spans.iter()) + .map(|(index, span)| { + index + .parse::() + .map_err(|_| ParseError::InvalidIntegerTupleIndex { + span: span.clone(), + index, }) - }) - .collect::, _>>() - } - None => Err(ParseError::InvalidTupleIndex { span, index: token }), + .map(Either::Left) + }) + .collect::>, _>>() } + None => Err(ParseError::InvalidTupleIndex { span, index: token }), } - _ => Err(ParseError::InvalidTupleIndex { span, index: token }), - }); + } + _ => Err(ParseError::InvalidTupleIndex { span, index: token }), + }); parser .then(just(Token::Dot).ignore_then(indices).repeated().flatten()) - .foldl(|expr, index| ast::Expr::TupleIndex { + .foldl(|expr, field| ast::Expr::TupleFieldAccess { tuple: Box::new(expr), - index, + field, }) } @@ -484,10 +498,12 @@ fn ident<'sc>() -> impl Parser, ast::Ident, Error = ParseError<'sc>> fn type_<'sc>() -> impl Parser, ast::Type, Error = ParseError<'sc>> + Clone { recursive(|type_| { - let tuple = type_ + let tuple = (ident().then_ignore(just(Token::Colon))) + .or_not() + .then(type_) .separated_by(just(Token::Comma)) .allow_trailing() - .delimited_by(just(Token::ParenOpen), just(Token::ParenClose)) + .delimited_by(just(Token::BraceOpen), just(Token::BraceClose)) .validate(|args, span, emit| { if args.is_empty() { emit(ParseError::EmptyTupleType { span }) diff --git a/yurtc/src/parser/tests.rs b/yurtc/src/parser/tests.rs index d65df5e85..c0fbbc78c 100644 --- a/yurtc/src/parser/tests.rs +++ b/yurtc/src/parser/tests.rs @@ -49,12 +49,14 @@ fn types() { expect_test::expect!["String"], ); check( - &run_parser!(type_(), "(int, real, string)"), - expect_test::expect!["Tuple([Int, Real, String])"], + &run_parser!(type_(), "{int, real, string}"), + expect_test::expect!["Tuple([(None, Int), (None, Real), (None, String)])"], ); check( - &run_parser!(type_(), "(int, (real, int), string)"), - expect_test::expect!["Tuple([Int, Tuple([Real, Int]), String])"], + &run_parser!(type_(), "{int, {real, int}, string}"), + expect_test::expect![ + "Tuple([(None, Int), (None, Tuple([(None, Real), (None, Int)])), (None, String)])" + ], ); } @@ -664,9 +666,7 @@ fn code_blocks() { check( &format!("{:?}", run_parser!(let_decl(expr()), "let x = {};")), - expect_test::expect![[ - r#""@9..10: found \"}\" but expected \"!\", \"+\", \"-\", \"{\", \"(\", \"if\", \"cond\", \"let\", or \"constraint\"\n""# - ]], + expect_test::expect![[r#""@8..10: empty tuple expressions are not allowed\n""#]], ); } @@ -704,80 +704,167 @@ fn if_exprs() { #[test] fn tuple_expressions() { check( - &run_parser!(expr(), r#"(0,)"#), - expect_test::expect!["Tuple([Immediate(Int(0))])"], + &run_parser!(expr(), r#"{0}"#), // This is not a tuple. It is a code block expr. + expect_test::expect!["Block(Block { statements: [], final_expr: Immediate(Int(0)) })"], + ); + + check( + &run_parser!(expr(), r#"{x: 0}"#), // This is a tuple because the field is named so there is no ambiguity + expect_test::expect![[r#"Tuple([(Some(Ident("x")), Immediate(Int(0)))])"#]], + ); + + check( + &run_parser!(expr(), r#"{0,}"#), // This is a tuple + expect_test::expect!["Tuple([(None, Immediate(Int(0)))])"], ); check( - &run_parser!(expr(), r#"(0, 1.0, "foo")"#), + &run_parser!(expr(), r#"{x: 0,}"#), // This is a tuple + expect_test::expect![[r#"Tuple([(Some(Ident("x")), Immediate(Int(0)))])"#]], + ); + + check( + &run_parser!(expr(), r#"{0, 1.0, "foo"}"#), expect_test::expect![[ - r#"Tuple([Immediate(Int(0)), Immediate(Real(1.0)), Immediate(String("foo"))])"# + r#"Tuple([(None, Immediate(Int(0))), (None, Immediate(Real(1.0))), (None, Immediate(String("foo")))])"# ]], ); check( - &run_parser!(expr(), r#"(0, (1.0, "bar"), "foo")"#), + &run_parser!(expr(), r#"{x: 0, y: 1.0, z: "foo"}"#), expect_test::expect![[ - r#"Tuple([Immediate(Int(0)), Tuple([Immediate(Real(1.0)), Immediate(String("bar"))]), Immediate(String("foo"))])"# + r#"Tuple([(Some(Ident("x")), Immediate(Int(0))), (Some(Ident("y")), Immediate(Real(1.0))), (Some(Ident("z")), Immediate(String("foo")))])"# ]], ); check( - &run_parser!(expr(), r#"( { 42 }, if c { 2 } else { 3 }, foo() )"#), + &run_parser!(expr(), r#"{0, {1.0, "bar"}, "foo"}"#), expect_test::expect![[ - r#"Tuple([Block(Block { statements: [], final_expr: Immediate(Int(42)) }), If(IfExpr { condition: Ident(Ident("c")), then_block: Block { statements: [], final_expr: Immediate(Int(2)) }, else_block: Block { statements: [], final_expr: Immediate(Int(3)) } }), Call { name: Ident("foo"), args: [] }])"# + r#"Tuple([(None, Immediate(Int(0))), (None, Tuple([(None, Immediate(Real(1.0))), (None, Immediate(String("bar")))])), (None, Immediate(String("foo")))])"# ]], ); check( - &run_parser!(expr(), r#"t.0 + t.9999999"#), + &run_parser!(expr(), r#"{x: 0, {y: 1.0, "bar"}, z: "foo"}"#), expect_test::expect![[ - r#"BinaryOp { op: Add, lhs: TupleIndex { tuple: Ident(Ident("t")), index: 0 }, rhs: TupleIndex { tuple: Ident(Ident("t")), index: 9999999 } }"# + r#"Tuple([(Some(Ident("x")), Immediate(Int(0))), (None, Tuple([(Some(Ident("y")), Immediate(Real(1.0))), (None, Immediate(String("bar")))])), (Some(Ident("z")), Immediate(String("foo")))])"# ]], ); check( - &run_parser!(expr(), r#"(0, 1).0"#), - expect_test::expect![ - "TupleIndex { tuple: Tuple([Immediate(Int(0)), Immediate(Int(1))]), index: 0 }" - ], + &run_parser!(expr(), r#"{ { 42 }, if c { 2 } else { 3 }, foo() }"#), + expect_test::expect![[ + r#"Tuple([(None, Block(Block { statements: [], final_expr: Immediate(Int(42)) })), (None, If(IfExpr { condition: Ident(Ident("c")), then_block: Block { statements: [], final_expr: Immediate(Int(2)) }, else_block: Block { statements: [], final_expr: Immediate(Int(3)) } })), (None, Call { name: Ident("foo"), args: [] })])"# + ]], + ); + + check( + &run_parser!( + expr(), + r#"{ x: { 42 }, y: if c { 2 } else { 3 }, z: foo() }"# + ), + expect_test::expect![[ + r#"Tuple([(Some(Ident("x")), Block(Block { statements: [], final_expr: Immediate(Int(42)) })), (Some(Ident("y")), If(IfExpr { condition: Ident(Ident("c")), then_block: Block { statements: [], final_expr: Immediate(Int(2)) }, else_block: Block { statements: [], final_expr: Immediate(Int(3)) } })), (Some(Ident("z")), Call { name: Ident("foo"), args: [] })])"# + ]], + ); + + check( + &run_parser!(expr(), r#"t.0 + t.9999999 + t.x"#), + expect_test::expect![[ + r#"BinaryOp { op: Add, lhs: BinaryOp { op: Add, lhs: TupleFieldAccess { tuple: Ident(Ident("t")), field: Left(0) }, rhs: TupleFieldAccess { tuple: Ident(Ident("t")), field: Left(9999999) } }, rhs: TupleFieldAccess { tuple: Ident(Ident("t")), field: Right(Ident("x")) } }"# + ]], + ); + + check( + &run_parser!(expr(), r#"{0, 1}.0"#), + expect_test::expect!["TupleFieldAccess { tuple: Tuple([(None, Immediate(Int(0))), (None, Immediate(Int(1)))]), field: Left(0) }"], + ); + + check( + &run_parser!(expr(), r#"{0, 1}.x"#), + expect_test::expect![[ + r#"TupleFieldAccess { tuple: Tuple([(None, Immediate(Int(0))), (None, Immediate(Int(1)))]), field: Right(Ident("x")) }"# + ]], ); check( &run_parser!(expr(), r#"t.0 .0"#), expect_test::expect![[ - r#"TupleIndex { tuple: TupleIndex { tuple: Ident(Ident("t")), index: 0 }, index: 0 }"# + r#"TupleFieldAccess { tuple: TupleFieldAccess { tuple: Ident(Ident("t")), field: Left(0) }, field: Left(0) }"# + ]], + ); + + check( + &run_parser!(expr(), r#"t.x .y"#), + expect_test::expect![[ + r#"TupleFieldAccess { tuple: TupleFieldAccess { tuple: Ident(Ident("t")), field: Right(Ident("x")) }, field: Right(Ident("y")) }"# ]], ); check( &run_parser!(expr(), "t \r .1 .2.2. \n 3 . \t 13 . 1.1"), expect_test::expect![[ - r#"TupleIndex { tuple: TupleIndex { tuple: TupleIndex { tuple: TupleIndex { tuple: TupleIndex { tuple: TupleIndex { tuple: TupleIndex { tuple: Ident(Ident("t")), index: 1 }, index: 2 }, index: 2 }, index: 3 }, index: 13 }, index: 1 }, index: 1 }"# + r#"TupleFieldAccess { tuple: TupleFieldAccess { tuple: TupleFieldAccess { tuple: TupleFieldAccess { tuple: TupleFieldAccess { tuple: TupleFieldAccess { tuple: TupleFieldAccess { tuple: Ident(Ident("t")), field: Left(1) }, field: Left(2) }, field: Left(2) }, field: Left(3) }, field: Left(13) }, field: Left(1) }, field: Left(1) }"# + ]], + ); + + check( + &run_parser!(expr(), "t \r .x .1.2. \n w . \t t. 3.4"), + expect_test::expect![[ + r#"TupleFieldAccess { tuple: TupleFieldAccess { tuple: TupleFieldAccess { tuple: TupleFieldAccess { tuple: TupleFieldAccess { tuple: TupleFieldAccess { tuple: TupleFieldAccess { tuple: Ident(Ident("t")), field: Right(Ident("x")) }, field: Left(1) }, field: Left(2) }, field: Right(Ident("w")) }, field: Right(Ident("t")) }, field: Left(3) }, field: Left(4) }"# ]], ); check( &run_parser!(expr(), r#"foo().0.1"#), expect_test::expect![[ - r#"TupleIndex { tuple: TupleIndex { tuple: Call { name: Ident("foo"), args: [] }, index: 0 }, index: 1 }"# + r#"TupleFieldAccess { tuple: TupleFieldAccess { tuple: Call { name: Ident("foo"), args: [] }, field: Left(0) }, field: Left(1) }"# ]], ); check( - &run_parser!(expr(), r#"{ (0, 0) }.0"#), - expect_test::expect!["TupleIndex { tuple: Block(Block { statements: [], final_expr: Tuple([Immediate(Int(0)), Immediate(Int(0))]) }), index: 0 }"], + &run_parser!(expr(), r#"foo().a.b.0.1"#), + expect_test::expect![[ + r#"TupleFieldAccess { tuple: TupleFieldAccess { tuple: TupleFieldAccess { tuple: TupleFieldAccess { tuple: Call { name: Ident("foo"), args: [] }, field: Right(Ident("a")) }, field: Right(Ident("b")) }, field: Left(0) }, field: Left(1) }"# + ]], ); check( - &run_parser!(expr(), r#"if true { (0, 0) } else { (0, 0) }.0"#), - expect_test::expect!["TupleIndex { tuple: If(IfExpr { condition: Immediate(Bool(true)), then_block: Block { statements: [], final_expr: Tuple([Immediate(Int(0)), Immediate(Int(0))]) }, else_block: Block { statements: [], final_expr: Tuple([Immediate(Int(0)), Immediate(Int(0))]) } }), index: 0 }"], + &run_parser!(expr(), r#"{ {0, 0} }.0"#), + expect_test::expect!["TupleFieldAccess { tuple: Block(Block { statements: [], final_expr: Tuple([(None, Immediate(Int(0))), (None, Immediate(Int(0)))]) }), field: Left(0) }"], + ); + + check( + &run_parser!(expr(), r#"{ {0, 0} }.a"#), + expect_test::expect![[ + r#"TupleFieldAccess { tuple: Block(Block { statements: [], final_expr: Tuple([(None, Immediate(Int(0))), (None, Immediate(Int(0)))]) }), field: Right(Ident("a")) }"# + ]], + ); + + check( + &run_parser!(expr(), r#"if true { {0, 0} } else { {0, 0} }.0"#), + expect_test::expect!["TupleFieldAccess { tuple: If(IfExpr { condition: Immediate(Bool(true)), then_block: Block { statements: [], final_expr: Tuple([(None, Immediate(Int(0))), (None, Immediate(Int(0)))]) }, else_block: Block { statements: [], final_expr: Tuple([(None, Immediate(Int(0))), (None, Immediate(Int(0)))]) } }), field: Left(0) }"], + ); + + check( + &run_parser!(expr(), r#"if true { {0, 0} } else { {0, 0} }.x"#), + expect_test::expect![[ + r#"TupleFieldAccess { tuple: If(IfExpr { condition: Immediate(Bool(true)), then_block: Block { statements: [], final_expr: Tuple([(None, Immediate(Int(0))), (None, Immediate(Int(0)))]) }, else_block: Block { statements: [], final_expr: Tuple([(None, Immediate(Int(0))), (None, Immediate(Int(0)))]) } }), field: Right(Ident("x")) }"# + ]], ); // This parses because `1 + 2` is an expression, but it should fail in semantic analysis. check( &run_parser!(expr(), "1 + 2 .3"), - expect_test::expect!["BinaryOp { op: Add, lhs: Immediate(Int(1)), rhs: TupleIndex { tuple: Immediate(Int(2)), index: 3 } }"], + expect_test::expect!["BinaryOp { op: Add, lhs: Immediate(Int(1)), rhs: TupleFieldAccess { tuple: Immediate(Int(2)), field: Left(3) } }"], + ); + + // This parses because `1 + 2` is an expression, but it should fail in semantic analysis. + check( + &run_parser!(expr(), "1 + 2 .a"), + expect_test::expect![[ + r#"BinaryOp { op: Add, lhs: Immediate(Int(1)), rhs: TupleFieldAccess { tuple: Immediate(Int(2)), field: Right(Ident("a")) } }"# + ]], ); check( @@ -826,14 +913,7 @@ fn tuple_expressions() { ); check( - &run_parser!(let_decl(expr()), "let x = t.a;"), - expect_test::expect![[r#" - @10..11: invalid value "a" for tuple index - "#]], - ); - - check( - &run_parser!(let_decl(expr()), "let bad_typle:() = ();"), + &run_parser!(let_decl(expr()), "let bad_tuple:{} = {};"), expect_test::expect![[r#" @14..16: empty tuple types are not allowed @19..21: empty tuple expressions are not allowed @@ -884,7 +964,7 @@ fn cond_exprs() { check( &run_parser!(cond_expr(expr()), r#"cond { a => b, }"#), expect_test::expect![[r#" - @15..16: found "}" but expected "!", "+", "-", "{", "(", "if", "else", or "cond" + @15..16: found "}" but expected "!", "+", "-", "{", "{", "if", "else", or "cond" "#]], ); @@ -922,7 +1002,7 @@ fn with_errors() { check( &run_parser!(yurt_program(), "let low_val: bad = 1.23"), expect_test::expect![[r#" - @13..16: found "bad" but expected "(", "real", "int", "bool", or "string" + @13..16: found "bad" but expected "{", "real", "int", "bool", or "string" "#]], ); } @@ -939,7 +1019,7 @@ fn fn_errors() { check( &run_parser!(yurt_program(), "fn foo() -> real {}"), expect_test::expect![[r#" - @18..19: found "}" but expected "!", "+", "-", "{", "(", "if", "cond", "let", or "constraint" + @18..19: found "}" but expected "!", "+", "-", "{", "{", "if", "cond", "let", or "constraint" "#]], ); } diff --git a/yurtc/tests/basic_tests/tuples.yrt b/yurtc/tests/basic_tests/tuples.yrt new file mode 100644 index 000000000..b5254e73c --- /dev/null +++ b/yurtc/tests/basic_tests/tuples.yrt @@ -0,0 +1,26 @@ +let t0 = { 0, }; let t0_0 = t0.0; +let t1 = { x: 0 }; let t1_0 = t1.0; let t1_x = t1.x; +let t2 = { x: 0, }; let t2_0 = t2.0; let t2_x = t2.x; +let t3: { x: int } = { 0, }; let t3_0 = t3.0; let t3_x = t3.x; +let t3: { x: int } = { x: 0, }; let t4_0 = t4.0; let t4_x = t4.x; +let t4: { x: int, } = { x: 0 }; let t5_0 = t5.0; let t5_x = t5.x; + +let t5 = { 0, 5.0 }; let t5_0 = t5.0; let t5_1 = t5.1; +let t6 = { x: 0, 5.0 }; let t6_0 = t6.0; let t6_x = t6.x; let t6_1 = t6.1; +let t7: { x: int, real } = { 0, 5.0 }; let t7_0 = t7.0; let t7_x = t7.x; let t7_1 = t7.1; +let t8: { x: int, real } = { x: 0, 5.0 }; let t8_0 = t8.0; let t8_x = t8.x; let t8_1 = t8.1; +let t9 = { x: 0, y: 5.0 }; let t9_0 = t9.0; let t9_x = t9.x; let t9_1 = t9.1; let t9_y = t9.x; +let t10: { x: int, y: real } = { 0, 5.0 }; let t10_0 = t10.0; let t10_x = t10.x; let t10_1 = t10.1; let t10_y = t10.x; +let t11: { x: int, y: real } = { x: 0, 5.0 }; let t11_0 = t11.0; let t11_x = t11.x; let t11_1 = t11.1; let t11_y = t11.x; +let t12: { x: int, y: real } = { x: 0, y: 5.0 }; let t12_0 = t12.0; let t12_x = t12.x; let t12_1 = t12.1; let t12_x = t12.x; +let t13: { x: int, y: real } = { y: 5.0, x: 0 }; let t13_0 = t13.0; let t13_x = t13.x; let t13_1 = t13.1; let t13_y = t11.x; + +let t14: { x: int, y: real, z: { int, real, w: string } } = { z: { 0, 6.0, "yurt" }, y: 5.0, x: 0, }; +let t14_2_0 = t14.2.0; +let t14_2_1 = t14.2.1; +let t14_2_2 = t14.2.2; +let t14_2_2 = t14.2.2; +let t14_z_0 = t14.z.0; +let t14_z_1 = t14.z.1; +let t14_z_2 = t14.z.2; +let t14_z_w = t14.z.w;