diff --git a/schala-lang/src/parsing/combinator.rs b/schala-lang/src/parsing/combinator.rs index bf28072..2b00366 100644 --- a/schala-lang/src/parsing/combinator.rs +++ b/schala-lang/src/parsing/combinator.rs @@ -300,6 +300,16 @@ fn expression_no_struct(input: Span) -> ParseResult { })(input) } +fn expr_or_block(input: Span) -> ParseResult { + let (input, pos) = position(input)?; + let id = fresh_id(&input); + let location = pos.location_offset().into(); + alt(( + block, + map(expression, move |expr| Statement { id, location, kind: StatementKind::Expression(expr) }.into()), + ))(input) +} + fn type_anno(input: Span) -> ParseResult { preceded(tok(char(':')), type_identifier)(input) } @@ -444,11 +454,13 @@ fn primary_expr(allow_struct: bool) -> impl FnMut(Span) -> ParseResult ParseResult { + println!("PRIM EXPR: {}", input.fragment()); context( "primary-expr-no-struct", alt(( while_expr, for_expr, + if_expr, list_expr, paren_expr, string_literal, @@ -482,10 +494,119 @@ fn while_expr(input: Span) -> ParseResult { })(input) } +fn if_expr(input: Span) -> ParseResult { + fn else_case(input: Span) -> ParseResult> { + opt(preceded(kw("else"), expr_or_block))(input) + } + + fn cond_block(input: Span) -> ParseResult { + map(block_template(cond_arm), IfExpressionBody::CondList)(input) + } + + fn cond_arm(input: Span) -> ParseResult { + let condition = map(preceded(kw("is"), pattern), Condition::Pattern); + let condition_guard = opt(preceded(kw("if"), expression)); + alt(( + map(preceded(kw("else"), expr_or_block), |body| ConditionArm { + condition: Condition::Else, + guard: None, + body, + }), + map( + tuple((condition, condition_guard, kw("then"), expr_or_block)), + |(condition, guard, _, body)| ConditionArm { condition, guard, body }, + ), + ))(input) + } + + fn simple_pattern_match(input: Span) -> ParseResult { + map( + tuple((preceded(kw("is"), pattern), preceded(kw("then"), pair(expr_or_block, else_case)))), + |(pattern, (then_case, else_case))| IfExpressionBody::SimplePatternMatch { + pattern, + then_case, + else_case, + }, + )(input) + } + + fn simple_conditional(input: Span) -> ParseResult { + map(preceded(kw("then"), pair(expr_or_block, else_case)), |(then_case, else_case)| { + IfExpressionBody::SimpleConditional { then_case, else_case } + })(input) + } + + fn if_expr_body(input: Span) -> ParseResult { + alt((cond_block, simple_pattern_match, simple_conditional))(input) + } + + map(preceded(kw("if"), pair(opt(expression_no_struct), if_expr_body)), |(discriminator, body)| { + ExpressionKind::IfExpression { discriminator: discriminator.map(Box::new), body: Box::new(body) } + })(input) +} + +fn pattern(input: Span) -> ParseResult { + alt(( + map( + delimited(tok(char('(')), separated_list1(tok(char(',')), pattern), tok(char(')'))), + Pattern::TuplePattern, + ), + simple_pattern, + ))(input) +} + +fn simple_pattern(input: Span) -> ParseResult { + fn record_pattern_entry(input: Span) -> ParseResult<(Rc, Pattern)> { + let id = fresh_id(&input); + alt(( + map(separated_pair(identifier, tok(char(':')), pattern), |(ident, pat)| { + (rc_string(ident.fragment()), pat) + }), + map(identifier, move |ident| { + let qn = QualifiedName { id, components: vec![rc_string(ident.fragment())] }; + (rc_string(ident.fragment()), Pattern::VarOrName(qn)) + }), + ))(input) + } + + alt(( + pattern_literal, + map( + pair( + qualified_identifier, + delimited(tok(char('(')), separated_list0(tok(char(',')), pattern), tok(char(')'))), + ), + |(qn, members)| Pattern::TupleStruct(qn, members), + ), + map( + pair( + qualified_identifier, + delimited( + tok(char('{')), + separated_list0(tok(char(',')), record_pattern_entry), + tok(char('}')), + ), + ), + |(qn, members)| Pattern::Record(qn, members), + ), + map(qualified_identifier, Pattern::VarOrName), + ))(input) +} + +fn pattern_literal(input: Span) -> ParseResult { + alt(( + value(Pattern::Ignored, kw("_")), + value(Pattern::Literal(PatternLiteral::BoolPattern(true)), kw("true")), + value(Pattern::Literal(PatternLiteral::BoolPattern(false)), kw("false")), + map(tok(bare_string_literal), |s| Pattern::Literal(PatternLiteral::StringPattern(Rc::new(s)))), + map(pair(opt(tok(char('-'))), alt((float_literal, number_literal))), |(sign, num)| { + Pattern::Literal(PatternLiteral::NumPattern { neg: sign.is_some(), num }) + }), + ))(input) +} + fn for_expr(input: Span) -> ParseResult { - println!("IN FOR EXPR: {}", input.fragment()); fn for_enumerators(input: Span) -> ParseResult> { - println!("NEUMS: {}", input.fragment()); alt(( delimited(tok(char('{')), separated_list0(tok(char(',')), enumerator), tok(char('}'))), map(enumerator, |enumerator| vec![enumerator]), @@ -543,6 +664,7 @@ fn string_literal(input: Span) -> ParseResult { } fn bare_string_literal(input: Span) -> ParseResult { + println!("BARE STRING: {}", input.fragment()); let string_escape_transforms = alt((value("\\", tag("\\")), value("\"", tag("\"")), value("\n", tag("n")), value("\t", tag("t")))); alt(( diff --git a/schala-lang/src/parsing/test.rs b/schala-lang/src/parsing/test.rs index 0d608eb..71739c1 100644 --- a/schala-lang/src/parsing/test.rs +++ b/schala-lang/src/parsing/test.rs @@ -134,7 +134,7 @@ macro_rules! assert_expr { macro_rules! assert_expr_comb { ($input:expr, $correct:expr) => { let mut parser = Parser::new(); - let expr = parser.expression_comb($input); + let expr = parser.expression_comb($input.trim_start()); if expr.is_err() { println!("Expression parse error: {}", expr.unwrap_err().msg); panic!(); @@ -1097,7 +1097,7 @@ fn imports() { #[test] fn if_exprs() { use ExpressionKind::*; - assert_expr!( + assert_expr_comb!( "if a() then { tuah(); }", expr(IfExpression { discriminator: Some(bx(expr(Call { f: bx(expr(Value(qn!(a)))), arguments: vec![] }))), @@ -1108,7 +1108,8 @@ fn if_exprs() { }) ); - assert_expr!( + //TODO add tests for named expressions + assert_expr_comb!( "if a then b else c", expr(IfExpression { discriminator: Some(bx(expr(Value(qn!(a))))), @@ -1119,9 +1120,8 @@ fn if_exprs() { }) ); - assert_expr!( - r#" - if true then { + assert_expr_comb!( + r#"if true then { let a = 10 b } else { @@ -1151,7 +1151,7 @@ fn pattern_matching() { use ExpressionKind::*; for item in ["if x is Some(a) then { 4 } else { 9 }", "if x is Some(a) then 4 else 9"] { - assert_expr!( + assert_expr_comb!( item, expr(IfExpression { discriminator: Some(bx(expr(Value(qn!(x))))), @@ -1164,7 +1164,7 @@ fn pattern_matching() { ); } - assert_expr!( + assert_expr_comb!( "if x is Something { a, b: x } then { 4 } else { 9 }", expr(IfExpression { discriminator: Some(bx(expr(Value(qn!(x))))), @@ -1179,7 +1179,7 @@ fn pattern_matching() { }) ); - assert_expr!( + assert_expr_comb!( "if x is -1 then 1 else 2", expr(IfExpression { discriminator: Some(bx(expr(Value(qn!(x))))), @@ -1191,7 +1191,7 @@ fn pattern_matching() { }) ); - assert_expr!( + assert_expr_comb!( "if x is true then 1 else 2", expr(IfExpression { discriminator: Some(bx(expr(Value(qn!(x))))), @@ -1203,7 +1203,7 @@ fn pattern_matching() { }) ); - assert_expr!( + assert_expr_comb!( "if x { is 1 then 5; else 20 }", expr(IfExpression { discriminator: Some(bx(expr(Value(qn!(x))))), @@ -1225,7 +1225,7 @@ fn pattern_matching() { }) ); - assert_expr!( + assert_expr_comb!( r#"if x is "gnosticism" then 1 else 2"#, expr(IfExpression { discriminator: Some(bx(expr(Value(qn!(x))))), @@ -1237,7 +1237,7 @@ fn pattern_matching() { }) ); - assert_expr! { + assert_expr_comb! { r#" if (45, "panda", false, 2.2) { is (49, "pablo", _, 28.4) then "no"