Skip to content

Commit 98db342

Browse files
Parse tuple types and expressions (#93)
Closes #72 Fairly simple change: - Tuple types - Tuple expressions - Tuple indexing expressions. Error out on invalid indices such as `0xa` or anything that is not a `usize`. Note that `t.0.0` does not currently work, but `t.0 .0`. Once we implement #66, we can then write `(t.0).0`. In the future, we can support `t.0.0` which can be done in two ways: - Special case the lexer to _not_ parse the `0.0` as a real in the case of a tuple access (this means the lexer now has to concern itself with the context). - Break the real `0.0` apart in the pasrer, which kinda what Rust does (see rust-lang/rust#71322 after an attempt for the other method in rust-lang/rust#70420)
2 parents 5d0f987 + affce34 commit 98db342

File tree

5 files changed

+197
-28
lines changed

5 files changed

+197
-28
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> ::= <expr-atom> "." [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+
tuple: Box<Expr>,
69+
index: usize,
70+
},
6571
}
6672

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

yurtc/src/error.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ 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 integer value \"{}\" for tuple index", index)]
32+
InvalidIntegerForTupleIndex { span: Span, index: Token<'a> },
33+
#[error("Invalid value \"{}\" for tuple index", index)]
34+
InvalidTupleIndex { span: Span, index: Token<'a> },
3135
}
3236

3337
fn format_expected_found_error<'a>(
@@ -120,6 +124,8 @@ impl<'a> CompileError<'a> {
120124
ParseError::ExpectedFound { span, .. } => span.clone(),
121125
ParseError::KeywordAsIdent { span, .. } => span.clone(),
122126
ParseError::UntypedDecisionVar { span, .. } => span.clone(),
127+
ParseError::InvalidIntegerForTupleIndex { span, .. } => span.clone(),
128+
ParseError::InvalidTupleIndex { span, .. } => span.clone(),
123129
},
124130
}
125131
}

yurtc/src/lexer.rs

Lines changed: 18 additions & 10 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"),
@@ -258,12 +261,17 @@ fn lex_one_success(src: &str) -> Token<'_> {
258261
toks[0].0.clone()
259262
}
260263

261-
#[cfg(test)]
262-
fn lex_one_error(src: &str) -> CompileError {
263-
// Tokenise src, assume a single error.
264-
let (_, errs) = lex(src);
265-
assert_eq!(errs.len(), 1, "Testing for single error only.");
266-
errs[0].clone()
264+
#[test]
265+
fn control_tokens() {
266+
assert_eq!(lex_one_success(":"), Token::Colon);
267+
assert_eq!(lex_one_success(";"), Token::Semi);
268+
assert_eq!(lex_one_success(","), Token::Comma);
269+
assert_eq!(lex_one_success("{"), Token::BraceOpen);
270+
assert_eq!(lex_one_success("}"), Token::BraceClose);
271+
assert_eq!(lex_one_success("("), Token::ParenOpen);
272+
assert_eq!(lex_one_success(")"), Token::ParenClose);
273+
assert_eq!(lex_one_success("->"), Token::Arrow);
274+
assert_eq!(lex_one_success("."), Token::Dot);
267275
}
268276

269277
#[test]
@@ -275,12 +283,12 @@ fn reals() {
275283
assert_eq!(lex_one_success("0.34"), Token::RealLiteral("0.34"));
276284
assert_eq!(lex_one_success("-0.34"), Token::RealLiteral("-0.34"));
277285
check(
278-
&format!("{:?}", lex_one_error(".34")),
279-
expect_test::expect![[r#"Lex { span: 0..1, error: InvalidToken }"#]],
286+
&format!("{:?}", lex(".34")),
287+
expect_test::expect![[r#"([(Dot, 0..1), (IntLiteral("34"), 1..3)], [])"#]],
280288
);
281289
check(
282-
&format!("{:?}", lex_one_error("12.")),
283-
expect_test::expect!["Lex { span: 2..3, error: InvalidToken }"],
290+
&format!("{:?}", lex("12.")),
291+
expect_test::expect![[r#"([(IntLiteral("12"), 0..2), (Dot, 2..3)], [])"#]],
284292
);
285293
}
286294

yurtc/src/parser.rs

Lines changed: 151 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -213,22 +213,48 @@ 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+
219221
let atom = choice((
220222
immediate().map(ast::Expr::Immediate),
221223
unary_op(expr.clone()),
222224
code_block_expr(expr.clone()).map(ast::Expr::Block),
223225
if_expr(expr.clone()),
224226
call,
227+
tuple,
225228
ident().map(ast::Expr::Ident),
226229
));
227230

228-
comparison_op(additive_op(multiplicative_op(atom)))
231+
comparison_op(additive_op(multiplicative_op(tuple_index(atom))))
229232
})
230233
}
231234

235+
fn tuple_index<'sc, P>(
236+
parser: P,
237+
) -> impl Parser<Token<'sc>, ast::Expr, Error = ParseError<'sc>> + Clone
238+
where
239+
P: Parser<Token<'sc>, ast::Expr, Error = ParseError<'sc>> + Clone,
240+
{
241+
// This extracts a `usize` index. Fails for everything else (therefore, `t.0.0` is not
242+
// supported - but `t.0 .0` is fine).
243+
let index = filter_map(|span, token| match token {
244+
Token::IntLiteral(num_str) => num_str
245+
.parse::<usize>()
246+
.map_err(|_| ParseError::InvalidIntegerForTupleIndex { span, index: token }),
247+
_ => Err(ParseError::InvalidTupleIndex { span, index: token }),
248+
});
249+
250+
parser
251+
.then(just(Token::Dot).ignore_then(index).repeated())
252+
.foldl(|expr, index| ast::Expr::TupleIndex {
253+
tuple: Box::new(expr),
254+
index,
255+
})
256+
}
257+
232258
fn multiplicative_op<'sc, P>(
233259
parser: P,
234260
) -> impl Parser<Token<'sc>, ast::Expr, Error = ParseError<'sc>> + Clone
@@ -321,12 +347,20 @@ fn ident<'sc>() -> impl Parser<Token<'sc>, ast::Ident, Error = ParseError<'sc>>
321347
}
322348

323349
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-
))
350+
recursive(|type_| {
351+
let tuple = type_
352+
.separated_by(just(Token::Comma))
353+
.allow_trailing()
354+
.delimited_by(just(Token::ParenOpen), just(Token::ParenClose));
355+
356+
choice((
357+
just(Token::Real).to(ast::Type::Real),
358+
just(Token::Int).to(ast::Type::Int),
359+
just(Token::Bool).to(ast::Type::Bool),
360+
just(Token::String).to(ast::Type::String),
361+
tuple.map(ast::Type::Tuple),
362+
))
363+
})
330364
}
331365

332366
fn immediate<'sc>() -> impl Parser<Token<'sc>, ast::Immediate, Error = ParseError<'sc>> + Clone {
@@ -373,6 +407,25 @@ fn check(actual: &str, expect: expect_test::Expect) {
373407
expect.assert_eq(actual);
374408
}
375409

410+
#[test]
411+
fn types() {
412+
check(&run_parser!(type_(), "int"), expect_test::expect!["Int"]);
413+
check(&run_parser!(type_(), "real"), expect_test::expect!["Real"]);
414+
check(&run_parser!(type_(), "bool"), expect_test::expect!["Bool"]);
415+
check(
416+
&run_parser!(type_(), "string"),
417+
expect_test::expect!["String"],
418+
);
419+
check(
420+
&run_parser!(type_(), "(int, real, string)"),
421+
expect_test::expect!["Tuple([Int, Real, String])"],
422+
);
423+
check(
424+
&run_parser!(type_(), "(int, (real, int), string)"),
425+
expect_test::expect!["Tuple([Int, Tuple([Real, Int]), String])"],
426+
);
427+
}
428+
376429
#[test]
377430
fn let_decls() {
378431
check(
@@ -839,7 +892,7 @@ fn code_blocks() {
839892
check(
840893
&format!("{:?}", run_parser!(let_decl(expr()), "let x = {};")),
841894
expect_test::expect![[
842-
r#""@9..10: found \"}\" but expected \"!\", \"+\", \"-\", \"{\", \"if\", \"var\", \"let\", or \"constraint\"\n""#
895+
r#""@9..10: found \"}\" but expected \"!\", \"+\", \"-\", \"{\", \"(\", \"if\", \"var\", \"let\", or \"constraint\"\n""#
843896
]],
844897
);
845898
}
@@ -881,6 +934,93 @@ fn if_exprs() {
881934
);
882935
}
883936

937+
#[test]
938+
fn tuple_expressions() {
939+
check(
940+
&run_parser!(expr(), r#"(0,)"#),
941+
expect_test::expect!["Tuple([Immediate(Int(0))])"],
942+
);
943+
944+
check(
945+
&run_parser!(expr(), r#"(0, 1.0, "foo")"#),
946+
expect_test::expect![[
947+
r#"Tuple([Immediate(Int(0)), Immediate(Real(1.0)), Immediate(String("foo"))])"#
948+
]],
949+
);
950+
951+
check(
952+
&run_parser!(expr(), r#"(0, (1.0, "bar"), "foo")"#),
953+
expect_test::expect![[
954+
r#"Tuple([Immediate(Int(0)), Tuple([Immediate(Real(1.0)), Immediate(String("bar"))]), Immediate(String("foo"))])"#
955+
]],
956+
);
957+
958+
check(
959+
&run_parser!(expr(), r#"( { 42 }, if cond { 2 } else { 3 }, foo() )"#),
960+
expect_test::expect![[
961+
r#"Tuple([Block(Block { statements: [], final_expr: Immediate(Int(42)) }), If(IfExpr { condition: Ident(Ident("cond")), then_block: Block { statements: [], final_expr: Immediate(Int(2)) }, else_block: Block { statements: [], final_expr: Immediate(Int(3)) } }), Call { name: Ident("foo"), args: [] }])"#
962+
]],
963+
);
964+
965+
check(
966+
&run_parser!(expr(), r#"t.0 + t.9999999"#),
967+
expect_test::expect![[
968+
r#"BinaryOp { op: Add, lhs: TupleIndex { tuple: Ident(Ident("t")), index: 0 }, rhs: TupleIndex { tuple: Ident(Ident("t")), index: 9999999 } }"#
969+
]],
970+
);
971+
972+
check(
973+
&run_parser!(expr(), r#"(0, 1).0"#),
974+
expect_test::expect![
975+
"TupleIndex { tuple: Tuple([Immediate(Int(0)), Immediate(Int(1))]), index: 0 }"
976+
],
977+
);
978+
979+
check(
980+
&run_parser!(expr(), r#"t.0 .0"#),
981+
expect_test::expect![[
982+
r#"TupleIndex { tuple: TupleIndex { tuple: Ident(Ident("t")), index: 0 }, index: 0 }"#
983+
]],
984+
);
985+
986+
check(
987+
&run_parser!(expr(), r#"foo().0"#),
988+
expect_test::expect![[
989+
r#"TupleIndex { tuple: Call { name: Ident("foo"), args: [] }, index: 0 }"#
990+
]],
991+
);
992+
993+
check(
994+
&run_parser!(expr(), r#"{ (0, 0) }.0"#),
995+
expect_test::expect!["TupleIndex { tuple: Block(Block { statements: [], final_expr: Tuple([Immediate(Int(0)), Immediate(Int(0))]) }), index: 0 }"],
996+
);
997+
998+
check(
999+
&run_parser!(expr(), r#"if true { (0, 0) } else { (0, 0) }.0"#),
1000+
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 }"],
1001+
);
1002+
1003+
// This parses because `1 + 2` is an expression, but it should fail in semantic analysis.
1004+
check(
1005+
&run_parser!(expr(), "1 + 2 .3"),
1006+
expect_test::expect!["BinaryOp { op: Add, lhs: Immediate(Int(1)), rhs: TupleIndex { tuple: Immediate(Int(2)), index: 3 } }"],
1007+
);
1008+
1009+
check(
1010+
&run_parser!(let_decl(expr()), "let x = t.0xa;"),
1011+
expect_test::expect![[r#"
1012+
@10..13: Invalid integer value "0xa" for tuple index
1013+
"#]],
1014+
);
1015+
1016+
check(
1017+
&run_parser!(let_decl(expr()), "let x = t.xx;"),
1018+
expect_test::expect![[r#"
1019+
@10..12: Invalid value "xx" for tuple index
1020+
"#]],
1021+
);
1022+
}
1023+
8841024
#[test]
8851025
fn basic_program() {
8861026
let src = r#"
@@ -907,7 +1047,7 @@ fn with_errors() {
9071047
check(
9081048
&run_parser!(yurt_program(), "let low_val: bad = 1.23"),
9091049
expect_test::expect![[r#"
910-
@13..16: found "bad" but expected "real", "int", "bool", or "string"
1050+
@13..16: found "bad" but expected "(", "real", "int", "bool", or "string"
9111051
"#]],
9121052
);
9131053
}
@@ -924,7 +1064,7 @@ fn fn_errors() {
9241064
check(
9251065
&run_parser!(yurt_program(), "fn foo() -> real {}"),
9261066
expect_test::expect![[r#"
927-
@18..19: found "}" but expected "!", "+", "-", "{", "if", "var", "let", or "constraint"
1067+
@18..19: found "}" but expected "!", "+", "-", "{", "(", "if", "var", "let", or "constraint"
9281068
"#]],
9291069
);
9301070
}

0 commit comments

Comments
 (0)