Interp - an Interpreted Programming Language Pt.9 - While

3 minute read

Hello! This time in Pt. 9 we implement looping.

Parsing

LineTypes

Add LineTypes for while, break, and continue

src/parser/line.rs:

pub enum LineType {

    ...

    While(Expr),
    NamedWhile(Expr, Expr),
    Break(Option<Expr>),
    Continue(Option<Expr>),
}

Grammar

Add the tokens and grammar rules.

match {

    ...
    
    "while",
    "brk",
    "cnt",
    ":",
}

...

pub Line: Line = {
    
    ...

    <l:@L> "while" <a:expr> "{" => Line { type_: LineType::While(a), loc: Loc { filename: filename.to_owned(), line_nr, index: l } },
    <l:@L> <a:variable> ":" "while" <b:expr> "{" => Line { type_: LineType::NamedWhile(a, b), loc: Loc { filename: filename.to_owned(), line_nr, index: l } },
    <l:@L> "brk" => Line { type_: LineType::Break(None), loc: Loc { filename: filename.to_owned(), line_nr, index: l } },
    <l:@L> "brk" <a:variable> => Line { type_: LineType::Break(Some(a)), loc: Loc { filename: filename.to_owned(), line_nr, index: l } },
    <l:@L> "cnt" => Line { type_: LineType::Continue(None), loc: Loc { filename: filename.to_owned(), line_nr, index: l } },
    <l:@L> "cnt" <a:variable> => Line { type_: LineType::Continue(Some(a)), loc: Loc { filename: filename.to_owned(), line_nr, index: l } },
}

VM

VM State

src/vm/mod.rs:

pub enum ScopeState {

    ...

    While {
        name: Option<String>,
        cond_expr: Expr,
    },

    Break    { name: Option<String> },
    Continue { name: Option<String> },
}

exec_line

First we add While to increment our scope counter.

pub fn exec_line(vm: &mut VM, parsed_line: &Line) {
    if vm.scope != 0 { // Means gathering mode is on
        if vm.scope == 1 {
            match parsed_line.type_ {
                LineType::If(_) | LineType::While(_) => { // Add While
                    vm.scope = 2;
                    vm.line_buffer.push(parsed_line.clone());
                    return;
                }
                LineType::ElseIf(_) |
                LineType::Else |
                LineType::EndScope => { }
                _ => {
                    vm.line_buffer.push(parsed_line.clone());
                    return;
                }
            }
        } else {
            match parsed_line.type_ {
                LineType::If(_) | LineType::While(_) => vm.scope += 1, // Add While
                LineType::EndScope => vm.scope -= 1,
                _ => { }
            }
            vm.line_buffer.push(parsed_line.clone());
            return;
        }
    }

Add logic. EndScope has a loop that evaluates the while-condition Expr. If true, the line buffer is executed and the loop continues. On a false case the loop is terminated.

LineType::EndScope => {
    vm.scope = 0;
    let prev_line_buffer = std::mem::replace(&mut vm.line_buffer, vec![]);
    match std::mem::replace(&mut vm.scope_state, ScopeState::Regular) {

        ...
ScopeState::While { cond_expr, name: while_name } => {
    'outer: loop {
        let cond_value = expr_to_value(vm, &cond_expr);
        if let value::Value::Boolean(cond) = cond_value {
            if cond {
                for line in &prev_line_buffer {
                    exec_line(vm, line);
                    if let ScopeState::Break { name } = &vm.scope_state {
                        if &while_name == name {
                            vm.scope_state = ScopeState::Regular;
                        }
                        break 'outer;
                    }
                    if let ScopeState::Continue { name } = &vm.scope_state {
                        if &while_name == name {
                            vm.scope_state = ScopeState::Regular;
                            continue 'outer;
                        } else {
                            break 'outer;
                        }
                    }
                }
            } else {
                break;
            }
        } else {
            err::throw_loc(err::Err::TokenExpected
                           { expected: "condition".to_owned() }, &cond_expr.loc);
            break;
        }
    }
}

Add the remaining LineType cases.

LineType::While(cond_expr) => {
    vm.scope_state = ScopeState::While { cond_expr: cond_expr.clone(), name: None };
    vm.scope = 1; // Into gathering mode
}
LineType::NamedWhile(name_expr, cond_expr) => {
    if let ExprType::Variable(name) = &name_expr.type_ {
        vm.scope_state = ScopeState::While
        { cond_expr: cond_expr.clone(), name: Some(name.to_owned()) };
        vm.scope = 1; // Into gathering mode
    } else { panic!() }
}
LineType::Break(name_expr) => {
    if let Some(name_expr) = name_expr {
        if let ExprType::Variable(name) = &name_expr.type_ {
            vm.scope_state = ScopeState::Break { name: Some(name.to_owned()) };
        }
    } else {
        vm.scope_state = ScopeState::Break { name: None }
    }
}
LineType::Continue(name_expr) => {
    if let Some(name_expr) = name_expr {
        if let ExprType::Variable(name) = &name_expr.type_ {
                    vm.scope_state = ScopeState::Continue { name: Some(name.to_owned()) };
        }
    } else {
        vm.scope_state = ScopeState::Continue { name: None }
    }
}

Testing

A simple while-loop:

var i = 0
while i < 10 {
    Print(i)
    i += 1
}
0
1
2
3
4
5
6
7
8
9

Source Code

Source code for this part can be found here.

Next Up

Click for Pt. 10 or click here for all parts.

Updated: