Changeset - 949789bac3cf
[Not reviewed]
1 6 1
mh - 4 years ago 2021-12-20 09:53:17
contact@maxhenger.nl
Parsing of select statement
7 files changed with 167 insertions and 32 deletions:
0 comments (0 inline, 0 general)
src/protocol/ast.rs
Show inline comments
 
@@ -1348,25 +1348,25 @@ pub struct EndForkStatement {
 
}
 

	
 
#[derive(Debug, Clone)]
 
pub struct SelectStatement {
 
    pub this: SelectStatementId,
 
    pub span: InputSpan, // of the "select" keyword
 
    pub cases: Vec<SelectCase>,
 
    pub end_select: EndSelectStatementId,
 
}
 

	
 
#[derive(Debug, Clone)]
 
pub struct SelectCase {
 
    pub guard_var: Option<MemoryStatementId>, // optional memory declaration
 
    pub guard_var: MemoryStatementId, // invalid ID if there is no declaration of a variable
 
    pub guard_expr: ExpressionStatementId, // if `guard_var.is_some()`, then always assignment expression
 
    pub block: BlockStatementId,
 
}
 

	
 
#[derive(Debug, Clone)]
 
pub struct EndSelectStatement {
 
    pub this: EndSelectStatementId,
 
    pub start_select: SelectStatementId,
 
    pub next: StatementId,
 
}
 

	
 
#[derive(Debug, Clone)]
src/protocol/ast_printer.rs
Show inline comments
 
@@ -532,27 +532,27 @@ impl ASTWriter {
 
                    .with_s_key("EndFork");
 
                self.kv(indent2).with_s_key("StartFork").with_disp_val(&stmt.start_fork.0.index);
 
                self.kv(indent2).with_s_key("Next").with_disp_val(&stmt.next.index);
 
            },
 
            Statement::Select(stmt) => {
 
                self.kv(indent).with_id(PREFIX_SELECT_STMT_ID, stmt.this.0.index)
 
                    .with_s_key("Select");
 
                self.kv(indent2).with_s_key("EndSelect").with_disp_val(&stmt.end_select.0.index);
 
                self.kv(indent2).with_s_key("Cases");
 
                let indent3 = indent2 + 1;
 
                let indent4 = indent3 + 1;
 
                for case in &stmt.cases {
 
                    if let Some(guard_var_id) = case.guard_var {
 
                    if !case.guard_var.is_invalid() {
 
                        self.kv(indent3).with_s_key("GuardStatement");
 
                        self.write_stmt(heap, guard_var_id.upcast().upcast(), indent4);
 
                        self.write_stmt(heap, case.guard_var.upcast().upcast(), indent4);
 
                    } else {
 
                        self.kv(indent3).with_s_key("GuardStatement").with_s_val("None");
 
                    }
 

	
 
                    self.kv(indent3).with_s_key("GuardExpression");
 
                    self.write_stmt(heap, case.guard_expr.upcast(), indent4);
 

	
 
                    self.kv(indent3).with_s_key("Block");
 
                    self.write_stmt(heap, case.block.upcast(), indent4);
 
                }
 
            },
 
            Statement::EndSelect(stmt) => {
src/protocol/parser/pass_definitions.rs
Show inline comments
 
@@ -478,35 +478,37 @@ impl PassDefinitions {
 
            } else if ident == KW_STMT_NEW {
 
                let id = self.consume_new_statement(module, iter, ctx)?;
 
                section.push(id.upcast());
 
            } else if ident == KW_STMT_CHANNEL {
 
                let id = self.consume_channel_statement(module, iter, ctx)?;
 
                section.push(id.upcast().upcast());
 
            } else if iter.peek() == Some(TokenKind::Colon) {
 
                self.consume_labeled_statement(module, iter, ctx, section)?;
 
            } else {
 
                // Two fallback possibilities: the first one is a memory
 
                // declaration, the other one is to parse it as a normal
 
                // expression. This is a bit ugly.
 
                if let Some((memory_stmt_id, assignment_stmt_id)) = self.maybe_consume_memory_statement(module, iter, ctx)? {
 
                if let Some((memory_stmt_id, assignment_stmt_id)) = self.maybe_consume_memory_statement_without_semicolon(module, iter, ctx)? {
 
                    consume_token(&module.source, iter, TokenKind::SemiColon)?;
 
                    section.push(memory_stmt_id.upcast().upcast());
 
                    section.push(assignment_stmt_id.upcast());
 
                } else {
 
                    let id = self.consume_expression_statement(module, iter, ctx)?;
 
                    section.push(id.upcast());
 
                }
 
            }
 
        } else if next == TokenKind::OpenParen {
 
            // Same as above: memory statement or normal expression
 
            if let Some((memory_stmt_id, assignment_stmt_id)) = self.maybe_consume_memory_statement(module, iter, ctx)? {
 
            if let Some((memory_stmt_id, assignment_stmt_id)) = self.maybe_consume_memory_statement_without_semicolon(module, iter, ctx)? {
 
                consume_token(&module.source, iter, TokenKind::SemiColon);
 
                section.push(memory_stmt_id.upcast().upcast());
 
                section.push(assignment_stmt_id.upcast());
 
            } else {
 
                let id = self.consume_expression_statement(module, iter, ctx)?;
 
                section.push(id.upcast());
 
            }
 
        } else {
 
            let id = self.consume_expression_statement(module, iter, ctx)?;
 
            section.push(id.upcast());
 
        }
 

	
 
        return Ok(());
 
@@ -682,46 +684,52 @@ impl PassDefinitions {
 
            right_body,
 
            end_fork: EndForkStatementId::new_invalid(),
 
        }))
 
    }
 

	
 
    fn consume_select_statement(
 
        &mut self, module: &Module, iter: &mut TokenIter, ctx: &mut PassCtx
 
    ) -> Result<SelectStatementId, ParseError> {
 
        let select_span = consume_exact_ident(&module.source, iter, KW_STMT_SELECT)?;
 
        consume_token(&module.source, iter, TokenKind::OpenCurly)?;
 

	
 
        let mut cases = Vec::new();
 
        consume_comma_separated_until(
 
            TokenKind::CloseCurly, &module.source, iter, ctx,
 
            |source, iter, ctx| {
 
                // A select arm starts with a guard, being something of the form
 
                // `defined_var = get(port)`, `get(port)` or
 
                // `Type var = get(port)`. So:
 
                let (guard_var, guard_expr) = match self.maybe_consume_memory_statement(module, iter, ctx)? {
 
                    Some((guard_var, guard_expr)) => {
 
                        (Some(guard_var), guard_expr)
 
                    },
 
                    None => {
 
                        let guard_expr = self.consume_expression_statement(module, iter, ctx)?;
 
                        (None, guard_expr)
 
                    },
 
                };
 
                consume_token(source, iter, TokenKind::ArrowRight)?;
 
                let block = self.consume_block_or_wrapped_statement(module, iter, ctx)?;
 
        let mut next = iter.next();
 

	
 
                Ok(SelectCase{ guard_var, guard_expr, block })
 
            },
 
            &mut cases, "select arm", None
 
        )?;
 
        while Some(TokenKind::CloseCurly) != next {
 
            let (guard_var, guard_expr) = match self.maybe_consume_memory_statement_without_semicolon(module, iter, ctx)? {
 
                Some(guard_var_and_expr) => guard_var_and_expr,
 
                None => {
 
                    let start_pos = iter.last_valid_pos();
 
                    let expr = self.consume_expression(module, iter, ctx)?;
 
                    let end_pos = iter.last_valid_pos();
 

	
 
                    let guard_expr = ctx.heap.alloc_expression_statement(|this| ExpressionStatement{
 
                        this,
 
                        span: InputSpan::from_positions(start_pos, end_pos),
 
                        expression: expr,
 
                        next: StatementId::new_invalid(),
 
                    });
 

	
 
                    (MemoryStatementId::new_invalid(), guard_expr)
 
                },
 
            };
 
            consume_token(&module.source, iter, TokenKind::ArrowRight)?;
 
            let block = self.consume_block_or_wrapped_statement(module, iter, ctx)?;
 
            cases.push(SelectCase{ guard_var, guard_expr, block });
 

	
 
            next = iter.next();
 
        }
 

	
 
        consume_token(&module.source, iter, TokenKind::CloseCurly)?;
 

	
 
        Ok(ctx.heap.alloc_select_statement(|this| SelectStatement{
 
            this,
 
            span: select_span,
 
            cases,
 
            end_select: EndSelectStatementId::new_invalid(),
 
        }))
 
    }
 

	
 
    fn consume_return_statement(
 
        &mut self, module: &Module, iter: &mut TokenIter, ctx: &mut PassCtx
 
    ) -> Result<ReturnStatementId, ParseError> {
 
@@ -908,50 +916,49 @@ impl PassDefinitions {
 
            // Produce the labeled statement using the first statement, and push
 
            // the remaining ones at the end.
 
            let inner_statements = inner_section.into_vec();
 
            section.push(stmt_id.upcast());
 
            for idx in 1..inner_statements.len() {
 
                section.push(inner_statements[idx])
 
            }
 
        }
 

	
 
        Ok(())
 
    }
 

	
 
    fn maybe_consume_memory_statement(
 
    fn maybe_consume_memory_statement_without_semicolon(
 
        &mut self, module: &Module, iter: &mut TokenIter, ctx: &mut PassCtx
 
    ) -> Result<Option<(MemoryStatementId, ExpressionStatementId)>, ParseError> {
 
        // This is a bit ugly. It would be nicer if we could somehow
 
        // consume the expression with a type hint if we do get a valid
 
        // type, but we don't get an identifier following it
 
        let iter_state = iter.save();
 
        let definition_id = self.cur_definition;
 
        let poly_vars = ctx.heap[definition_id].poly_vars();
 

	
 
        let parser_type = self.type_parser.consume_parser_type(
 
            iter, &ctx.heap, &module.source, &ctx.symbols, poly_vars,
 
            definition_id, SymbolScope::Definition(definition_id), true, None
 
        );
 

	
 
        if let Ok(parser_type) = parser_type {
 
            if Some(TokenKind::Ident) == iter.next() {
 
                // Assume this is a proper memory statement
 
                let identifier = consume_ident_interned(&module.source, iter, ctx)?;
 
                let memory_span = InputSpan::from_positions(parser_type.full_span.begin, identifier.span.end);
 
                let assign_span = consume_token(&module.source, iter, TokenKind::Equal)?;
 

	
 
                let initial_expr_begin_pos = iter.last_valid_pos();
 
                let initial_expr_id = self.consume_expression(module, iter, ctx)?;
 
                let initial_expr_end_pos = iter.last_valid_pos();
 
                consume_token(&module.source, iter, TokenKind::SemiColon)?;
 

	
 
                // Allocate the memory statement with the variable
 
                let local_id = ctx.heap.alloc_variable(|this| Variable{
 
                    this,
 
                    kind: VariableKind::Local,
 
                    identifier: identifier.clone(),
 
                    parser_type,
 
                    relative_pos_in_block: 0,
 
                    unique_id_in_scope: -1,
 
                });
 
                let memory_stmt_id = ctx.heap.alloc_memory_statement(|this| MemoryStatement{
 
                    this,
src/protocol/parser/pass_validation_linking.rs
Show inline comments
 
@@ -427,24 +427,77 @@ impl Visitor for PassValidationLinking {
 
        self.visit_block_stmt(ctx, left_body_id)?;
 
        assign_then_erase_next_stmt!(self, ctx, end_fork_id.upcast());
 

	
 
        if let Some(right_body_id) = right_body_id {
 
            self.visit_block_stmt(ctx, right_body_id)?;
 
            assign_then_erase_next_stmt!(self, ctx, end_fork_id.upcast());
 
        }
 

	
 
        self.prev_stmt = end_fork_id.upcast();
 
        Ok(())
 
    }
 

	
 
    fn visit_select_stmt(&mut self, ctx: &mut Ctx, id: SelectStatementId) -> VisitorResult {
 
        let select_stmt = &ctx.heap[id];
 
        let end_select_id = select_stmt.end_select;
 

	
 
        // Select statements may only occur inside sync blocks
 
        if self.in_sync.is_invalid() {
 
            return Err(ParseError::new_error_str_at_span(
 
                &ctx.module().source, select_stmt.span,
 
                "select statements may only occur inside sync blocks"
 
            ));
 
        }
 

	
 
        if !self.def_type.is_primitive() {
 
            return Err(ParseError::new_error_str_at_span(
 
                &ctx.module().source, select_stmt.span,
 
                "select statements may only be used in primitive components"
 
            ));
 
        }
 

	
 
        // Visit the various arms in the select block
 
        // note: three statements per case, so we lookup as `3 * index + offset`
 
        let mut case_stmt_ids = self.statement_buffer.start_section();
 
        let num_cases = select_stmt.cases.len();
 
        for case in &select_stmt.cases {
 
            case_stmt_ids.push(case.guard_var.upcast().upcast());
 
            case_stmt_ids.push(case.guard_expr.upcast());
 
            case_stmt_ids.push(case.block.upcast());
 
        }
 

	
 
        assign_then_erase_next_stmt!(self, ctx, id.upcast());
 

	
 
        // Link up the "Select" with the "EndSelect". If there are no cases then
 
        // runtime will pick the "EndSelect" immediately.
 
        for idx in 0..num_cases {
 
            let base_idx = 3 * idx;
 
            let guard_var_id  = case_stmt_ids[base_idx    ];
 
            let guard_expr_id = case_stmt_ids[base_idx + 1];
 
            let arm_block_id  = case_stmt_ids[base_idx + 2];
 

	
 
            if !guard_var_id.is_invalid() {
 
                self.visit_stmt(ctx, guard_var_id)?;
 
            }
 

	
 
            self.visit_stmt(ctx, guard_expr_id)?;
 
            self.visit_stmt(ctx, arm_block_id)?;
 

	
 
            assign_then_erase_next_stmt!(self, ctx, end_select_id.upcast());
 
        }
 

	
 
        self.prev_stmt = end_select_id.upcast();
 
        Ok(())
 
    }
 

	
 
    fn visit_return_stmt(&mut self, ctx: &mut Ctx, id: ReturnStatementId) -> VisitorResult {
 
        // Check if "return" occurs within a function
 
        let stmt = &ctx.heap[id];
 
        if !self.def_type.is_function() {
 
            return Err(ParseError::new_error_str_at_span(
 
                &ctx.module().source, stmt.span,
 
                "return statements may only appear in function bodies"
 
            ));
 
        }
 

	
 
        // If here then we are within a function
 
        assign_then_erase_next_stmt!(self, ctx, id.upcast());
 
@@ -1210,26 +1263,26 @@ impl Visitor for PassValidationLinking {
 
                let is_valid_binding = match self.expr_parent {
 
                    ExpressionParent::Expression(expr_id, idx) => {
 
                        match &ctx.heap[expr_id] {
 
                            Expression::Binding(_binding_expr) => {
 
                                // Nested binding is disallowed, and because of
 
                                // the check above we know we're directly at the
 
                                // LHS of the binding expression
 
                                debug_assert_eq!(_binding_expr.this, self.in_binding_expr);
 
                                debug_assert_eq!(idx, 0);
 
                                true
 
                            }
 
                            Expression::Literal(lit_expr) => {
 
                                // Only struct, unions and arrays can have
 
                                // subexpressions, so we're always fine
 
                                // Only struct, unions, tuples and arrays can
 
                                // have subexpressions, so we're always fine
 
                                if cfg!(debug_assertions) {
 
                                    match lit_expr.value {
 
                                        Literal::Struct(_) | Literal::Union(_) | Literal::Array(_) | Literal::Tuple(_) => {},
 
                                        _ => unreachable!(),
 
                                    }
 
                                }
 

	
 
                                true
 
                            },
 
                            _ => false,
 
                        }
 
                    },
src/protocol/tests/mod.rs
Show inline comments
 
@@ -3,25 +3,25 @@
 
 *
 
 * Contains tests for various parts of the lexer/parser and the evaluator of the
 
 * code. These are intended to be temporary tests such that we're sure that we
 
 * don't break existing functionality.
 
 *
 
 * In the future these should be replaced by proper testing protocols.
 
 *
 
 * If any of these tests fail, and you think they're not needed anymore, feel
 
 * free to cast them out into oblivion, where dead code goes to die.
 
 */
 

	
 
mod utils;
 
mod lexer;
 
mod parser_after_tokenizing;
 
mod parser_binding;
 
mod parser_imports;
 
mod parser_inference;
 
mod parser_literals;
 
mod parser_monomorphs;
 
mod parser_types;
 
mod parser_validation;
 
mod eval_binding;
 
mod eval_calls;
 
mod eval_casting;
 
mod eval_operators;
 
mod eval_silly;
src/protocol/tests/parser_after_tokenizing.rs
Show inline comments
 
file renamed from src/protocol/tests/lexer.rs to src/protocol/tests/parser_after_tokenizing.rs
 
/// lexer.rs
 
/// parser_after_tokenizing
 
///
 
/// Simple tests for the lexer. Only tests the lexing of the input source and
 
/// the resulting AST without relying on the validation/typing pass
 

	
 
use super::*;
 

	
 
#[test]
 
fn test_disallowed_inference() {
 
    Tester::new_single_source_expect_err(
 
        "argument auto inference",
 
            "func thing(auto arg) -> s32 { return 0; }"
 
    ).error(|e| { e
src/protocol/tests/parser_validation.rs
Show inline comments
 
@@ -519,13 +519,88 @@ fn test_polymorph_array_types() {
 
    ).for_struct("Bar", |s| { s
 
        .for_field("world", |f| { f.assert_parser_type("Foo<u32>[]"); });
 
    });
 

	
 
    Tester::new_single_source_expect_ok(
 
        "array of port in struct",
 
        "
 
        struct Bar { in<u32>[] inputs }
 
        "
 
    ).for_struct("Bar", |s| { s
 
        .for_field("inputs", |f| { f.assert_parser_type("in<u32>[]"); });
 
    });
 
}
 

	
 
#[test]
 
fn test_correct_modifying_operators() {
 
    // Not testing the types, just that it parses
 
    Tester::new_single_source_expect_ok(
 
        "valid uses",
 
        "
 
        func f() -> u32 {
 
            auto a = 5;
 
            a += 2; a -= 2; a *= 2; a /= 2; a %= 2;
 
            a <<= 2; a >>= 2;
 
            a |= 2; a &= 2; a ^= 2;
 
            return a;
 
        }
 
        "
 
    );
 
}
 

	
 
#[test]
 
fn test_incorrect_modifying_operators() {
 
    Tester::new_single_source_expect_err(
 
        "wrong declaration",
 
        "func f() -> u8 { auto a += 2; return a; }"
 
    ).error(|e| { e.assert_msg_has(0, "expected '='"); });
 

	
 
    Tester::new_single_source_expect_err(
 
        "inside function",
 
        "func f(u32 a) -> u32 { auto b = 0; auto c = f(a += 2); }"
 
    ).error(|e| { e.assert_msg_has(0, "assignments are statements"); });
 

	
 
    Tester::new_single_source_expect_err(
 
        "inside tuple",
 
        "func f(u32 a) -> u32 { auto b = (a += 2, a /= 2); return 0; }"
 
    ).error(|e| { e.assert_msg_has(0, "assignments are statements"); });
 
}
 

	
 
#[test]
 
fn test_correct_select_statement() {
 
    Tester::new_single_source_expect_ok(
 
        "correct single-use", "
 
        primitive f() {
 
            channel unused_output -> input;
 
            u32 outer_value = 0;
 
            sync select {
 
                outer_value = get(input) -> outer_value = 0;
 
                auto new_value = get(input) -> {
 
                    f();
 
                    outer_value = new_value;
 
                }
 
                get(input) + get(input) ->
 
                    outer_value = 8;
 
                get(input) ->
 
                    {}
 
                outer_value %= get(input) -> {
 
                    outer_value *= outer_value;
 
                    auto new_value = get(input);
 
                    outer_value += new_value;
 
                }
 
            }
 
        }
 
        "
 
    );
 
}
 

	
 
#[test]
 
fn test_incorrect_select_statement() {
 
    Tester::new_single_source_expect_err(
 
        "outside sync",
 
        "func f() -> u32 { select {} return 0; }"
 
    ).error(|e| { e
 
        .assert_num(1)
 
        .assert_occurs_at(0, "select")
 
        .assert_msg_has(0, "inside sync blocks");
 
    });
 
}
 
\ No newline at end of file
0 comments (0 inline, 0 general)