Skip to content

Commit c7f1235

Browse files
authored
feat: indirection parsing (#4356)
1 parent 76c4f76 commit c7f1235

File tree

12 files changed

+773
-877
lines changed

12 files changed

+773
-877
lines changed

prqlc/prqlc-ast/src/expr.rs

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,11 @@ pub struct Expr {
4242

4343
#[derive(Debug, EnumAsInner, PartialEq, Clone, Serialize, Deserialize, strum::AsRefStr)]
4444
pub enum ExprKind {
45-
Ident(Ident),
45+
Ident(String),
46+
Indirection {
47+
base: Box<Expr>,
48+
field: IndirectionKind,
49+
},
4650
Literal(Literal),
4751
Pipeline(Pipeline),
4852

@@ -65,6 +69,13 @@ pub enum ExprKind {
6569
Internal(String),
6670
}
6771

72+
#[derive(Debug, EnumAsInner, PartialEq, Clone, Serialize, Deserialize)]
73+
pub enum IndirectionKind {
74+
Name(String),
75+
Position(i64),
76+
Star,
77+
}
78+
6879
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
6980
pub struct BinaryExpr {
7081
pub left: Box<Expr>,
@@ -144,12 +155,6 @@ impl From<Literal> for ExprKind {
144155
}
145156
}
146157

147-
impl From<Ident> for ExprKind {
148-
fn from(value: Ident) -> Self {
149-
ExprKind::Ident(value)
150-
}
151-
}
152-
153158
impl From<Func> for ExprKind {
154159
fn from(value: Func) -> Self {
155160
ExprKind::Func(Box::new(value))

prqlc/prqlc-parser/src/expr.rs

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ pub fn expr() -> impl Parser<TokenKind, Expr, Error = PError> + Clone {
2222
recursive(|expr| {
2323
let literal = select! { TokenKind::Literal(lit) => ExprKind::Literal(lit) };
2424

25-
let ident_kind = ident().map(ExprKind::Ident);
25+
let ident_kind = ident_part().map(ExprKind::Ident);
2626

2727
let nested_expr = pipeline(lambda_func(expr.clone()).or(func_call(expr.clone()))).boxed();
2828

@@ -132,6 +132,26 @@ pub fn expr() -> impl Parser<TokenKind, Expr, Error = PError> + Clone {
132132
.or(pipeline)
133133
.boxed();
134134

135+
// indirections
136+
let term = term
137+
.then(
138+
ctrl('.')
139+
.ignore_then(choice((
140+
ident_part().map(IndirectionKind::Name),
141+
ctrl('*').to(IndirectionKind::Star),
142+
select! {
143+
TokenKind::Literal(Literal::Integer(i)) => IndirectionKind::Position(i)
144+
},
145+
)))
146+
.map_with_span(|f, s| (f, s))
147+
.repeated(),
148+
)
149+
.foldl(|base, (field, span)| {
150+
let base = Box::new(base);
151+
into_expr(ExprKind::Indirection { base, field }, span)
152+
})
153+
.boxed();
154+
135155
// Unary operators
136156
let term = term
137157
.clone()
@@ -385,10 +405,9 @@ where
385405
}
386406

387407
pub fn ident() -> impl Parser<TokenKind, Ident, Error = PError> {
388-
let star = ctrl('*').to("*".to_string());
389-
390408
ident_part()
391-
.chain(ctrl('.').ignore_then(ident_part().or(star)).repeated())
409+
.separated_by(ctrl('.'))
410+
.at_least(1)
392411
.map(Ident::from_path::<String>)
393412
}
394413

prqlc/prqlc-parser/src/interpolation.rs

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,10 @@ fn parse_interpolate() {
3636
Expr {
3737
expr: Expr {
3838
kind: Ident(
39-
Ident {
40-
path: [],
41-
name: "a",
42-
},
39+
"a",
4340
),
4441
span: Some(
45-
0:7-10,
42+
0:8-9,
4643
),
4744
alias: None,
4845
},
@@ -84,13 +81,10 @@ fn parse_interpolate() {
8481
Expr {
8582
expr: Expr {
8683
kind: Ident(
87-
Ident {
88-
path: [],
89-
name: "a",
90-
},
84+
"a",
9185
),
9286
span: Some(
93-
0:13-16,
87+
0:14-15,
9488
),
9589
alias: None,
9690
},
@@ -105,21 +99,28 @@ fn parse_interpolate() {
10599

106100
fn parser(span_base: ParserSpan) -> impl Parser<char, Vec<InterpolateItem>, Error = Cheap<char>> {
107101
let expr = ident_part()
102+
.map_with_span(move |name, s| (name, offset_span(span_base, s)))
108103
.separated_by(just('.'))
109104
.at_least(1)
105+
.map(|ident_parts| {
106+
let mut parts = ident_parts.into_iter();
107+
108+
let (first, first_span) = parts.next().unwrap();
109+
let mut base = Box::new(into_expr(ExprKind::Ident(first), first_span));
110+
111+
for (part, span) in parts {
112+
let field = IndirectionKind::Name(part);
113+
base = Box::new(into_expr(ExprKind::Indirection { base, field }, span));
114+
}
115+
base
116+
})
110117
.then(
111118
just(':')
112119
.ignore_then(filter(|c| *c != '}').repeated().collect::<String>())
113120
.or_not(),
114121
)
115122
.delimited_by(just('{'), just('}'))
116-
.map_with_span(move |(ident, format), s| {
117-
let ident = ExprKind::Ident(Ident::from_path(ident));
118-
let expr = into_expr(ident, offset_span(span_base, s));
119-
let expr = Box::new(expr);
120-
121-
InterpolateItem::Expr { expr, format }
122-
});
123+
.map(|(expr, format)| InterpolateItem::Expr { expr, format });
123124

124125
// Convert double braces to single braces, and fail on any single braces.
125126
let string = (just("{{").to('{'))

prqlc/prqlc-parser/src/snapshots/prqlc_parser__test__pipeline_parse_tree.snap

Lines changed: 34 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -10,154 +10,119 @@ expression: "parse_single(r#\"\nfrom employees\nfilter country == \"USA\"
1010
exprs:
1111
- FuncCall:
1212
name:
13-
Ident:
14-
- from
13+
Ident: from
1514
args:
16-
- Ident:
17-
- employees
15+
- Ident: employees
1816
- FuncCall:
1917
name:
20-
Ident:
21-
- filter
18+
Ident: filter
2219
args:
2320
- Binary:
2421
left:
25-
Ident:
26-
- country
22+
Ident: country
2723
op: Eq
2824
right:
2925
Literal:
3026
String: USA
3127
- FuncCall:
3228
name:
33-
Ident:
34-
- derive
29+
Ident: derive
3530
args:
3631
- Tuple:
3732
- Binary:
3833
left:
39-
Ident:
40-
- salary
34+
Ident: salary
4135
op: Add
4236
right:
43-
Ident:
44-
- payroll_tax
37+
Ident: payroll_tax
4538
alias: gross_salary
4639
- Binary:
4740
left:
48-
Ident:
49-
- gross_salary
41+
Ident: gross_salary
5042
op: Add
5143
right:
52-
Ident:
53-
- benefits_cost
44+
Ident: benefits_cost
5445
alias: gross_cost
5546
- FuncCall:
5647
name:
57-
Ident:
58-
- filter
48+
Ident: filter
5949
args:
6050
- Binary:
6151
left:
62-
Ident:
63-
- gross_cost
52+
Ident: gross_cost
6453
op: Gt
6554
right:
6655
Literal:
6756
Integer: 0
6857
- FuncCall:
6958
name:
70-
Ident:
71-
- group
59+
Ident: group
7260
args:
7361
- Tuple:
74-
- Ident:
75-
- title
76-
- Ident:
77-
- country
62+
- Ident: title
63+
- Ident: country
7864
- FuncCall:
7965
name:
80-
Ident:
81-
- aggregate
66+
Ident: aggregate
8267
args:
8368
- Tuple:
8469
- FuncCall:
8570
name:
86-
Ident:
87-
- average
71+
Ident: average
8872
args:
89-
- Ident:
90-
- salary
73+
- Ident: salary
9174
- FuncCall:
9275
name:
93-
Ident:
94-
- average
76+
Ident: average
9577
args:
96-
- Ident:
97-
- gross_salary
78+
- Ident: gross_salary
9879
- FuncCall:
9980
name:
100-
Ident:
101-
- sum
81+
Ident: sum
10282
args:
103-
- Ident:
104-
- salary
83+
- Ident: salary
10584
- FuncCall:
10685
name:
107-
Ident:
108-
- sum
86+
Ident: sum
10987
args:
110-
- Ident:
111-
- gross_salary
88+
- Ident: gross_salary
11289
- FuncCall:
11390
name:
114-
Ident:
115-
- average
91+
Ident: average
11692
args:
117-
- Ident:
118-
- gross_cost
93+
- Ident: gross_cost
11994
- FuncCall:
12095
name:
121-
Ident:
122-
- sum
96+
Ident: sum
12397
args:
124-
- Ident:
125-
- gross_cost
98+
- Ident: gross_cost
12699
alias: sum_gross_cost
127100
- FuncCall:
128101
name:
129-
Ident:
130-
- count
102+
Ident: count
131103
args:
132-
- Ident:
133-
- salary
104+
- Ident: salary
134105
alias: ct
135106
- FuncCall:
136107
name:
137-
Ident:
138-
- sort
108+
Ident: sort
139109
args:
140-
- Ident:
141-
- sum_gross_cost
110+
- Ident: sum_gross_cost
142111
- FuncCall:
143112
name:
144-
Ident:
145-
- filter
113+
Ident: filter
146114
args:
147115
- Binary:
148116
left:
149-
Ident:
150-
- ct
117+
Ident: ct
151118
op: Gt
152119
right:
153120
Literal:
154121
Integer: 200
155122
- FuncCall:
156123
name:
157-
Ident:
158-
- take
124+
Ident: take
159125
args:
160126
- Literal:
161127
Integer: 20
162128
span: "0:1-714"
163-

prqlc/prqlc-parser/src/stmt.rs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,20 @@ fn query_def() -> impl Parser<TokenKind, Stmt, Error = PError> {
7676
// have this awkward construction in the meantime.
7777
let other = args
7878
.remove("target")
79-
.map(|v| match v.kind {
80-
ExprKind::Ident(value) => Ok(value.to_string()),
81-
_ => Err("target must be a string literal".to_string()),
79+
.map(|v| {
80+
match v.kind {
81+
ExprKind::Ident(name) => return Ok(name.to_string()),
82+
ExprKind::Indirection {
83+
base,
84+
field: IndirectionKind::Name(field),
85+
} => {
86+
if let ExprKind::Ident(name) = base.kind {
87+
return Ok(name.to_string() + "." + &field);
88+
}
89+
}
90+
_ => {}
91+
};
92+
Err("target must be a string literal".to_string())
8293
})
8394
.transpose()
8495
.map_err(|msg| Simple::custom(span, msg))?

0 commit comments

Comments
 (0)