Interp - an Interpreted Programming Language Pt.14 - Built-In Functions

3 minute read

What’s up? Pt. 14 shows you how to implement built-in functions.

Built-ins To Implement

Print

Print value representation followed by a newline

Output

Print value representation without a newline

Intput

Read a line of input from the command line

Int

Convert value into an integer

Float

Convert value into a floating point number

ReadFile

Read file contents

WriteLine

Write to file

Exec

Execute a program with the specified arguments.

VM

Value

Add a value for each built-in

src/vm/value.rs:

pub enum Value {

    ...

    PrintBuiltin,
    OutputBuiltin,
    InputBuiltin,
    IntBuiltin,
    FloatBuiltin,
    ReadFileBuiltin,
    WriteFileBuiltin,
    ExecBuiltin,
}

expr_to_value

Add our function names to the Var case. Let’s also add command-line arguments.

src/vm/expr_to_value.rs:

pub fn expr_to_value(vm: &mut VM, expr: &Expr) -> value::Value {
    match &expr.type_ {

        ...

        ExprType::Variable(var) => {
            match var.as_str() {

                ...

                "Print"     => value::Value::PrintBuiltin,
                "Output"    => value::Value::OutputBuiltin,
                "Input"     => value::Value::InputBuiltin,
                "Float"     => value::Value::FloatBuiltin,
                "Int"       => value::Value::IntBuiltin,
                "ReadFile"  => value::Value::ReadFileBuiltin,
                "WriteFile" => value::Value::WriteFileBuiltin,
                "Exec"      => value::Value::ExecBuiltin,
                "clargs"    => value::Value::List(std::env::args().map(
                    |a| value::Value::String(a)).collect()),

                ...

            }
        }

Add the Call cases for our built-ins

        ExprType::Call(fn_expr, arg_exprs) => {
            let fn_value = expr_to_value(vm, &fn_expr);
            match fn_value {
value::Value::PrintBuiltin => {
    println!("{}", arg_exprs.into_iter().map(
        |a| value_repr(expr_to_value(vm, a)))
        .collect::<Vec<String>>().join(" "));
    value::Value::Void
}
value::Value::OutputBuiltin => {
    print!("{}", arg_exprs.into_iter().map(
        |a| value_repr(expr_to_value(vm, a)))
        .collect::<Vec<String>>().join(" "));
    std::io::stdout().flush().unwrap();
    value::Value::Void
}
value::Value::InputBuiltin => {
    if arg_exprs.len() != 0 {
        err::throw_loc(err::Err::WrongNumberOfArgs { n: 0 }, &fn_expr.loc);
    }

    let mut buf = String::new();
    match std::io::stdin().read_line(&mut buf) {
        Ok(_) => {
            if buf.is_empty() {
                value::Value::Null
            } else {
                buf.truncate(buf.len() - 1);
                value::Value::String(buf)
            }
        },
        Err(_) => value::Value::Null,
    }
}
value::Value::FloatBuiltin => {
    if arg_exprs.len() != 1 {
        err::throw_loc(err::Err::WrongNumberOfArgs { n: 0 }, &fn_expr.loc);
    }

    if arg_exprs.is_empty() {
        return value::Value::Null;
    }

    match expr_to_value(vm, &arg_exprs[0]) {
        a @ value::Value::Float(_) => a,
        value::Value::Int(int) => value::Value::Float(int as f64),
        value::Value::String(str) => {
            match str.parse() {
                Ok(float) => value::Value::Float(float),
                Err(_) => value::Value::Null,
            }
        }
        _ => value::Value::Null
    }
}
value::Value::IntBuiltin => {
    if arg_exprs.len() != 1 {
        err::throw_loc(err::Err::WrongNumberOfArgs { n: 0 }, &fn_expr.loc);
    }

    if arg_exprs.is_empty() {
        return value::Value::Null;
    }

    match expr_to_value(vm, &arg_exprs[0]) {
        value::Value::Float(a) => value::Value::Int(a.round() as i64),
        a @ value::Value::Int(_) => a,
        value::Value::String(str) => {
            match str.parse() {
                Ok(a) => value::Value::Int(a),
                Err(_) => value::Value::Null,
            }
        }
        _ => value::Value::Null
    }
}
value::Value::ReadFileBuiltin => {
    if arg_exprs.len() != 1 {
        err::throw_loc(err::Err::WrongNumberOfArgs { n: 0 }, &fn_expr.loc);
    }

    if arg_exprs.is_empty() {
        return value::Value::Null;
    }

    if let value::Value::String(filename) =
    expr_to_value(vm, &arg_exprs[0]) {
        match std::fs::read_to_string(filename) {
            Ok(text) => value::Value::String(text),
            Err(_) => value::Value::Null
        }
    } else {
        err::throw_loc(err::Err::TokenExpected
                       { expected: "string".to_owned() }, &arg_exprs[0].loc);
        value::Value::Null
    }
}
value::Value::WriteFileBuiltin => {
    if arg_exprs.len() != 2 {
        err::throw_loc(err::Err::WrongNumberOfArgs { n: 2 }, &fn_expr.loc);
    }

    if arg_exprs.len() < 2 {
        return value::Value::Null;
    }

    let filename = expr_to_value(vm, &arg_exprs[0]);
    let text = expr_to_value(vm, &arg_exprs[1]);
    match (filename, text) {
        (value::Value::String(filename), value::Value::String(text)) => {
            let _ = std::fs::write(filename, text);
        }
        _ => {
            err::throw_loc(err::Err::TokenExpected
                           { expected: "strings".to_owned() }, &arg_exprs[0].loc);
        }
    }
    value::Value::Void
}
value::Value::ExecBuiltin => {
    if arg_exprs.is_empty() {
        err::throw_loc(err::Err::WrongNumberOfArgs { n: 1 }, &fn_expr.loc);
        return value::Value::Null;
    }

    let mut iter = arg_exprs.into_iter();
    let filename = value_repr(expr_to_value(vm, iter.next().unwrap()));
    let args: Vec<String> = iter.map(
        |a| value_repr(expr_to_value(vm, a))).collect();
    match std::process::Command::new(filename).args(args).output() {
        Ok(output) => match std::str::from_utf8(&output.stdout) {
            Ok(output) => value::Value::String(output.to_owned()),
            Err(_) => value::Value::Null,
        }
        Err(_) => value::Value::Null,
    }
}
_ => {
    err::throw_loc(err::Err::CannotCall, &fn_expr.loc);
    value::Value::Void
}

is_var_illegal

Add the names of our built-ins to the illegal variable name list

src/vm/mod.rs:

fn is_var_illegal(var: &str) -> bool {
    if let "void" | "null" | "Print" | "Output" | "Input" | "ReadFile" | "WriteFile"
    | "Float" | "Int" | "Exex" | "clargs" | "true" | "false" = var {
        true
    } else { false }
}

value_repr

Add a fallback case for our built-ins

src/vm/value_repr.rs:

pub fn value_repr(val: value::Value) -> String {
    match val {
        ...
        val => format!("{:?}", val),
    }
}

Options

We must add this for our program to allow command-line arguments

pub struct Opt {
    ...
    pub args: Vec<String>,
}

Wrapping it up

You should now be confident to add other built-ins as you please. :)

Source Code

Source code for this part can be found here.

Next Up

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

Updated: