#include #include #include #include #include #include "actor.hpp" #include "instr.hpp" #include "arith.hpp" #include "argument_separator.hpp" #include "assert_test.hpp" using namespace std; using namespace boost; #define FUNCTION_USAGE_ERROR "usage: -function, FUNC_NAME, NUM_ARGS" #define ASSERT_USAGE_ERROR "usage: -assert, ASSERTION_TYPE, [args]" #define ACC_EQUALS_USAGE_ERROR "usage: -assert, acc, VALUE" #define ALLOCFIELDS_USAGE_ERROR "usage: -allocfields, NUM_FIELDS" #define INIT_USAGE_ERROR "usage: -init, FUNC_NAME" namespace BMD { typedef argument_separator arg_sep; typedef tokenizer arg_sep_tokenizer; typedef tokenizer::iterator tok_itr; /** load the BMD instructions from a file */ void Actor::load_from_file(string filename) { ifstream in(filename.c_str()); if (!in.is_open()) throw file_parse_error("unable to open file: " + filename); clear(); // first pass: reading in labels string line; int op_num = 0; while (getline(in, line)) { trim(line); // skip empty lines if (line.empty()) continue; // check labels if (is_label(line)) { string label_name = trim_right_copy_if(line, is_any_of(": ")); label_map.insert(make_pair(label_name, op_num)); } // ignore comments else if (is_comment(line)) continue; // compier signals else if (is_execution_signal(line)) { try { parse_execution_signal(line, op_num); } catch (...) { clear(); throw; } } else ++op_num; } in.close(); ifstream in2(filename.c_str()); if (!in2.is_open()) { clear(); throw file_parse_error("unable to open file: " + filename); } int line_num = 0; while (getline(in2, line) && ++line_num) { trim(line); if (line.empty() || is_label(line) || is_comment(line) || is_execution_signal(line)) continue; try { // parse the instruction InstructionRef new_instr(parse_instruction(line)); // append the file information new_instr->set_line_number(line_num); // add the instruction to the list add_instruction(new_instr); } catch (instruction_parse_error e) { // reject all instructions, since having an incomplete load is useless clear(); // attach the line number to any error with insturction parsing and // rethrow ostringstream error_msg; error_msg << filename << ":" << line_num << " - " << e.what(); throw instruction_parse_error(error_msg.str()); } } in2.close(); // once we're done loading, try to run the function, and reset the vm afterwards if (init_func != "") { try { vector args; call_function(init_func, args); vm->reset(); } catch (runtime_error e) { throw runtime_error(string("Error running initialization function: ") + e.what()); } } } /** returns true iff the line is a comment. Comments start with //. */ bool Actor::is_comment(string line) { return istarts_with(line, "//"); } /** returns true iff the line is a label Labels end with :. */ bool Actor::is_label(string line) { return iends_with(line, ":"); } /** execution signals start with a hyphen (-) * @return true iff the line is a special compiler. */ bool Actor::is_execution_signal(string line) { return istarts_with(line, "-"); } void Actor::parse_execution_signal(string line, int op_num) { // take off the hyphen string signal = line.substr(1); tokenizer< argument_separator > signal_tok(signal); tokenizer< argument_separator >::iterator signal_itr = signal_tok.begin(), end = signal_tok.end(); if (signal_itr == end) throw instruction_parse_error("signal does not have a keyword"); string signal_name = *signal_itr; to_lower(signal_name); /** function annotations **/ if (signal_name == "function") { // increment the token iterator and make sure we have a function name if (++signal_itr == end) throw instruction_parse_error(FUNCTION_USAGE_ERROR); // collect function information string function_name = *signal_itr; // increment, make sure we have a number if (++signal_itr == end) throw instruction_parse_error(FUNCTION_USAGE_ERROR); unsigned int args = parse_nonnegative_integer(*signal_itr, "-function"); function_map[function_name] = function_info(op_num, args); return; } /** assertion tests **/ if (signal_name == "assert") { // get the assertion type if (++signal_itr == end) throw instruction_parse_error(ASSERT_USAGE_ERROR); string assertion_name = trim_copy(*signal_itr); to_lower(assertion_name); if (assertion_name == "error") { should_throw = true; } else if (assertion_name == "acc") { // read the value if (++signal_itr == end) throw instruction_parse_error(ACC_EQUALS_USAGE_ERROR); Value v = Value::parse_value(*signal_itr); // add it to the assertion list assertions.insert(make_pair(op_num, new acc_equals_test(v))); return; } else if (assertion_name == "does_not_reach") { assertions.insert(make_pair(op_num, new does_not_reach_test())); } else throw instruction_parse_error("unrecognized assert: " + assertion_name); return; } /** field allocation **/ if (signal_name == "allocfields") { if (++signal_itr == end) throw instruction_parse_error(ALLOCFIELDS_USAGE_ERROR); unsigned int num_fields = parse_nonnegative_integer(*signal_itr, "allocfields"); alloc_fields(num_fields); return; } /** function initialization **/ if (signal_name == "init") { if (++signal_itr == end) throw instruction_parse_error(INIT_USAGE_ERROR); init_func = *signal_itr; return; } throw instruction_parse_error("unrecognized signal: " + signal_name); } /** parses a single line containing an instruction, returning the appropriate reference */ Instruction* Actor::parse_instruction(string instr) { tokenizer instr_tokenizer(instr); tokenizer::iterator itr = instr_tokenizer.begin(); const tokenizer::iterator end = instr_tokenizer.end(); string instr_name = *itr; to_lower(instr_name); ++itr; #define NO_ARG_INSTRUCTION(name, constructor) \ if (instr_name == name) \ return new constructor() // instructions without arguments NO_ARG_INSTRUCTION("push", push_instr); NO_ARG_INSTRUCTION("read", read_instr); NO_ARG_INSTRUCTION("print", print_instr); NO_ARG_INSTRUCTION("println", println_instr); NO_ARG_INSTRUCTION("swap", swap_instr); NO_ARG_INSTRUCTION("stop", stop_instr); NO_ARG_INSTRUCTION("return", return_instr); NO_ARG_INSTRUCTION("cons", cons_instr); NO_ARG_INSTRUCTION("car", car_instr); NO_ARG_INSTRUCTION("cdr", cdr_instr); NO_ARG_INSTRUCTION("nil", nil_instr); NO_ARG_INSTRUCTION("isnull", isnull_instr); NO_ARG_INSTRUCTION("concat", concat_instr); NO_ARG_INSTRUCTION("add", add_instr); NO_ARG_INSTRUCTION("sub", sub_instr); NO_ARG_INSTRUCTION("mul", mul_instr); NO_ARG_INSTRUCTION("div", div_instr); NO_ARG_INSTRUCTION("divi", divi_instr); NO_ARG_INSTRUCTION("remi", remi_instr); NO_ARG_INSTRUCTION("and", and_instr); NO_ARG_INSTRUCTION("or", or_instr); NO_ARG_INSTRUCTION("neg", neg_instr); NO_ARG_INSTRUCTION("frac", frac_instr); NO_ARG_INSTRUCTION("int", int_instr); NO_ARG_INSTRUCTION("not", not_instr); NO_ARG_INSTRUCTION("lt", lt_instr); NO_ARG_INSTRUCTION("gt", gt_instr); NO_ARG_INSTRUCTION("lte", lte_instr); NO_ARG_INSTRUCTION("gte", gte_instr); NO_ARG_INSTRUCTION("eq", eq_instr); NO_ARG_INSTRUCTION("neq", neq_instr); NO_ARG_INSTRUCTION("apply", apply_instr); /**** optional zero or one argument instructions ****/ #define OPTIONAL_NO_ARG_INSTRUCTION(name, constructor) \ if (instr_name == name && (itr == end)) \ return new constructor() OPTIONAL_NO_ARG_INSTRUCTION("pop", pop_instr); OPTIONAL_NO_ARG_INSTRUCTION("const", const_instr); OPTIONAL_NO_ARG_INSTRUCTION("getelem", getelem_NP_instr); OPTIONAL_NO_ARG_INSTRUCTION("setelem", setelem_NP_instr); /**** one argument instructions ****/ if (itr == end) throw instruction_parse_error(instr_name + " cannot be parsed as an instruction because" + " it is not a zero-argument instruction."); // construct the next argument string arg1 = *itr; ++itr; #define INTEGER_ARG_INSTRUCTION(name, constructor) \ if (instr_name == name) \ return new constructor( \ parse_nonnegative_integer(arg1, name)) INTEGER_ARG_INSTRUCTION("pop", pop_instr); INTEGER_ARG_INSTRUCTION("assign", assign_instr); INTEGER_ARG_INSTRUCTION("acc", acc_instr); INTEGER_ARG_INSTRUCTION("rev", rev_instr); INTEGER_ARG_INSTRUCTION("getelem", getelem_instr); INTEGER_ARG_INSTRUCTION("setelem", setelem_instr); INTEGER_ARG_INSTRUCTION("getfield", get_field_instr); INTEGER_ARG_INSTRUCTION("setfield", set_field_instr); if (instr_name == "makeblock") return new make_block_instr( (Block::Tag)parse_nonnegative_integer(arg1, "makeblock")); if (instr_name == "makefilledblock") return new make_filled_block_instr( (Block::Tag)parse_nonnegative_integer(arg1, "makefilledblock")); if (instr_name == "const") return new const_instr(Value::parse_value(arg1)); if (instr_name == "pushsf") return new push_sf_instr(get_label_address(arg1)); if (instr_name == "call") return new call_instr(arg1, get_label_address(arg1)); if (instr_name == "jmp") return new jmp_instr(get_label_address(arg1)); if (instr_name == "jz") return new jz_instr(get_label_address(arg1)); if (instr_name == "jnz") return new jnz_instr(get_label_address(arg1)); if (instr_name == "rpc") return new rpc_instr(arg1); /**** optional one or two-arg instructions ****/ if (instr_name == "dump" && (itr == end)) { try { return new stack_dump_instr(Value::parse_value(arg1).get_string()); } catch (runtime_error e) { throw instruction_parse_error("first argument of dump should be a string"); } } /**** two-argument instructions ****/ if (itr == end) throw instruction_parse_error(instr_name + string(" cannot be parsed as an instruction because ") + string("it is not a 1-argument instruction.")); // read in the second argument string arg2 = *itr; if (instr_name == "makeblockstatic") return new make_block_static_instr( (Block::Tag)parse_nonnegative_integer(arg1, "makeblockstatic"), parse_nonnegative_integer(arg2, "makeblockstatic")); if (instr_name == "dump") try { return new stack_dump_instr( Value::parse_value(arg1).get_string(), parse_nonnegative_integer(arg2, "dump")); } catch (runtime_error e) { throw instruction_parse_error("arguments to dump incorrect, expects string, [num'"); } // if we've reached here, then we haven't implemented // a way to parse the instruction throw instruction_parse_error(instr_name + " cannot be parsed as an instruction."); } /** * @param name the name of the instructions * @throws instruction_parse_error if the argument is not a nonnegative * integer */ unsigned int Actor::parse_nonnegative_integer(string arg, string name) { istringstream iss(arg); int ret; iss >> ret; if (iss.bad() || ret < 0) throw instruction_parse_error(name + " expects nonnegative integer value(s)" + (", given " + arg)); return (unsigned int)ret; } }