diff --git a/CMakeLists.txt b/CMakeLists.txt index e8b3e43..ab1d2d2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,6 +10,8 @@ add_executable(biscuit_interpreter src/common.hpp) target_sources( biscuit_interpreter PRIVATE src/main.cpp src/parse.cpp + src/runtime.cpp + src/util.cpp src/keywords.cpp ) include_directories(${CMAKE_SOURCE_DIR}) diff --git a/biscuit_doc.odt b/biscuit_doc.odt deleted file mode 100644 index 3036d75..0000000 Binary files a/biscuit_doc.odt and /dev/null differ diff --git a/doc/biscuit_doc.odt b/doc/biscuit_doc.odt new file mode 100644 index 0000000..2d42f75 Binary files /dev/null and b/doc/biscuit_doc.odt differ diff --git a/doc/biscuit_doc.pdf b/doc/biscuit_doc.pdf new file mode 100644 index 0000000..a14f64c Binary files /dev/null and b/doc/biscuit_doc.pdf differ diff --git a/examples/faculty.bisc b/examples/faculty.bisc new file mode 100644 index 0000000..380f84a --- /dev/null +++ b/examples/faculty.bisc @@ -0,0 +1,9 @@ +PRINT "This program calculates the faculty of a number." +ASSIGN 1 fac +NUMIN "Please input your number!" num +ASSIGN num i +MUL fac i fac +SUB i 1 i +GOTO 5 i +PRINT "The faculty of " num " is " fac "." +EXIT diff --git a/examples/fibonacci.bisc b/examples/fibonacci.bisc new file mode 100644 index 0000000..7626583 --- /dev/null +++ b/examples/fibonacci.bisc @@ -0,0 +1,11 @@ +ASSIGN 8 n +ASSIGN 0 a +ASSIGN 1 b +PRINT a +ADD a b a +PRINT a +ADD a b b +PRINT b +SUB n 1 n +GOTO 5 n +EXIT diff --git a/examples/guessthenumber.bisc b/examples/guessthenumber.bisc new file mode 100644 index 0000000..7b74b34 --- /dev/null +++ b/examples/guessthenumber.bisc @@ -0,0 +1,13 @@ +PRINT "Welcome to the number guessing game!" +RAND 100 number +NUMIN "Please input your guess!" guess +EQUAL number guess is_equal +GOTO 12 is_equal # win if equal +SUB number guess is_less +GOTO 10 is_less +PRINT "Too big! Try again!" +GOTO 3 1 +PRINT "Too small! Try again!" +GOTO 3 1 +PRINT "Correct! The number was " number "." +EXIT diff --git a/examples/helloworld.bisc b/examples/helloworld.bisc index 79b1c55..3706bfd 100644 --- a/examples/helloworld.bisc +++ b/examples/helloworld.bisc @@ -1,2 +1,2 @@ -print #hello_world -print 1.01234 +PRINT "Hello world!" # This is a comment +EXIT diff --git a/examples/numbers.bisc b/examples/numbers.bisc new file mode 100644 index 0000000..a0eba48 --- /dev/null +++ b/examples/numbers.bisc @@ -0,0 +1,5 @@ +ASSIGN 10 i +PRINT i +SUB i 1 i +GOTO 2 i +EXIT diff --git a/examples/test.bisc b/examples/test.bisc new file mode 100644 index 0000000..73e3abc --- /dev/null +++ b/examples/test.bisc @@ -0,0 +1,11 @@ +PRINT "Hello World, this is a number: " 15 +ASSIGN number 15 +PRINT "Let's double it's value, shall we?" +ADD number number number +PRINT "There we go: " number +PRINT "Now, what if we multiplied it by 5?" +MUL number 5 number +PRINT number +PRINT "Not bad :)" +PRINT "The program will now terminate!" +EXIT diff --git a/src/common.hpp b/src/common.hpp index 41ff8c9..1862394 100644 --- a/src/common.hpp +++ b/src/common.hpp @@ -1,6 +1,8 @@ #ifndef BISCUIT_INTERPRETER_COMMON_HPP #define BISCUIT_INTERPRETER_COMMON_HPP +#include + enum TokenType { KEYWORD, @@ -16,4 +18,14 @@ enum ValueType ANY }; +// some prototypes +struct Token; +class Instruction; +struct Keyword; +class Runtime; + +const std::string resolve_TokenType_str(enum TokenType); +const std::string resolve_ValueType_str(enum ValueType); + + #endif //BISCUIT_INTERPRETER_COMMON_HPP diff --git a/src/exceptions.hpp b/src/exceptions.hpp index 286b782..48d806c 100644 --- a/src/exceptions.hpp +++ b/src/exceptions.hpp @@ -4,6 +4,7 @@ #include #include "common.hpp" +// Parsetime errors struct MalformedTokenExcept : public std::exception { std::string malformed_str; @@ -34,12 +35,26 @@ struct WrongTokenExcept : public std::exception : expected(_expected), got(_got), keyword_name(_keyword_name), token_str(_token_str) {} }; + +// Runtime errors struct TypeErrorExcept : public std::exception { enum ValueType expected, got; - std::string keyword_name, token_str; - inline TypeErrorExcept(const std::string& _keyword_name, const std::string& _token_str, const enum ValueType& _expected, const enum ValueType& _got) - : expected(_expected), got(_got), keyword_name(_keyword_name), token_str(_token_str) {} + std::string token_str; + inline TypeErrorExcept(const std::string& _token_str, const enum ValueType& _expected, const enum ValueType& _got) + : expected(_expected), got(_got), token_str(_token_str) {} +}; + +struct UndeclaredVariableExcept : public std::exception +{ + std::string identifier_str; + inline UndeclaredVariableExcept(const std::string& str) : identifier_str(str) {} +}; + +struct JumpOutOfBoundsExcept : public std::exception +{ + int from, to; + inline JumpOutOfBoundsExcept(int _from, int _to) : from(_from), to(_to) {} }; #endif //BISCUIT_INTERPRETER_EXCEPTIONS_HPP diff --git a/src/keywords.cpp b/src/keywords.cpp index 7612481..65429f3 100644 --- a/src/keywords.cpp +++ b/src/keywords.cpp @@ -1,5 +1,7 @@ #include #include +#include +#include #include "common.hpp" #include "keywords.hpp" @@ -10,10 +12,277 @@ const std::vector keywords = .name = "print", .expected_num_args = -1, .expected_token_types = {TokenType::ID_OR_LIT}, - .expected_value_types = {ValueType::ANY} + .expected_value_types = {ValueType::ANY}, + .func = [](Runtime& rt, const std::vector& tokens) + { + for(auto& arg : tokens) + { + if(arg.type == TokenType::LITERAL) + { + if(arg.value_type == ValueType::STRING) + std::cout << arg.val_string; + else + std::cout << arg.val_number; + } + else if(arg.type == TokenType::IDENTIFIER) + { + if(rt.resolve_var_type(arg.val_string) == ValueType::STRING) + std::cout << rt.resolve_var_str(arg.val_string); + else if (rt.resolve_var_type(arg.val_string) == ValueType::NUMBER) + std::cout << rt.resolve_var_num(arg.val_string); + } + } + std::cout << std::endl; + } }, { .name = "exit", - .expected_num_args = 0 + .expected_num_args = 0, + .func = [](Runtime& rt, const std::vector& tokens) + { + std::exit(EXIT_SUCCESS); + } + }, + { + .name = "assign", + .expected_num_args = 2, + .expected_token_types = {TokenType::ID_OR_LIT, TokenType::IDENTIFIER}, + .expected_value_types = {ValueType::ANY, ValueType::ANY}, + .func = [](Runtime& rt, const std::vector& tokens) + { + if(tokens[0].type == TokenType::LITERAL) + { + if(tokens[0].value_type == ValueType::NUMBER) + rt.assign_var_num(tokens[1].val_string, tokens[0].val_number); + else + rt.assign_var_str(tokens[1].val_string, tokens[0].val_string); + } + else if(tokens[0].type == TokenType::IDENTIFIER) + { + if(rt.resolve_var_type(tokens[0].val_string) == ValueType::NUMBER) + rt.assign_var_num(tokens[1].val_string, rt.resolve_var_num(tokens[0].val_string)); + else + rt.assign_var_str(tokens[1].val_string, rt.resolve_var_str(tokens[0].val_string)); + } + } + }, + { + .name = "add", + .expected_num_args = 3, + .expected_token_types = {TokenType::ID_OR_LIT, TokenType::ID_OR_LIT, TokenType::IDENTIFIER}, + .expected_value_types = {ValueType::NUMBER, ValueType::NUMBER, ValueType::NUMBER}, + .func = [](Runtime& rt, const std::vector& tokens) + { + float operands[2], res; + + // Retrieve operands + for(unsigned int i = 0; i < 2; i++) + { + if(tokens[i].type == TokenType::LITERAL) + operands[i] = tokens[i].val_number; + else + operands[i] = rt.resolve_var_num(tokens[i].val_string); + } + + res = operands[0] + operands[1]; + + rt.assign_var_num(tokens[2].val_string, res); + } + }, + { + .name = "mul", + .expected_num_args = 3, + .expected_token_types = {TokenType::ID_OR_LIT, TokenType::ID_OR_LIT, TokenType::IDENTIFIER}, + .expected_value_types = {ValueType::NUMBER, ValueType::NUMBER, ValueType::NUMBER}, + .func = [](Runtime& rt, const std::vector& tokens) + { + float operands[2], res; + + // Retrieve operands + for(unsigned int i = 0; i < 2; i++) + { + if(tokens[i].type == TokenType::LITERAL) + operands[i] = tokens[i].val_number; + else + operands[i] = rt.resolve_var_num(tokens[i].val_string); + } + + res = operands[0] * operands[1]; + + rt.assign_var_num(tokens[2].val_string, res); + } + }, + { + .name = "div", + .expected_num_args = 3, + .expected_token_types = {TokenType::ID_OR_LIT, TokenType::ID_OR_LIT, TokenType::IDENTIFIER}, + .expected_value_types = {ValueType::NUMBER, ValueType::NUMBER, ValueType::NUMBER}, + .func = [](Runtime& rt, const std::vector& tokens) + { + float operands[2], res; + + // Retrieve operands + for(unsigned int i = 0; i < 2; i++) + { + if(tokens[i].type == TokenType::LITERAL) + operands[i] = tokens[i].val_number; + else + operands[i] = rt.resolve_var_num(tokens[i].val_string); + } + + res = operands[0] / operands[1]; + + rt.assign_var_num(tokens[2].val_string, res); + } + }, + { + .name = "sub", + .expected_num_args = 3, + .expected_token_types = {TokenType::ID_OR_LIT, TokenType::ID_OR_LIT, TokenType::IDENTIFIER}, + .expected_value_types = {ValueType::NUMBER, ValueType::NUMBER, ValueType::NUMBER}, + .func = [](Runtime& rt, const std::vector& tokens) + { + float operands[2], res; + + // Retrieve operands + for(unsigned int i = 0; i < 2; i++) + { + if(tokens[i].type == TokenType::LITERAL) + operands[i] = tokens[i].val_number; + else + operands[i] = rt.resolve_var_num(tokens[i].val_string); + } + + res = operands[0] - operands[1]; + + rt.assign_var_num(tokens[2].val_string, res); + } + }, + { + .name = "goto", + .expected_num_args = 2, + .expected_token_types = {TokenType::ID_OR_LIT, TokenType::ID_OR_LIT}, + .expected_value_types = {ValueType::NUMBER, ValueType::NUMBER}, + .func = [](Runtime& rt, const std::vector& tokens) + { + int dest; + float condition; + + if(tokens[0].type == TokenType::LITERAL) + dest = std::floor(tokens[0].val_number); + else + dest = std::floor(rt.resolve_var_num(tokens[0].val_string)); + + if(tokens[1].type == TokenType::LITERAL) + condition = std::floor(tokens[1].val_number); + else + condition = std::floor(rt.resolve_var_num(tokens[1].val_string)); + + if(condition > 0.0f) + rt.jump_instructions(dest); + } + }, + { + .name = "jump", + .expected_num_args = 2, + .expected_token_types = {TokenType::ID_OR_LIT, TokenType::ID_OR_LIT}, + .expected_value_types = {ValueType::NUMBER, ValueType::NUMBER}, + .func = [](Runtime& rt, const std::vector& tokens) + { + int offset, dest; + float condition; + + if(tokens[0].type == TokenType::LITERAL) + offset = std::floor(tokens[0].val_number); + else + offset = std::floor(rt.resolve_var_num(tokens[0].val_string)); + + if(tokens[1].type == TokenType::LITERAL) + condition = std::floor(tokens[1].val_number); + else + condition = std::floor(rt.resolve_var_num(tokens[1].val_string)); + + dest = rt.instruction_counter + dest; + + if(condition > 0.0f) + rt.jump_instructions(dest); + } + }, + { + .name = "strin", + .expected_num_args = 2, + .expected_token_types = {TokenType::ID_OR_LIT, TokenType::IDENTIFIER}, + .expected_value_types = {ValueType::STRING, ValueType::ANY}, + .func = [](Runtime& rt, const std::vector& tokens) + { + if(tokens[0].type == TokenType::LITERAL) + std::cout << tokens[0].val_string << " "; + else + std::cout << rt.resolve_var_str(tokens[0].val_string) << " "; + + std::string str; + std::cin >> str; + + rt.assign_var_str(tokens[1].val_string, str); + } + }, + { + .name = "numin", + .expected_num_args = 2, + .expected_token_types = {TokenType::ID_OR_LIT, TokenType::IDENTIFIER}, + .expected_value_types = {ValueType::STRING, ValueType::ANY}, + .func = [](Runtime& rt, const std::vector& tokens) + { + if(tokens[0].type == TokenType::LITERAL) + std::cout << tokens[0].val_string << " "; + else + std::cout << rt.resolve_var_str(tokens[0].val_string) << " "; + + float num; + std::cin >> num; + + rt.assign_var_num(tokens[1].val_string, num); + } + }, + { + .name = "rand", + .expected_num_args = 2, + .expected_token_types = {TokenType::ID_OR_LIT, TokenType::IDENTIFIER}, + .expected_value_types = {ValueType::NUMBER, ValueType::ANY}, + .func = [](Runtime& rt, const std::vector& tokens) + { + int limit; + if(tokens[0].type == TokenType::LITERAL) + limit = tokens[0].val_number; + else + limit = rt.resolve_var_num(tokens[0].val_string); + + int number = rand() % limit; + + rt.assign_var_num(tokens[1].val_string, number); + } + }, + { + .name = "equal", + .expected_num_args = 3, + .expected_token_types = {TokenType::ID_OR_LIT, TokenType::ID_OR_LIT, TokenType::IDENTIFIER}, + .expected_value_types = {ValueType::NUMBER, ValueType::NUMBER, ValueType::NUMBER}, + .func = [](Runtime& rt, const std::vector& tokens) + { + float operands[2], res; + + // Retrieve operands + for(unsigned int i = 0; i < 2; i++) + { + if(tokens[i].type == TokenType::LITERAL) + operands[i] = tokens[i].val_number; + else + operands[i] = rt.resolve_var_num(tokens[i].val_string); + } + + res = std::round(operands[0]) == std::round(operands[1]); + + rt.assign_var_num(tokens[2].val_string, res); + } } }; diff --git a/src/keywords.hpp b/src/keywords.hpp index 9f3ee83..231a64f 100644 --- a/src/keywords.hpp +++ b/src/keywords.hpp @@ -4,8 +4,12 @@ #include #include #include +#include +#include "parse.hpp" #include "common.hpp" +#include "runtime.hpp" +#include "parse.hpp" struct Keyword { @@ -14,6 +18,8 @@ struct Keyword const std::vector expected_token_types; const std::vector expected_value_types; + + std::function&)> func; }; extern const std::vector keywords; diff --git a/src/main.cpp b/src/main.cpp index d70630f..b12b8cf 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -11,6 +11,7 @@ #include "common.hpp" #include "exceptions.hpp" #include "parse.hpp" +#include "runtime.hpp" bool request_console(); @@ -46,19 +47,12 @@ std::ifstream get_infile(int argc, char ** argv) return infile; } -std::string code = \ -R"del( -PRINT 10.0 |hello_world -)del"; - int main(int argc, char * argv[]) { - /* // Get the entire source code from the file std::ifstream infile = get_infile(argc, argv); std::stringstream buffer; buffer << infile.rdbuf(); - */ // On Windows, we have to explicitly request a console /*if(!request_console()) @@ -67,13 +61,10 @@ int main(int argc, char * argv[]) exit(EXIT_FAILURE); }*/ - std::cerr << "POOP" << std::endl; + std::vector instructions = parse_instructions(buffer.str()); - std::vector instructions = parse_instructions(code); - - std::cerr << "Number of instructions parsed: " << instructions.size() << std::endl; - for(auto& instr : instructions) - instr.print(); + Runtime rt(instructions); + rt.run(); return 0; } diff --git a/src/parse.cpp b/src/parse.cpp index 1a0de21..7bfdf1f 100644 --- a/src/parse.cpp +++ b/src/parse.cpp @@ -20,18 +20,44 @@ std::vector tokenize_line(const std::string& line) std::size_t end, begin = line.find_first_not_of(blank_space, 0); while(begin != std::string::npos) { - end = line.find_first_of(blank_space, begin); - std::string word = line.substr(begin, end - begin); - tokens.emplace_back(word); + if(line[begin] == '"') // We have struck a string literal!!! + { + end = line.find("\"", begin + 1); - begin = line.find_first_not_of(blank_space, end); + if(end == std::string::npos) + { + std::cout << "ERROR: Unterminated string literal!" << std::endl; + std::exit(EXIT_FAILURE); + } + + end+=1; + std::string word = line.substr(begin, end - begin); + tokens.emplace_back(word); + + begin = line.find_first_not_of(blank_space, end); + } + else if(line[begin] == '#') // It's a comment.. discard rest of line! + { + break; + } + else + { + end = line.find_first_of(blank_space, begin); + std::string word = line.substr(begin, end - begin); + tokens.emplace_back(word); + + begin = line.find_first_not_of(blank_space, end); + } } return std::move(tokens); } +// Main parsing function std::vector parse_instructions(const std::string& code_arg) { + unsigned int line_number = 1; + // Make a copy of the code and append a line_end symbol to make sure the // parser doesn't break on EOF std::string code = code_arg; @@ -44,12 +70,51 @@ std::vector parse_instructions(const std::string& code_arg) { end = code.find_first_of(line_end, begin); std::string line = code.substr(begin, end - begin); - std::vector tokens = tokenize_line(line); - instructions.emplace_back(tokens); + + try + { + std::vector tokens = tokenize_line(line); + instructions.emplace_back(tokens); + } + catch(MalformedTokenExcept& e) + { + std::cout << "ERROR: In line " << line_number << ": " + << "Malformed identifier '" << e.malformed_str << "'" + << std::endl; + std::exit(EXIT_FAILURE); + } + catch(UnknownKeywordExcept& e) + { + std::cout << "ERROR: In line " << line_number << ": " + << "Unknown keyword '" << e.unknown_keyword << "'" + << std::endl; + std::exit(EXIT_FAILURE); + } + catch(WrongArgumentCountExcept& e) + { + std::cout << "ERROR: In line " << line_number << ": " + << "Wrong argument count. " << e.keyword_name + << " expects " << e.expected << " arguments but got " + << e.got << "." << std::endl; + std::exit(EXIT_FAILURE); + } + catch(WrongTokenExcept &e) + { + std::cout << "ERROR: In line " << line_number << ": " + << "Token '" << e.token_str << "' is wrong kind of token. Keyword '" + << e.keyword_name << "' expected " + << resolve_TokenType_str(e.expected) << " but got " + << resolve_TokenType_str(e.got) << "." << std::endl; + + std::exit(EXIT_FAILURE); + } begin = code.find_first_not_of(line_end, end); + + ++line_number; } + return std::move(instructions); } @@ -71,7 +136,7 @@ bool is_keyword(std::string str) bool is_string_literal(const std::string& str) { - if(str[0] == '#') + if(str[0] == '"' && str[str.size()-1] == '"') return true; else return false; @@ -88,8 +153,13 @@ bool is_decimal_literal(const std::string& str) bool is_identifier(const std::string& str) { + // First char must be alphabetic + if(!std::isalpha(str[0])) return false; + + // Rest of identifier must be alphanumeric, + // but can also contain underscores for(const char& c : str) - if(!(std::isalpha(c) || c == '_')) + if(!(std::isalnum(c)) && c != '_') return false; return true; } @@ -97,11 +167,15 @@ bool is_identifier(const std::string& str) bool Token::parse_as_keyword(const std::string &str) { - if(!is_keyword(str)) + auto str_cpy = str; + for(char &c : str_cpy) + c = (char) std::tolower(c); + + if(!is_keyword(str_cpy)) return false; type = TokenType::KEYWORD; - val_string = str; + val_string = str_cpy; return true; } @@ -112,17 +186,21 @@ bool Token::parse_as_literal(const std::string &str) if(is_string_literal(str)) { type = TokenType::LITERAL; - literal_type = ValueType::STRING; + value_type = ValueType::STRING; auto str_copy = str; + + // Erase the quotation marks str_copy.erase(0, 1); + str_copy.erase(str.size()-2, 1); + val_string = str_copy; return true; } else if(is_decimal_literal(str)) { type = TokenType::LITERAL; - literal_type = ValueType::NUMBER; - val_float = std::stof(str); + value_type = ValueType::NUMBER; + val_number = std::stof(str); val_string = str; return true; } @@ -137,9 +215,9 @@ bool Token::parse_as_identifier(const std::string &str) if(is_identifier(str)) { type = TokenType::IDENTIFIER; - literal_type = ValueType::ANY; + value_type = ValueType::ANY; val_string = str; - val_float = 0.0f; + val_number = 0.0f; return true; } return false; @@ -148,7 +226,7 @@ bool Token::parse_as_identifier(const std::string &str) Token::Token(const std::string &str) { // First try to parse as str, then as literal, then as identifier - if(!(parse_as_keyword(str) && parse_as_literal(str) && parse_as_identifier(str))) + if(!parse_as_keyword(str) && !parse_as_literal(str) && !parse_as_identifier(str)) throw MalformedTokenExcept(str); // if all fails -> malformed token } @@ -157,24 +235,27 @@ void Token::print() const switch(type) { case TokenType::IDENTIFIER: - std::cerr << "Identifier: \"" << val_string << "\""; + std::cout << "Identifier: " << val_string; break; case TokenType::KEYWORD: - std::cerr << "Keyword: \"" << val_string << "\""; + std::cout << "Keyword: \"" << val_string << "\""; break; case TokenType::LITERAL: - std::cerr << "Literal of type "; - if(literal_type == ValueType::NUMBER) - std::cerr << "decimal: " << val_float; - else if(literal_type == ValueType::STRING) - std::cerr << "string: \"" << val_string << "\""; + std::cout << "Literal of type "; + if(value_type == ValueType::NUMBER) + std::cerr << "Number: " << val_number; + else if(value_type == ValueType::STRING) + std::cout << "String: \"" << val_string << "\""; break; + + // Correctly parsed tokens should NEVER have this type: case TokenType::ID_OR_LIT: - std::cerr << "Identifier or Literal?! Something went terribly wrong!!"; + std::cout << "Something went wrong parsing this token!! " + << __FILE__ << " " << __LINE__; } - std::cerr << std::endl; + std::cout << std::endl; } Instruction::Instruction(std::vector& _token_list) @@ -203,9 +284,21 @@ Instruction::Instruction(std::vector& _token_list) // Check the arguments supplied are the right kind of tokens for(int i = 0; i < keyword_ptr->expected_num_args; i++) { - if(keyword_ptr->expected_token_types[i] != tokens[i+1].type) - throw WrongTokenExcept(keyword_ptr->name, tokens[i+1].val_string, keyword_ptr->expected_token_types[i], - tokens[i+1].type); + switch(keyword_ptr->expected_token_types[i]) + { + case TokenType::ID_OR_LIT: + if(tokens[i+1].type != TokenType::IDENTIFIER && tokens[i+1].type != TokenType::LITERAL) + throw WrongTokenExcept(keyword_ptr->name, tokens[i+1].val_string, + keyword_ptr->expected_token_types[i], + tokens[i+1].type); + break; + default: + if(tokens[i+1].type != keyword_ptr->expected_token_types[i]) + throw WrongTokenExcept(keyword_ptr->name, tokens[i+1].val_string, + keyword_ptr->expected_token_types[i], + tokens[i+1].type); + break; + } } // if arbitrary number of args are expected, they're all one type @@ -213,9 +306,21 @@ Instruction::Instruction(std::vector& _token_list) { for (auto it = tokens.begin() + 1; it != tokens.end(); it++) { - if (keyword_ptr->expected_token_types[0] != it->type) - throw WrongTokenExcept(keyword_ptr->name, it->val_string, keyword_ptr->expected_token_types[0], - it->type); + switch(keyword_ptr->expected_token_types[0]) + { + case TokenType::ID_OR_LIT: + if(it->type != TokenType::IDENTIFIER && it->type != TokenType::LITERAL) + throw WrongTokenExcept(keyword_ptr->name, it->val_string, + keyword_ptr->expected_token_types[0], + it->type); + break; + default: + if(it->type != keyword_ptr->expected_token_types[0]) + throw WrongTokenExcept(keyword_ptr->name, it->val_string, + keyword_ptr->expected_token_types[0], + it->type); + break; + } } } diff --git a/src/parse.hpp b/src/parse.hpp index 82613d4..e100585 100644 --- a/src/parse.hpp +++ b/src/parse.hpp @@ -6,6 +6,7 @@ #include "keywords.hpp" #include "common.hpp" +#include "runtime.hpp" struct Token { @@ -19,8 +20,8 @@ private: public: enum TokenType type; - enum ValueType literal_type; - float val_float; + enum ValueType value_type; + float val_number; std::string val_string; Token(const std::string& str); @@ -28,12 +29,11 @@ public: void print() const; }; -class Instruction +struct Instruction { std::vector tokens; const Keyword * keyword_ptr; -public: // Try to parse a list of symbols as instructions Instruction(std::vector& _token_list); void print(); diff --git a/src/runtime.cpp b/src/runtime.cpp new file mode 100644 index 0000000..afa1024 --- /dev/null +++ b/src/runtime.cpp @@ -0,0 +1,150 @@ +#include +#include + +#include "runtime.hpp" +#include "exceptions.hpp" + +bool Runtime::var_exists(const std::string& identifier) +{ + return std::any_of(variables.begin(), variables.end(), + [&identifier](auto& var) { + return var.name == identifier; + }); +} + +const std::vector::iterator Runtime::var_find(const std::string& identifier) +{ + auto it = std::find_if(variables.begin(), variables.end(), + [&identifier](auto& var) { + return var.name == identifier; + }); + + if(it == variables.end()) + throw UndeclaredVariableExcept(identifier); + + return it; +} + +ValueType Runtime::resolve_var_type(const std::string& identifier) +{ + for(auto& var : variables) + { + if(var.name == identifier) + return var.type; + } + + throw UndeclaredVariableExcept(identifier); +} + +const std::string Runtime::resolve_var_str(const std::string& identifier) +{ + if(resolve_var_type(identifier) != ValueType::STRING) + throw TypeErrorExcept(identifier, ValueType::STRING, ValueType::NUMBER); + + return var_find(identifier)->val_string; +} + +float Runtime::resolve_var_num(const std::string& identifier) +{ + if(resolve_var_type(identifier) != ValueType::NUMBER) + throw TypeErrorExcept(identifier, ValueType::NUMBER, ValueType::STRING); + + return var_find(identifier)->val_number; +} + +void Runtime::assign_var_num(const std::string& identifier, float num) +{ + // Because redeclaration of variables is allowed + if(var_exists(identifier)) + { + auto it = std::find_if(variables.begin(), variables.end(), + [&identifier](auto& var) { + return identifier == var.name; + }); + + it->type = ValueType::NUMBER; + it->val_number = num; + it->val_string = ""; + } + else + variables.emplace_back(identifier, ValueType::NUMBER, "", num); +} + +void Runtime::assign_var_str(const std::string& identifier, const std::string& str) +{ + // Because redeclaration of variables is allowed + if(var_exists(identifier)) + { + auto it = std::find_if(variables.begin(), variables.end(), + [&identifier](auto& var) { + return identifier == var.name; + }); + + it->type = ValueType::NUMBER; + it->val_number = 0; + it->val_string = str; + } + else + variables.emplace_back(identifier, ValueType::STRING, str, 0); +} + +void Runtime::execute_instruction(const Instruction& instruction) +{ + instruction.keyword_ptr->func(*this, instruction.tokens); +} + +void Runtime::jump_instructions(int new_instr_counter) +{ + if(new_instr_counter > instructions.size() || new_instr_counter < 1) + throw JumpOutOfBoundsExcept(instruction_counter + 1, new_instr_counter); + else + instruction_counter = new_instr_counter - 2; // -1 to account for increment after each instr +} + +int Runtime::run() +{ + // reset everything + instruction_counter = 0; + variables.clear(); + + for(;;) + { + if(instruction_counter >= instructions.size()) + { + std::cout << "ERROR: Unexpected end of instructions!" << std::endl; + std::exit(EXIT_FAILURE); + } + try // Execute our instruction + { + execute_instruction(instructions[instruction_counter]); + } + catch(TypeErrorExcept& e) + { + std::cout << "ERROR: In line " << instruction_counter + 1 << ": " + << "Type mismatch. Argument '" << e.token_str + << "' has type " << resolve_ValueType_str(e.got) + << " but " << resolve_ValueType_str(e.expected) + << " was expected." << std::endl; + std::exit(EXIT_FAILURE); + } + catch(UndeclaredVariableExcept& e) + { + std::cout << "ERROR: In line " << instruction_counter + 1 << ": " + << "Undefined variable '" << e.identifier_str + << "'" << std::endl; + std::exit(EXIT_FAILURE); + } + catch(JumpOutOfBoundsExcept& e) + { + std::cout << "ERROR: In line " << e.from << ": " + << "Jump target '" << e.to << "' is out of bounds." + << std::endl; + std::exit(EXIT_FAILURE); + } + + // Increment instruction counter + ++instruction_counter; + } + + return 0; +} diff --git a/src/runtime.hpp b/src/runtime.hpp new file mode 100644 index 0000000..b9a385e --- /dev/null +++ b/src/runtime.hpp @@ -0,0 +1,45 @@ +#ifndef BISCUIT_RUNTIME_HPP +#define BISCUIT_RUNTIME_HPP + +#include +#include "common.hpp" +#include "parse.hpp" + +struct Variable +{ + std::string name; + ValueType type; + + std::string val_string; + float val_number; + + Variable(const std::string& _name, ValueType _type, const std::string& _string, float _number) + : name(_name), type(_type), val_string(_string), val_number(_number) {} +}; + +struct Runtime +{ + int instruction_counter; + std::vector instructions; + std::vector variables; + + inline Runtime(const std::vector& _instructions) + : instruction_counter(1), instructions{_instructions} {} + + const std::vector::iterator var_find(const std::string& identifier); + bool var_exists(const std::string& identifier); + ValueType resolve_var_type(const std::string& identifier); + const std::string resolve_var_str(const std::string& identifier); + float resolve_var_num(const std::string& identifier); + + void assign_var_num(const std::string& identifier, float num); + void assign_var_str(const std::string& identifier, const std::string& str); + + void jump_instructions(int new_instr_counter); + + void execute_instruction(const Instruction& instruction); + + int run(); +}; + +#endif diff --git a/src/util.cpp b/src/util.cpp new file mode 100644 index 0000000..24d4106 --- /dev/null +++ b/src/util.cpp @@ -0,0 +1,41 @@ +#include "common.hpp" +#include + +const std::string resolve_TokenType_str(enum TokenType ttype) +{ + switch(ttype) + { + case TokenType::KEYWORD: + return "Keyword"; + break; + case TokenType::IDENTIFIER: + return "Identifier"; + break; + case TokenType::LITERAL: + return "Literal"; + break; + case TokenType::ID_OR_LIT: + return "Identifer or Literal"; + break; + } + + return "UNKNOW TOKENTYPE"; +} + +const std::string resolve_ValueType_str(enum ValueType vtype) +{ + switch(vtype) + { + case ValueType::STRING: + return "String"; + break; + case ValueType::NUMBER: + return "Number"; + break; + case ValueType::ANY: + return "Any"; + break; + } + + return "UNKNOWN VALUETYPE"; +}