Skip to content

Commit

Permalink
Update tuple syntax and allow named tuples
Browse files Browse the repository at this point in the history
  • Loading branch information
mohammadfawaz committed Jul 27, 2023
1 parent 963396b commit 01218d5
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 74 deletions.
10 changes: 6 additions & 4 deletions specs/src/lang/language_primitives.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,10 +158,10 @@ The syntax for a types is as follows:
| "string"
| <tuple-ty>
<tuple-ty> ::= "(" ( <ty> "," ... ) ")"
<tuple-ty> ::= "{" ( <ty> "," ... ) "}"
```

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.

## Expressions

Expand Down Expand Up @@ -286,10 +286,12 @@ let string = "first line\
Tuple Expressions are written as:

```ebnf
<tuple-expr> ::= "(" ( <expr> "," ... ) ")"
<tuple-expr> ::= "{" ( <expr> "," ... ) "}"
```

For example: `let t = (5, 3, "foo");`.
For example: `let t = { 5, 3, "foo" };`.

Tuple expressions that contain a single 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 indexing expressions are written as:

Expand Down
8 changes: 5 additions & 3 deletions yurtc/src/ast.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use itertools::Either;

#[derive(Clone, Debug, PartialEq)]
pub(super) enum UseTree {
Glob,
Expand Down Expand Up @@ -46,7 +48,7 @@ pub(super) enum Type {
Int,
Bool,
String,
Tuple(Vec<Type>),
Tuple(Vec<(Option<Ident>, Type)>),
}

#[derive(Clone, Debug, PartialEq)]
Expand All @@ -69,10 +71,10 @@ pub(super) enum Expr {
Block(Block),
If(IfExpr),
Cond(CondExpr),
Tuple(Vec<Expr>),
Tuple(Vec<(Option<Ident>, Expr)>),
TupleIndex {
tuple: Box<Expr>,
index: usize,
index: Either<usize, Ident>,
},
}

Expand Down
116 changes: 65 additions & 51 deletions yurtc/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -278,12 +279,19 @@ fn expr<'sc>() -> impl Parser<Token<'sc>, 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);

Expand Down Expand Up @@ -316,54 +324,58 @@ fn tuple_index<'sc, P>(
where
P: Parser<Token<'sc>, ast::Expr, Error = ParseError<'sc>> + Clone,
{
let indices =
filter_map(|span, token| match &token {
Token::IntLiteral(num_str) => num_str
.parse::<usize>()
.map(|index| vec![index])
.map_err(|_| ParseError::InvalidIntegerTupleIndex {
span,
index: num_str,
}),

// If the next token is of the form `<int>.<int>` 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::<usize>().map_err(|_| {
ParseError::InvalidIntegerTupleIndex {
span: span.clone(),
index,
}
let indices = filter_map(|span, token| match &token {
Token::Ident(ident) => Ok(vec![Either::Right(ast::Ident(
ident.to_owned().to_string(),
))]),
//Token::Ident(id) => Ok(ast::Ident(id.to_owned())),
Token::IntLiteral(num_str) => num_str
.parse::<usize>()
.map(|index| vec![Either::Left(index)])
.map_err(|_| ParseError::InvalidIntegerTupleIndex {
span,
index: num_str,
}),

// If the next token is of the form `<int>.<int>` 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::<usize>()
.map_err(|_| ParseError::InvalidIntegerTupleIndex {
span: span.clone(),
index,
})
})
.collect::<Result<Vec<usize>, _>>()
}
None => Err(ParseError::InvalidTupleIndex { span, index: token }),
.map(Either::Left)
})
.collect::<Result<Vec<Either<usize, ast::Ident>>, _>>()
}
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())
Expand Down Expand Up @@ -484,10 +496,12 @@ fn ident<'sc>() -> impl Parser<Token<'sc>, ast::Ident, Error = ParseError<'sc>>

fn type_<'sc>() -> impl Parser<Token<'sc>, 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 })
Expand Down
35 changes: 19 additions & 16 deletions yurtc/src/parser/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,11 @@ fn types() {
expect_test::expect!["String"],
);
check(
&run_parser!(type_(), "(int, real, string)"),
&run_parser!(type_(), "{int, real, string}"),
expect_test::expect!["Tuple([Int, Real, String])"],
);
check(
&run_parser!(type_(), "(int, (real, int), string)"),
&run_parser!(type_(), "{int, {real, int}, string}"),
expect_test::expect!["Tuple([Int, Tuple([Real, Int]), String])"],
);
}
Expand Down Expand Up @@ -664,9 +664,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""#]],
);
}

Expand Down Expand Up @@ -704,26 +702,31 @@ fn if_exprs() {
#[test]
fn tuple_expressions() {
check(
&run_parser!(expr(), r#"(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#"{0,}"#), // This is a tuple
expect_test::expect!["Tuple([Immediate(Int(0))])"],
);

check(
&run_parser!(expr(), r#"(0, 1.0, "foo")"#),
&run_parser!(expr(), r#"{0, 1.0, "foo"}"#),
expect_test::expect![[
r#"Tuple([Immediate(Int(0)), Immediate(Real(1.0)), Immediate(String("foo"))])"#
]],
);

check(
&run_parser!(expr(), r#"(0, (1.0, "bar"), "foo")"#),
&run_parser!(expr(), r#"{0, {1.0, "bar"}, "foo"}"#),
expect_test::expect![[
r#"Tuple([Immediate(Int(0)), Tuple([Immediate(Real(1.0)), Immediate(String("bar"))]), Immediate(String("foo"))])"#
]],
);

check(
&run_parser!(expr(), r#"( { 42 }, if c { 2 } else { 3 }, foo() )"#),
&run_parser!(expr(), r#"{ { 42 }, if c { 2 } else { 3 }, 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: [] }])"#
]],
Expand All @@ -737,7 +740,7 @@ fn tuple_expressions() {
);

check(
&run_parser!(expr(), r#"(0, 1).0"#),
&run_parser!(expr(), r#"{0, 1}.0"#),
expect_test::expect![
"TupleIndex { tuple: Tuple([Immediate(Int(0)), Immediate(Int(1))]), index: 0 }"
],
Expand Down Expand Up @@ -765,12 +768,12 @@ fn tuple_expressions() {
);

check(
&run_parser!(expr(), r#"{ (0, 0) }.0"#),
&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 }"],
);

check(
&run_parser!(expr(), r#"if true { (0, 0) } else { (0, 0) }.0"#),
&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 }"],
);

Expand Down Expand Up @@ -833,7 +836,7 @@ fn tuple_expressions() {
);

check(
&run_parser!(let_decl(expr()), "let bad_typle:() = ();"),
&run_parser!(let_decl(expr()), "let bad_typle:{} = {};"),
expect_test::expect![[r#"
@14..16: empty tuple types are not allowed
@19..21: empty tuple expressions are not allowed
Expand Down Expand Up @@ -884,7 +887,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"
"#]],
);

Expand Down Expand Up @@ -922,7 +925,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"
"#]],
);
}
Expand All @@ -939,7 +942,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"
"#]],
);
}
Expand Down

0 comments on commit 01218d5

Please sign in to comment.