diff --git a/prqlc/prqlc-ast/src/expr.rs b/prqlc/prqlc-ast/src/expr.rs index c93b0f26ba9a..00c2b4d6c061 100644 --- a/prqlc/prqlc-ast/src/expr.rs +++ b/prqlc/prqlc-ast/src/expr.rs @@ -42,7 +42,11 @@ pub struct Expr { #[derive(Debug, EnumAsInner, PartialEq, Clone, Serialize, Deserialize, strum::AsRefStr)] pub enum ExprKind { - Ident(Ident), + Ident(String), + Indirection { + base: Box, + field: IndirectionKind, + }, Literal(Literal), Pipeline(Pipeline), @@ -65,6 +69,13 @@ pub enum ExprKind { Internal(String), } +#[derive(Debug, EnumAsInner, PartialEq, Clone, Serialize, Deserialize)] +pub enum IndirectionKind { + Name(String), + Position(i64), + Star, +} + #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] pub struct BinaryExpr { pub left: Box, @@ -144,12 +155,6 @@ impl From for ExprKind { } } -impl From for ExprKind { - fn from(value: Ident) -> Self { - ExprKind::Ident(value) - } -} - impl From for ExprKind { fn from(value: Func) -> Self { ExprKind::Func(Box::new(value)) diff --git a/prqlc/prqlc-parser/src/expr.rs b/prqlc/prqlc-parser/src/expr.rs index 98b4bf6ad5e0..7d030ea28d08 100644 --- a/prqlc/prqlc-parser/src/expr.rs +++ b/prqlc/prqlc-parser/src/expr.rs @@ -22,7 +22,7 @@ pub fn expr() -> impl Parser + Clone { recursive(|expr| { let literal = select! { TokenKind::Literal(lit) => ExprKind::Literal(lit) }; - let ident_kind = ident().map(ExprKind::Ident); + let ident_kind = ident_part().map(ExprKind::Ident); let nested_expr = pipeline(lambda_func(expr.clone()).or(func_call(expr.clone()))).boxed(); @@ -132,6 +132,26 @@ pub fn expr() -> impl Parser + Clone { .or(pipeline) .boxed(); + // indirections + let term = term + .then( + ctrl('.') + .ignore_then(choice(( + ident_part().map(IndirectionKind::Name), + ctrl('*').to(IndirectionKind::Star), + select! { + TokenKind::Literal(Literal::Integer(i)) => IndirectionKind::Position(i) + }, + ))) + .map_with_span(|f, s| (f, s)) + .repeated(), + ) + .foldl(|base, (field, span)| { + let base = Box::new(base); + into_expr(ExprKind::Indirection { base, field }, span) + }) + .boxed(); + // Unary operators let term = term .clone() @@ -385,10 +405,9 @@ where } pub fn ident() -> impl Parser { - let star = ctrl('*').to("*".to_string()); - ident_part() - .chain(ctrl('.').ignore_then(ident_part().or(star)).repeated()) + .separated_by(ctrl('.')) + .at_least(1) .map(Ident::from_path::) } diff --git a/prqlc/prqlc-parser/src/interpolation.rs b/prqlc/prqlc-parser/src/interpolation.rs index cf9cd53648b9..e8822c806d37 100644 --- a/prqlc/prqlc-parser/src/interpolation.rs +++ b/prqlc/prqlc-parser/src/interpolation.rs @@ -36,13 +36,10 @@ fn parse_interpolate() { Expr { expr: Expr { kind: Ident( - Ident { - path: [], - name: "a", - }, + "a", ), span: Some( - 0:7-10, + 0:8-9, ), alias: None, }, @@ -84,13 +81,10 @@ fn parse_interpolate() { Expr { expr: Expr { kind: Ident( - Ident { - path: [], - name: "a", - }, + "a", ), span: Some( - 0:13-16, + 0:14-15, ), alias: None, }, @@ -105,21 +99,28 @@ fn parse_interpolate() { fn parser(span_base: ParserSpan) -> impl Parser, Error = Cheap> { let expr = ident_part() + .map_with_span(move |name, s| (name, offset_span(span_base, s))) .separated_by(just('.')) .at_least(1) + .map(|ident_parts| { + let mut parts = ident_parts.into_iter(); + + let (first, first_span) = parts.next().unwrap(); + let mut base = Box::new(into_expr(ExprKind::Ident(first), first_span)); + + for (part, span) in parts { + let field = IndirectionKind::Name(part); + base = Box::new(into_expr(ExprKind::Indirection { base, field }, span)); + } + base + }) .then( just(':') .ignore_then(filter(|c| *c != '}').repeated().collect::()) .or_not(), ) .delimited_by(just('{'), just('}')) - .map_with_span(move |(ident, format), s| { - let ident = ExprKind::Ident(Ident::from_path(ident)); - let expr = into_expr(ident, offset_span(span_base, s)); - let expr = Box::new(expr); - - InterpolateItem::Expr { expr, format } - }); + .map(|(expr, format)| InterpolateItem::Expr { expr, format }); // Convert double braces to single braces, and fail on any single braces. let string = (just("{{").to('{')) diff --git a/prqlc/prqlc-parser/src/snapshots/prqlc_parser__test__pipeline_parse_tree.snap b/prqlc/prqlc-parser/src/snapshots/prqlc_parser__test__pipeline_parse_tree.snap index 5ffb11b89503..14613dcde02b 100644 --- a/prqlc/prqlc-parser/src/snapshots/prqlc_parser__test__pipeline_parse_tree.snap +++ b/prqlc/prqlc-parser/src/snapshots/prqlc_parser__test__pipeline_parse_tree.snap @@ -10,155 +10,123 @@ expression: "parse_single(r#\"\nfrom db.employees\nfilter country == \"USA\" exprs: - FuncCall: name: - Ident: - - from + Ident: from args: - - Ident: - - db - - employees + - Indirection: + base: + Ident: db + field: + Name: employees - FuncCall: name: - Ident: - - filter + Ident: filter args: - Binary: left: - Ident: - - country + Ident: country op: Eq right: Literal: String: USA - FuncCall: name: - Ident: - - derive + Ident: derive args: - Tuple: - Binary: left: - Ident: - - salary + Ident: salary op: Add right: - Ident: - - payroll_tax + Ident: payroll_tax alias: gross_salary - Binary: left: - Ident: - - gross_salary + Ident: gross_salary op: Add right: - Ident: - - benefits_cost + Ident: benefits_cost alias: gross_cost - FuncCall: name: - Ident: - - filter + Ident: filter args: - Binary: left: - Ident: - - gross_cost + Ident: gross_cost op: Gt right: Literal: Integer: 0 - FuncCall: name: - Ident: - - group + Ident: group args: - Tuple: - - Ident: - - title - - Ident: - - country + - Ident: title + - Ident: country - FuncCall: name: - Ident: - - aggregate + Ident: aggregate args: - Tuple: - FuncCall: name: - Ident: - - average + Ident: average args: - - Ident: - - salary + - Ident: salary - FuncCall: name: - Ident: - - average + Ident: average args: - - Ident: - - gross_salary + - Ident: gross_salary - FuncCall: name: - Ident: - - sum + Ident: sum args: - - Ident: - - salary + - Ident: salary - FuncCall: name: - Ident: - - sum + Ident: sum args: - - Ident: - - gross_salary + - Ident: gross_salary - FuncCall: name: - Ident: - - average + Ident: average args: - - Ident: - - gross_cost + - Ident: gross_cost - FuncCall: name: - Ident: - - sum + Ident: sum args: - - Ident: - - gross_cost + - Ident: gross_cost alias: sum_gross_cost - FuncCall: name: - Ident: - - count + Ident: count args: - - Ident: - - salary + - Ident: salary alias: ct - FuncCall: name: - Ident: - - sort + Ident: sort args: - - Ident: - - sum_gross_cost + - Ident: sum_gross_cost - FuncCall: name: - Ident: - - filter + Ident: filter args: - Binary: left: - Ident: - - ct + Ident: ct op: Gt right: Literal: Integer: 200 - FuncCall: name: - Ident: - - take + Ident: take args: - Literal: Integer: 20 span: "0:1-717" - diff --git a/prqlc/prqlc-parser/src/stmt.rs b/prqlc/prqlc-parser/src/stmt.rs index 388d4dd171bf..b86b7285a6f9 100644 --- a/prqlc/prqlc-parser/src/stmt.rs +++ b/prqlc/prqlc-parser/src/stmt.rs @@ -76,9 +76,20 @@ fn query_def() -> impl Parser { // have this awkward construction in the meantime. let other = args .remove("target") - .map(|v| match v.kind { - ExprKind::Ident(value) => Ok(value.to_string()), - _ => Err("target must be a string literal".to_string()), + .map(|v| { + match v.kind { + ExprKind::Ident(name) => return Ok(name.to_string()), + ExprKind::Indirection { + base, + field: IndirectionKind::Name(field), + } => { + if let ExprKind::Ident(name) = base.kind { + return Ok(name.to_string() + "." + &field); + } + } + _ => {} + }; + Err("target must be a string literal".to_string()) }) .transpose() .map_err(|msg| Simple::custom(span, msg))? diff --git a/prqlc/prqlc-parser/src/test.rs b/prqlc/prqlc-parser/src/test.rs index ecf69b1c558a..bfaa8bd8a675 100644 --- a/prqlc/prqlc-parser/src/test.rs +++ b/prqlc/prqlc-parser/src/test.rs @@ -131,8 +131,7 @@ fn test_take() { value: FuncCall: name: - Ident: - - take + Ident: take args: - Literal: Integer: 10 @@ -147,8 +146,7 @@ fn test_take() { value: FuncCall: name: - Ident: - - take + Ident: take args: - Range: start: ~ @@ -166,8 +164,7 @@ fn test_take() { value: FuncCall: name: - Ident: - - take + Ident: take args: - Range: start: @@ -211,25 +208,24 @@ fn test_ranges() { "###); assert_yaml_snapshot!(parse_expr(r#"(-2..(-5 | abs))"#).unwrap(), @r###" - --- - Range: - start: - Unary: - op: Neg - expr: - Literal: - Integer: 2 - end: - Pipeline: - exprs: - - Unary: - op: Neg - expr: - Literal: - Integer: 5 - - Ident: - - abs - "###); + --- + Range: + start: + Unary: + op: Neg + expr: + Literal: + Integer: 2 + end: + Pipeline: + exprs: + - Unary: + op: Neg + expr: + Literal: + Integer: 5 + - Ident: abs + "###); assert_yaml_snapshot!(parse_expr(r#"(2 + 5)..'a'"#).unwrap(), @r###" --- @@ -249,16 +245,18 @@ fn test_ranges() { "###); assert_yaml_snapshot!(parse_expr(r#"1.6..rel.col"#).unwrap(), @r###" - --- - Range: - start: - Literal: - Float: 1.6 - end: - Ident: - - rel - - col - "###); + --- + Range: + start: + Literal: + Float: 1.6 + end: + Indirection: + base: + Ident: rel + field: + Name: col + "###); assert_yaml_snapshot!(parse_expr(r#"6.."#).unwrap(), @r###" --- @@ -299,116 +297,96 @@ fn test_ranges() { #[test] fn test_basic_exprs() { assert_yaml_snapshot!(parse_expr(r#"country == "USA""#).unwrap(), @r###" - --- - Binary: - left: - Ident: - - country - op: Eq - right: - Literal: - String: USA - "###); + --- + Binary: + left: + Ident: country + op: Eq + right: + Literal: + String: USA + "###); assert_yaml_snapshot!(parse_expr("select {a, b, c}").unwrap(), @r###" - --- - FuncCall: - name: - Ident: - - select - args: - - Tuple: - - Ident: - - a - - Ident: - - b - - Ident: - - c - "###); + --- + FuncCall: + name: + Ident: select + args: + - Tuple: + - Ident: a + - Ident: b + - Ident: c + "###); assert_yaml_snapshot!(parse_expr( "group {title, country} ( aggregate {sum salary} )" ).unwrap(), @r###" - --- - FuncCall: - name: - Ident: - - group - args: - - Tuple: - - Ident: - - title - - Ident: - - country - - FuncCall: - name: - Ident: - - aggregate - args: - - Tuple: - - FuncCall: - name: - Ident: - - sum - args: - - Ident: - - salary - "###); + --- + FuncCall: + name: + Ident: group + args: + - Tuple: + - Ident: title + - Ident: country + - FuncCall: + name: + Ident: aggregate + args: + - Tuple: + - FuncCall: + name: + Ident: sum + args: + - Ident: salary + "###); assert_yaml_snapshot!(parse_expr( r#" filter country == "USA""# ).unwrap(), @r###" - --- - FuncCall: - name: - Ident: - - filter - args: - - Binary: - left: - Ident: - - country - op: Eq - right: - Literal: - String: USA - "###); + --- + FuncCall: + name: + Ident: filter + args: + - Binary: + left: + Ident: country + op: Eq + right: + Literal: + String: USA + "###); assert_yaml_snapshot!(parse_expr("{a, b, c,}").unwrap(), @r###" - --- - Tuple: - - Ident: - - a - - Ident: - - b - - Ident: - - c - "###); + --- + Tuple: + - Ident: a + - Ident: b + - Ident: c + "###); assert_yaml_snapshot!(parse_expr( r#"{ gross_salary = salary + payroll_tax, gross_cost = gross_salary + benefits_cost }"# ).unwrap(), @r###" - --- - Tuple: - - Binary: - left: - Ident: - - salary - op: Add - right: - Ident: - - payroll_tax - alias: gross_salary - - Binary: - left: - Ident: - - gross_salary - op: Add - right: - Ident: - - benefits_cost - alias: gross_cost - "###); + --- + Tuple: + - Binary: + left: + Ident: salary + op: Add + right: + Ident: payroll_tax + alias: gross_salary + - Binary: + left: + Ident: gross_salary + op: Add + right: + Ident: benefits_cost + alias: gross_cost + "###); // Currently not putting comments in our parse tree, so this is blank. assert_yaml_snapshot!(parse_single( r#"# this is a comment @@ -421,37 +399,30 @@ fn test_basic_exprs() { value: FuncCall: name: - Ident: - - select + Ident: select args: - - Ident: - - a + - Ident: a span: "0:28-36" "###); assert_yaml_snapshot!(parse_expr( "join side:left country (id==employee_id)" ).unwrap(), @r###" - --- - FuncCall: - name: - Ident: - - join - args: - - Ident: - - country - - Binary: - left: - Ident: - - id - op: Eq - right: - Ident: - - employee_id - named_args: - side: - Ident: - - left - "###); + --- + FuncCall: + name: + Ident: join + args: + - Ident: country + - Binary: + left: + Ident: id + op: Eq + right: + Ident: employee_id + named_args: + side: + Ident: left + "###); assert_yaml_snapshot!(parse_expr("1 + 2").unwrap(), @r###" --- Binary: @@ -547,28 +518,29 @@ Canada #[test] fn test_s_string() { assert_yaml_snapshot!(parse_expr(r#"s"SUM({col})""#).unwrap(), @r###" - --- - SString: - - String: SUM( - - Expr: - expr: - Ident: - - col - format: ~ - - String: ) - "###); + --- + SString: + - String: SUM( + - Expr: + expr: + Ident: col + format: ~ + - String: ) + "###); assert_yaml_snapshot!(parse_expr(r#"s"SUM({rel.`Col name`})""#).unwrap(), @r###" - --- - SString: - - String: SUM( - - Expr: - expr: - Ident: - - rel - - Col name - format: ~ - - String: ) - "###) + --- + SString: + - String: SUM( + - Expr: + expr: + Indirection: + base: + Ident: rel + field: + Name: Col name + format: ~ + - String: ) + "###) } #[test] @@ -603,24 +575,23 @@ fn test_tuple() { Integer: 2 "###); assert_yaml_snapshot!(parse_expr(r#"{1 + (f 1), 2}"#).unwrap(), @r###" - --- - Tuple: - - Binary: - left: - Literal: - Integer: 1 - op: Add - right: - FuncCall: - name: - Ident: - - f - args: - - Literal: - Integer: 1 - - Literal: - Integer: 2 - "###); + --- + Tuple: + - Binary: + left: + Literal: + Integer: 1 + op: Add + right: + FuncCall: + name: + Ident: f + args: + - Literal: + Integer: 1 + - Literal: + Integer: 2 + "###); // Line breaks assert_yaml_snapshot!(parse_expr( r#"{1, @@ -638,59 +609,49 @@ fn test_tuple() { let ab = parse_expr(r#"{a b}"#).unwrap(); let a_comma_b = parse_expr(r#"{a, b}"#).unwrap(); assert_yaml_snapshot!(ab, @r###" - --- - Tuple: - - FuncCall: - name: - Ident: - - a - args: - - Ident: - - b - "###); + --- + Tuple: + - FuncCall: + name: + Ident: a + args: + - Ident: b + "###); assert_yaml_snapshot!(a_comma_b, @r###" - --- - Tuple: - - Ident: - - a - - Ident: - - b - "###); + --- + Tuple: + - Ident: a + - Ident: b + "###); assert_ne!(ab, a_comma_b); assert_yaml_snapshot!(parse_expr(r#"{amount, +amount, -amount}"#).unwrap(), @r###" - --- - Tuple: - - Ident: - - amount - - Unary: - op: Add - expr: - Ident: - - amount - - Unary: - op: Neg - expr: - Ident: - - amount - "###); + --- + Tuple: + - Ident: amount + - Unary: + op: Add + expr: + Ident: amount + - Unary: + op: Neg + expr: + Ident: amount + "###); // Operators in tuple items assert_yaml_snapshot!(parse_expr(r#"{amount, +amount, -amount}"#).unwrap(), @r###" - --- - Tuple: - - Ident: - - amount - - Unary: - op: Add - expr: - Ident: - - amount - - Unary: - op: Neg - expr: - Ident: - - amount - "###); + --- + Tuple: + - Ident: amount + - Unary: + op: Add + expr: + Ident: amount + - Unary: + op: Neg + expr: + Ident: amount + "###); } #[test] @@ -732,9 +693,9 @@ fn test_number() { assert!(parse_expr("_").unwrap().kind.into_ident().is_ok()); // We don't allow trailing periods - assert!(parse_expr(r#"add 1. 2"#).is_err()); + assert!(parse_expr(r#"add 1. (2, 3)"#).is_err()); - assert!(parse_expr("_2.3").is_err()); + assert!(parse_expr("_2.3").unwrap().kind.is_indirection()); assert_yaml_snapshot!(parse_expr(r#"2e3"#).unwrap(), @r###" --- @@ -757,13 +718,11 @@ fn test_filter() { value: FuncCall: name: - Ident: - - filter + Ident: filter args: - Binary: left: - Ident: - - country + Ident: country op: Eq right: Literal: @@ -780,19 +739,19 @@ fn test_filter() { value: FuncCall: name: - Ident: - - filter + Ident: filter args: - Binary: left: FuncCall: name: - Ident: - - text - - upper + Indirection: + base: + Ident: text + field: + Name: upper args: - - Ident: - - country + - Ident: country op: Eq right: Literal: @@ -819,27 +778,21 @@ fn test_aggregate() { value: FuncCall: name: - Ident: - - group + Ident: group args: - Tuple: - - Ident: - - title + - Ident: title - FuncCall: name: - Ident: - - aggregate + Ident: aggregate args: - Tuple: - FuncCall: name: - Ident: - - sum + Ident: sum args: - - Ident: - - salary - - Ident: - - count + - Ident: salary + - Ident: count span: "0:0-77" "###); let aggregate = parse_single( @@ -857,25 +810,20 @@ fn test_aggregate() { value: FuncCall: name: - Ident: - - group + Ident: group args: - Tuple: - - Ident: - - title + - Ident: title - FuncCall: name: - Ident: - - aggregate + Ident: aggregate args: - Tuple: - FuncCall: name: - Ident: - - sum + Ident: sum args: - - Ident: - - salary + - Ident: salary span: "0:0-70" "###); } @@ -885,23 +833,21 @@ fn test_derive() { assert_yaml_snapshot!( parse_expr(r#"derive {x = 5, y = (-x)}"#).unwrap() , @r###" - --- - FuncCall: - name: - Ident: - - derive - args: - - Tuple: - - Literal: - Integer: 5 - alias: x - - Unary: - op: Neg - expr: - Ident: - - x - alias: y - "###); + --- + FuncCall: + name: + Ident: derive + args: + - Tuple: + - Literal: + Integer: 5 + alias: x + - Unary: + op: Neg + expr: + Ident: x + alias: y + "###); } #[test] @@ -909,48 +855,41 @@ fn test_select() { assert_yaml_snapshot!( parse_expr(r#"select x"#).unwrap() , @r###" - --- - FuncCall: - name: - Ident: - - select - args: - - Ident: - - x - "###); + --- + FuncCall: + name: + Ident: select + args: + - Ident: x + "###); assert_yaml_snapshot!( parse_expr(r#"select !{x}"#).unwrap() , @r###" - --- - FuncCall: - name: - Ident: - - select - args: - - Unary: - op: Not - expr: - Tuple: - - Ident: - - x - "###); + --- + FuncCall: + name: + Ident: select + args: + - Unary: + op: Not + expr: + Tuple: + - Ident: x + "###); assert_yaml_snapshot!( parse_expr(r#"select {x, y}"#).unwrap() , @r###" - --- - FuncCall: - name: - Ident: - - select - args: - - Tuple: - - Ident: - - x - - Ident: - - y - "###); + --- + FuncCall: + name: + Ident: select + args: + - Tuple: + - Ident: x + - Ident: y + "###); } #[test] @@ -958,69 +897,61 @@ fn test_expr() { assert_yaml_snapshot!( parse_expr(r#"country == "USA""#).unwrap() , @r###" - --- - Binary: - left: - Ident: - - country - op: Eq - right: - Literal: - String: USA - "###); + --- + Binary: + left: + Ident: country + op: Eq + right: + Literal: + String: USA + "###); assert_yaml_snapshot!(parse_expr( r#"{ gross_salary = salary + payroll_tax, gross_cost = gross_salary + benefits_cost, }"#).unwrap(), @r###" - --- - Tuple: - - Binary: - left: - Ident: - - salary - op: Add - right: - Ident: - - payroll_tax - alias: gross_salary - - Binary: - left: - Ident: - - gross_salary - op: Add - right: - Ident: - - benefits_cost - alias: gross_cost - "###); + --- + Tuple: + - Binary: + left: + Ident: salary + op: Add + right: + Ident: payroll_tax + alias: gross_salary + - Binary: + left: + Ident: gross_salary + op: Add + right: + Ident: benefits_cost + alias: gross_cost + "###); assert_yaml_snapshot!( parse_expr( "(salary + payroll_tax) * (1 + tax_rate)" ).unwrap(), @r###" - --- + --- + Binary: + left: Binary: left: - Binary: - left: - Ident: - - salary - op: Add - right: - Ident: - - payroll_tax - op: Mul + Ident: salary + op: Add right: - Binary: - left: - Literal: - Integer: 1 - op: Add - right: - Ident: - - tax_rate - "###); + Ident: payroll_tax + op: Mul + right: + Binary: + left: + Literal: + Integer: 1 + op: Add + right: + Ident: tax_rate + "###); } #[test] @@ -1055,8 +986,7 @@ fn test_function() { body: Binary: left: - Ident: - - x + Ident: x op: Add right: Literal: @@ -1078,8 +1008,7 @@ fn test_function() { Func: return_ty: ~ body: - Ident: - - x + Ident: x params: - name: x default_value: ~ @@ -1099,8 +1028,7 @@ fn test_function() { body: Binary: left: - Ident: - - x + Ident: x op: Add right: Literal: @@ -1124,8 +1052,7 @@ fn test_function() { body: Binary: left: - Ident: - - x + Ident: x op: Add right: Literal: @@ -1150,30 +1077,25 @@ fn test_function() { body: FuncCall: name: - Ident: - - some_func + Ident: some_func args: - FuncCall: name: - Ident: - - foo + Ident: foo args: - Binary: left: - Ident: - - bar + Ident: bar op: Add right: Literal: Integer: 1 - Binary: left: - Ident: - - plax + Ident: plax op: Sub right: - Ident: - - baz + Ident: baz params: - name: x default_value: ~ @@ -1215,8 +1137,7 @@ fn test_function() { - String: SUM( - Expr: expr: - Ident: - - X + Ident: X format: ~ - String: ) params: @@ -1250,29 +1171,22 @@ fn test_function() { exprs: - FuncCall: name: - Ident: - - window + Ident: window args: - - Ident: - - x + - Ident: x - FuncCall: name: - Ident: - - by + Ident: by args: - - Ident: - - sec_id + - Ident: sec_id - FuncCall: name: - Ident: - - sort + Ident: sort args: - - Ident: - - date + - Ident: date - FuncCall: name: - Ident: - - lag + Ident: lag args: - Literal: Integer: 1 @@ -1295,20 +1209,17 @@ fn test_function() { body: Binary: left: - Ident: - - x + Ident: x op: Add right: - Ident: - - to + Ident: to params: - name: x default_value: ~ named_params: - name: to default_value: - Ident: - - a + Ident: a generic_type_params: [] span: "0:0-27" "###); @@ -1321,54 +1232,50 @@ fn test_func_call() { let ident = ast.kind.into_ident().unwrap(); assert_yaml_snapshot!( ident, @r###" - --- - - count - "###); + --- + count + "###); let ast = parse_expr(r#"s 'foo'"#).unwrap(); assert_yaml_snapshot!( ast, @r###" - --- - FuncCall: - name: - Ident: - - s - args: - - Literal: - String: foo - "###); + --- + FuncCall: + name: + Ident: s + args: + - Literal: + String: foo + "###); // A non-friendly option for #154 let ast = parse_expr(r#"count s'*'"#).unwrap(); let func_call: FuncCall = ast.kind.into_func_call().unwrap(); assert_yaml_snapshot!( func_call, @r###" - --- - name: - Ident: - - count - args: - - SString: - - String: "*" - "###); + --- + name: + Ident: count + args: + - SString: + - String: "*" + "###); parse_expr("plus_one x:0 x:0 ").unwrap_err(); let ast = parse_expr(r#"add bar to=3"#).unwrap(); assert_yaml_snapshot!( ast, @r###" - --- - FuncCall: - name: - Ident: - - add - args: - - Ident: - - bar - - Literal: - Integer: 3 - alias: to - "###); + --- + FuncCall: + name: + Ident: add + args: + - Ident: bar + - Literal: + Integer: 3 + alias: to + "###); } #[test] @@ -1446,66 +1353,56 @@ fn test_op_precedence() { "###); assert_yaml_snapshot!(parse_expr(r#"a && b || !c && d"#).unwrap(), @r###" - --- + --- + Binary: + left: Binary: left: - Binary: - left: - Ident: - - a - op: And - right: - Ident: - - b - op: Or + Ident: a + op: And right: - Binary: - left: - Unary: - op: Not - expr: - Ident: - - c - op: And - right: - Ident: - - d - "###); + Ident: b + op: Or + right: + Binary: + left: + Unary: + op: Not + expr: + Ident: c + op: And + right: + Ident: d + "###); assert_yaml_snapshot!(parse_expr(r#"a && b + c || (d e) && f"#).unwrap(), @r###" - --- + --- + Binary: + left: Binary: left: - Binary: - left: - Ident: - - a - op: And - right: - Binary: - left: - Ident: - - b - op: Add - right: - Ident: - - c - op: Or + Ident: a + op: And right: Binary: left: - FuncCall: - name: - Ident: - - d - args: - - Ident: - - e - op: And + Ident: b + op: Add right: - Ident: - - f - "###); + Ident: c + op: Or + right: + Binary: + left: + FuncCall: + name: + Ident: d + args: + - Ident: e + op: And + right: + Ident: f + "###); } #[test] @@ -1520,12 +1417,13 @@ fn test_var_def() { value: FuncCall: name: - Ident: - - from + Ident: from args: - - Ident: - - db - - employees + - Indirection: + base: + Ident: db + field: + Name: employees span: "0:0-42" "###); @@ -1551,44 +1449,37 @@ fn test_var_def() { exprs: - FuncCall: name: - Ident: - - from + Ident: from args: - - Ident: - - db - - employees + - Indirection: + base: + Ident: db + field: + Name: employees - FuncCall: name: - Ident: - - group + Ident: group args: - - Ident: - - country + - Ident: country - FuncCall: name: - Ident: - - aggregate + Ident: aggregate args: - Tuple: - FuncCall: name: - Ident: - - average + Ident: average args: - - Ident: - - salary + - Ident: salary alias: average_country_salary - FuncCall: name: - Ident: - - sort + Ident: sort args: - - Ident: - - tenure + - Ident: tenure - FuncCall: name: - Ident: - - take + Ident: take args: - Literal: Integer: 50 @@ -1628,19 +1519,18 @@ fn test_var_def() { exprs: - FuncCall: name: - Ident: - - from + Ident: from args: - - Ident: - - db - - x_table + - Indirection: + base: + Ident: db + field: + Name: x_table - FuncCall: name: - Ident: - - select + Ident: select args: - - Ident: - - foo + - Ident: foo alias: only_in_x span: "0:0-87" - VarDef: @@ -1649,12 +1539,13 @@ fn test_var_def() { value: FuncCall: name: - Ident: - - from + Ident: from args: - - Ident: - - db - - x + - Indirection: + base: + Ident: db + field: + Name: x span: "0:99-108" "###); } @@ -1662,19 +1553,17 @@ fn test_var_def() { #[test] fn test_inline_pipeline() { assert_yaml_snapshot!(parse_expr("(salary | percentile 50)").unwrap(), @r###" - --- - Pipeline: - exprs: - - Ident: - - salary - - FuncCall: - name: - Ident: - - percentile - args: - - Literal: - Integer: 50 - "###); + --- + Pipeline: + exprs: + - Ident: salary + - FuncCall: + name: + Ident: percentile + args: + - Literal: + Integer: 50 + "###); assert_yaml_snapshot!(parse_single("let median = x -> (x | percentile 50)\n").unwrap(), @r###" --- - VarDef: @@ -1686,12 +1575,10 @@ fn test_inline_pipeline() { body: Pipeline: exprs: - - Ident: - - x + - Ident: x - FuncCall: name: - Ident: - - percentile + Ident: percentile args: - Literal: Integer: 50 @@ -1722,29 +1609,27 @@ fn test_sql_parameters() { exprs: - FuncCall: name: - Ident: - - from + Ident: from args: - - Ident: - - db - - mytable + - Indirection: + base: + Ident: db + field: + Name: mytable - FuncCall: name: - Ident: - - filter + Ident: filter args: - Tuple: - Binary: left: - Ident: - - first_name + Ident: first_name op: Eq right: Param: "1" - Binary: left: - Ident: - - last_name + Ident: last_name op: Eq right: Param: 2.name @@ -1786,52 +1671,46 @@ join `my-proj`.`dataset`.`table` exprs: - FuncCall: name: - Ident: - - from + Ident: from args: - - Ident: - - a/*.parquet + - Ident: a/*.parquet - FuncCall: name: - Ident: - - aggregate + Ident: aggregate args: - Tuple: - FuncCall: name: - Ident: - - max + Ident: max args: - - Ident: - - c + - Ident: c - FuncCall: name: - Ident: - - join + Ident: join args: - - Ident: - - schema.table + - Ident: schema.table - Unary: op: EqSelf expr: - Ident: - - id + Ident: id - FuncCall: name: - Ident: - - join + Ident: join args: - - Ident: - - my-proj.dataset.table + - Ident: my-proj.dataset.table - FuncCall: name: - Ident: - - join + Ident: join args: - - Ident: - - my-proj - - dataset - - table + - Indirection: + base: + Indirection: + base: + Ident: my-proj + field: + Name: dataset + field: + Name: table span: "0:1-127" "###); } @@ -1855,66 +1734,55 @@ fn test_sort() { exprs: - FuncCall: name: - Ident: - - from + Ident: from args: - - Ident: - - db - - invoices + - Indirection: + base: + Ident: db + field: + Name: invoices - FuncCall: name: - Ident: - - sort + Ident: sort args: - - Ident: - - issued_at + - Ident: issued_at - FuncCall: name: - Ident: - - sort + Ident: sort args: - Unary: op: Neg expr: - Ident: - - issued_at + Ident: issued_at - FuncCall: name: - Ident: - - sort + Ident: sort args: - Tuple: - - Ident: - - issued_at + - Ident: issued_at - FuncCall: name: - Ident: - - sort + Ident: sort args: - Tuple: - Unary: op: Neg expr: - Ident: - - issued_at + Ident: issued_at - FuncCall: name: - Ident: - - sort + Ident: sort args: - Tuple: - - Ident: - - issued_at + - Ident: issued_at - Unary: op: Neg expr: - Ident: - - amount + Ident: amount - Unary: op: Add expr: - Ident: - - num_of_articles + Ident: num_of_articles span: "0:9-178" "###); } @@ -1934,22 +1802,21 @@ fn test_dates() { exprs: - FuncCall: name: - Ident: - - from + Ident: from args: - - Ident: - - db - - employees + - Indirection: + base: + Ident: db + field: + Name: employees - FuncCall: name: - Ident: - - derive + Ident: derive args: - Tuple: - Binary: left: - Ident: - - age + Ident: age op: Add right: Literal: @@ -1996,11 +1863,9 @@ fn test_multiline_string() { value: FuncCall: name: - Ident: - - derive + Ident: derive args: - - Ident: - - r + - Ident: r alias: x span: "0:9-39" "### ) @@ -2021,21 +1886,20 @@ fn test_coalesce() { exprs: - FuncCall: name: - Ident: - - from + Ident: from args: - - Ident: - - db - - employees + - Indirection: + base: + Ident: db + field: + Name: employees - FuncCall: name: - Ident: - - derive + Ident: derive args: - Binary: left: - Ident: - - amount + Ident: amount op: Coalesce right: Literal: @@ -2057,8 +1921,7 @@ fn test_literal() { value: FuncCall: name: - Ident: - - derive + Ident: derive args: - Literal: Boolean: true @@ -2084,45 +1947,42 @@ fn test_allowed_idents() { exprs: - FuncCall: name: - Ident: - - from + Ident: from args: - - Ident: - - db - - employees + - Indirection: + base: + Ident: db + field: + Name: employees - FuncCall: name: - Ident: - - join + Ident: join args: - - Ident: - - _salary + - Ident: _salary - Unary: op: EqSelf expr: - Ident: - - employee_id + Ident: employee_id - FuncCall: name: - Ident: - - filter + Ident: filter args: - Binary: left: - Ident: - - first_name + Ident: first_name op: Eq right: Param: "1" - FuncCall: name: - Ident: - - select + Ident: select args: - Tuple: - - Ident: - - _employees - - _underscored_column + - Indirection: + base: + Ident: _employees + field: + Name: _underscored_column span: "0:9-176" "###) } @@ -2145,60 +2005,53 @@ fn test_gt_lt_gte_lte() { exprs: - FuncCall: name: - Ident: - - from + Ident: from args: - - Ident: - - db - - people + - Indirection: + base: + Ident: db + field: + Name: people - FuncCall: name: - Ident: - - filter + Ident: filter args: - Binary: left: - Ident: - - age + Ident: age op: Gte right: Literal: Integer: 100 - FuncCall: name: - Ident: - - filter + Ident: filter args: - Binary: left: - Ident: - - num_grandchildren + Ident: num_grandchildren op: Lte right: Literal: Integer: 10 - FuncCall: name: - Ident: - - filter + Ident: filter args: - Binary: left: - Ident: - - salary + Ident: salary op: Gt right: Literal: Integer: 0 - FuncCall: name: - Ident: - - filter + Ident: filter args: - Binary: left: - Ident: - - num_eyes + Ident: num_eyes op: Lt right: Literal: @@ -2222,36 +2075,35 @@ join (db.salaries | select {s = this}) (==id) exprs: - FuncCall: name: - Ident: - - from + Ident: from args: - - Ident: - - db - - employees + - Indirection: + base: + Ident: db + field: + Name: employees - FuncCall: name: - Ident: - - join + Ident: join args: - Pipeline: exprs: - - Ident: - - db - - salaries + - Indirection: + base: + Ident: db + field: + Name: salaries - FuncCall: name: - Ident: - - select + Ident: select args: - Tuple: - - Ident: - - this + - Ident: this alias: s - Unary: op: EqSelf expr: - Ident: - - id + Ident: id span: "0:1-65" "###); } @@ -2259,24 +2111,18 @@ join (db.salaries | select {s = this}) (==id) #[test] fn test_ident_with_keywords() { assert_yaml_snapshot!(parse_expr(r"select {andrew, orion, lettuce, falsehood, null0}").unwrap(), @r###" - --- - FuncCall: - name: - Ident: - - select - args: - - Tuple: - - Ident: - - andrew - - Ident: - - orion - - Ident: - - lettuce - - Ident: - - falsehood - - Ident: - - null0 - "###); + --- + FuncCall: + name: + Ident: select + args: + - Tuple: + - Ident: andrew + - Ident: orion + - Ident: lettuce + - Ident: falsehood + - Ident: null0 + "###); assert_yaml_snapshot!(parse_expr(r"{false}").unwrap(), @r###" --- @@ -2292,25 +2138,23 @@ fn test_case() { nickname != null => nickname, true => null ]"#).unwrap(), @r###" - --- - Case: - - condition: - Binary: - left: - Ident: - - nickname - op: Ne - right: - Literal: "Null" - value: - Ident: - - nickname - - condition: - Literal: - Boolean: true - value: + --- + Case: + - condition: + Binary: + left: + Ident: nickname + op: Ne + right: Literal: "Null" - "###); + value: + Ident: nickname + - condition: + Literal: + Boolean: true + value: + Literal: "Null" + "###); } #[test] @@ -2337,11 +2181,9 @@ fn test_unicode() { value: FuncCall: name: - Ident: - - from + Ident: from args: - - Ident: - - tète + - Ident: tète span: "0:0-9" "###); } @@ -2358,8 +2200,7 @@ fn test_var_defs() { kind: Let name: a value: - Ident: - - x + Ident: x span: "0:9-42" "###); @@ -2372,8 +2213,7 @@ fn test_var_defs() { kind: Into name: a value: - Ident: - - x + Ident: x span: "0:9-25" "###); @@ -2385,8 +2225,7 @@ fn test_var_defs() { kind: Main name: main value: - Ident: - - x + Ident: x span: "0:9-11" "###); } @@ -2437,12 +2276,10 @@ fn test_annotation() { body: Binary: left: - Ident: - - a + Ident: a op: Add right: - Ident: - - b + Ident: b params: - name: a default_value: ~ @@ -2549,19 +2386,18 @@ fn test_target() { exprs: - FuncCall: name: - Ident: - - from + Ident: from args: - - Ident: - - db - - film + - Indirection: + base: + Ident: db + field: + Name: film - FuncCall: name: - Ident: - - remove + Ident: remove args: - - Ident: - - film2 + - Ident: film2 span: "0:45-81" "###); } @@ -2592,10 +2428,52 @@ fn test_module() { kind: Let name: man value: - Ident: - - module - - world + Indirection: + base: + Ident: module + field: + Name: world span: "0:64-86" span: "0:11-98" "###); } + +#[test] +fn test_indirection_01() { + assert_yaml_snapshot!(parse_expr( + r#" + {a = {x = 2}}.a.x + "#, + ).unwrap(), @r###" + --- + Indirection: + base: + Indirection: + base: + Tuple: + - Tuple: + - Literal: + Integer: 2 + alias: x + alias: a + field: + Name: a + field: + Name: x + "###); +} + +#[test] +fn test_indirection_02() { + assert_yaml_snapshot!(parse_expr( + r#" + hello.* + "#, + ).unwrap(), @r###" + --- + Indirection: + base: + Ident: hello + field: Star + "###); +} diff --git a/prqlc/prqlc/src/cli/mod.rs b/prqlc/prqlc/src/cli/mod.rs index a316fcd35f00..089f34c0fee8 100644 --- a/prqlc/prqlc/src/cli/mod.rs +++ b/prqlc/prqlc/src/cli/mod.rs @@ -687,18 +687,14 @@ sort full exprs: - FuncCall: name: - Ident: - - from + Ident: from args: - - Ident: - - x + - Ident: x - FuncCall: name: - Ident: - - select + Ident: select args: - - Ident: - - y + - Ident: y span: 1:0-17 "###); } diff --git a/prqlc/prqlc/src/codegen/ast.rs b/prqlc/prqlc/src/codegen/ast.rs index 030e6a313e91..72fd6b370ac2 100644 --- a/prqlc/prqlc/src/codegen/ast.rs +++ b/prqlc/prqlc/src/codegen/ast.rs @@ -53,7 +53,24 @@ impl WriteSource for ExprKind { use ExprKind::*; match &self { - Ident(ident) => ident.write(opt), + Ident(ident) => Some(write_ident_part(ident)), + Indirection { base, field } => { + let mut r = base.write(opt.clone())?; + opt.consume_width(r.len() as u16)?; + + r += opt.consume(".")?; + match field { + IndirectionKind::Name(n) => { + r += opt.consume(n)?; + } + IndirectionKind::Position(i) => { + r += &opt.consume(i.to_string())?; + } + IndirectionKind::Star => r += "*", + } + Some(r) + } + Pipeline(pipeline) => SeparatedExprs { inline: " | ", line_end: "", @@ -475,12 +492,10 @@ mod test { #[test] fn test_pipeline() { - let short = Expr::new(ExprKind::Ident(Ident::from_path(vec!["short"]))); - let long = Expr::new(ExprKind::Ident(Ident::from_path(vec![ - "some_module", - "submodule", - "a_really_long_name", - ]))); + let short = Expr::new(ExprKind::Ident("short".to_string())); + let long = Expr::new(ExprKind::Ident( + "some_really_long_and_really_long_name".to_string(), + )); let mut opt = WriteOpt { indent: 1, @@ -501,8 +516,8 @@ mod test { assert_snapshot!(pipeline.write(opt.clone()).unwrap(), @r###" ( short - some_module.submodule.a_really_long_name - some_module.submodule.a_really_long_name + some_really_long_and_really_long_name + some_really_long_and_really_long_name short ) "###); diff --git a/prqlc/prqlc/src/semantic/ast_expand.rs b/prqlc/prqlc/src/semantic/ast_expand.rs index b01c12c49ff4..950ec1875333 100644 --- a/prqlc/prqlc/src/semantic/ast_expand.rs +++ b/prqlc/prqlc/src/semantic/ast_expand.rs @@ -1,6 +1,7 @@ use std::collections::HashMap; use itertools::Itertools; +use prqlc_ast::error::WithErrorInfo; use crate::ast::*; use crate::ir::decl; @@ -11,7 +12,28 @@ use crate::{Error, Result}; /// An AST pass that maps AST to PL. pub fn expand_expr(expr: Expr) -> Result { let kind = match expr.kind { - ExprKind::Ident(v) => pl::ExprKind::Ident(v), + ExprKind::Ident(v) => pl::ExprKind::Ident(Ident::from_name(v)), + ExprKind::Indirection { base, field } => { + let field_as_name = match field { + IndirectionKind::Name(n) => n, + IndirectionKind::Position(_) => Err(Error::new_simple( + "Positional indirection not supported yet", + ) + .with_span(expr.span))?, + IndirectionKind::Star => "*".to_string(), + }; + + // convert indirections into ident + // (in the future, resolve will support proper indirection handling) + let base = expand_expr_box(base)?; + let base_ident = base.kind.into_ident().map_err(|_| { + Error::new_simple("Indirection (the dot) is supported only on names.") + .with_span(expr.span) + })?; + + let ident = base_ident + Ident::from_name(field_as_name); + pl::ExprKind::Ident(ident) + } ExprKind::Literal(v) => pl::ExprKind::Literal(v), ExprKind::Pipeline(v) => { let mut e = desugar_pipeline(v)?; @@ -283,7 +305,15 @@ fn restrict_exprs(exprs: Vec) -> Vec { fn restrict_expr_kind(value: pl::ExprKind) -> ExprKind { match value { - pl::ExprKind::Ident(v) => ExprKind::Ident(v), + pl::ExprKind::Ident(v) => { + let mut parts = v.into_iter(); + let mut base = Box::new(Expr::new(ExprKind::Ident(parts.next().unwrap()))); + for part in parts { + let field = IndirectionKind::Name(part); + base = Box::new(Expr::new(ExprKind::Indirection { base, field })) + } + base.kind + } pl::ExprKind::Literal(v) => ExprKind::Literal(v), pl::ExprKind::Tuple(v) => ExprKind::Tuple(restrict_exprs(v)), pl::ExprKind::Array(v) => ExprKind::Array(restrict_exprs(v)), @@ -336,13 +366,10 @@ fn restrict_expr_kind(value: pl::ExprKind) -> ExprKind { // TODO: these are not correct, they are producing invalid PRQL pl::ExprKind::All { within, .. } => restrict_expr(*within).kind, - pl::ExprKind::TransformCall(tc) => ExprKind::Ident(Ident::from_name(format!( - "({} ...)", - tc.kind.as_ref().as_ref() - ))), - pl::ExprKind::RqOperator { name, .. } => { - ExprKind::Ident(Ident::from_name(format!("({} ...)", name))) + pl::ExprKind::TransformCall(tc) => { + ExprKind::Ident(format!("({} ...)", tc.kind.as_ref().as_ref())) } + pl::ExprKind::RqOperator { name, .. } => ExprKind::Ident(format!("({} ...)", name)), } } diff --git a/prqlc/prqlc/tests/integration/ast_code_matches.rs b/prqlc/prqlc/tests/integration/ast_code_matches.rs index c0f7b623bfd1..b70f76376c38 100644 --- a/prqlc/prqlc/tests/integration/ast_code_matches.rs +++ b/prqlc/prqlc/tests/integration/ast_code_matches.rs @@ -17,12 +17,28 @@ fn test_expr_ast_code_matches() { -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Clone, PartialEq, Serialize, Deserialize)] @@ .. @@ + - Ident(String), + - Indirection { + - base: Box, + - field: IndirectionKind, + + Ident(Ident), + + All { + + within: Box, + + except: Box, + @@ .. @@ - Pipeline(Pipeline), @@ .. @@ - Range(Range), - Binary(BinaryExpr), - Unary(UnaryExpr), @@ .. @@ + -#[derive(Debug, EnumAsInner, PartialEq, Clone, Serialize, Deserialize)] + -pub enum IndirectionKind { + - Name(String), + - Position(i64), + - Star, + -} + - -#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] -pub struct BinaryExpr { - pub left: Box, diff --git a/prqlc/prqlc/tests/integration/bad_error_messages.rs b/prqlc/prqlc/tests/integration/bad_error_messages.rs index 5099963b7053..9460906417b7 100644 --- a/prqlc/prqlc/tests/integration/bad_error_messages.rs +++ b/prqlc/prqlc/tests/integration/bad_error_messages.rs @@ -101,11 +101,11 @@ fn select_with_extra_fstr() { select lower f"{x}/{y}" "#).unwrap_err(), @r###" Error: - ╭─[:3:20] + ╭─[:3:21] │ 3 │ select lower f"{x}/{y}" - │ ─┬─ - │ ╰─── Unknown name `x` + │ ┬ + │ ╰── Unknown name `x` ───╯ "###); } diff --git a/prqlc/prqlc/tests/integration/sql.rs b/prqlc/prqlc/tests/integration/sql.rs index b215632374c4..7ace6378d184 100644 --- a/prqlc/prqlc/tests/integration/sql.rs +++ b/prqlc/prqlc/tests/integration/sql.rs @@ -3604,11 +3604,11 @@ fn test_direct_table_references() { ) .unwrap_err(), @r###" Error: - ╭─[:3:14] + ╭─[:3:15] │ 3 │ select s"{x}.field" - │ ─┬─ - │ ╰─── table instance cannot be referenced directly + │ ┬ + │ ╰── table instance cannot be referenced directly │ │ Help: did you forget to specify the column name? ───╯