Skip to content

Commit 969a9bc

Browse files
committed
Tuple types and tuple expressions
1 parent 5d0f987 commit 969a9bc

File tree

5 files changed

+140
-21
lines changed

5 files changed

+140
-21
lines changed

specs/src/lang/language_primitives.md

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ Identifiers have the following syntax:
6262
<ident> ::= _?[A-Za-z][A-Za-z0-9]* % excluding keywords
6363
```
6464

65-
A number of keywords are reserved and cannot be used as identifiers. The keywords are: `bool`, `constraint`, `else`, `false`, `real`, `fn`, `if`, `int`, `let`, `maximize`, `minimize`, `satisfy`, `solve`, `true`, `type`.
65+
A number of keywords are reserved and cannot be used as identifiers. The keywords are: `bool`, `constraint`, `else`, `false`, `real`, `fn`, `if`, `int`, `let`, `maximize`, `minimize`, `satisfy`, `solve`, `true`.
6666

6767
## High-level Intent Structure
6868

@@ -138,7 +138,8 @@ Expressions represent values and have the following syntax:
138138
| <int-literal>
139139
| <real-literal>
140140
| <string-literal>
141-
| <tuple-literal>
141+
| <tuple-expr>
142+
| <tuple-index-expr>
142143
| <if-expr>
143144
| <call-expr>
144145
```
@@ -249,15 +250,23 @@ let string = "first line\
249250
third line";
250251
```
251252

252-
#### Tuple Literals
253+
#### Tuple Expressions and Tuple Indexing Expressions
253254

254-
Tuple literals are written as:
255+
Tuple Expressions are written as:
255256

256257
```ebnf
257-
<tuple-literal> ::= "(" <expr> "," [ <expr> "," ... ] ")"
258+
<tuple-expr> ::= "(" <expr> "," [ <expr> "," ... ] ")"
258259
```
259260

260-
For example: `let t = (5, 3, "foo")`;
261+
For example: `let t = (5, 3, "foo");`.
262+
263+
Tuple indexing expressions are written as:
264+
265+
```ebnf
266+
<tuple-index-expr> ::= <ident> "." [0-9]+
267+
```
268+
269+
For example: `let second = t.1;` which extracts the second element of tuple `t` and stores it into `second`.
261270

262271
#### "If" Expressions
263272

@@ -279,7 +288,7 @@ Call expressions are used to call functions and have the following syntax:
279288
<call-expr> ::= <ident> "(" ( <expr> "," ... ) ")"
280289
```
281290

282-
For example, `x = foo(5, 2);`
291+
For example: `x = foo(5, 2);`.
283292

284293
The type of the expressions passed as arguments must match the argument types of the called function. The return type of the function must also be appropriate for the calling context.
285294

yurtc/src/ast.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ pub(super) enum Type {
4141
Int,
4242
Bool,
4343
String,
44+
Tuple(Vec<Type>),
4445
}
4546

4647
#[derive(Clone, Debug, PartialEq)]
@@ -62,6 +63,11 @@ pub(super) enum Expr {
6263
},
6364
Block(Block),
6465
If(IfExpr),
66+
Tuple(Vec<Expr>),
67+
TupleIndex {
68+
name: Ident,
69+
index: usize,
70+
},
6571
}
6672

6773
#[derive(Clone, Debug, PartialEq)]

yurtc/src/error.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ pub(super) enum ParseError<'a> {
2828
"type annotation or initializer needed for decision variable \"{}\"", name.0
2929
)]
3030
UntypedDecisionVar { span: Span, name: ast::Ident },
31+
#[error("Invalid index value \"{}\"", index)]
32+
InvalidTupleIndex { span: Span, index: Token<'a> },
3133
}
3234

3335
fn format_expected_found_error<'a>(
@@ -120,6 +122,7 @@ impl<'a> CompileError<'a> {
120122
ParseError::ExpectedFound { span, .. } => span.clone(),
121123
ParseError::KeywordAsIdent { span, .. } => span.clone(),
122124
ParseError::UntypedDecisionVar { span, .. } => span.clone(),
125+
ParseError::InvalidTupleIndex { span, .. } => span.clone(),
123126
},
124127
}
125128
}

yurtc/src/lexer.rs

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ pub(super) enum Token<'sc> {
4949
ParenClose,
5050
#[token("->")]
5151
Arrow,
52+
#[token(".")]
53+
Dot,
5254

5355
#[token("real")]
5456
Real,
@@ -150,6 +152,7 @@ impl<'sc> fmt::Display for Token<'sc> {
150152
Token::ParenOpen => write!(f, "("),
151153
Token::ParenClose => write!(f, ")"),
152154
Token::Arrow => write!(f, "->"),
155+
Token::Dot => write!(f, "."),
153156
Token::Real => write!(f, "real"),
154157
Token::Int => write!(f, "int"),
155158
Token::Bool => write!(f, "bool"),
@@ -266,6 +269,19 @@ fn lex_one_error(src: &str) -> CompileError {
266269
errs[0].clone()
267270
}
268271

272+
#[test]
273+
fn control_tokens() {
274+
assert_eq!(lex_one_success(":"), Token::Colon);
275+
assert_eq!(lex_one_success(";"), Token::Semi);
276+
assert_eq!(lex_one_success(","), Token::Comma);
277+
assert_eq!(lex_one_success("{"), Token::BraceOpen);
278+
assert_eq!(lex_one_success("}"), Token::BraceClose);
279+
assert_eq!(lex_one_success("("), Token::ParenOpen);
280+
assert_eq!(lex_one_success(")"), Token::ParenClose);
281+
assert_eq!(lex_one_success("->"), Token::Arrow);
282+
assert_eq!(lex_one_success("."), Token::Dot);
283+
}
284+
269285
#[test]
270286
fn reals() {
271287
assert_eq!(lex_one_success("1.05"), Token::RealLiteral("1.05"));
@@ -275,12 +291,12 @@ fn reals() {
275291
assert_eq!(lex_one_success("0.34"), Token::RealLiteral("0.34"));
276292
assert_eq!(lex_one_success("-0.34"), Token::RealLiteral("-0.34"));
277293
check(
278-
&format!("{:?}", lex_one_error(".34")),
279-
expect_test::expect![[r#"Lex { span: 0..1, error: InvalidToken }"#]],
294+
&format!("{:?}", lex(".34")),
295+
expect_test::expect![[r#"([(Dot, 0..1), (IntLiteral("34"), 1..3)], [])"#]],
280296
);
281297
check(
282-
&format!("{:?}", lex_one_error("12.")),
283-
expect_test::expect!["Lex { span: 2..3, error: InvalidToken }"],
298+
&format!("{:?}", lex("12.")),
299+
expect_test::expect![[r#"([(IntLiteral("12"), 0..2), (Dot, 2..3)], [])"#]],
284300
);
285301
}
286302

yurtc/src/parser.rs

Lines changed: 95 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -213,15 +213,36 @@ fn expr<'sc>() -> impl Parser<Token<'sc>, ast::Expr, Error = ParseError<'sc>> +
213213
.delimited_by(just(Token::ParenOpen), just(Token::ParenClose));
214214

215215
let call = ident()
216-
.then(args)
216+
.then(args.clone())
217217
.map(|(name, args)| ast::Expr::Call { name, args });
218218

219+
let tuple = args.map(ast::Expr::Tuple);
220+
221+
// This extracts a `usize` index. Fails for negative, hex, and binary literals.
222+
// Recovers with `0`.
223+
let index = select! {
224+
Token::IntLiteral(num_str) => (Token::IntLiteral(num_str), num_str.parse::<usize>()),
225+
}
226+
.validate(|(token, index_result), span, emit| {
227+
index_result.unwrap_or_else(|_| {
228+
emit(ParseError::InvalidTupleIndex { span, index: token });
229+
0
230+
})
231+
});
232+
233+
let tuple_index_expr = ident()
234+
.then_ignore(just(Token::Dot))
235+
.then(index)
236+
.map(|(name, index)| ast::Expr::TupleIndex { name, index });
237+
219238
let atom = choice((
220239
immediate().map(ast::Expr::Immediate),
221240
unary_op(expr.clone()),
222241
code_block_expr(expr.clone()).map(ast::Expr::Block),
223242
if_expr(expr.clone()),
224243
call,
244+
tuple_index_expr,
245+
tuple,
225246
ident().map(ast::Expr::Ident),
226247
));
227248

@@ -321,12 +342,20 @@ fn ident<'sc>() -> impl Parser<Token<'sc>, ast::Ident, Error = ParseError<'sc>>
321342
}
322343

323344
fn type_<'sc>() -> impl Parser<Token<'sc>, ast::Type, Error = ParseError<'sc>> + Clone {
324-
choice((
325-
just(Token::Real).to(ast::Type::Real),
326-
just(Token::Int).to(ast::Type::Int),
327-
just(Token::Bool).to(ast::Type::Bool),
328-
just(Token::String).to(ast::Type::String),
329-
))
345+
recursive(|type_| {
346+
let tuple = type_
347+
.separated_by(just(Token::Comma))
348+
.allow_trailing()
349+
.delimited_by(just(Token::ParenOpen), just(Token::ParenClose));
350+
351+
choice((
352+
just(Token::Real).to(ast::Type::Real),
353+
just(Token::Int).to(ast::Type::Int),
354+
just(Token::Bool).to(ast::Type::Bool),
355+
just(Token::String).to(ast::Type::String),
356+
tuple.map(ast::Type::Tuple),
357+
))
358+
})
330359
}
331360

332361
fn immediate<'sc>() -> impl Parser<Token<'sc>, ast::Immediate, Error = ParseError<'sc>> + Clone {
@@ -373,6 +402,25 @@ fn check(actual: &str, expect: expect_test::Expect) {
373402
expect.assert_eq(actual);
374403
}
375404

405+
#[test]
406+
fn types() {
407+
check(&run_parser!(type_(), "int"), expect_test::expect!["Int"]);
408+
check(&run_parser!(type_(), "real"), expect_test::expect!["Real"]);
409+
check(&run_parser!(type_(), "bool"), expect_test::expect!["Bool"]);
410+
check(
411+
&run_parser!(type_(), "string"),
412+
expect_test::expect!["String"],
413+
);
414+
check(
415+
&run_parser!(type_(), "(int, real, string)"),
416+
expect_test::expect!["Tuple([Int, Real, String])"],
417+
);
418+
check(
419+
&run_parser!(type_(), "(int, (real, int), string)"),
420+
expect_test::expect!["Tuple([Int, Tuple([Real, Int]), String])"],
421+
);
422+
}
423+
376424
#[test]
377425
fn let_decls() {
378426
check(
@@ -839,7 +887,7 @@ fn code_blocks() {
839887
check(
840888
&format!("{:?}", run_parser!(let_decl(expr()), "let x = {};")),
841889
expect_test::expect![[
842-
r#""@9..10: found \"}\" but expected \"!\", \"+\", \"-\", \"{\", \"if\", \"var\", \"let\", or \"constraint\"\n""#
890+
r#""@9..10: found \"}\" but expected \"!\", \"+\", \"-\", \"{\", \"(\", \"if\", \"var\", \"let\", or \"constraint\"\n""#
843891
]],
844892
);
845893
}
@@ -881,6 +929,43 @@ fn if_exprs() {
881929
);
882930
}
883931

932+
#[test]
933+
fn tuple_expressions() {
934+
check(
935+
&run_parser!(expr(), r#"(0,)"#),
936+
expect_test::expect!["Tuple([Immediate(Int(0))])"],
937+
);
938+
939+
check(
940+
&run_parser!(expr(), r#"(0, 1.0, "foo")"#),
941+
expect_test::expect![[
942+
r#"Tuple([Immediate(Int(0)), Immediate(Real(1.0)), Immediate(String("foo"))])"#
943+
]],
944+
);
945+
946+
check(
947+
&run_parser!(expr(), r#"(0, (1.0, "bar"), "foo")"#),
948+
expect_test::expect![[
949+
r#"Tuple([Immediate(Int(0)), Tuple([Immediate(Real(1.0)), Immediate(String("bar"))]), Immediate(String("foo"))])"#
950+
]],
951+
);
952+
953+
check(
954+
&run_parser!(expr(), r#"t.0 + t.9999999"#),
955+
expect_test::expect![[
956+
r#"BinaryOp { op: Add, lhs: TupleIndex { name: Ident("t"), index: 0 }, rhs: TupleIndex { name: Ident("t"), index: 9999999 } }"#
957+
]],
958+
);
959+
960+
check(
961+
&run_parser!(expr(), r#"t.0xa + t.0b1"#),
962+
expect_test::expect![[r#"
963+
@2..5: Invalid index value "0xa"
964+
@10..13: Invalid index value "0b1"
965+
"#]],
966+
);
967+
}
968+
884969
#[test]
885970
fn basic_program() {
886971
let src = r#"
@@ -907,7 +992,7 @@ fn with_errors() {
907992
check(
908993
&run_parser!(yurt_program(), "let low_val: bad = 1.23"),
909994
expect_test::expect![[r#"
910-
@13..16: found "bad" but expected "real", "int", "bool", or "string"
995+
@13..16: found "bad" but expected "(", "real", "int", "bool", or "string"
911996
"#]],
912997
);
913998
}
@@ -924,7 +1009,7 @@ fn fn_errors() {
9241009
check(
9251010
&run_parser!(yurt_program(), "fn foo() -> real {}"),
9261011
expect_test::expect![[r#"
927-
@18..19: found "}" but expected "!", "+", "-", "{", "if", "var", "let", or "constraint"
1012+
@18..19: found "}" but expected "!", "+", "-", "{", "(", "if", "var", "let", or "constraint"
9281013
"#]],
9291014
);
9301015
}

0 commit comments

Comments
 (0)