diff options
author | Paul Gilbert | 2019-10-27 21:17:49 -0700 |
---|---|---|
committer | Paul Gilbert | 2019-11-11 18:20:29 -0800 |
commit | 7ee61fa5d4a6dfd6880e48b83a8dae1d485d1e94 (patch) | |
tree | 0272e5cc42c21fe9e8ed59291594bcfad5e343b3 | |
parent | 0b3f59c3f87debc8fc25cb5e540be87904207cbb (diff) | |
download | scummvm-rg350-7ee61fa5d4a6dfd6880e48b83a8dae1d485d1e94.tar.gz scummvm-rg350-7ee61fa5d4a6dfd6880e48b83a8dae1d485d1e94.tar.bz2 scummvm-rg350-7ee61fa5d4a6dfd6880e48b83a8dae1d485d1e94.zip |
GLK: ARCHETYPE: Added converted files
42 files changed, 6619 insertions, 4 deletions
diff --git a/engines/glk/archetype/archetype.cpp b/engines/glk/archetype/archetype.cpp index d2409bfce3..4b19c57854 100644 --- a/engines/glk/archetype/archetype.cpp +++ b/engines/glk/archetype/archetype.cpp @@ -20,8 +20,16 @@ * */ -#include "glk/archetype/archetype.h" #include "common/config-manager.h" +#include "glk/archetype/archetype.h" +#include "glk/archetype/crypt.h" +#include "glk/archetype/expression.h" +#include "glk/archetype/heap_sort.h" +#include "glk/archetype/misc.h" +#include "glk/archetype/saveload.h" +#include "glk/archetype/sys_object.h" +#include "glk/archetype/timestamp.h" +#include "glk/archetype/wrap.h" namespace Glk { namespace Archetype { @@ -29,15 +37,44 @@ namespace Archetype { Archetype *g_vm; Archetype::Archetype(OSystem *syst, const GlkGameDescription &gameDesc) : GlkAPI(syst, gameDesc), - _saveSlot(-1) { + _saveSlot(-1) { g_vm = this; } void Archetype::runGame() { - + initialize(); + interpret(); + deinitialize(); } bool Archetype::initialize() { + crypt_init(); + expression_init(); + heap_sort_init(); + misc_init(); + saveload_init(); + sys_object_init(); + timestamp_init(); + + // keywords + new_xarray(Literals); + new_xarray(Vocabulary); + + new_xarray(Type_ID_List); + new_xarray(Object_ID_List); + new_xarray(Attribute_ID_List); + + // parser + Abbreviate = 0x7fffffff; + new_list(Proximate); + new_list(object_names); + new_list(verb_names); + + // semantic + new_xarray(Type_List); + new_xarray(Object_List); + NullStr = NewConstStr("null"); + return true; } @@ -54,5 +91,838 @@ Common::Error Archetype::writeGameData(Common::WriteStream *ws) { return Common::kWritingFailed; } +void Archetype::interpret() { + Translating = false; + bool success = loadGame(); + _gameFile.close(); + + if (!success) + error("Could not load game"); + + ContextType context; + ResultType result; + undefine(result); + + if (!send_message(OP_SEND, find_message("START"), MainObject, result, context)) + error("Cannot execute; no ''START'' message for main object."); + + cleanup(result); +} + +bool Archetype::loadGame() { + return false; +} + +void Archetype::write(const String &fmt, ...) { + // TODO +} + +void Archetype::writeln(const String &fmt, ...) { + // TODO +} + +void Archetype::readln(String &s) { + // TODO +} + +char Archetype::ReadKey() { + // TODO + return '\0'; +} + +void Archetype::lookup(int the_obj, int the_attr, ResultType &result, ContextType &context, + DesiredType desired) { + NodePtr np; + bool done, first_pass; + ListType attrs; + int parent; + void *p, *original; + ExprTree e; + ContextType c; + + cleanup(result); + + if (desired == NAME) { + result._kind = IDENT; + result._ident.ident_kind = ATTRIBUTE_ID; + result._ident.ident_int = the_attr; + return; + } + + if (the_obj == 0) + // system object - all attributes UNDEFINED + return; + + if (!index_xarray(Object_List, the_obj, original)) { + g_vm->writeln("Internal error: cannot reference object %d", the_obj); + return; + } + + // Return UNDEFINED for attempting to reference any attribute of a destroyed object + if (original == nullptr) + return; + + // It is important to change the context before a lookup so that any non-scalar expressions + // that are referenced will be evaluated in the light of that object's context + c = context; + c.self = the_obj; // references to self must be right + c.each = 0; + + first_pass = true; + p = original; + done = false; + + // Inheritance loop + do { + ObjectType &obj = *((ObjectPtr)p); + attrs = obj.attributes; + parent = obj.inherited_from; + + np = find_item(attrs, the_attr); + if (np != nullptr || parent == 0) { + done = true; + } else { + // Track back + if (!index_xarray(Type_List, parent, p)) { + writeln("Internal error: lookup cannot find parent type %d", parent); + return; + } + + first_pass = false; + } + } while (!done); + + if (np == nullptr) + // not found anywhere + return; + + switch (desired) { + case RVALUE: + eval_expr((ExprPtr)np->data, result, c, RVALUE); + break; + + // Getting an inherited LVALUE is tricky. We must remember that since we have come + // this far, we definitely will return an ATTR_PTR result + case LVALUE: + if (first_pass) { + result._kind = ATTR_PTR; + result._attr.acl_attr = np; + } else { + // inherited - must create new node } + result._kind = ATTR_PTR; + result._attr.acl_attr = (NodePtr)malloc(sizeof(NodeType)); + + e = (ExprTree)malloc(sizeof(ExprNode)); + undefine(*e); + eval_expr((ExprPtr)np->data, *e, c, RVALUE); + + result._attr.acl_attr->data = e; + result._attr.acl_attr->key = the_attr; + insert_item(((ObjectPtr)original)->attributes, result._attr.acl_attr); + } + break; + + default: + break; + } +} + +bool Archetype::send_message(int transport, int message_sent, int recipient, + ResultType &result, ContextType &context) { + bool done, find_other; + ObjectPtr op, original; + ResultType r; + NodePtr np; + StatementPtr st; + void *p; + ContextType c; + + if (message_sent == 0) { + cleanup(result); + return false; + } + + if ((Debug & DEBUG_MSGS) > 0) { + r._kind = IDENT; + r._ident.ident_kind = OBJECT_ID; + r._ident.ident_int = context.self; + wrapout(" : ", false); + display_result(r); + + if (transport == OP_SEND) + wrapout(" sending ", false); + else + wrapout(" passing ", false); + + if (index_xarray(Vocabulary, message_sent, p)) { + String str = String::format("'%s'", ((StringPtr)p)->c_str()); + wrapout(str, false); + } + + if (transport == OP_SEND_TO_TYPE) + r._ident.ident_kind = TYPE_ID; + + wrapout(" to ", false); + r._ident.ident_int = recipient; + display_result(r); + wrapout("", true); + } + + // Trying to send a message to a destroyed object results in UNDEFINED + + if ((((transport == OP_SEND_TO_TYPE) && index_xarray(Type_List, recipient, p)) + || index_xarray(Object_List, recipient, p)) + && (p != nullptr)) { + c = context; + c.each = 0; + c.message = message_sent; + if (transport == OP_SEND) { + c.sender = context.self; + c.self = recipient; + } + + op = (ObjectPtr)p; + original = op; + done = false; + find_other = false; + while (!done) { + if (find_other) { + st = op->other; + } else { + np = find_item(op->methods, message_sent); + if (np != nullptr) + st = (StatementPtr)np->data; + else + st = nullptr; + } + + if (st != nullptr) { + // found it + exec_stmt(st, result, c); + return true; + } else { + // no message for recipient + if (op->inherited_from == 0) { + if (find_other) { + done = true; + } else { + op = original; + find_other = true; + } + } + else if (index_xarray(Type_List, op->inherited_from, p)) { + op = (ObjectPtr)p; + } else { + wraperr("Internal error: invalid inheritance"); + return false; + } + } + } + } + + // If we get here, it means that there was not even a "default" handler for + // the message in the given object or its lineage. Return ABSENT + result._kind = RESERVED; + result._reserved.keyword = RW_ABSENT; + + return false; +} + +void Archetype::eval_expr(ExprTree the_expr, ResultType &result, ContextType &context, DesiredType desired) { + ResultType r1, r2; + int i; + ExprTree e; + bool b; + + // It is very important to make sure that the "kind" fields of our temporary result variables + // are properly set to RESERVED/UNDEFINED before doing anything with them, so that if someone + // tries to clean them up later on, they won"t try to dispose of a string that isn't there + undefine(r1); + undefine(r2); + + cleanup(result); + + if (the_expr == nullptr) + return; + + // Check: if this is a lone attribute, look it up in this object"s table + if (the_expr->_kind == IDENT && the_expr->_ident.ident_kind == ATTRIBUTE_ID) + lookup(context.self, the_expr->_ident.ident_int, result, context, desired); + + else if (the_expr->_kind == RESERVED) { + // it is a special reserved word that requires an action + switch (the_expr->_reserved.keyword) { + case RW_READ: + case RW_KEY: + result._kind = STR_PTR; + if (the_expr->_reserved.keyword == RW_READ) + result._str.acl_str = ReadLine(true); // read full line + else + result._str.acl_str = ReadLine(false); // read single key + + Rows = 0; + cursor_reset(); // user will have had to hit <RETURN> + break; + + case RW_MESSAGE: + result._kind = MESSAGE; + result._msgTextQuote.index = context.message; + break; + + case RW_EACH: + case RW_SELF: + case RW_SENDER: + result._kind = IDENT; + result._ident.ident_kind = OBJECT_ID; + + switch (the_expr->_reserved.keyword) { + case RW_EACH: + result._ident.ident_int = context.each; + break; + case RW_SELF: + result._ident.ident_int = context.self; + break; + case RW_SENDER: + result._ident.ident_int = context.sender; + break; + default: + break; + } + break; + + default: + break; + } + } else if (the_expr->_kind == OPER) { + // It's an operator, need to evaulate it + switch (the_expr->_oper.op_name) { + case OP_SEND: + case OP_PASS: + eval_expr(the_expr->_oper.left, r1, context, RVALUE); + eval_expr(the_expr->_oper.right, r2, context, RVALUE); + + if (r2._kind == IDENT && (r2._ident.ident_kind == OBJECT_ID || r2._ident.ident_kind == TYPE_ID)) { + // Object 0 is the system object and always receives string messages + + if (r2._ident.ident_kind == OBJECT_ID && r2._ident.ident_int == 0) { + if (convert_to(STR_PTR, r1)) + send_to_system(the_expr->_oper.op_name, *r1._str.acl_str, result, context); + + } else if (convert_to(MESSAGE, r1)) { + if (r2._ident.ident_kind == TYPE_ID) + b = send_message(OP_SEND_TO_TYPE, r1._msgTextQuote.index, r2._ident.ident_int, + result, context); + else + b = send_message(the_expr->_oper.op_name, r1._msgTextQuote.index, + r2._ident.ident_int, result, context); + } + } + break; + + case OP_DOT: + eval_expr(the_expr->_oper.left, r1, context, RVALUE); + + if (r1._kind == IDENT && r1._ident.ident_kind == OBJECT_ID) { + eval_expr(the_expr->_oper.right, r2, context, NAME); + if (r2._kind == IDENT && r2._ident.ident_kind == ATTRIBUTE_ID) + lookup(r1._ident.ident_int, r2._ident.ident_int, result, context, desired); + } + break; + + case OP_ASSIGN: + if (desired == NAME) + return; + + eval_expr(the_expr->_oper.right, result, context, RVALUE); + eval_expr(the_expr->_oper.left, r1, context, LVALUE); + + if (!assignment(r1, result)) + cleanup(result); + else if (desired == LVALUE) { + cleanup(result); + result._kind = ATTR_PTR; + result._attr.acl_attr = r1._attr.acl_attr; + } + break; + + case OP_C_MULTIPLY: + case OP_C_DIVIDE: + case OP_C_PLUS: + case OP_C_MINUS: + case OP_C_CONCAT: + if (desired == NAME) + return; + + // Do the two operations using a dummy expression node + e = (ExprTree)malloc(sizeof(ExprNode)); + *e = *the_expr; + + switch (the_expr->_oper.op_name) { + case OP_C_MULTIPLY: + e->_oper.op_name = OP_MULTIPLY; + break; + case OP_C_DIVIDE: + e->_oper.op_name = OP_DIVIDE; + break; + case OP_C_PLUS: + e->_oper.op_name = OP_PLUS; + break; + case OP_C_MINUS: + e->_oper.op_name = OP_MINUS; + break; + case OP_C_CONCAT: + e->_oper.op_name = OP_CONCAT; + break; + default: + break; + } + + eval_expr(e, r1, context, RVALUE); + e->_oper.op_name = OP_ASSIGN; + e->_oper.right = &r1; + + eval_expr(e, result, context, desired); + free(e); + break; + + case OP_CHS: + case OP_NUMERIC: + eval_expr(the_expr->_oper.right, result, context, RVALUE); + if (!convert_to(NUMERIC, result)) + cleanup(result); + else if (the_expr->_oper.op_name == OP_CHS) + result._numeric.acl_int = -result._numeric.acl_int; + break; + + case OP_STRING: + eval_expr(the_expr->_oper.right, result, context, RVALUE); + if (!convert_to(STR_PTR, result)) + cleanup(result); + break; + + case OP_LENGTH: + eval_expr(the_expr->_oper.right, r1, context, RVALUE); + if (convert_to(STR_PTR, r1)) { + result._kind = NUMERIC; + result._numeric.acl_int = r1._str.acl_str->size(); + } + break; + + // For the random operator, we must be careful: ? "01234" should select a random digit + // out of that set, not attempt to convert it to 1234 and take a random number in the + // range 1 - 1234. However, we can neither immediately convert it to string, because + // ? 6 should produce a value in the range 1 - 6, not the character "6". } + case OP_RANDOM: + eval_expr(the_expr->_oper.right, result, context, RVALUE); + if (result._kind == NUMERIC) + // convert x < range to 1 <= x <= range + result._numeric.acl_int = g_vm->getRandomNumber(result._numeric.acl_int - 1) + 1; + else if (convert_to(STR_PTR, result)) { + // Replace the string with a single random character for it + String &s = *result._str.acl_str; + s = s[g_vm->getRandomNumber(s.size() - 1)]; + } + break; + + case OP_NOT: + result._kind = RESERVED; + if (eval_condition(the_expr->_oper.right, context)) + result._reserved.keyword = RW_FALSE; + else + result._reserved.keyword = RW_TRUE; + break; + + case OP_PLUS: + case OP_MINUS: + case OP_MULTIPLY: + case OP_DIVIDE: + eval_expr(the_expr->_oper.left, r1, context, RVALUE); + eval_expr(the_expr->_oper.right, r2, context, RVALUE); + if (convert_to(NUMERIC, r1) && convert_to(NUMERIC, r2)) { + result._kind = NUMERIC; + switch (the_expr->_oper.op_name) { + case OP_PLUS: + result._numeric.acl_int = r1._numeric.acl_int + r2._numeric.acl_int; + break; + case OP_MINUS: + result._numeric.acl_int = r1._numeric.acl_int - r2._numeric.acl_int; + break; + case OP_MULTIPLY: + result._numeric.acl_int = r1._numeric.acl_int * r2._numeric.acl_int; + break; + case OP_DIVIDE: + result._numeric.acl_int = r1._numeric.acl_int / r2._numeric.acl_int; + break; + default: + break; + } + } + break; + + case OP_AND: + result._kind = RESERVED; + if (eval_condition(the_expr->_oper.left, context) && eval_condition(the_expr->_oper.right, context)) + result._reserved.keyword = RW_TRUE; + else + result._reserved.keyword = RW_FALSE; + break; + + case OP_OR: + if (eval_condition(the_expr->_oper.left, context) || eval_condition(the_expr->_oper.right, context)) + result._reserved.keyword = RW_TRUE; + else + result._reserved.keyword = RW_FALSE; + break; + + case OP_POWER: + eval_expr(the_expr->_oper.right, r2, context, RVALUE); + eval_expr(the_expr->_oper.left, r1, context, RVALUE); + if (convert_to(NUMERIC, r2) && convert_to(NUMERIC, r1)) { + result._kind = NUMERIC; + result._numeric.acl_int = 1; + for (i = 1; i <= r2._numeric.acl_int; ++i) + result._numeric.acl_int *= r1._numeric.acl_int; + } + break; + + case OP_CONCAT: + eval_expr(the_expr->_oper.left, r1, context, RVALUE); + eval_expr(the_expr->_oper.right, r2, context, RVALUE); + if (convert_to(STR_PTR, r1) && convert_to(STR_PTR, r2)) { + result._kind = STR_PTR; + result._str.acl_str = MakeNewDynStr(*r1._str.acl_str + *r2._str.acl_str); + } + break; + + case OP_LEFTFROM: + case OP_RIGHTFROM: + eval_expr(the_expr->_oper.left, r1, context, RVALUE); + eval_expr(the_expr->_oper.right, r2, context, RVALUE); + if (convert_to(STR_PTR, r1) && convert_to(NUMERIC, r2)) { + result._kind = STR_PTR; + if (the_expr->_oper.op_name == OP_LEFTFROM) + result._str.acl_str = MakeNewDynStr(r1._str.acl_str->left(r2._numeric.acl_int)); + else + result._str.acl_str = MakeNewDynStr(r1._str.acl_str->right(r2._numeric.acl_int)); + } + break; + + case OP_WITHIN: + eval_expr(the_expr->_oper.left, r1, context, RVALUE); + eval_expr(the_expr->_oper.right, r2, context, RVALUE); + if (convert_to(STR_PTR, r1) && convert_to(STR_PTR, r2)) { + result._kind = NUMERIC; + result._numeric.acl_int = r2._str.acl_str->indexOf(*r1._str.acl_str); + if (result._numeric.acl_int == -1) + cleanup(result); + } + break; + + case OP_EQ: + case OP_NE: + case OP_LT: + case OP_GT: + case OP_LE: + case OP_GE: + eval_expr(the_expr->_oper.left, r1, context, RVALUE); + eval_expr(the_expr->_oper.right, r2, context, RVALUE); + + result._kind = RESERVED; + if (result_compare(the_expr->_oper.op_name, r1, r2)) + result._reserved.keyword = RW_TRUE; + else + result._reserved.keyword = RW_FALSE; + break; + + default: + g_vm->writeln("Internal error: \"%s\" not yet supported", Operators[the_expr->_oper.op_name]); + break; + } + + cleanup(r1); + cleanup(r2); + + if ((Debug & DEBUG_EXPR) > 0) { + wrapout(" -- ", false); + display_expr(the_expr); + wrapout(" ==> ", false); + display_result(result); + wrapout("", true); + } + } else { + switch (desired) { + case RVALUE: + copy_result(result, *the_expr); + break; + case LVALUE: + result = *the_expr; + break; + default: + break; + } + } +} + +bool Archetype::eval_condition(ExprTree the_expr, ContextType &context) { + ResultType result; + bool failure; + + undefine(result); + eval_expr(the_expr, result, context, RVALUE); + + failure = (result._kind == RESERVED) && (result._reserved.keyword = RW_UNDEFINED + || result._reserved.keyword == RW_FALSE || result._reserved.keyword == RW_ABSENT); + + cleanup(result); + return !failure; +} + +void Archetype::exec_stmt(StatementPtr the_stmt, ResultType &result, ContextType &context) { + NodePtr np; + void *p, *q; + ResultType r1, r2; + CasePairPtr this_case; + bool b; + ContextType c; + int i; + ObjectPtr the_object; + bool verbose; + + undefine(r1); + undefine(r2); + cleanup(result); + + verbose = (Debug & DEBUG_STMT) > 0; + + if (verbose) + wrapout(" == ", false); + + switch (the_stmt->_kind) { + case COMPOUND: + np = nullptr; + b = false; + while (!b && iterate_list(the_stmt->_compound.statements, np)) { + cleanup(result); + exec_stmt((StatementPtr)np->data, result, context); + + b = (result._kind == RESERVED) && (result._reserved.keyword == RW_BREAK); + } + break; + + case ST_EXPR: + if (verbose) + display_expr(the_stmt->_expr.expression); + + switch (the_stmt->_expr.expression->_kind) { + case QUOTE_LIT: + if (index_xarray(Literals, the_stmt->_expr.expression->_msgTextQuote.index, p)) { + result._kind = TEXT_LIT; + result._msgTextQuote.index = the_stmt->_expr.expression->_msgTextQuote.index; + wrapout(*((StringPtr)p), true); + } + break; + + case MESSAGE: + b = send_message(OP_PASS, the_stmt->_expr.expression->_msgTextQuote.index, + context.self, result, context); + default: + eval_expr(the_stmt->_expr.expression, result, context, RVALUE); + break; + } + break; + + case ST_WRITE: + case ST_WRITES: + case ST_STOP: + if (verbose) { + switch (the_stmt->_kind) { + case ST_WRITE: + wrapout("write ", false); + break; + case ST_WRITES: + wrapout("writes ", false); + break; + case ST_STOP: + wrapout("stop ", false); + break; + default: + break; + } + + wrapout(" ", false); + np = nullptr; + while (iterate_list(the_stmt->_write.print_list, np)) { + display_expr((ExprTree)np->data); + if (np->next != the_stmt->_write.print_list) + wrapout(", ", false); + } + + wrapout("", true); + } + + np = nullptr; + while (iterate_list(the_stmt->_write.print_list, np)) { + cleanup(result); + eval_expr((ExprTree)np->data, result, context, RVALUE); + write_result(result); + } + + if (the_stmt->_kind == ST_WRITE) { + wrapout("", true); + } + else if (the_stmt->_kind == ST_STOP) { + g_vm->writeln(); + g_vm->writeln(); + error("%s", VERSION); + } + break; + + case ST_IF: + if (verbose) { + wrapout("if: Testing ", false); + display_expr(the_stmt->_if.condition); + } + if (eval_condition(the_stmt->_if.condition, context)) { + if (verbose) + wrapout(" Evaluated true; executing then branch", true); + exec_stmt(the_stmt->_if.then_branch, result, context); + + } + else if (the_stmt->_if.else_branch != nullptr) { + if (verbose) + wrapout(" Evaluated false; executing else branch", true); + exec_stmt(the_stmt->_if.else_branch, result, context); + } + break; + + case ST_CASE: + if (verbose) { + wrapout("case ", false); + display_expr(the_stmt->_case.test_expr); + wrapout(" of", false); + wrapout("", true); + } + + eval_expr(the_stmt->_case.test_expr, r1, context, RVALUE); + np = nullptr; + + while (iterate_list(the_stmt->_case.cases, np)) { + this_case = (CasePairPtr)np->data; + + //with this_case^ do begin + eval_expr(this_case->value, r2, context, RVALUE); + if ((r2._kind == RESERVED && r2._reserved.keyword == RW_DEFAULT) + || result_compare(OP_EQ, r1, r2)) { + exec_stmt(this_case->action, result, context); + cleanup(r1); + cleanup(r2); + return; + } + + cleanup(r2); + } + + cleanup(result); + cleanup(r1); + break; + + case ST_BREAK: + result._kind = RESERVED; + result._reserved.keyword = RW_BREAK; + break; + + case ST_FOR: + b = false; + c = context; + c.each = 1; + + while (!b && c.each < (int)Object_List.size()) { + if (eval_condition(the_stmt->_loop.selection, c)) { + exec_stmt(the_stmt->_loop.action, result, c); + b = (result._kind == RESERVED) && (result._reserved.keyword == RW_BREAK); + cleanup(result); + } + + ++c.each; + } + break; + + case ST_WHILE: + b = false; + while (!b && eval_condition(the_stmt->_loop.selection, context)) { + exec_stmt(the_stmt->_loop.action, result, context); + b = (result._kind == RESERVED) && (result._reserved.keyword == RW_BREAK); + cleanup(result); + } + break; + + case ST_CREATE: + eval_expr(the_stmt->_create.new_name, r1, context, LVALUE); + + // Attempt a dummy assignment just to see if it works + result._kind = IDENT; + result._ident.ident_kind = OBJECT_ID; + result._ident.ident_int = 0; + + if (!assignment(r1, result)) { + cleanup(result); + } + else { + // do it for real + the_object = (ObjectPtr)malloc(sizeof(ObjectType)); + the_object->inherited_from = the_stmt->_create.archetype; + new_list(the_object->attributes); + new_list(the_object->methods); + the_object->other = nullptr; + p = the_object; + + // NOTE: Search the list for an empty slot; if none found, append + i = Dynamic; + b = true; + while (access_xarray(Object_List, i, q, PEEK_ACCESS) && q != nullptr) + ++i; + + if (i >= (int)Object_List.size()) + append_to_xarray(Object_List, p); + else + b = access_xarray(Object_List, i, p, POKE_ACCESS); + + // Now we know its number; go back and update the result"s object reference. + // "Return" this same value + ((ExprPtr)r1._attr.acl_attr->data)->_ident.ident_int = i; + copy_result(result, *(ExprPtr)r1._attr.acl_attr->data); + + cleanup(r1); + } + break; + + // Just dispose of the indicated object in the Object_List. Shrink the list only if + // the very last object was destroyed + case ST_DESTROY: + eval_expr(the_stmt->_destroy.victim, result, context, RVALUE); + if (result._kind == IDENT && result._ident.ident_kind == OBJECT_ID + && index_xarray(Object_List, result._ident.ident_int, p)) { + the_object = (ObjectPtr)p; + dispose_object(the_object); + p = nullptr; + b = access_xarray(Object_List, result._ident.ident_int, p, POKE_ACCESS); + if (result._ident.ident_int == ((int)Object_List.size() - 1)) + shrink_xarray(Object_List); + } else { + wraperr("Can only destroy previously created objects"); + } + + cleanup(result); + break; + + default: + wraperr("Internal error: statement not supported yet"); + break; + } + + if (verbose) + wrapout("", true); // finish off dangling lines +} + } // End of namespace Archetype } // End of namespace Glk diff --git a/engines/glk/archetype/archetype.h b/engines/glk/archetype/archetype.h index 709ebc643b..dec950ca74 100644 --- a/engines/glk/archetype/archetype.h +++ b/engines/glk/archetype/archetype.h @@ -24,6 +24,11 @@ #define ARCHETYPE_ARCHETYPE #include "glk/glk_api.h" +#include "glk/archetype/array.h" +#include "glk/archetype/interpreter.h" +#include "glk/archetype/semantic.h" +#include "glk/archetype/statement.h" +#include "glk/archetype/string.h" namespace Glk { namespace Archetype { @@ -34,6 +39,23 @@ namespace Archetype { class Archetype : public GlkAPI { private: int _saveSlot; + bool Translating; +public: + // keywords.cpp + XArrayType Literals, Vocabulary; + XArrayType Type_ID_List, Object_ID_List, Attribute_ID_List; + + // parser.cpp + String Command; + int Abbreviate; + ListType Proximate; + ListType verb_names; + ListType object_names; + + // semantic.cpp + XArrayType Type_List, Object_List; + ListType Overlooked; + StringPtr NullStr; private: /** * Engine initialization @@ -44,6 +66,64 @@ private: * Engine cleanup */ void deinitialize(); + + /** + * Main interpreter method + */ + void interpret(); + + /** + * Loads the text adventure game + */ + bool loadGame(); + + /** + * Given an object number, attribute number, anddesired_type, returns the value of the lookup + * in the given result.If the desired_type is LVALUE, then it creates a new attribute node + * in the object's own attribute list(if not already existing) and returns a pointer to it. + * If RVALUE, it evaluates any expression it may find, returning the result of the evaluation. + * + * Also performs inheritance, looking back through the object's family tree to find the attribute. + */ + void lookup(int the_obj, int the_attr, ResultType &result, ContextType &context, DesiredType desired); + + /** + * Sends the given message number to the object of the given number. This procedure performs + * inheritance; that is, it will search back through the object's ancestry in order to find + * someone to perform the message.Has to do something tricky with the default message: + * it must first search the entire ancestry for an explicit message, then search again for + * a default, if none found. + * @param transport how to send the message : sending to an object, + * passing to an object, or sending(passing) to a type. + * @param message message to send + * @param recipient number of object to receive message + * @param result Output result of the sending + * @param context Context + * @returns true if the recipient handles the message; false if it doesn't + */ + bool send_message(int transport, int message_sent, int recipient, ResultType &result, + ContextType &context); + + /** + * Evaluates the given expression + */ + void eval_expr(ExprTree the_expr, ResultType &result, ContextType &context, DesiredType desired); + + /** + * Evaluates the given expression as though it were a condition. Will succeed if the given + * expression is not UNDEFINED and not false. + * @param the_expr Expression to evaluate + * @returns true if the condition can be considered true; false otherwise + */ + bool eval_condition(ExprTree the_expr, ContextType &context); + + /** + * Given a pointer to a statement, executes that statement. Very heavily called + * @param the_stmt pointer to statement to be executed + * @param result the "value" of the execution(for example, the last expression + * of a compound statement + */ + void exec_stmt(StatementPtr the_stmt, ResultType &result, ContextType &context); public: /** * Constructor @@ -78,6 +158,21 @@ public: bool loadingSavegame() const { return _saveSlot != -1; } + + /** + * Write some text to the screen + */ + void write(const String &fmt, ...); + + /** + * Write a line to the screen + */ + void writeln(const String &fmt, ...); + void writeln() { writeln(""); } + + void readln(String &s); + + char ReadKey(); }; extern Archetype *g_vm; diff --git a/engines/glk/archetype/array.cpp b/engines/glk/archetype/array.cpp new file mode 100644 index 0000000000..b816306fc3 --- /dev/null +++ b/engines/glk/archetype/array.cpp @@ -0,0 +1,66 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "glk/archetype/array.h" + +namespace Glk { +namespace Archetype { + +void new_xarray(XArrayType &the_xarray) { + the_xarray.clear(); +} + +void dispose_xarray(XArrayType &the_xarray) { + the_xarray.clear(); +} + +void append_to_xarray(XArrayType &the_xarray, void *element) { + the_xarray.push_back(element); +} + +bool access_xarray(XArrayType &the_xarray, int index, void *&result, AccessType direction) { + if (index < 0 || index >= (int)the_xarray.size()) + return false; + + switch (direction) { + case PEEK_ACCESS: + result = the_xarray[index]; + break; + case POKE_ACCESS: + the_xarray[index] = result; + break; + } + + return true; +} + +bool index_xarray(XArrayType &the_xarray, int index, void *&result) { + return access_xarray(the_xarray, index, result, PEEK_ACCESS); +} + +void shrink_xarray(XArrayType &the_xarray) { + if (!the_xarray.empty()) + the_xarray.resize(the_xarray.size() - 1); +} + +} // End of namespace Archetype +} // End of namespace Glk diff --git a/engines/glk/archetype/array.h b/engines/glk/archetype/array.h new file mode 100644 index 0000000000..17379f1057 --- /dev/null +++ b/engines/glk/archetype/array.h @@ -0,0 +1,69 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef ARCHETYPE_XARRAY +#define ARCHETYPE_XARRAY + +#include "common/array.h" + +namespace Glk { +namespace Archetype { + +typedef Common::Array<void *> XArrayType; + +enum AccessType { POKE_ACCESS, PEEK_ACCESS }; + +/** + * Initialize an extendible array + */ +extern void new_xarray(XArrayType &the_xarray); + +/** + * Disposes the array + */ +extern void dispose_xarray(XArrayType &the_xarray); + +/** + * Appends a new element to the end of the array + */ +extern void append_to_xarray(XArrayType &the_xarray, void *element); + +/** + * Access a given index in the array to either set or retrieve an entry + * @param the_xarray The array to access + */ +extern bool access_xarray(XArrayType &the_xarray, int index, void *&result, AccessType direction); + +/** + * Access a given index in the array + */ +extern bool index_xarray(XArrayType &the_xarray, int index, void *&result); + +/** + * Removes the last element of the array + */ +extern void shrink_xarray(XArrayType &the_xarray); + +} // End of namespace Archetype +} // End of namespace Glk + +#endif diff --git a/engines/glk/archetype/crypt.cpp b/engines/glk/archetype/crypt.cpp new file mode 100644 index 0000000000..d99dab0907 --- /dev/null +++ b/engines/glk/archetype/crypt.cpp @@ -0,0 +1,82 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "glk/archetype/crypt.h" +#include "glk/archetype/archetype.h" + +namespace Glk { +namespace Archetype { + +byte CryptMask; +EncryptionType Encryption; + +void crypt_init() { + Encryption = NONE; + CryptMask = 0x55; +} + +void cryptinit(EncryptionType crypt_kind, uint seed) { + CryptMask = seed & 0xff; + Encryption = crypt_kind; + + if (Encryption == COMPLEX) + g_vm->setRandomNumberSeed(seed); +} + +void cryptstr(Common::String &s) { + byte nextMask; + + switch (Encryption) { + case SIMPLE: + for (uint i = 0; i < s.size(); ++i) + s.setChar(s[i] ^ CryptMask, i); + break; + + case PURPLE: + for (uint i = 0; i < s.size(); ++i) { + s.setChar(s[i] ^ CryptMask, i); + CryptMask += s[i] & 7; + } + break; + + case UNPURPLE: + for (uint i = 0; i < s.size(); ++i) { + nextMask = CryptMask + (s[i] & 7); + s.setChar(s[i] ^ CryptMask, i); + CryptMask = nextMask; + } + break; + + case COMPLEX: + for (uint i = 0; i < s.size(); ++i) { + s.setChar(s[i] ^ CryptMask, i); + CryptMask = g_vm->getRandomNumber(0x100); + } + break; + + default: + break; + } +} + +} // End of namespace Archetype +} // End of namespace Glk diff --git a/engines/glk/archetype/crypt.h b/engines/glk/archetype/crypt.h new file mode 100644 index 0000000000..d978210e9a --- /dev/null +++ b/engines/glk/archetype/crypt.h @@ -0,0 +1,64 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef ARCHETYPE_CRYPT +#define ARCHETYPE_CRYPT + +#include "common/scummsys.h" +#include "common/str.h" + +namespace Glk { +namespace Archetype { + +enum EncryptionType { + NONE, SIMPLE, PURPLE, UNPURPLE, COMPLEX, DEBUGGING_ON +}; + +extern byte CryptMask; +extern EncryptionType Encryption; + +/** + * Initializes fields local to the file + */ +extern void crypt_init(); + +extern void cryptinit(EncryptionType crypt_kind, uint seed); + +/** + * Encrypts or decrypts a string.Since all encryption methods are based on XOR, + * the same method both encrypts anddecrypts. + * If <method> is SIMPLE, the CryptMask is simply XORed with each byte in the string. + * If <method> is PURPLE, the CryptMask is changed each time after using it, + * by adding to it the lowest three bits of the result of the last encrypted + * byte.This way the mask changes frequently anddynamically in a way that + * is difficult to predict. + * If <method> is UNPURPLE, the same algorithm as PURPLE is used except that + * the next CryptMask must be determined before altering the byte under consideration. + * if <method> is COMPLEX, a pseudorandom sequence is used to alter the + * CryptMask.This can make prediction well-nigh impossible. + */ +extern void cryptstr(Common::String &s); + +} // End of namespace Archetype +} // End of namespace Glk + +#endif diff --git a/engines/glk/archetype/error.cpp b/engines/glk/archetype/error.cpp new file mode 100644 index 0000000000..18cb4d6acb --- /dev/null +++ b/engines/glk/archetype/error.cpp @@ -0,0 +1,80 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "glk/archetype/error.h" +#include "glk/archetype/archetype.h" +#include "glk/archetype/token.h" + +namespace Glk { +namespace Archetype { + +void hit_eof(progfile &f, AclType expecting, int specific) { + if (KeepLooking) { + KeepLooking = false; + g_vm->write("Found end of file; expected "); + write_token(expecting, specific); + g_vm->writeln(); + } +} + +void expected(progfile &f, AclType expect_ttype, int expect_specific) { + if (KeepLooking) { + f.sourcePos(); + g_vm->write("Expected "); + write_token(expect_ttype, expect_specific); + g_vm->write("; found "); + write_token(f.ttype, f.tnum); + g_vm->writeln(); + } +} + +void expect_general(progfile &f, const String &general_desc) { + if (KeepLooking) { + f.sourcePos(); + g_vm->write("Expected %s; found ", general_desc.c_str()); + write_token(f.ttype, f.tnum); + g_vm->writeln(); + } +} + +void error_message(progfile &f, const String &message) { + if (KeepLooking) { + f.sourcePos(); + g_vm->writeln(message); + } +} + +bool insist_on(progfile &f, AclType some_type, int some_number) { + if (!get_token(f)) { + hit_eof(f, some_type, some_number); + return false; + } else if (f.ttype != some_type && f.tnum != some_number) { + expected(f, some_type, some_number); + KeepLooking = false; + return false; + } else { + return true; + } +} + +} // End of namespace Archetype +} // End of namespace Glk diff --git a/engines/glk/archetype/error.h b/engines/glk/archetype/error.h new file mode 100644 index 0000000000..c6b70c0228 --- /dev/null +++ b/engines/glk/archetype/error.h @@ -0,0 +1,49 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef ARCHETYPE_ERROR +#define ARCHETYPE_ERROR + +/* Writes out all kinds of compile-time errors.Does not perform a halt; + * expects the program itself to "unravel" the process + */ + +#include "glk/archetype/misc.h" + +namespace Glk { +namespace Archetype { + +extern void hit_eof(progfile &f, AclType expecting, int specific); +extern void expected(progfile &f, AclType expect_ttype, int expect_specific); +extern void expect_general(progfile &f, const String &general_desc); +extern void error_message(progfile &f, const String &message); + +/** + * Used when a particular token is insisted upon by the syntax, usually + * for readability.It will be an error for the token not to exist. + */ +extern bool insist_on(progfile &f, AclType some_type, int some_number); + +} // End of namespace Archetype +} // End of namespace Glk + +#endif diff --git a/engines/glk/archetype/expression.cpp b/engines/glk/archetype/expression.cpp new file mode 100644 index 0000000000..6f71dd502d --- /dev/null +++ b/engines/glk/archetype/expression.cpp @@ -0,0 +1,129 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "glk/archetype/expression.h" + +namespace Glk { +namespace Archetype { + +bool Right_Assoc[NUM_OPERS + 1]; +bool Binary[NUM_OPERS + 1]; +int8 Precedence[NUM_OPERS + 1]; + +void expression_init() { + Binary[OP_LPAREN] = false; + Binary[OP_DOT] = true; + Binary[OP_CHS] = false; + Binary[OP_NUMERIC] = false; + Binary[OP_STRING] = false; + Binary[OP_RANDOM] = false; + Binary[OP_LENGTH] = false; + Binary[OP_POWER] = true; + Binary[OP_MULTIPLY] = true; + Binary[OP_DIVIDE] = true; + Binary[OP_PLUS] = true; + Binary[OP_MINUS] = true; + Binary[OP_CONCAT] = true; + Binary[OP_WITHIN] = true; + Binary[OP_LEFTFROM] = true; + Binary[OP_RIGHTFROM] = true; + Binary[OP_EQ] = true; + Binary[OP_NE] = true; + Binary[OP_GT] = true; + Binary[OP_LT] = true; + Binary[OP_GE] = true; + Binary[OP_LE] = true; + Binary[OP_NOT] = false; + Binary[OP_AND] = true; + Binary[OP_OR] = true; + Binary[OP_C_MULTIPLY] = true; + Binary[OP_C_DIVIDE] = true; + Binary[OP_C_PLUS] = true; + Binary[OP_C_MINUS] = true; + Binary[OP_C_CONCAT] = true; + Binary[OP_ASSIGN] = true; + Binary[OP_SEND] = true; + Binary[OP_PASS] = true; + + // Initialize the Right_Assoc table as follows : anything unary must be right-associative; + // all others are assumed left-associative.After the loop, right-associative binary operators + // are explicity set + for (int i = 0; i <= NUM_OPERS; ++i) + Right_Assoc[i] = !Binary[i]; + + Right_Assoc[OP_POWER] = true; + Right_Assoc[OP_C_MULTIPLY] = true; + Right_Assoc[OP_C_DIVIDE] = true; + Right_Assoc[OP_C_PLUS] = true; + Right_Assoc[OP_C_MINUS] = true; + Right_Assoc[OP_C_CONCAT] = true; + Right_Assoc[OP_ASSIGN] = true; + + + Precedence[OP_LPAREN] = 14; // must always be the higest + Precedence[OP_DOT] = 13; + + Precedence[OP_CHS] = 12; + Precedence[OP_NUMERIC] = 12; + Precedence[OP_STRING] = 12; + Precedence[OP_RANDOM] = 12; + Precedence[OP_LENGTH] = 12; + + Precedence[OP_POWER] = 11; + + Precedence[OP_MULTIPLY] = 10; + Precedence[OP_DIVIDE] = 10; + + Precedence[OP_PLUS] = 9; + Precedence[OP_MINUS] = 9; + Precedence[OP_CONCAT] = 9; + + Precedence[OP_WITHIN] = 8; + + Precedence[OP_LEFTFROM] = 7; + Precedence[OP_RIGHTFROM] = 7; + + Precedence[OP_SEND] = 6; + Precedence[OP_PASS] = 6; + + Precedence[OP_EQ] = 5; + Precedence[OP_NE] = 5; + Precedence[OP_GT] = 5; + Precedence[OP_LT] = 5; + Precedence[OP_GE] = 5; + Precedence[OP_LE] = 5; + + Precedence[OP_NOT] = 4; + Precedence[OP_AND] = 3; + Precedence[OP_OR] = 2; + + Precedence[OP_C_MULTIPLY] = 1; + Precedence[OP_C_DIVIDE] = 1; + Precedence[OP_C_PLUS] = 1; + Precedence[OP_C_MINUS] = 1; + Precedence[OP_C_CONCAT] = 1; + Precedence[OP_ASSIGN] = 1; +} + + +} // End of namespace Archetype +} // End of namespace Glk diff --git a/engines/glk/archetype/expression.h b/engines/glk/archetype/expression.h new file mode 100644 index 0000000000..3fc600787a --- /dev/null +++ b/engines/glk/archetype/expression.h @@ -0,0 +1,90 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef ARCHETYPE_EXPRESSION +#define ARCHETYPE_EXPRESSION + +#include "glk/archetype/keywords.h" +#include "glk/archetype/linked_list.h" +#include "glk/archetype/misc.h" + +namespace Glk { +namespace Archetype { + +const int OP_LPAREN = NUM_OPERS + 1; // book-keeping operator +const int OP_SEND_TO_TYPE = NUM_OPERS + 2; // for use with interpreter + +struct OperNode { + int8 op_name; + union ExprNode *left, *right; +}; + +struct MessageTextQuoteNode { + int index; +}; + +struct NumericNode { + int acl_int; +}; + +struct StrNode { + StringPtr acl_str; +}; + +struct AttrNode { + NodePtr acl_attr; +}; + +struct ReservedNode { + int8 keyword; +}; + +struct IdentNode { + ClassifyType ident_kind; + int ident_int; +}; + +union ExprNode { + AclType _kind; + OperNode _oper; + NumericNode _numeric; + MessageTextQuoteNode _msgTextQuote; + StrNode _str; + AttrNode _attr; + ReservedNode _reserved; + IdentNode _ident; +}; + +typedef ExprNode *ExprPtr; +typedef ExprPtr ExprTree; + +// Global variables +extern bool Right_Assoc[NUM_OPERS + 1]; +extern bool Binary[NUM_OPERS + 1]; +extern int8 Precedence[NUM_OPERS + 1]; + +extern void expression_init(); + +} // End of namespace Archetype +} // End of namespace Glk + +#endif diff --git a/engines/glk/archetype/game_stat.cpp b/engines/glk/archetype/game_stat.cpp new file mode 100644 index 0000000000..9f6a8b85d6 --- /dev/null +++ b/engines/glk/archetype/game_stat.cpp @@ -0,0 +1,121 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "glk/archetype/game_stat.h" +#include "glk/archetype/archetype.h" +#include "glk/archetype/crypt.h" +#include "glk/archetype/saveload.h" +#include "glk/archetype/statement.h" +#include "glk/archetype/timestamp.h" + +namespace Glk { +namespace Archetype { + +void save_game_state(Common::WriteStream *bfile, XArrayType &objects) { + int i; + void *p; + + // Write out the timestamp associated with the original game + bfile->writeUint32LE(GTimeStamp); + + // Get the encryption straight - reset the seed + cryptinit(Encryption, GTimeStamp); + + for (i = 0; i < Dynamic - 1; ++i) { + if (index_xarray(objects, i, p)) { + ObjectPtr op = (ObjectPtr)p; + bfile->writeUint32LE(vContSeq); + + dump_item_list(bfile, op->attributes, EXPR_LIST); + } + } + + for (i = Dynamic; i < (int)objects.size(); ++i) { + if (index_xarray(objects, i, p)) { + bfile->writeUint32LE(vContSeq); + dump_object(bfile, (ObjectPtr)p); + } + } + + bfile->writeUint32LE(vEndSeq); +} + +bool load_game_state(Common::ReadStream *bfile, XArrayType &objects) { + int i; + void *p; + ObjectPtr op; + TimestampType tstamp; + StatementKind sentinel; + + // Check the time stamp + tstamp = bfile->readUint32LE(); + if (tstamp != GTimeStamp) { + g_vm->writeln("State file does not match original .ACX file"); + return false; + } + + // Get the encryption straight - reset the seed.Be careful upon loading since we have + // to do UNPURPLE instead of PURPLE + if (Encryption == PURPLE) + Encryption = UNPURPLE; + cryptinit(Encryption, GTimeStamp); + + // Need to flush out the previous attributes andload in the new ones. Dynamically allocated + // objects are a little different since they might vary between game states + for (i = 0; i < Dynamic - 1; ++i) { + if (index_xarray(objects, i, p)) { + sentinel = (StatementKind)bfile->readUint32LE(); + op = (ObjectPtr)p; + dispose_item_list(op->attributes, EXPR_LIST); + load_item_list(bfile, op->attributes, EXPR_LIST); + } + } + + // Flush dynamic objects.Dispose of each object andshrink back the xarray + + for (i = objects.size() - 1; i >= Dynamic; --i) { + if (index_xarray(objects, i, p)) { + op = (ObjectPtr)p; + dispose_object(op); + } + + shrink_xarray(objects); + } + + // sentinel has been set from before + sentinel = (StatementKind)bfile->readUint32LE(); + while (sentinel == CONT_SEQ) { + load_object(bfile, op); + p = op; + append_to_xarray(objects, p); + + sentinel = (StatementKind)bfile->readUint32LE(); + } + + if (Encryption == UNPURPLE) + Encryption = PURPLE; + + return true; +} + +} // End of namespace Archetype +} // End of namespace Glk diff --git a/engines/glk/archetype/game_stat.h b/engines/glk/archetype/game_stat.h new file mode 100644 index 0000000000..ae4a1bb2ef --- /dev/null +++ b/engines/glk/archetype/game_stat.h @@ -0,0 +1,46 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef ARCHETYPE_GAME_STAT +#define ARCHETYPE_GAME_STAT + +/* Functions and procedures that help with the saving and loading of + * game states. Only the attribute records of a given object list are ever + * saved or loaded; statements and other object information, such as + * the Dynamic pointer, are really constant across a game; as long as + * we know that the game states belong to a particular game, we don't + * need to save any more. + */ + +#include "glk/archetype/array.h" +#include "common/stream.h" + +namespace Glk { +namespace Archetype { + +extern void save_game_state(Common::WriteStream *bfile, XArrayType &objects); +extern bool load_game_state(Common::ReadStream *bfile, XArrayType &objects); + +} // End of namespace Archetype +} // End of namespace Glk + +#endif diff --git a/engines/glk/archetype/heap_sort.cpp b/engines/glk/archetype/heap_sort.cpp new file mode 100644 index 0000000000..98753ccc70 --- /dev/null +++ b/engines/glk/archetype/heap_sort.cpp @@ -0,0 +1,148 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "glk/archetype/heap_sort.h" +#include "glk/archetype/archetype.h" +#include "glk/archetype/misc.h" + +namespace Glk { +namespace Archetype { + +const char *const CANT_PEEK = "Internal error: cannot peek into heap"; +const char *const CANT_POKE = "Internal error: cannot poke into heap"; + +HeapType H; + +void heap_sort_init() { + new_xarray(H); +} + +static bool lighter(const Element one, const Element two) { + return *static_cast<StringPtr>(one) < *static_cast<StringPtr>(two); +} + +static void heapup() { + int L, parent; + Element Lp, parentp = nullptr; + Element temp; + + L = H.size(); + while (L > 0) { + if ((L % 2) == 0) + parent = L / 2; + else + parent = (L - 1) / 2; + + if (!(access_xarray(H, L, Lp, PEEK_ACCESS) && access_xarray(H, parent, parentp, PEEK_ACCESS))) + g_vm->writeln(CANT_PEEK); + + if (lighter(Lp, parentp)) { + temp = parentp; + if (!(access_xarray(H, parent, Lp, POKE_ACCESS) && access_xarray(H, L, temp, POKE_ACCESS))) + g_vm->writeln(CANT_POKE); + L = parent; + } else { + L = 0; + } + } +} + +static void heapdown() { + uint L, compare, lc, rc; + Element lp; + Element lcp, rcp; + Element comparep; + Element temp; + + L = 0; + while (L < H.size()) { + lc = L * 2; + if (lc >= H.size()) { + L = lc; + } else { + rc = lc + 1; + if (!access_xarray(H, lc, lcp, PEEK_ACCESS)) + g_vm->writeln(CANT_PEEK); + + if (rc >= H.size()) { + compare = lc; + comparep = lcp; + } else { + if (!access_xarray(H, rc, rcp, PEEK_ACCESS)) + g_vm->writeln(CANT_PEEK); + if (lighter(lcp, rcp)) { + compare = lc; + comparep = lcp; + } else { + compare = rc; + comparep = rcp; + } + } + + if (!access_xarray(H, L, lp, PEEK_ACCESS)) + g_vm->writeln(CANT_PEEK); + if (!lighter(comparep, lp)) { + temp = comparep; + if (!(access_xarray(H, compare, lp, POKE_ACCESS) && access_xarray(H, L, temp, POKE_ACCESS))) + g_vm->writeln(CANT_POKE); + L = compare; + } else { + L = H.size(); + } + } + } +} + +bool pop_heap(Element &e) { + Element temp; + + if (H.empty()) { + return false; + } else { + if (!(access_xarray(H, 0, e, PEEK_ACCESS) && access_xarray(H, H.size() - 1, temp, PEEK_ACCESS) + && access_xarray(H, 0, temp, POKE_ACCESS))) + g_vm->writeln(CANT_PEEK); + + shrink_xarray(H); + heapdown(); + return true; + } +} + +void drop_on_heap(Element e) { + append_to_xarray(H, e); + heapup(); +} + +void drop_str_on_heap(const String &s) { + StringPtr sp = NewDynStr(s); + void *p = (void *)sp; + drop_on_heap(p); +} + +void reinit_heap() { + dispose_xarray(H); + new_xarray(H); +} + +} // End of namespace Archetype +} // End of namespace Glk diff --git a/engines/glk/archetype/heap_sort.h b/engines/glk/archetype/heap_sort.h new file mode 100644 index 0000000000..5e2512407f --- /dev/null +++ b/engines/glk/archetype/heap_sort.h @@ -0,0 +1,48 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef ARCHETYPE_HEAP_SORT +#define ARCHETYPE_HEAP_SORT + +#include "glk/archetype/array.h" +#include "glk/archetype/string.h" + +namespace Glk { +namespace Archetype { + +typedef XArrayType HeapType; +typedef void *Element; + +extern HeapType H; + +// Public methods +extern void heap_sort_init(); +extern bool pop_heap(Element &e); +extern void drop_on_heap(Element e); +extern void drop_str_on_heap(const String &s); +extern void reinit_heap(); + + +} // End of namespace Archetype +} // End of namespace Glk + +#endif diff --git a/engines/glk/archetype/id_table.cpp b/engines/glk/archetype/id_table.cpp new file mode 100644 index 0000000000..a611503b71 --- /dev/null +++ b/engines/glk/archetype/id_table.cpp @@ -0,0 +1,73 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "glk/archetype/id_table.h" +#include "glk/archetype/array.h" + +namespace Glk { +namespace Archetype { + +ClassifyType DefaultClassification; + +// Static variables +IdRecPtr hash[BUCKETS]; +XArrayType h_index; + +int add_ident(const String &id_str) { + int hasher; + IdRecPtr p, new_rec; + + hasher = (int)(toupper(id_str[1])) - 65; // A..Z => 65..90 => 0..25 + if (hasher < 0 || hasher > 25) + hasher = 26; + + p = hash[hasher]; + while (p->next && *p->next->id_name < id_str) + p = p->next; + + if (p->next == nullptr || *p->next->id_name > id_str) { + new_rec = new IdRecType(); + append_to_xarray(h_index, new_rec); + + new_rec->id_kind = DefaultClassification; + new_rec->id_index = h_index.size() - 1; + new_rec->id_integer = new_rec->id_index; + new_rec->id_name = NewConstStr(id_str); + new_rec->next = p->next; + + p->next = new_rec; + return h_index.size() - 1; + } else { + // found existing identifier + return p->next->id_index; + } +} + +bool index_ident(int index, IdRecPtr &id_ptr) { + void *p; + bool result = index_xarray(h_index, index, p); + id_ptr = (IdRecPtr)p; + return result; +} + +} // End of namespace Archetype +} // End of namespace Glk diff --git a/engines/glk/archetype/id_table.h b/engines/glk/archetype/id_table.h new file mode 100644 index 0000000000..2aff6d6ce9 --- /dev/null +++ b/engines/glk/archetype/id_table.h @@ -0,0 +1,77 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef ARCHETYPE_ID_TABLE +#define ARCHETYPE_ID_TABLE + +#include "glk/archetype/misc.h" +#include "glk/archetype/string.h" + +/** + * Contains the necessary data structures andfunctions for adding to andreferring + * to the ID table(a very busy little structure). + * + * The ID table is a 27-element hash table with one bucket for each letter; identifiers + * are hashed according to their first letter. The last bucket is for identifiers beginning with + * an underscore. The ID table is cross_indexed by an xarray containing pointers to id_records. + * The ID table is complex enough that it should probably not be accessed directly but rather + * only through its procedures.In this way the data type, its primary instantiation, andassociated + * code comprise one stand-alone module which must be "used" by any module wishing to modify or + * query the table. + */ +namespace Glk { +namespace Archetype { + +const int BUCKETS = 27; // 1 per letter of alphabet, plus the underscore + +struct IdRecType { + ClassifyType id_kind; + int id_integer; // What integer the ID gets written as + int id_index; // The ID's index in the ID table + StringPtr id_name; + IdRecType *next; +}; +typedef IdRecType *IdRecPtr; + +// Public methods +/** + * Adds the given identifier to the ID table, andreturns its index. There are no duplications; + * if the identifier already exists, its existing index is returned. + * @param id_str String containing identifier name + * @returns The index of the identifier + */ +extern int add_ident(const String &id_str); + +/** + * A quick little wrapper to the index_xarray function. + * @param index Number of the identifier + * @param id_ptr Out pointer to the id_record for that identifier + * @returns True if the requested identifier exists in the table + */ +extern bool index_ident(int index, IdRecPtr &id_ptr); + +extern ClassifyType DefaultClassification; + +} // End of namespace Archetype +} // End of namespace Glk + +#endif diff --git a/engines/glk/archetype/interpreter.cpp b/engines/glk/archetype/interpreter.cpp new file mode 100644 index 0000000000..108b948605 --- /dev/null +++ b/engines/glk/archetype/interpreter.cpp @@ -0,0 +1,478 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "glk/archetype/interpreter.h" +#include "glk/archetype/archetype.h" +#include "glk/archetype/misc.h" +#include "glk/archetype/saveload.h" +#include "glk/archetype/timestamp.h" +#include "glk/archetype/wrap.h" + +namespace Glk { +namespace Archetype { + +int MainObject; + +void interpreter_init() { + MainObject = 0; +} + +StringPtr MakeNewDynStr(const String &s) { + return NewDynStr(s); +} + +int find_message(const String &message) { + void *p; + + for (uint i = 0; i < g_vm->Vocabulary.size(); ++i) { + if (!index_xarray(g_vm->Vocabulary, i, p)) + g_vm->writeln("Internal error - cannot index element %d of Vocabulary", i); + else if (message == *((StringPtr)p)) + return i; + } + + return -1; +} + +bool convert_to(AclType target_type, ResultType &the_scalar) { + int code; + char dir_from = '\0'; + int the_number = -1; + String s1; + void *p; + bool boolval = false; + + if (target_type == the_scalar._kind) + return true; + + switch (target_type) { + case QUOTE_LIT: + case TEXT_LIT: + case IDENT: + case RESERVED: + return false; + default: + break; + } + + switch (the_scalar._kind) { + case NUMERIC: + dir_from = 'N'; + the_number = the_scalar._numeric.acl_int; + break; + + case MESSAGE: + dir_from = 'S'; + if (index_xarray(g_vm->Vocabulary, the_scalar._msgTextQuote.index, p)) + s1 = *(StringPtr)p; + break; + + case TEXT_LIT: + case QUOTE_LIT: + dir_from = 'S'; + if (index_xarray(g_vm->Literals, the_scalar._msgTextQuote.index, p)) + s1 = *(StringPtr)p; + break; + + case STR_PTR: + // string memory will be disposed ONLY if successful convert + dir_from = 'S'; + s1 = *the_scalar._str.acl_str; + break; + + case IDENT: + //with the_scalar do begin + dir_from = 'S'; + + switch (the_scalar._ident.ident_kind) { + case ENUMERATE_ID: + dir_from = 'N'; + the_number = the_scalar._ident.ident_int; + break; + + case OBJECT_ID: + if (the_scalar._ident.ident_int == 0) + s1 = "system"; + else if (index_xarray(g_vm->Object_ID_List, the_scalar._ident.ident_int, p)) { + if (p == nullptr) + s1 = "null"; + else + s1 = *(StringPtr)p; + } else { + return false; + } + break; + + case TYPE_ID: + if (the_scalar._ident.ident_int == 0) + s1 = "null"; + else if (index_xarray(g_vm->Type_ID_List, the_scalar._ident.ident_int, p)) + s1 = *(StringPtr)p; + else + return false; + break; + + case ATTRIBUTE_ID: + if (index_xarray(g_vm->Attribute_ID_List, the_scalar._ident.ident_int, p)) + s1 = *((StringPtr)p); + else + return false; + break; + + default: + break; + } + break; + + case RESERVED: + if (the_scalar._reserved.keyword == RW_TRUE || the_scalar._reserved.keyword == RW_FALSE) { + dir_from = 'B'; + boolval = (the_scalar._reserved.keyword == RW_TRUE); + } else { + return false; + } + break; + + default: + break; + } + + if (target_type == STR_PTR || target_type == MESSAGE) { + if (the_scalar._kind == STR_PTR) + FreeDynStr(the_scalar._str.acl_str); // we know this will succeed + + the_scalar._kind = target_type; + + switch (dir_from) { + case 'N': + s1 = String::format("%d", the_number); + break; + case 'B': + s1 = boolval ? "TRUE" : "false"; + break; + default: + break; + } + + if (target_type == MESSAGE) + the_scalar._msgTextQuote.index = find_message(s1); + else + the_scalar._str.acl_str = NewDynStr(s1); + + return true; + } else { + // numeric conversions + switch (dir_from) { + case 'N': + the_scalar._kind = NUMERIC; + the_scalar._numeric.acl_int = the_number; + return true; + + case 'B': + the_scalar._kind = NUMERIC; + the_scalar._numeric.acl_int = boolval ? 1 : 0; + break; + + case 'S': + s1.trim(); + the_number = s1.val(&code); + + if (code != 0) { + return false; + } else { + // successful + if (the_scalar._kind == STR_PTR) + FreeDynStr(the_scalar._str.acl_str); // memory no longer needed + + the_scalar._kind = NUMERIC; + the_scalar._numeric.acl_int = the_number; + } + + return true; + + default: + break; + } + } + + return false; +} + +void undefine(ResultType &result) { + result._kind = RESERVED; + result._reserved.keyword = RW_UNDEFINED; +} + +void cleanup(ResultType &result) { + if (result._kind == STR_PTR) + FreeDynStr(result._str.acl_str); + result._kind = RESERVED; + result._reserved.keyword = RW_UNDEFINED; +} + +void copy_result(ResultType &r1, const ResultType &r2) { + cleanup(r1); + r1 = r2; + if (r1._kind == STR_PTR) + r1._str.acl_str = NewDynStr(*r2._str.acl_str); +} + +bool result_compare(short comparison, ResultType &r1, ResultType &r2) { + bool verdict = false; + + // Try numeric reckoning first, then string reckoning + if (convert_to(NUMERIC, r1) && convert_to(NUMERIC, r2)) { + switch (comparison) { + case OP_EQ: + case OP_NE: + verdict = r1._numeric.acl_int == r2._numeric.acl_int; + break; + case OP_LT: + verdict = r1._numeric.acl_int < r2._numeric.acl_int; + break; + case OP_LE: + verdict = r1._numeric.acl_int <= r2._numeric.acl_int; + break; + case OP_GT: + verdict = r1._numeric.acl_int > r2._numeric.acl_int; + break; + case OP_GE: + verdict = r1._numeric.acl_int >= r2._numeric.acl_int; + break; + default: + break; + } + + } else if (convert_to(STR_PTR, r1) && convert_to(STR_PTR, r2)) { + switch (comparison) { + case OP_EQ: + case OP_NE: + verdict = *r1._str.acl_str == *r2._str.acl_str; + break; + case OP_LT: + verdict = *r1._str.acl_str < *r2._str.acl_str; + break; + case OP_LE: + verdict = *r1._str.acl_str <= *r2._str.acl_str; + break; + case OP_GT: + verdict = *r1._str.acl_str > *r2._str.acl_str; + break; + case OP_GE: + verdict = *r1._str.acl_str >= *r2._str.acl_str; + break; + default: + break; + } + + } else if (r1._kind == r2._kind) { + switch (r1._kind) { + case RESERVED: + switch (comparison) { + case OP_EQ: + case OP_NE: + verdict = r1._reserved.keyword == r2._reserved.keyword; + break; + default: + break; + } + + case IDENT: + if (r1._ident.ident_kind == r2._ident.ident_kind) { + switch (comparison) { + case OP_EQ: + case OP_NE: + verdict = r1._ident.ident_int == r2._ident.ident_int; + break; + default: + break; + } + } + break; + + default: + break; + } + } + + if (comparison == OP_NE) + return !verdict; + else + return verdict; +} + +bool assignment(ResultType &target, ResultType &value) { + ExprPtr e; + + if (target._kind != ATTR_PTR) { + wraperr("Warning: attempted assignment to a non-attribute"); + return false; + } else { + e = (ExprPtr)target._attr.acl_attr->data; + + // If the current expression starts with an operator, we know it isn't a flat result + // and must therefore be disposed of before proceeding. Otherwise simply clean up + // the previous expression node + if (e->_kind != OPER) { + cleanup(*e); + } else { + dispose_expr(e); + e = (ExprPtr)malloc(sizeof(ExprNode)); + undefine(*e); + } + } + + copy_result(*e, value); + target._attr.acl_attr->data = e; + + return true; +} + +void write_result(ResultType &result) { + ResultType r1; + + undefine(r1); + if (result._kind == STR_PTR) + wrapout(*result._str.acl_str, false); + else if (result._kind == RESERVED) + wrapout(Reserved_Wds[result._reserved.keyword], false); + else { + if (result._kind == ATTR_PTR) + copy_result(r1, *(ResultType *)result._attr.acl_attr->data); + else + copy_result(r1, result); + if (convert_to(STR_PTR, r1)) + wrapout(*r1._str.acl_str, false); + cleanup(r1); + } +} + +void display_result(ResultType &result) { + String enclose; + + switch (result._kind) { + case STR_PTR: + case TEXT_LIT: + enclose = "\""; + break; + case QUOTE_LIT: + enclose = " "; + wrapout(">>", false); + break; + case MESSAGE: + enclose = "\'"; + default: + enclose = ' '; + } + + if (enclose != " ") + wrapout(enclose, false); + write_result(result); + if (enclose != " ") + wrapout(enclose, false); +} + +void display_expr(ExprTree the_tree) { + if (the_tree->_kind != OPER) { + display_result(*the_tree); + } else { + if (Binary[the_tree->_oper.op_name]) { + wrapout(" (", false); + display_expr(the_tree->_oper.left); + wrapout(") ", false); + } + + wrapout(Operators[the_tree->_oper.op_name], false); + wrapout(" (", false); + display_expr(the_tree->_oper.right); + wrapout(") ", false); + } +} + +bool load_game(Common::ReadStream *f_in) { + int length; + char ch = '\0'; + double fileVersion; + + // First, check the initial version string against that in the misc unit + length = strlen(VERSION_STUB); + for (int i = 0; i < length; ++i) { + ch = (char)f_in->readByte(); + if (ch != VERSION_STUB[i]) { + g_vm->writeln("This file is not an Archetype file."); + return false; + } + } + + // Bleed off string version information + while (!f_in->eos() && ch != 26) + ch = f_in->readByte(); + + // Check encoded version + // TODO: Figure out real format + fileVersion = 0; + (void)f_in->readUint32LE(); + + if (fileVersion > VERSION_NUM) { + g_vm->writeln("This version of PERFORM is %.1f; file version is %.1f", + VERSION_NUM, fileVersion); + g_vm->writeln("Cannot PERFORM this file."); + return false; + } + + // Get encryption information + Encryption = (EncryptionType)f_in->readUint16LE(); + + // Read the timestamp. It is used to verify saved game states, and also to prime the encryption + GTimeStamp = f_in->readUint32LE(); + + // Initialize the encrypter. This is done by using the global time stamp as a starting point + // and using the Encryption variable to decide the method. Be careful here; the PURPLE + // or Dynamic encryption works differently in that we have to set Encryption to UNPURPLE + // (since we're decoding) and then back to PURPLE again in case they save any game states. + // See load_game_state in the GAMESTAT unit for similar machinations + if (Encryption == PURPLE) + Encryption = UNPURPLE; + cryptinit(Encryption, GTimeStamp); + + // Where's the main object? + MainObject = f_in->readSint32LE(); + + load_obj_list(f_in, g_vm->Object_List); + load_obj_list(f_in, g_vm->Type_List); + + load_text_list(f_in, g_vm->Literals); + load_text_list(f_in, g_vm->Vocabulary); + + if (Encryption == DEBUGGING_ON) { + g_vm->writeln("Loading debugging information"); + load_id_info(f_in); + } + + if (Encryption == UNPURPLE) + Encryption = PURPLE; + + return true; +} + +} // End of namespace Archetype +} // End of namespace Glk diff --git a/engines/glk/archetype/interpreter.h b/engines/glk/archetype/interpreter.h new file mode 100644 index 0000000000..2ee357aad5 --- /dev/null +++ b/engines/glk/archetype/interpreter.h @@ -0,0 +1,130 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef ARCHETYPE_INTERPRETER +#define ARCHETYPE_INTERPRETER + +#include "glk/archetype/expression.h" +#include "common/stream.h" + +namespace Glk { +namespace Archetype { + +enum DesiredType { LVALUE, RVALUE, NAME }; + +typedef ExprNode ResultType; + +struct ContextType { + int sender, self, each, message; +}; + +extern int MainObject; + +extern void interpreter_init(); + +/** + * A short wrapper to NewDynStr which basically uses the stack as temporary string storage. + * If you want to use a string constructor expression as an argument, call this function, + * since it does not take strings by reference but by value. Expensive on the stack but + * only briefly; it saves cluttering eval_expr + */ +extern StringPtr MakeNewDynStr(const String &s); + +/** + * Given a string message, returns its number in the Vocabulary list, or -1 if it was not found. + * At present, it simply uses a very inefficient O(N) lookup. If speed begins to become a + * consideration, this can be changed. + * @param message message to find number of + * @returns the number of the message in the Vocabulary list. + */ +extern int find_message(const String &message); + +/** + * Converts a scalar expression node to a target type. Deals primarily with numeric -> string + * or string -> numeric conversions in their many incarnations. + * @param target_type type to convert to + * @param the_scalar scalar to convert + */ +extern bool convert_to(AclType target_type, ResultType &the_scalar); + +/** + * Used to initialize previously unused result records. Does not expect that there might be + * a string pointer lurking within. + */ +extern void undefine(ResultType &result); + +/** + * To be used on temporary result variables after their usefulness is finished. Like 'undefine' above, + * except that it is used only for results that have actually been used - in other words, results with + * their "kind" field set properly. + */ +extern void cleanup(ResultType &result); + +/** + * Does an rvalue-like copy from r2 to r1 + */ +extern void copy_result(ResultType &r1, const ResultType &r2); + +/** + * Compares two result nodes according to the given operator. + * @returns true if they can; false if they cannot + */ +extern bool result_compare(short comparison, ResultType &r1, ResultType &r2); + +/** + * Given the result of an LVALUE evaluation and a result to assign to the attribute, + * performs the assignment if possible. + * @param target hopefully points to attribute to receive assignment + * @param value Result to assign + * @returns Returns true if the assignment was successful; false otherwise + */ +extern bool assignment(ResultType &target, ResultType &value); + +/** + * Writes the given result to screen w/o terminating it with a newline + */ +extern void write_result(ResultType &result); + +/** + * For purposes of debugging. + * Strings are enclosed in double quotes. + * Messages are enclosed in single quotes. + * Quote literals are preceded by >> + */ +extern void display_result(ResultType &result); + +/** + * Given an expression tree, displays the thing on screen. + */ +extern void display_expr(ExprTree the_tree); + +/** + * Loads a game into memory from a binary input file. Checks for errors + * in the header or incompatible versions. + * @param f_in Input file + */ +extern bool load_game(Common::ReadStream *f_in); + +} // End of namespace Archetype +} // End of namespace Glk + +#endif diff --git a/engines/glk/archetype/keywords.cpp b/engines/glk/archetype/keywords.cpp new file mode 100644 index 0000000000..c794750699 --- /dev/null +++ b/engines/glk/archetype/keywords.cpp @@ -0,0 +1,152 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "glk/archetype/keywords.h" +#include "glk/archetype/archetype.h" +#include "glk/archetype/misc.h" + +namespace Glk { +namespace Archetype { + +LookupType Reserved_Wds = { + nullptr, + "ABSENT", + "FALSE", + "TRUE", + "UNDEFINED", + "based", + "break", + "case", + "create", + "default", + "destroy", + "do", + "each", + "else", + "end", + "for", + "if", + "include", + "key", + "keyword", + "message", + "methods", + "named", + "null", + "of", + "on", + "read", + "self", + "sender", + "stop", + "then", + "type", + "while", + "write", + "writes" +}; + +LookupType Operators = { + nullptr, + "&", + "&:=", + "*", + "*:=", + "+", + "+:=", + "-", + "-->", + "-:=", + "->", + ".", + "/", + "/:=", + ":=", + "<", + "<=", + "=", + ">", + ">=", + "?", + "^", + "and", + "chs", + "leftfrom", + "length", + "not", + "numeric", + "or", + "rightfrom", + "string", + "within", + "~=", + nullptr, + nullptr +}; + +void load_text_list(Common::ReadStream *fIn, XArrayType &the_list) { + int i, n; + String s; + + new_xarray(the_list); + n = fIn->readUint16LE(); + for (i = 0; i < n; ++i) { + load_string(fIn, s); + append_to_xarray(the_list, NewConstStr(s)); + } +} + +void dump_text_list(Common::WriteStream *fOut, XArrayType &the_list) { + void *p; + + fOut->writeUint16LE(the_list.size()); + for (uint i = 0; i < the_list.size(); ++i) { + if (index_xarray(the_list, i, p)) + dump_string(fOut, *(StringPtr)(p)); + } +} + +void dispose_text_list(XArrayType &the_list) { + void *p; + + for (uint i = 0; i < the_list.size(); ++i) { + if (index_xarray(the_list, i, p)) + free((StringPtr)p); + } + + dispose_xarray(the_list); +} + +void load_id_info(Common::ReadStream *bfile) { + load_text_list(bfile, g_vm->Type_ID_List); + load_text_list(bfile, g_vm->Object_ID_List); + load_text_list(bfile, g_vm->Attribute_ID_List); +} + +void dump_id_info(Common::WriteStream *bfile) { + dump_text_list(bfile, g_vm->Type_ID_List); + dump_text_list(bfile, g_vm->Object_ID_List); + dump_text_list(bfile, g_vm->Attribute_ID_List); +} + +} // End of namespace Archetype +} // End of namespace Glk diff --git a/engines/glk/archetype/keywords.h b/engines/glk/archetype/keywords.h new file mode 100644 index 0000000000..f36c66e20b --- /dev/null +++ b/engines/glk/archetype/keywords.h @@ -0,0 +1,149 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef ARCHETYPE_KEYWORDS +#define ARCHETYPE_KEYWORDS + +#include "common/stream.h" +#include "glk/archetype/array.h" +#include "glk/archetype/string.h" + +namespace Glk { +namespace Archetype { + +// max length of any reserved word or operator +const int SHORT_STR_LEN = 9; + +enum { + NUM_RWORDS = 34, + NUM_OPERS = 32, + MAX_ELEMENTS = 34 // max(NUM_RWORDS, NUM_OPERS) +}; + +enum ReservedWordId { + RW_ABSENT = 1, + RW_FALSE = 2, + RW_TRUE = 3, + RW_UNDEFINED = 4, + RW_BASED = 5, + RW_BREAK = 6, + RW_CASE = 7, + RW_CREATE = 8, + RW_DEFAULT = 9, + RW_DESTROY = 10, + RW_DO = 11, + RW_EACH = 12, + RW_ELSE = 13, + RW_END = 14, + RW_FOR = 15, + RW_IF = 16, + RW_INCLUDE = 17, + RW_KEY = 18, + RW_KEYWORD = 19, + RW_MESSAGE = 20, + RW_METHODS = 21, + RW_NAMED = 22, + RW_NULL = 23, + RW_OF = 24, + RW_ON = 25, + RW_READ = 26, + RW_SELF = 27, + RW_SENDER = 28, + RW_STOP = 29, + RW_THEN = 30, + RW_TYPE = 31, + RW_WHILE = 32, + RW_WRITE = 33, + RW_WRITES = 34 +}; + +enum OperatorId { + OP_CONCAT = 1, + OP_C_CONCAT = 2, + OP_MULTIPLY = 3, + OP_C_MULTIPLY = 4, + OP_PLUS = 5, + OP_C_PLUS = 6, + OP_MINUS = 7, + OP_PASS = 8, + OP_C_MINUS = 9, + OP_SEND = 10, + OP_DOT = 11, + OP_DIVIDE = 12, + OP_C_DIVIDE = 13, + OP_ASSIGN = 14, + OP_LT = 15, + OP_LE = 16, + OP_EQ = 17, + OP_GT = 18, + OP_GE = 19, + OP_RANDOM = 20, + OP_POWER = 21, + OP_AND = 22, + OP_CHS = 23, + OP_LEFTFROM = 24, + OP_LENGTH = 25, + OP_NOT = 26, + OP_NUMERIC = 27, + OP_OR = 28, + OP_RIGHTFROM = 29, + OP_STRING = 30, + OP_WITHIN = 31, + OP_NE = 32 +}; + +//typedef char ShortStrType[SHORT_STR_LEN]; +typedef const char *const LookupType[MAX_ELEMENTS + 1]; + +extern LookupType Reserved_Wds, Operators; + +// Methods + +/** + * Loads an xarray of test literals into memory from the given file + */ +extern void load_text_list(Common::ReadStream *fIn, XArrayType &the_list); + +/** + * Dumps the given xarray of text literals to the given file + */ +extern void dump_text_list(Common::WriteStream *fOut, XArrayType &the_list); + +/** + * Disposes with all memory associated with the given xarray of text literals + */ +extern void dispose_text_list(XArrayType &the_list); + +/** + * Loads all ID information from the given binary file + */ +extern void load_id_info(Common::ReadStream *bfile); + +/** + * Dumps all ID information to the given binary file + */ +extern void dump_id_info(Common::WriteStream *bfile); + +} // End of namespace Archetype +} // End of namespace Glk + +#endif diff --git a/engines/glk/archetype/linked_list.cpp b/engines/glk/archetype/linked_list.cpp new file mode 100644 index 0000000000..90cca17981 --- /dev/null +++ b/engines/glk/archetype/linked_list.cpp @@ -0,0 +1,90 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "glk/archetype/linked_list.h" +#include "glk/archetype/misc.h" + +namespace Glk { +namespace Archetype { + +void new_list(ListType &the_list) { + the_list = new NodeType(); +} + +void dispose_list(ListType &the_list) { + NodePtr theNode, axe; + for (theNode = the_list->next; theNode != the_list; ) { + axe = theNode; + theNode = theNode->next; + add_bytes(-(int)sizeof(*axe)); + free(axe); + } + +} + +bool iterate_list(ListType &the_list, NodePtr index) { + if (index == nullptr) + index = the_list->next; + else + index = index->next; + + return index != the_list; +} + +void append_to_list(ListType &the_list, NodePtr the_node) { + the_list->data = the_node->data; + the_list->key = the_node->key; + the_node->next = the_list->next; + the_list->next = the_node; + + the_list = the_node; +} + +NodePtr index_list(ListType &the_list, int number) { + int i = 0; + NodePtr p = the_list->next; + + while (i < number && p != the_list) { + p = p->next; + ++i; + } + + return (p == the_list) ? nullptr : p; +} + +void insert_item(ListType &the_list, NodePtr the_item) { + NodePtr p; + for (p = the_list; p->next != the_list && p->next->key > the_item->key; p = p->next) {} + + the_item->next = p->next; + p->next = the_item; +} + +NodePtr find_item(ListType &the_list, int the_key) { + NodePtr p; + for (p = the_list->next; p != the_list && the_key < p->key; p = p->next) {} + + return (p == the_list || the_key != p->key) ? nullptr : p; +} + +} // End of namespace Archetype +} // End of namespace Glk diff --git a/engines/glk/archetype/linked_list.h b/engines/glk/archetype/linked_list.h new file mode 100644 index 0000000000..372de365b8 --- /dev/null +++ b/engines/glk/archetype/linked_list.h @@ -0,0 +1,80 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef ARCHETYPE_LINKED_LIST +#define ARCHETYPE_LINKED_LIST + +#include "common/list.h" + +namespace Glk { +namespace Archetype { + +struct NodeType { + void *data; + int key; + NodeType *next; +}; +typedef NodeType *NodePtr; + +typedef NodePtr ListType; + +/** + * Allocates a header node and points it to itself + */ +extern void new_list(ListType &the_list); + +/** + * Throws away all the memory that makes up an entire list structure. Is a "shallow dispose"; + * i.e. only disposes of the structure, not the data. + */ +extern void dispose_list(ListType &the_list); + +/** + * Iterates through the given list + */ +extern bool iterate_list(ListType &the_list, NodePtr index); + +/** + * Appends a new item to the list + */ +extern void append_to_list(ListType &the_list, NodePtr the_node); + +/** + * Permits a linked list to be indexed like an array in O(N) time. + */ +extern NodePtr index_list(ListType &the_list, int number); + +/** + * Ordered insert; average time O(N/2). Inserts in descending order + */ +extern void insert_item(ListType &the_list, NodePtr the_item); + +/** + * Given a list and a key, finds the first item in the list corresponding to that key. + * Expects that the elements have been sorted in descending order + */ +extern NodePtr find_item(ListType &the_list, int the_key); + +} // End of namespace Archetype +} // End of namespace Glk + +#endif diff --git a/engines/glk/archetype/misc.cpp b/engines/glk/archetype/misc.cpp new file mode 100644 index 0000000000..34c067c2b0 --- /dev/null +++ b/engines/glk/archetype/misc.cpp @@ -0,0 +1,197 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "glk/archetype/misc.h" +#include "glk/archetype/archetype.h" +#include "glk/quetzal.h" + +namespace Glk { +namespace Archetype { + +const char *const VERSION_STUB = "Archetype version "; +const char *const VERSION = "Archetype version 1.02"; +const double VERSION_NUM = 1.01; + +size_t Bytes; +int Debug; +bool KeepLooking; +bool AllErrors; + +void *Prior, *NextExit; + +void misc_init() { + //NextExit = ExitProc; + //ExitProc = @exit_prog; + //HeapError = @HeapFunc; + //Mark(Prior) + Bytes = 0; + Debug = 0; + KeepLooking = true; + AllErrors = false; +} + +/*----------------------------------------------------------------------*/ + +bool progfile::open(const String &name) { + filename = name; + + if (!_file.open(name)) { + return false; + + } else { + file_line = 0; + line_buffer = ""; + line_pos = 0; + newlines = false; + consumed = true; + last_ch = NULL_CH; + + return true; + } +} + +void progfile::close() { + _file.close(); +} + +bool progfile::readChar(char &ch) { + if (last_ch != NULL_CH) { + ch = last_ch; + last_ch = NULL_CH; + } else { + ++line_pos; + while (line_pos >= (int)line_buffer.size()) { + if (_file.eos()) { + ch = NULL_CH; + return false; + } + + line_buffer = QuetzalReader::readString(&_file); + line_buffer += NEWLINE_CH; + ++file_line; + line_pos = 0; + } + + ch = line_buffer[line_pos]; + } + + return true; +} + +void progfile::unreadChar(char ch) { + last_ch = ch; +} + +void progfile::sourcePos() { + /* With the /A switch specified, multiple source_pos messages can be called, + * so long as there is no fatal syntax error.Otherwise, the first error + * of any kind, regardless of severity, is the only error printed.This is + * done as a courtesy to those of us without scrolling DOS windows + */ + if (KeepLooking) { + if (!AllErrors) + KeepLooking = false; + + g_vm->writeln("Error in %s at line %d", filename.c_str(), file_line); + g_vm->writeln(line_buffer); + + String s; + for (int i = 0; i < line_pos; ++i) + s += ' '; + s += '^'; + g_vm->writeln(s); + } +} + +/*----------------------------------------------------------------------*/ + +void add_bytes(int delta) { + Bytes += delta; + + if ((Debug & DEBUG_BYTES) != 0) { + if (delta >= 0) + g_vm->write("Allocated "); + else + g_vm->write("Deallocated "); + + g_vm->writeln("%.3d bytes. Current consumed memory: %.6d", ABS(delta), Bytes); + } +} + +String formatFilename(const String &name, const String &ext, bool replace) { + String s; + int period = 0; + bool noExt; + + // Check for a period for an extension + period = name.lastIndexOf('.'); + noExt = period == -1; + + if (replace || noExt) { + return name + "." + ext; + } else { + return String(name.c_str(), name.c_str() + period + 1) + ext; + } +} + +void load_string(Common::ReadStream *fIn, String &the_string) { + char buffer[257]; + size_t strSize = fIn->readByte(); + fIn->read(buffer, strSize); + buffer[strSize] = '\0'; + + the_string = String(buffer); + cryptstr(the_string); +} + +void dump_string(Common::WriteStream *fOut, const String &the_string) { + assert(the_string.size() < 256); + fOut->writeByte(the_string.size()); + + if (Encryption == NONE) { + fOut->write(the_string.c_str(), the_string.size()); + + } else { + String tmp = the_string; + cryptstr(tmp); + fOut->write(tmp.c_str(), tmp.size()); + } +} + +StringPtr NewConstStr(const String &s) { + return new String(s); +} + +void FreeConstStr(StringPtr &sp) { + delete sp; +} + +StringPtr NewDynStr(const String &s) { + return new String(s); +} + +void FreeDynStr(StringPtr &sp) { + delete sp; +} + +} // End of namespace Archetype +} // End of namespace Glk diff --git a/engines/glk/archetype/misc.h b/engines/glk/archetype/misc.h new file mode 100644 index 0000000000..0799077a85 --- /dev/null +++ b/engines/glk/archetype/misc.h @@ -0,0 +1,184 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef ARCHETYPE_MISC +#define ARCHETYPE_MISC + +#include "common/file.h" +#include "glk/archetype/crypt.h" +#include "glk/archetype/string.h" + +namespace Glk { +namespace Archetype { + +#define MAX_STRING 255 +#define NULL_CH '\0' +#define NEWLINE_CH '\r' + +enum { + DEBUG_BYTES = 0x01, + DEBUG_MSGS = 0x02, + DEBUG_EXPR = 0x04, + DEBUG_STMT = 0x08 +}; + +enum AclType { + RESERVED, IDENT, MESSAGE, OPER, TEXT_LIT, QUOTE_LIT, NUMERIC, PUNCTUATION, + STR_PTR, ATTR_PTR, BAD_TOKEN, NEWLINE +}; + +/** + * Source program file/accounting structure.With such a file, it is important to keep + * not only the file pointer, but also fields to keep track of position in the source file + * andthe compiler state, or the context of the tokenizer + */ +class progfile { +private: + Common::File _file; // The physical file +public: + String filename; // to do with error tracking + String line_buffer; + int file_line; + int line_pos; + + bool newlines; // having to do with the tokenizer context + char last_ch; + bool consumed; + AclType ttype; + int tnum; +public: + /** + * Constructor + */ + progfile() : file_line(0), line_pos(0), newlines(false), last_ch(NULL_CH), + consumed(false), ttype(RESERVED), tnum(0) {} + + /** + * Opens an Archetype program source file. + * @param name Filename + */ + bool open(const String &name); + + /** + * Closes an Archetype program source code file. + */ + void close(); + + /** + * Reads a single character from the given progfile, performing all appropriate housekeeping. + * + * It appends an internal newline to the end of every line taken from the file; it is up to + * the tokenizer as to whether to consider it white space or a token. + * @param c The output character + * @returns True if the character was safely read from the file + */ + bool readChar(char &ch); + + /** + * Has the effect of putting a character back on the data stream. + * Closely cooperates with read_char above. + * @param ch Character to un-read + */ + void unreadChar(char ch); + + /** + * Writes out the current position in the source file nicely for error messages + * and so forth.It will, however, only write this out once per execution of the program. + * This is to prevent messages scrolling uncontrollably off the screen. + */ + void sourcePos(); +}; + +enum ClassifyType { TYPE_ID, OBJECT_ID, ATTRIBUTE_ID, ENUMERATE_ID, UNDEFINED_ID }; + +extern const char *const VERSION; +extern const char *const VERSION_STUB; +extern const double VERSION_NUM; +extern size_t Bytes; // Bytes consumed by allocated memory +extern int Debug; +extern bool KeepLooking; +extern bool AllErrors; + +/** + * Performs initialization of fields local to the file + */ +extern void misc_init(); + +/** + * Provides a method of keeping track of the size, in allocation, of the used part of the heap. + * @param delta if positive, the number allocated; if negative, the number deallocated. + */ +extern void add_bytes(int delta); + +/** + * Given a name andextension, tacks on the given extension if none given. + * of <name> are ".<ext>", or else tacks those four characters on. + * @param name Filename + * @param ext Extension + * @param replace Whether to replace existing extension + */ +extern String formatFilename(const String &name, const String &ext, bool replace); + +/** + * Given an input stream, reads in a Pascal style string with the first byte being the length. + * Secondarily, it also decrypts the string + * @param fIn Input file + * @param the_string Output string + */ +extern void load_string(Common::ReadStream *fIn, String &the_string); + +/** + * Given an untyped file variable anda string variable, writes to the file first the length + * of the string andthen the string itself. + * @param fOut Output file + * @param the_string The string to output + */ +extern void dump_string(Common::WriteStream *fOut, const String &the_string); + +/** + * Used for allocating string space that is not expected to be disposed of + * until the end of the program andis never expected to change. + * Only the very minimum space necessary to store the string is used; + * thereby using minimal space andincurring no fragmentation + */ +extern StringPtr NewConstStr(const String &s); + +/** + * Frees a const string + */ +extern void FreeConstStr(StringPtr &sp); + +/** + * Dynamic strings were originally strings where we need speed and yet we need to allocate + * only the string space necessary. These days, we can simply just allocate a new string + */ +extern StringPtr NewDynStr(const String &s); + +/** + * Frees a dynamic string + */ +extern void FreeDynStr(StringPtr &sp); + +} // End of namespace Archetype +} // End of namespace Glk + +#endif diff --git a/engines/glk/archetype/parser.cpp b/engines/glk/archetype/parser.cpp new file mode 100644 index 0000000000..0063e2818c --- /dev/null +++ b/engines/glk/archetype/parser.cpp @@ -0,0 +1,291 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "glk/archetype/parser.h" +#include "glk/archetype/archetype.h" +#include "common/algorithm.h" + +namespace Glk { +namespace Archetype { + +const int WORD_LEN = 32; + +struct ParseType { + StringPtr word; + int object; +}; +typedef ParseType *ParsePtr; + +/** + * Is a word character + */ +static bool isWordChar(char c) { + return Common::isAlnum(c) || c == '-' || c == '\"'; +} + +/** + * Puts into lowercase the given character. + */ +static char locase(char ch) { + return tolower(ch); +} + +void normalize_string(const String &first, String &second) { + int i, j, lfirst; + bool in_word, done; + + i = j = 0; + in_word = false; + done = false; + lfirst = first.size() - 1; + second = " "; + + do { + if (i > lfirst || !isWordChar(first[i])) { + if (in_word) { + j = 0; + in_word = false; + second = second + " "; + } + else { + ++i; + } + + if (i > lfirst) + done = true; + + } else if (in_word) { + if (j < g_vm->Abbreviate) { + second = second + locase(first[i]); + ++j; + } + + ++i; + } else { + in_word = true; + } + } while (!done); +} + +void add_parse_word(TargetListType which_list, String &the_word, int the_object) { + ListType the_list; + String tempstr; + NodePtr np; + ParsePtr pp; + int bar; + + if (which_list == PARSER_VERBLIST) + the_list = g_vm->verb_names; + else + the_list = g_vm->object_names; + + the_word += '|'; + + do { + bar = the_word.indexOf('|'); + if (bar != -1) { + pp = (ParsePtr)malloc(sizeof(ParseType)); + tempstr = the_word.left(bar - 1).left(g_vm->Abbreviate); + + pp->word = NewConstStr(tempstr); + pp->word->toLowercase(); + the_word = String(the_word.c_str() + bar + 1); + pp->object = the_object; + + np = (NodePtr)malloc(sizeof(NodeType)); + np->key = pp->word->size(); + np->data = pp; + + insert_item(the_list, np); + } + } while (bar != 0); +} + +static void parse_sentence_substitute(int start, ParsePtr pp, int &next_starting) { + int sublen = pp->word->size(); + + if (sublen > g_vm->Abbreviate) + sublen = g_vm->Abbreviate; + + g_vm->Command = g_vm->Command.left(start) + + String::format("%%%c%c^", pp->object >> 8, pp->object & 0xff) + + String(g_vm->Command.c_str() + start + sublen + 1); + + next_starting = next_starting - sublen + 4; +} + +static bool parse_sentence_next_chunk(int &start_at, String &the_chunk, int &next_starting) { + int i; + + if (next_starting == 0) { + return false; + } else { + do { + start_at = next_starting; + the_chunk = g_vm->Command.mid(start_at); + + i = the_chunk.indexOf('%'); + if (i == -1) { + next_starting = 0; + } else { + the_chunk = the_chunk.left(i - 1); + next_starting = next_starting + i + 3; + } + + the_chunk.trim(); + } while (!(next_starting == 0 || !the_chunk.empty())); + + return !the_chunk.empty(); + } +} + +void parse_sentence() { + const int nfillers = 3; + const char *const FILTERS[nfillers] = { " a ", " an ", " the " }; + int next_starting; + String s; + NodePtr np, near_match, far_match; + ParsePtr pp; + int i, lchunk; + + // Rip out those fillers + s = g_vm->Command; + for (i = 0; i < nfillers; ++i) { + int filterIndex; + while ((filterIndex = g_vm->Command.indexOf(FILTERS[i])) != -1) + g_vm->Command.del(filterIndex, strlen(FILTERS[i]) - 1); + } + + // Restore the original string if filler removal destroyed it completely + if (g_vm->Command == " ") + g_vm->Command = s; + + // Two passes: one matching all verbs and prepositions from the verb list, longest strings first + + np = nullptr; + while (iterate_list(g_vm->verb_names, np)) { + pp = (ParsePtr)np->data; + s = String::format(" %s ", pp->word->left(g_vm->Abbreviate).c_str()); + + i = g_vm->Command.indexOf(s); + if (i != -1) + parse_sentence_substitute(i, pp, next_starting); + } + + // Second pass: carefully search for the remaining string chunks; search only the part + // of the noun list of the same length; give preference to those in the Proximate list + next_starting = 1; + + while (parse_sentence_next_chunk(i, s, next_starting)) { + lchunk = s.size() - 1; + + np = find_item(g_vm->object_names, lchunk); + if (np != nullptr) { + near_match = nullptr; + far_match = nullptr; + + do { + pp = (ParsePtr)np->data; + if (pp->word->left(g_vm->Abbreviate) == s) { + if (find_item(g_vm->Proximate, pp->object) != nullptr) + near_match = np; + else + far_match = np; + } + } while (!(iterate_list(g_vm->object_names, np) && (lchunk == (int)((ParsePtr)np->data)->word->size()))); + + if (near_match != nullptr) + parse_sentence_substitute(i, (ParsePtr)near_match->data, next_starting); + else if (far_match != nullptr) + parse_sentence_substitute(i, (ParsePtr)far_match->data, next_starting); + } + } + + g_vm->Command.trim(); +} + +bool pop_object(int &intback, String &strback) { + int i; + + if (g_vm->Command.empty()) { + return false; + } else { + if (g_vm->Command.firstChar() == '%') { + // parsed object + intback = ((int)g_vm->Command[1] << 8) | ((int)g_vm->Command[2]); + g_vm->Command.del(0, 4); + } + else { + intback = -1; + i = g_vm->Command.indexOf('%'); + if (i < 0) + i = g_vm->Command.size(); + + strback = g_vm->Command.left(i); + g_vm->Command = g_vm->Command.mid(i); + strback.trim(); + } + + g_vm->Command.trim(); + return true; + } +} + +int find_object(const String &s) { + NodePtr np; + + np = nullptr; + while (iterate_list(g_vm->object_names, np)) { + if (*((ParsePtr)np->data)->word == s) + return ((ParsePtr)np->data)->object; + } + + np = nullptr; + while (iterate_list(g_vm->verb_names, np)) { + if (*((ParsePtr)np->data)->word == s) + return ((ParsePtr)np->data)->object; + } + + return 0; +} + +void clear_parse_list(ListType &the_list) { + ParsePtr pp; + NodePtr np = nullptr; + + while (iterate_list(the_list, np)) { + pp = (ParsePtr)np->data; + FreeConstStr(pp->word); + free(pp); + } + + dispose_list(the_list); + new_list(the_list); +} + +void new_parse_list() { + clear_parse_list(g_vm->verb_names); + clear_parse_list(g_vm->object_names); +} + +} // End of namespace Archetype +} // End of namespace Glk diff --git a/engines/glk/archetype/parser.h b/engines/glk/archetype/parser.h new file mode 100644 index 0000000000..c5185b0f35 --- /dev/null +++ b/engines/glk/archetype/parser.h @@ -0,0 +1,89 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef ARCHETYPE_PARSER +#define ARCHETYPE_PARSER + +#include "glk/archetype/string.h" + +namespace Glk { +namespace Archetype { + +enum TargetListType { PARSER_VERBLIST, PARSER_NOUNLIST }; + +/** + * Given a string, creates a string with one and only one space between each word + * @param first the string to be normalized + * @param second the normalized string + */ +extern void normalize_string(const String &first, String &second); + +/** + * Adds another word to one of the lists to match. If the given word has vertical bars in it, + * the bars are considered delimiters and each delimited word is added to the available list + */ +extern void add_parse_word(TargetListType which_list, String &the_word, int the_object); + +/** + * Parses the previously given sentence into a string of object references. + * The verbpreps list is searched first, followed by the nounphrases list. + * It does not identify any parts of speech; it is strictly substitutional. + * + * Also removes all instances of the words "a", "an", "the". + * + * NOTES: + * When an object is matched, its name is replaced by the sequence + * <percent sign><high byte><low byte><caret>. The percent will + * indicate the beginning of the encoded number; the caret indicates + * the end and also serves the purpose of keeping the trim() procedure + * from trimming off objects 9 or 13 or the like. + * + * Objects are matched as words; they must be surrounded by spaces. + * When they are replaced in the Command string, they leave the spaces + * on both sides so as not to interfere with further matching + */ +extern void parse_sentence(); + +/** + * Pops the first object number off of the parsed Command string and sends the number back. + * If Command does not begin with an object marker, sends back the unparseable string. + * @param intback will be -1 if there was no object; otherwise, the number of the object. + * @param strback will contain the (trimmed) unparseable chunk if intback is -1; unchanged otherwise + * @returns true if there was anything to be popped; false otherwise + */ +extern bool pop_object(int &intback, String &strback); + +/** + * Performs a subset of the normal parse_sentence algorithm. Given a single string, + * find the number of the first object that matches. + */ +extern int find_object(const String &s); + +/** + * Called in order to force a full deletion of the parse lists, in order that new ones may be built up + */ +extern void new_parse_list(); + +} // End of namespace Archetype +} // End of namespace Glk + +#endif diff --git a/engines/glk/archetype/saveload.cpp b/engines/glk/archetype/saveload.cpp new file mode 100644 index 0000000000..2a965e3039 --- /dev/null +++ b/engines/glk/archetype/saveload.cpp @@ -0,0 +1,672 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "glk/archetype/saveload.h" +#include "glk/archetype/id_table.h" +#include "glk/archetype/semantic.h" + +namespace Glk { +namespace Archetype { + +StatementKind vEndSeq, vContSeq; // to make BlockWrite happy +int Dynamic; +bool Translating; + +void saveload_init() { + vEndSeq = END_SEQ; + vContSeq = CONT_SEQ; + + Dynamic = 0; + Translating = true; +} + +// ===================== Forward Declarations ======================= + +static void walk_item_list(MissionType mission, Common::Stream *bfile, + ListType elements, ContentType content); +static void walk_expr(MissionType mission, Common::Stream *bfile, ExprTree &the_expr); +static void walk_stmt(MissionType mission, Common::Stream *bfile, StatementPtr &the_stmt); + +// ========================== Item Lists ============================ + +// Wrappers + +void load_item_list(Common::ReadStream *f_in, ListType elements, ContentType content) { + walk_item_list(LOAD, f_in, elements, content); +} + +void dump_item_list(Common::WriteStream *f_out, ListType elements, ContentType content) { + walk_item_list(DUMP, f_out, elements, content); +} + +void dispose_item_list(ListType &elements, ContentType content) { + walk_item_list(FREE, nullptr, elements, content); +} + +/** + * Used for operating on general linked lists which are homogenous, all containing the same type + * of data, signified by the "content" variable. + * @param mission action to perform while walking through + * @param bfile binary file to read or write from + * @param elements list of items + * @param content contents of each of the items + */ +void walk_item_list(MissionType mission, Common::Stream *bfile, ListType elements, ContentType content) { + StatementKind sentinel; + StatementPtr this_stmt; + ExprTree this_expr; + CasePairPtr this_case; + NodePtr np = nullptr; + bool yet_more = false; + + Common::ReadStream *readStream = dynamic_cast<Common::ReadStream *>(bfile); + Common::WriteStream *writeStream = dynamic_cast<Common::WriteStream *>(bfile); + + // Prelude + switch (mission) { + case LOAD: + assert(readStream); + sentinel = (StatementKind)readStream->readUint32LE(); + new_list(elements); + yet_more = sentinel == CONT_SEQ; + break; + + case DUMP: + case FREE: + np = nullptr; + yet_more = iterate_list(elements, np); + break; + + default: + break; + } + + while (yet_more) { + // Main walk + switch (mission) { + case LOAD: + assert(readStream); + np = (NodePtr)malloc(sizeof(NodeType)); + add_bytes(sizeof(NodeType)); + np->key = readStream->readSint32LE(); + break; + + case DUMP: + assert(writeStream); + writeStream->writeUint32LE(vContSeq); + writeStream->writeSint32LE(np->key); + break; + + default: + break; + } + + switch (content) { + case EXPR_LIST: + switch (mission) { + case LOAD: + walk_expr(mission, bfile, this_expr); + np->data = this_expr; + break; + case DUMP: + case FREE: + this_expr = (ExprTree)np->data; + walk_expr(mission, bfile, this_expr); + break; + default: + break; + } + break; + + case STMT_LIST: + switch (mission) { + case LOAD: + walk_stmt(mission, bfile, this_stmt); + np->data = this_stmt; + break; + case DUMP: + case FREE: + this_stmt = (StatementPtr)np->data; + walk_stmt(mission, bfile, this_stmt); + break; + default: + break; + } + break; + + case CASE_LIST: + switch (mission) { + case LOAD: + this_case = (CasePairPtr)malloc(sizeof(CasePairType)); + add_bytes(sizeof(CasePairType)); + walk_expr(mission, bfile, this_case->value); + walk_stmt(mission, bfile, this_case->action); + + np->data = this_case; + break; + + case DUMP: + case FREE: + this_case = (CasePairPtr)np->data; + walk_expr(mission, bfile, this_case->value); + walk_stmt(mission, bfile, this_case->action); + + if (mission == FREE) { + add_bytes(sizeof(CasePairType)); + free(this_case); + } + break; + + default: + break; + } + break; + + default: + break; + } + + switch (mission) { + case LOAD: + assert(readStream); + append_to_list(elements, np); + sentinel = (StatementKind)readStream->readUint32LE(); + yet_more = sentinel == CONT_SEQ; + break; + + case DUMP: + case FREE: + yet_more = iterate_list(elements, np); + break; + + default: + break; + } + } + + // Postlude + switch (mission) { + case DUMP: + writeStream->writeUint32LE(vEndSeq); + break; + case FREE: + dispose_list(elements); + elements = nullptr; + break; + default: + break; + } +} + +// ============================ Expressions =========================== + +// Wrappers +void load_expr(Common::ReadStream *f_in, ExprTree &the_expr) { + walk_expr(LOAD, f_in, the_expr); +} + +void dump_expr(Common::WriteStream *f_out, ExprTree &the_expr) { + walk_expr(DUMP, f_out, the_expr); +} + +void dispose_expr(ExprTree &the_expr) { + walk_expr(FREE, nullptr, the_expr); +} + +/** + * Separated from walk_expr so as not to consume too much stack space with its large temporary string.. + */ +static StringPtr LoadDynStr(Common::ReadStream *f_in) { + String s; + load_string(f_in, s); + return NewDynStr(s); +} + +/** + * Walks through an expression tree. + * @param mission action to take on each visited element + * @param bfile binary file to read or write from as necessary + * @param the_expr expression tree to walk + */ +static void walk_expr(MissionType mission, Common::Stream *bfile, ExprTree &the_expr) { + int temp = 0; + ClassifyType ID_kind = UNDEFINED_ID; + + Common::ReadStream *readStream = dynamic_cast<Common::ReadStream *>(bfile); + Common::WriteStream *writeStream = dynamic_cast<Common::WriteStream *>(bfile); + + // Prelude + switch (mission) { + case LOAD: + assert(readStream); + the_expr = (ExprTree)malloc(sizeof(ExprNode)); + add_bytes(sizeof(ExprNode)); + the_expr->_kind = (AclType)readStream->readUint32LE(); + break; + + case DUMP: + if (the_expr == nullptr) + return; + + assert(writeStream); + while (the_expr->_kind == OPER && the_expr->_oper.op_name == OP_LPAREN) + the_expr = the_expr->_oper.right; + + writeStream->writeUint32LE(the_expr->_kind); + break; + + case FREE: + if (the_expr == nullptr) + return; + break; + + default: + break; + } + + // Main walk + switch (the_expr->_kind) { + case OPER: + switch (mission) { + case LOAD: + the_expr->_oper.op_name = readStream->readSByte(); + the_expr->_oper.left = nullptr; + break; + case DUMP: + writeStream->writeSByte(the_expr->_oper.op_name); + break; + default: + break; + } + + if (Binary[the_expr->_oper.op_name]) + walk_expr(mission, bfile, the_expr->_oper.left); + walk_expr(mission, bfile, the_expr->_oper.right); + break; + + case NUMERIC: + switch (mission) { + case LOAD: + the_expr->_numeric.acl_int = readStream->readSint32LE(); + break; + case DUMP: + writeStream->writeSint32LE(the_expr->_numeric.acl_int); + break; + default: + break; + } + break; + + case MESSAGE: + case TEXT_LIT: + case QUOTE_LIT: + switch (mission) { + case LOAD: + the_expr->_msgTextQuote.index = readStream->readSint32LE(); + break; + case DUMP: + writeStream->writeSint32LE(the_expr->_msgTextQuote.index); + break; + default: + break; + } + break; + + case IDENT: + switch (mission) { + case LOAD: + the_expr->_ident.ident_kind = (ClassifyType)readStream->readUint32LE(); + the_expr->_ident.ident_int = readStream->readSint32LE(); + break; + case DUMP: + if (Translating && the_expr->_ident.ident_kind == DefaultClassification) { + // may have changed meaning + get_meaning(the_expr->_ident.ident_int, ID_kind, temp); + if (ID_kind == UNDEFINED_ID) + add_undefined(the_expr->_ident.ident_int); + } else { + the_expr->_ident.ident_kind = ID_kind; + the_expr->_ident.ident_int = temp; + } + + writeStream->writeUint32LE(the_expr->_ident.ident_kind); + writeStream->writeSint32LE(the_expr->_ident.ident_int); + break; + default: + break; + } + break; + + case RESERVED: + switch (mission) { + case LOAD: + the_expr->_reserved.keyword = readStream->readSByte(); + break; + case DUMP: + writeStream->writeSByte(the_expr->_reserved.keyword); + break; + default: + break; + } + break; + + case STR_PTR: + switch (mission) { + case LOAD: + the_expr->_str.acl_str = LoadDynStr(readStream); + break; + case DUMP: + dump_string(writeStream, *the_expr->_str.acl_str); + break; + case FREE: + FreeDynStr(the_expr->_str.acl_str); + break; + default: + break; + } + break; + + default: + break; + } + + // Postlude + switch (mission) { + case FREE: + free(the_expr); + the_expr = nullptr; + break; + default: + break; + } +} + + +// =========================== Statements ========================= + +// Wrappers + +void load_stmt(Common::ReadStream *f_in, StatementPtr &the_stmt) { + walk_stmt(LOAD, f_in, the_stmt); +} + +void dump_stmt(Common::WriteStream *f_out, StatementPtr &the_stmt) { + walk_stmt(DUMP, f_out, the_stmt); +} + +void dispose_stmt(StatementPtr &the_stmt) { + walk_stmt(FREE, nullptr, the_stmt); +} + +/** + * Handles the control involved in walking through a statement. + * @param mission action to take for each statement + * @param bfile binary file to read or write as necessary + * @param the_stmt pointer to a statement record + */ +static void walk_stmt(MissionType mission, Common::Stream *bfile, StatementPtr &the_stmt) { + NodePtr np; // for appending to lists + StatementKind sentinel; + StatementPtr this_stmt = nullptr; + ExprTree this_expr; + + Common::ReadStream *readStream = dynamic_cast<Common::ReadStream *>(bfile); + Common::WriteStream *writeStream = dynamic_cast<Common::WriteStream *>(bfile); + + // Prelude + switch (mission) { + case LOAD: + the_stmt = nullptr; + if (readStream->eos()) + return; + + sentinel = (StatementKind)readStream->readUint32LE(); + if (sentinel == END_SEQ) + return; + + the_stmt = (StatementPtr)malloc(sizeof(StatementType)); + add_bytes(sizeof(StatementType)); + the_stmt->_kind = sentinel; + break; + + case DUMP: + if (the_stmt == nullptr) { + writeStream->writeUint32LE(vEndSeq); + return; + } else { + writeStream->writeUint32LE(this_stmt->_kind); + } + break; + + case FREE: + if (the_stmt == nullptr) + return; + break; + + default: + break; + } + + // Main walk + assert(this_stmt); + switch (this_stmt->_kind) { + case COMPOUND: + walk_item_list(mission, bfile, this_stmt->_compound.statements, STMT_LIST); + break; + + case ST_EXPR: + walk_expr(mission, bfile, this_stmt->_expr.expression); + break; + + case ST_IF: + walk_expr(mission, bfile, this_stmt->_if.condition); + walk_stmt(mission, bfile, this_stmt->_if.then_branch); + walk_stmt(mission, bfile, this_stmt->_if.else_branch); + break; + + case ST_CASE: + walk_expr(mission, bfile, this_stmt->_case.test_expr); + walk_item_list(mission, bfile, this_stmt->_case.cases, CASE_LIST); + break; + + case ST_CREATE: + switch (mission) { + case LOAD: + this_stmt->_create.archetype = readStream->readSint32LE(); + break; + case DUMP: + writeStream->writeSint32LE(this_stmt->_create.archetype); + break; + default: + break; + } + + walk_expr(mission, bfile, this_stmt->_create.new_name); + break; + + case ST_DESTROY: + walk_expr(mission, bfile, this_stmt->_destroy.victim); + break; + + case ST_FOR: + case ST_WHILE: + walk_expr(mission, bfile, this_stmt->_loop.selection); + walk_stmt(mission, bfile, this_stmt->_loop.action); + break; + + case ST_WRITE: + case ST_WRITES: + case ST_STOP: + switch (mission) { + case LOAD: + new_list(this_stmt->_write.print_list); + sentinel = (StatementKind)readStream->readUint32LE(); + + while (sentinel != END_SEQ) { + walk_expr(mission, bfile, this_expr); + np = (NodePtr)malloc(sizeof(NodeType)); + add_bytes(sizeof(NodeType)); + + np->data = this_expr; + append_to_list(this_stmt->_write.print_list, np); + + sentinel = (StatementKind)readStream->readUint32LE(); + } + break; + + case DUMP: + case FREE: + np = nullptr; + while (iterate_list(this_stmt->_write.print_list, np)) { + if (mission == DUMP) + writeStream->writeUint32LE(vContSeq); + this_expr = (ExprTree)np->data; + walk_expr(mission, bfile, this_expr); + + if (mission == FREE) + np->data = nullptr; + } + + if (mission == DUMP) + writeStream->writeUint32LE(vEndSeq); + else + dispose_list(this_stmt->_write.print_list); + break; + + default: + break; + } + break; + + default: + break; + } + + // Postlude + switch (mission) { + case FREE: + add_bytes(sizeof(StatementType)); + free(the_stmt); + break; + default: + break; + } +} + + +// ============================ Objects =========================== + +void load_object(Common::ReadStream *f_in, ObjectPtr &the_object) { + StatementKind sentinel; + + the_object = (ObjectPtr)malloc(sizeof(ObjectType)); + add_bytes(sizeof(ObjectType)); + + the_object->inherited_from = f_in->readSint32LE(); + load_item_list(f_in, the_object->attributes, EXPR_LIST); + load_item_list(f_in, the_object->methods, STMT_LIST); + + sentinel = (StatementKind)f_in->readUint32LE(); + if (sentinel == CONT_SEQ) + load_stmt(f_in, the_object->other); + else + the_object->other = nullptr; +} + +void dump_object(Common::WriteStream *f_out, const ObjectPtr the_object) { + f_out->writeUint32LE(the_object->inherited_from); + dump_item_list(f_out, the_object->attributes, EXPR_LIST); + dump_item_list(f_out, the_object->methods, STMT_LIST); + + if (the_object->other == nullptr) { + f_out->writeUint32LE(vEndSeq); + } else { + f_out->writeUint32LE(vContSeq); + dump_stmt(f_out, the_object->other); + } +} + +void dispose_object(ObjectPtr &the_object) { + dispose_item_list(the_object->attributes, EXPR_LIST); + dispose_item_list(the_object->methods, STMT_LIST); + if (the_object->other != nullptr) + dispose_stmt(the_object->other); + + add_bytes(sizeof(ObjectType)); + free(the_object); + the_object = nullptr; +} + + +// ============================= Object Lists ======================== + +void load_obj_list(Common::ReadStream *f_in, XArrayType &obj_list) { + ObjectPtr new_object; + void *p; + int i, list_size; + + new_xarray(obj_list); + list_size = f_in->readUint32LE(); + + for (i = 0; i < list_size; ++i) { + load_object(f_in, new_object); + p = new_object; + append_to_xarray(obj_list, p); + } + + // Objects may be dynamically allocated beneath this limit. It is okay to set that limit + // at this time since this routine is only invoked when initially loading a game + Dynamic = obj_list.size(); // TODO: Check if this should be size() + 1 +} + +void dump_obj_list(Common::WriteStream *f_out, XArrayType &obj_list) { + uint i; + void *p; + ObjectPtr this_obj; + + f_out->writeUint32LE(obj_list.size()); + + for (i = 0; i < obj_list.size(); ++i) { + if (index_xarray(obj_list, i, p)) { + this_obj = (ObjectPtr)p; + dump_object(f_out, this_obj); + } + } +} + +void dispose_obj_list(XArrayType &obj_list) { + uint i; + void *p; + ObjectPtr axe_obj; + + for (i = 0; i < obj_list.size(); ++i) { + if (index_xarray(obj_list, i, p)) { + axe_obj = (ObjectPtr)p; + dispose_object(axe_obj); + } + } + + dispose_xarray(obj_list); +} + +} // End of namespace Archetype +} // End of namespace Glk diff --git a/engines/glk/archetype/saveload.h b/engines/glk/archetype/saveload.h new file mode 100644 index 0000000000..fe6251be9c --- /dev/null +++ b/engines/glk/archetype/saveload.h @@ -0,0 +1,79 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef ARCHETYPE_ERROR +#define ARCHETYPE_ERROR + +/* Contains routines for both saving and loading binary ACX files. Also + * contains routines for disposing of the major ACL structures, in order to + * be able to throw away the old before loading in the new + */ + +#include "glk/archetype/linked_list.h" +#include "glk/archetype/statement.h" +#include "common/stream.h" + +namespace Glk { +namespace Archetype { + +enum ContentType { STMT_LIST, EXPR_LIST, CASE_LIST }; +enum MissionType { LOAD, DUMP, FREE, DISPLAY }; + +struct ObjectType { + int inherited_from; // index to Type_List + ListType attributes; + ListType methods; + StatementPtr other; +}; +typedef ObjectType *ObjectPtr; + +// Global variables +extern StatementKind vEndSeq, vContSeq; // to make BlockWrite happy +extern int Dynamic; +extern bool Translating; + +extern void saveload_init(); + +extern void load_item_list(Common::ReadStream *f_in, ListType elements, ContentType content); +extern void dump_item_list(Common::WriteStream *f_out, ListType elements, ContentType content); +extern void dispose_item_list(ListType &elements, ContentType content); + +extern void load_expr(Common::ReadStream *f_in, ExprTree &the_expr); +extern void dump_expr(Common::WriteStream *f_out, ExprTree &the_expr); +extern void dispose_expr(ExprTree &the_expr); + +extern void load_stmt(Common::ReadStream *f_in, StatementPtr &the_stmt); +extern void dump_stmt(Common::WriteStream *f_out, StatementPtr &the_stmt); +extern void dispose_stmt(StatementPtr &the_stmt); + +extern void load_object(Common::ReadStream *f_in, ObjectPtr &the_object); +extern void dump_object(Common::WriteStream *f_out, const ObjectPtr the_object); +extern void dispose_object(ObjectPtr &the_object); + +extern void load_obj_list(Common::ReadStream *f_in, XArrayType &obj_list); +extern void dump_obj_list(Common::WriteStream *f_out, XArrayType &obj_list); +extern void dispose_obj_list(XArrayType &obj_list); + +} // End of namespace Archetype +} // End of namespace Glk + +#endif diff --git a/engines/glk/archetype/semantic.cpp b/engines/glk/archetype/semantic.cpp new file mode 100644 index 0000000000..4e2cd37205 --- /dev/null +++ b/engines/glk/archetype/semantic.cpp @@ -0,0 +1,234 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "glk/archetype/semantic.h" +#include "glk/archetype/archetype.h" +#include "glk/archetype/error.h" +#include "glk/archetype/id_table.h" + +namespace Glk { +namespace Archetype { + +typedef int *IntegerPtr; + +int classify_as(progfile &f, int id_number, ClassifyType interpretation, void *ptr_to_data) { + IdRecPtr the_id_ptr; + String error_string; + int result = 0; + + if (!index_ident(id_number, the_id_ptr)) { + error_message(f, "Attempt to classify unencountered identifier"); + } else { + //with the_id_ptr^ do begin + if (the_id_ptr->id_kind == interpretation) + result = the_id_ptr->id_integer; + + // If the existing id_kind is the DefaultClassification, we're allowed to + // change it; otherwise there's a conflict + else if (the_id_ptr->id_kind == DefaultClassification) { + the_id_ptr->id_kind = interpretation; + the_id_ptr->id_integer = the_id_ptr->id_index; + + switch (the_id_ptr->id_kind) { + case TYPE_ID: + append_to_xarray(g_vm->Type_List, ptr_to_data); + append_to_xarray(g_vm->Type_ID_List, (void *)the_id_ptr->id_name); + the_id_ptr->id_integer = g_vm->Type_List.size() - 1; + break; + + case OBJECT_ID: + if (ptr_to_data == nullptr) { + the_id_ptr->id_integer = 0; + } else { + // Object_List may have grown by unnamed objects between calls to classify_as. + // Fill in the intervening spaces with "null" + while (g_vm->Object_ID_List.size() < g_vm->Object_List.size()) + append_to_xarray(g_vm->Object_ID_List, (void *)g_vm->NullStr); + + append_to_xarray(g_vm->Object_List, ptr_to_data); + append_to_xarray(g_vm->Object_ID_List, (void *)the_id_ptr->id_name); + the_id_ptr->id_integer = g_vm->Object_List.size() - 1; + } + break; + + case ATTRIBUTE_ID: + append_to_xarray(g_vm->Attribute_ID_List, (void *)the_id_ptr->id_name); + the_id_ptr->id_integer = g_vm->Attribute_ID_List.size() - 1; + break; + + default: + break; + } + } else { + error_string = String::format("Identifier type conflict: \"%s\" already declared as ", + the_id_ptr->id_name->c_str()); + + switch (the_id_ptr->id_kind) { + case TYPE_ID: + error_string = error_string + "a type"; + break; + case OBJECT_ID: + error_string = error_string + "an object"; + break; + case ATTRIBUTE_ID: + error_string = error_string + "an attribute"; + break; + case ENUMERATE_ID: + error_string = error_string + "a keyword"; + break; + default: + break; + } + + error_message(f, error_string); + the_id_ptr->id_integer = 0; + } + + result = the_id_ptr->id_integer; + } + + return result; +} + +void get_meaning(int id_number, ClassifyType &meaning, int &number) { + IdRecPtr the_id_ptr; + + if (!index_ident(id_number, the_id_ptr)) { + error("Internal error: attempt to find meaning of unencountered identifier"); + } else { + meaning = the_id_ptr->id_kind; + number = the_id_ptr->id_integer; + } +} + +void add_undefined(int the_ID) { + NodePtr np; + IntegerPtr ip; + + np = find_item(g_vm->Overlooked, the_ID); + if (np != nullptr) { + ++*((IntegerPtr)np->data); + } else { + np = (NodePtr)malloc(sizeof(NodeType)); + np->key = the_ID; + ip = (IntegerPtr)malloc(sizeof(int)); + *ip = 1; // TODO: Should this be 0-based? + np->data = ip; + insert_item(g_vm->Overlooked, np); + } +} + +bool display_undefined() { + NodePtr np = nullptr; + IntegerPtr ip; + IdRecPtr id_rec; + bool exists = false; + + while (iterate_list(g_vm->Overlooked, np)) { + if (!exists) { + g_vm->writeln("The following identifiers were not explicitly defined."); + exists = true; + } + + ip = (IntegerPtr)np->data; + g_vm->write("Used %d", *ip); + if (*ip == 1) + g_vm->write(" time: "); + else + g_vm->write(" times: "); + + if (index_ident(np->key, id_rec)) + g_vm->writeln("%s", id_rec->id_name->c_str()); + else + g_vm->writeln("<unknown identifier>"); + + free(ip); + } + + dispose_list(g_vm->Overlooked); + + return exists; +} + +bool verify_expr(progfile &f, ExprTree the_expr) { + bool success = true; + + switch (the_expr->_kind) { + case OPER: + switch (the_expr->_oper.op_name) { + case OP_DOT: + if (the_expr->_oper.right->_kind != IDENT) { + error_message(f, "Right side of dot must be an identifier"); + success = false; + } + else if (the_expr->_oper.right->_ident.ident_kind != ATTRIBUTE_ID) { + the_expr->_oper.right->_ident.ident_int = classify_as(f, + the_expr->_oper.right->_ident.ident_int, ATTRIBUTE_ID, nullptr); + } + + the_expr->_oper.right->_ident.ident_kind = ATTRIBUTE_ID; + if (the_expr->_oper.right->_ident.ident_int == 0) + success = false; + + case OP_ASSIGN: + case OP_C_CONCAT: + case OP_C_MULTIPLY: + case OP_C_DIVIDE: + case OP_C_PLUS: + case OP_C_MINUS: + if (the_expr->_oper.left->_kind == IDENT) { + get_meaning(the_expr->_oper.left->_ident.ident_int, + the_expr->_oper.left->_ident.ident_kind, the_expr->_oper.left->_ident.ident_int); + + if (the_expr->_oper.left->_ident.ident_kind != ATTRIBUTE_ID) { + error_message(f, "Left side of assignment is not an attribute"); + success = false; + } + } + else if (!(the_expr->_oper.left->_kind == OPER && + the_expr->_oper.left->_oper.op_name == OP_DOT)) { + error_message(f, "Left side of assignment must reference an attribute"); + success = false; + } + break; + + default: + break; + } + + if (success) { + if (Binary[the_expr->_oper.op_name]) + success = verify_expr(f, the_expr->_oper.left); + } + if (success) + success = verify_expr(f, the_expr->_oper.right); + break; + + default: + break; + } + + return success; +} + +} // End of namespace Archetype +} // End of namespace Glk diff --git a/engines/glk/archetype/semantic.h b/engines/glk/archetype/semantic.h new file mode 100644 index 0000000000..2166e81220 --- /dev/null +++ b/engines/glk/archetype/semantic.h @@ -0,0 +1,112 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef ARCHETYPE_SEMANTIC +#define ARCHETYPE_SEMANTIC + +/* Used by the SYNTAX unit, it provides the high-level semantic checking + * as well as .ACX file output. + */ + +#include "glk/archetype/array.h" +#include "glk/archetype/expression.h" +#include "glk/archetype/linked_list.h" + +namespace Glk { +namespace Archetype { + +/** + * Works closely with the ID_Table to create and verify the various semantic + * interpretations of identifiers, which are classified as either: + * TYPE_ID: names a type definition template in the type list. + * OBJECT_ID: names an object instantiation in the object list. + * ATTRIBUTE_ID: an attribute identifier. + * ENUMERATE_ID: an identifier like "open" or "closed" which is simply + * assigned so that it can be tested. + * UNDEFINED_ID: Not defined anywhere. If /K is asserted for CREATE, then + * this is the value returned by default; otherwise, + * ENUMERATE_ID is. + * + * @param f the progfile that is being read. Since this function is part + * of the first "pass", it needs access to the file being read. + * @param id_number the index in the ID table + * @param interpretation one of the constants above + * @param ptr_to_data (IN) if not nil, points to the data that the identifier represents + * (when first encountered) + * @returns depends on interpretation: + * TYPE_ID: the index in Type_List + * OBJECT_ID: the index in Object_List + * ATTRIBUTE_ID: the order the identifier was declared in, i.e. + * for the first attribute encountered, 1, for the + * second, 2, etc. + * ENUMERATE_ID: the unchanged id_number, for a simple unique number. + * UNDEFINED_ID: same as ENUMERATE_ID + * + * In any case, classify_as returns 0 if there was an error. + * Such an error will have been printed by this routine, so there + * will be no need for the caller to print out its own. + */ +extern int classify_as(progfile &f, int id_number, ClassifyType interpretation, void *ptr_to_data); + +/** + * Given an ID_Table index, finds what it represents and returns an appropriate enumerated + * type and index. + * + * If /K is asserted, default return is UNDEFINED_ID; else it is ENUMERATE_ID. + * @param id_number integer index to ID_Table + * @param meaning Output classification of ID + * @param number Output integer appropriate to classification + */ +extern void get_meaning(int id_number, ClassifyType &meaning, int &number); + +/** + * Used for adding the number of an undefined identifier to a list to be produced + * at the end of translation. + */ +extern void add_undefined(int the_ID); + +/** + * Displays the list of undefined identifiers collected with add_undefined + */ +extern bool display_undefined(); + +/** + * Assumes that expression tree contains no OP_LPAREN nodes. + * Ensures the following: + * 1. All OP_DOT operators have identifiers as their right-hand + * arguments, which are classified as ATTRIBUTE_ID's. + * 2. All assignment operators have OP_DOT operators or identifiers + * as their left-hand arguments, and any such identifiers are + * classified as ATTRIBUTE_ID's. + * This is necessary because the only way to use the OP_DOT operator is + * to discover the value of some attribute, and attributes are the only + * things which may be assigned to. + * + * @param f Program file (for logging errors) + * @param the_expr Expression to be verified + */ +extern bool verify_expr(progfile &f, ExprTree the_expr); + +} // End of namespace Archetype +} // End of namespace Glk + +#endif diff --git a/engines/glk/archetype/statement.h b/engines/glk/archetype/statement.h new file mode 100644 index 0000000000..0be8c1b9a0 --- /dev/null +++ b/engines/glk/archetype/statement.h @@ -0,0 +1,98 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef ARCHETYPE_STATEMENT +#define ARCHETYPE_STATEMENT + +#include "glk/archetype/expression.h" +#include "glk/archetype/linked_list.h" + +namespace Glk { +namespace Archetype { + +enum StatementKind { + COMPOUND, ST_EXPR, ST_IF, ST_CASE, ST_FOR, ST_WHILE, ST_BREAK, ST_CREATE, + ST_DESTROY, ST_WRITE, ST_WRITES, ST_STOP, CONT_SEQ, END_SEQ +}; + +union StatementType; +typedef StatementType *StatementPtr; + +struct StmtCompound { + ListType statements; +}; + +struct StmtExpr { + ExprTree expression; +}; + +struct StmtIf { + ExprTree condition; + StatementPtr then_branch; + StatementPtr else_branch; +}; + +struct StmtCase { + ExprTree test_expr; + ListType cases; +}; + +struct StmtCreate { + int archetype; + ExprTree new_name; +}; + +struct StmtDestroy { + ExprTree victim; +}; + +struct StmtLoop { + ExprTree selection; + StatementPtr action; +}; + +struct StmtWrite { + ListType print_list; +}; + +union StatementType { + StatementKind _kind; + StmtCompound _compound; + StmtExpr _expr; + StmtIf _if; + StmtCase _case; + StmtCreate _create; + StmtDestroy _destroy; + StmtLoop _loop; + StmtWrite _write; +}; + +struct CasePairType { + ExprTree value; + StatementPtr action; +}; +typedef CasePairType *CasePairPtr; + +} // End of namespace Archetype +} // End of namespace Glk + +#endif diff --git a/engines/glk/archetype/string.cpp b/engines/glk/archetype/string.cpp new file mode 100644 index 0000000000..4db4688a7d --- /dev/null +++ b/engines/glk/archetype/string.cpp @@ -0,0 +1,133 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "glk/archetype/string.h" +#include "common/algorithm.h" + +namespace Glk { +namespace Archetype { + +int String::indexOf(char c) const { + const char *p = strchr(c_str(), c); + return p ? p - c_str() : -1; +} + +int String::indexOf(const String &substr) const { + const char *c = strstr(c_str(), substr.c_str()); + return c ? c - c_str() : -1; +} + +int String::lastIndexOf(char c) const { + for (int i = (int)size() - 1; i >= 0; --i) { + if (operator[](i) == c) + return i; + } + + return -1; +} + +void String::trim() { + while (!empty() && (lastChar() == ' ' || lastChar() == '\t' || lastChar() == '\n' + || lastChar() == '\r')) + deleteLastChar(); +} + +String operator+(const String &x, const String &y) { + Common::String tmp = Common::operator+(x, y); + return String(tmp.c_str()); +} + +String operator+(const char *x, const String &y) { + Common::String tmp = Common::operator+(x, y); + return String(tmp.c_str()); +} + +String operator+(const String &x, const char *y) { + Common::String tmp = Common::operator+(x, y); + return String(tmp.c_str()); +} + +String operator+(const String &x, char y) { + Common::String tmp = Common::operator+(x, y); + return String(tmp.c_str()); +} + +String operator+(char x, const String &y) { + Common::String tmp = Common::operator+(x, y); + return String(tmp.c_str()); +} + +bool operator==(const char *x, const String &y) { + return Common::operator==(x, y); +} + +bool operator!=(const char *x, const String &y) { + return Common::operator!=(x, y); +} + +String String::format(const char *fmt, ...) { + va_list va; + va_start(va, fmt); + Common::String tmp = Common::String::vformat(fmt, va); + va_end(va); + return String(tmp); +} + +String String::vformat(const char *fmt, va_list args) { + Common::String tmp = Common::String::vformat(fmt, args); + return String(tmp); +} + +int String::val(int *code) { + if (code) + *code = 0; + + return atoi(c_str()); +} + +String String::left(size_t count) const { + return String(c_str(), c_str() + MIN(count, (size_t)size())); +} + +String String::right(size_t count) const { + size_t len = size(); + return String(c_str() + len - MIN(count, len), c_str() + len); +} + +String String::mid(size_t start, size_t count) const { + int max = (int)size() - start; + return String(c_str() + start, c_str() + start + MIN((int)count, max)); +} + +String String::mid(size_t start) const { + return String(c_str() + start); +} + +void String::del(size_t start, size_t count) { + if (start) + (*this) = left(start) + mid(start + count - 1); + else + (*this) = mid(count); +} + +} // End of namespace Archetype +} // End of namespace Glk diff --git a/engines/glk/archetype/string.h b/engines/glk/archetype/string.h new file mode 100644 index 0000000000..f85ed37d1a --- /dev/null +++ b/engines/glk/archetype/string.h @@ -0,0 +1,143 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef ARCHETYPE_STRING +#define ARCHETYPE_STRING + +#include "common/str.h" + +namespace Glk { +namespace Archetype { + +class String : public Common::String { +public: + String() : Common::String() {} + String(const char *str) : Common::String(str) {} + String(const char *str, uint32 len) : Common::String(str, len) {} + String(const char *beginP, const char *endP) : Common::String(beginP, endP) {} + String(const Common::String &str) : Common::String(str) {} + explicit String(char c) : Common::String(c) {} + + String &operator=(const char *str) { + Common::String::operator=(str); + return *this; + } + String &operator=(const Common::String &str) { + Common::String::operator=(str); + return *this; + } + String &operator=(char c) { + Common::String::operator=(c); + return *this; + } + String &operator+=(const char *str) { + Common::String::operator+=(str); + return *this; + } + String &operator+=(const Common::String &str) { + Common::String::operator+=(str); + return *this; + } + String &operator+=(char c) { + Common::String::operator+=(c); + return *this; + } + + static String format(const char *fmt, ...) GCC_PRINTF(1, 2); + + static String vformat(const char *fmt, va_list args); + + /** + * Returns the index of a character within this string + */ + int indexOf(char c) const; + + /** + * Returns the index of a substring within this string + */ + int indexOf(const String &substr) const; + + /** + * Returns the last index of a character in a string, or -1 if it isn't present + */ + int lastIndexOf(char c) const; + + /** + * Trims spaces(and tabs and newlines) off the ends of a given string + */ + void trim(); + + /** + * Gets a substring of the string + */ + String substring(int index, int count) const { + return String(c_str() + index, c_str() + index + count); + } + + /** + * Converts a string to a value + * @param code Optional returns non-value of character index + */ + int val(int *code); + + /** + * Returns a given number of chracters from the start of a string + */ + String left(size_t count) const; + + /** + * Returns a given number of characters from the end of a string + */ + String right(size_t count) const; + + /** + * Returns a substring of another string + */ + String mid(size_t start) const; + String mid(size_t start, size_t count) const; + + /** + * Delete a range within a string + */ + void del(size_t start, size_t count); +}; + +// Append two strings to form a new (temp) string +String operator+(const String &x, const String &y); + +String operator+(const char *x, const String &y); +String operator+(const String &x, const char *y); + +String operator+(const String &x, char y); +String operator+(char x, const String &y); + +// Some useful additional comparison operators for Strings +bool operator==(const char *x, const String &y); +bool operator!=(const char *x, const String &y); + +typedef String *StringPtr; +typedef String ShortStringType; + +} // End of namespace Archetype +} // End of namespace Glk + +#endif diff --git a/engines/glk/archetype/sys_object.cpp b/engines/glk/archetype/sys_object.cpp new file mode 100644 index 0000000000..c30a1b87f6 --- /dev/null +++ b/engines/glk/archetype/sys_object.cpp @@ -0,0 +1,305 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "glk/archetype/sys_object.h" +#include "glk/archetype/archetype.h" +#include "glk/archetype/game_stat.h" +#include "glk/archetype/heap_sort.h" +#include "glk/archetype/parser.h" +#include "glk/archetype/wrap.h" +#include "common/algorithm.h" +#include "common/savefile.h" + +namespace Glk { +namespace Archetype { + +enum SysStateType { + IDLING, INIT_SORTER, OPEN_SORTER, CLOSE_SORTER, NEXT_SORTED, PLAYER_CMD, + NORMALIZE, ABBR, OPEN_PARSER, VERB_LIST, NOUN_LIST, CLOSE_PARSER, INIT_PARSER, + WHICH_OBJECT, ROLL_CALL, PRESENT, PARSE, NEXT_OBJECT, DEBUG_MESSAGES, + DEBUG_EXPRESSIONS, DEBUG_STATEMENTS, DEBUG_MEMORY, FREE_MEMORY, SAVE_STATE, LOAD_STATE +}; + +const char *const StateLookup[LOAD_STATE + 1] = { + "IDLING", "INIT SORTER", "OPEN SORTER", "CLOSE SORTER", "NEXT SORTED", "PLAYER CMD", + "NORMALIZE", "ABBR", "OPEN PARSER", "VERB LIST", "NOUN LIST", "CLOSE PARSER", "INIT PARSER", + "WHICH OBJECT", "ROLL CALL", "PRESENT", "PARSE", "NEXT OBJECT", "DEBUG MESSAGES", + "DEBUG EXPRESSIONS", "DEBUG STATEMENTS", "DEBUG MEMORY", "FREE MEMORY", "SAVE STATE", "LOAD STATE" +}; + +// Global variables which retain the state of the system object between calls +SysStateType sys_state; +TargetListType target_list; + +void sys_object_init() { + sys_state = IDLING; + target_list = PARSER_VERBLIST; +} + +static bool figure_state(const String &s) { + for (int st = IDLING; st <= LOAD_STATE; ++st) { + if (StateLookup[st] == s) { + sys_state = (SysStateType)st; + return true; + } + } + + return false; +} + +void send_to_system(int transport, String &strmsg, ResultType &result, ContextType &context) { + int the_caller; + int obj_index; + String nomatch; + NodePtr np; + void *p; + + if (transport == OP_SEND) + the_caller = context.self; + else + the_caller = context.sender; + + do { + cleanup(result); + + switch (sys_state) { + case IDLING: + if (figure_state(strmsg)) { + switch (sys_state) { + case PLAYER_CMD: + case ABBR: + case SAVE_STATE: + case LOAD_STATE: + case OPEN_PARSER: + case OPEN_SORTER: + case WHICH_OBJECT: + return; // come back again! + + case INIT_SORTER: + reinit_heap(); + sys_state = OPEN_SORTER; + return; + + case INIT_PARSER: + new_parse_list(); + sys_state = OPEN_PARSER; + return; + + default: + break; + } + } + + case PLAYER_CMD: + normalize_string(strmsg, g_vm->Command); + sys_state = IDLING; + break; + + case NORMALIZE: + // last normalized command + result._kind = STR_PTR; + result._str.acl_str = NewDynStr(g_vm->Command); + sys_state = IDLING; + + case ABBR: + result._kind = STR_PTR; + result._str.acl_str = NewDynStr(strmsg); + + if (convert_to(NUMERIC, result)) { + g_vm->Abbreviate = result._numeric.acl_int; + } + else { + wraperr("Warning: non-numeric abbreviation message sent to system"); + cleanup(result); + } + sys_state = IDLING; + break; + + case OPEN_PARSER: + if (figure_state(strmsg)) { + switch (sys_state) { + case CLOSE_PARSER: + sys_state = IDLING; + break; + + case VERB_LIST: + target_list = PARSER_VERBLIST; + sys_state = OPEN_PARSER; + break; + + case NOUN_LIST: + target_list = PARSER_NOUNLIST; + sys_state = OPEN_PARSER; + break; + + default: + break; + } + } + else { + add_parse_word(target_list, strmsg, the_caller); + } + + return; + + case OPEN_SORTER: + if (figure_state(strmsg)) { + switch (sys_state) { + case CLOSE_SORTER: + sys_state = IDLING; + break; + default: + break; + } + } else { + drop_str_on_heap(strmsg); + } + return; + + case NEXT_SORTED: + if (!pop_heap(p)) { + cleanup(result); + } else { + result._kind = STR_PTR; + result._str.acl_str = (StringPtr)p; + sys_state = IDLING; + } + break; + + case WHICH_OBJECT: + obj_index = find_object(strmsg); + if (obj_index != 0) { + result._kind = IDENT; + result._ident.ident_kind = OBJECT_ID; + result._ident.ident_int = obj_index; + } + sys_state = IDLING; + break; + + case ROLL_CALL: + dispose_list(g_vm->Proximate); + new_list(g_vm->Proximate); + sys_state = IDLING; + break; + + case PRESENT: + np = (NodePtr)malloc(sizeof(NodeType)); + np->data = nullptr; + np->key = the_caller; + + insert_item(g_vm->Proximate, np); + sys_state = IDLING; + break; + + case PARSE: + parse_sentence(); + sys_state = IDLING; + break; + + case NEXT_OBJECT: + if (!pop_object(obj_index, nomatch)) { + cleanup(result); + } else if (obj_index < 0) { + result._kind = STR_PTR; + result._str.acl_str = NewDynStr(nomatch); + } else { + result._kind = IDENT; + result._ident.ident_kind = OBJECT_ID; + result._ident.ident_int = obj_index; + } + + sys_state = IDLING; + break; + + case DEBUG_MESSAGES: + Debug = Debug ^ DEBUG_MSGS; + sys_state = IDLING; + break; + + case DEBUG_EXPRESSIONS: + Debug = Debug ^ DEBUG_EXPR; + sys_state = IDLING; + break; + + case DEBUG_STATEMENTS: + Debug = Debug ^ DEBUG_STMT; + sys_state = IDLING; + break; + + case DEBUG_MEMORY: + wrapout("", true); // get to beginning of line + //g_vm->writeln("Maximum memory request: %d bytes", MaxAvail); + //g_vm->writeln("Actual free memory: %d bytes", MemAvail); + sys_state = IDLING; + break; + + case FREE_MEMORY: + result._kind = NUMERIC; + result._numeric.acl_int = 0xffff; // MemAvail; + sys_state = IDLING; + break; + + case SAVE_STATE: { + Common::OutSaveFile *stfile = g_system->getSavefileManager()->openForSaving(strmsg); + if (stfile == nullptr) { + g_vm->writeln("Error opening %s", strmsg.c_str()); + cleanup(result); + } + else { + save_game_state(stfile, g_vm->Object_List); + result._kind = RESERVED; + result._reserved.keyword = RW_TRUE; + + stfile->finalize(); + delete stfile; + } + + sys_state = IDLING; + break; + } + + case LOAD_STATE: { + Common::InSaveFile *stfile = g_system->getSavefileManager()->openForLoading(strmsg); + if (stfile == nullptr) { + g_vm->writeln("Error opening %s", strmsg.c_str()); + cleanup(result); + } else { + result._kind = RESERVED; + result._reserved.keyword = load_game_state(stfile, g_vm->Object_List) ? RW_TRUE : RW_FALSE; + delete stfile; + } + + sys_state = IDLING; + break; + } + + default: + break; + } + + if (g_vm->shouldQuit()) + sys_state = IDLING; + } while (sys_state != IDLING); +} + +} // End of namespace Archetype +} // End of namespace Glk diff --git a/engines/glk/archetype/sys_object.h b/engines/glk/archetype/sys_object.h new file mode 100644 index 0000000000..5fa9982d45 --- /dev/null +++ b/engines/glk/archetype/sys_object.h @@ -0,0 +1,46 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef ARCHETYPE_SYS_OBJECT +#define ARCHETYPE_SYS_OBJECT + +#include "glk/archetype/string.h" +#include "glk/archetype/interpreter.h" + +namespace Glk { +namespace Archetype { + +extern void sys_object_init(); + +/** + * Is the receiver of all "system calls" and the only object that receives + * messages in the form of strings rather than message constants. + * + * Notes: Uses a global variable called sys_state to keep track of its state + * between calls. + */ +extern void send_to_system(int transport, String &strmsg, ResultType &result, ContextType &context); + +} // End of namespace Archetype +} // End of namespace Glk + +#endif diff --git a/engines/glk/archetype/timestamp.cpp b/engines/glk/archetype/timestamp.cpp new file mode 100644 index 0000000000..d146e4c168 --- /dev/null +++ b/engines/glk/archetype/timestamp.cpp @@ -0,0 +1,50 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "glk/archetype/timestamp.h" +#include "common/system.h" + +namespace Glk { +namespace Archetype { + +TimestampType GTimeStamp; + +void timestamp_init() { + GTimeStamp = 0; +} + +void get_time_stamp(TimestampType &tstamp) { + // Get the time and date + TimeDate td; + g_system->getTimeAndDate(td); + + // Normalize the year + tstamp = ((td.tm_year - 1992) % 64) << 26; + tstamp |= td.tm_mon << 22; + tstamp |= td.tm_mday << 17; + tstamp |= td.tm_hour << 12; + tstamp |= td.tm_min << 6; + tstamp |= td.tm_sec; +} + +} // End of namespace Archetype +} // End of namespace Glk diff --git a/engines/glk/archetype/timestamp.h b/engines/glk/archetype/timestamp.h new file mode 100644 index 0000000000..893ba438ce --- /dev/null +++ b/engines/glk/archetype/timestamp.h @@ -0,0 +1,62 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef ARCHETYPE_TIMESTAMP +#define ARCHETYPE_TIMESTAMP + +#include "common/scummsys.h" + +namespace Glk { +namespace Archetype { + +typedef uint32 TimestampType; + +extern TimestampType GTimeStamp; + +extern void timestamp_init(); + +/** + * Creates a compressed long integer that contains all the necessary time information. + * There are enough bits in a 32-bit word to do this : + * + * Variable Range Bits + * -------- ----- ---- + * Year 0-63 6 + * Month 1-12 4 + * Day 0-31 5 + * Hour 0-23 5 + * Minute 0-59 6 + * Second 0-59 6 + * + * Note that Year does not quite fit comfortably into this scheme.The actual returned value + * is 1980-2099, a span of 119 years; but we are using only 63. Year 0 is considered 1992 + * and the upper limit is 2055 before it goes back to year 0 (1992) again. + * + * The DayOfWeek information is thrown away because it is redundant, and the Sec100 information + * is thrown away because it is unnecessarily precise + */ +extern void get_time_stamp(TimestampType &tstamp); + +} // End of namespace Archetype +} // End of namespace Glk + +#endif diff --git a/engines/glk/archetype/token.cpp b/engines/glk/archetype/token.cpp new file mode 100644 index 0000000000..3be0e2365b --- /dev/null +++ b/engines/glk/archetype/token.cpp @@ -0,0 +1,441 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers || c == whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License || c == or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not || c == g_vm->write to the Free Software + * Foundation || c == Inc. || c == 51 Franklin Street || c == Fifth Floor || c == Boston || c == MA 02110-1301 || c == USA. + * + */ + +#include "glk/archetype/token.h" +#include "glk/archetype/archetype.h" +#include "glk/archetype/id_table.h" +#include "glk/archetype/misc.h" +#include "glk/archetype/keywords.h" + +namespace Glk { +namespace Archetype { + +enum StateType { START, STOP, DECIDE, WHITE, COMMENT, QUOTE, LITERAL, IDENTIFIER, NUMBER, OPERATOR }; + +bool isWhitespace(char c) { + return c == ' ' || c == '\t' || c == NEWLINE_CH; +} + +bool isLiteralType(char c) { + return c == '"' || c == '\''; +} + +bool isLetter(char c) { + return Common::isAlpha(c); +} + +bool isDigit(char c) { + return Common::isDigit(c); +} + +bool isStartChar(char c) { + return Common::isAlpha(c) || c == '_'; +} + +bool isIdChar(char c) { + return isStartChar(c) || isDigit(c); +} + +bool isLongOper(char c) { + return c == '<' || c == '>' || c == ':' || c == '+' || c == '-' || c == '*' + || c == '/' || c == '&' || c == '~'; +} + +bool isOperChar(char c) { + return isLongOper(c) || c == '=' || c == '.' || c == '^' || c == '?'; +} + +/** + * Performs a binary search on the given ordered array, passing back the + * index of the given string if it's in the array. + * Used for quickly finding an operator or reserved word. + * @param the_array ordered array of short strings + * @param elements number of elements in the array + * @param match_str string to match + * @param a_index Outputs the array index + * @returns true if match_str was an element in the_array; false otherwise + */ +static bool binary_search(const LookupType &the_array, int elements, + const ShortStringType &match_str, int &a_index) { + int left = 0, right = elements - 1, mid; + + do { + mid = (left + right) / 2; + if (match_str < the_array[mid]) + right = mid - 1; + else + left = mid + 1; + } while (match_str != the_array[mid] && left <= right); + + if (match_str != the_array[mid]) { + return false; + } else { + a_index = mid; + return true; + } +} + +/** + * Searches the given unordered xarray for a string matching the given + * string; if found, returns the index in the list of the string. If + * not found, adds it to the list. + * @param the_xarray xarray to be searched + * @param the_str string to be compared + * @returns The index of the_str in the_xarray. + */ +static int add_unique_str(XArrayType &the_xarray, const String &the_str) { + StringPtr new_str; + int i; + void *p; + + // Duplicate the given string + new_str = NewConstStr(the_str); + + if (the_xarray.empty()) { + append_to_xarray(the_xarray, (void *)new_str); + return the_xarray.size() - 1; + } else { + i = 1; + while (index_xarray(the_xarray, i, p) && *((StringPtr)p) != the_str) + ++i; + + if (*((StringPtr)p) == the_str) { + FreeConstStr(new_str); + return i; + } else { + append_to_xarray(the_xarray, (void *)new_str); + return the_xarray.size() - 1; + } + } +} + +/** + * Similar to the above, except that it is to be used when the strings are + * not expected to repeat much. + */ +static int add_non_unique_str(XArrayType &the_xarray, const String &the_str) { + append_to_xarray(the_xarray, (void *)NewConstStr(the_str)); + return the_xarray.size() - 1; +} + +bool get_token(progfile &f) { + StateType state; + bool more_chars; + char bracket, next_ch = '\0'; + String s; + + // Check for old token. f.newlines may have changed while an old token was unconsumed, + // so if the unconsumed token was a NEWLINE and f.newlines is false, we must continue + // and get another token; otherwise we jump out with what we have + if (!f.consumed) { + f.consumed = true; + + if (!((f.ttype == NEWLINE) && !f.newlines)) + return true; + } + + more_chars = true; + state = START; + + while (state != STOP) { + switch (state) { + case START: + if (f.readChar(next_ch)) { + state = DECIDE; + } else { + more_chars = false; + state = STOP; + } + break; + + case DECIDE: + if (!more_chars) + state = STOP; + else if (isWhitespace(next_ch)) + state = WHITE; + else if (isLiteralType(next_ch)) + state = LITERAL; + else if (isStartChar(next_ch)) + state = IDENTIFIER; + else if (isDigit(next_ch)) + state = NUMBER; + else if (isOperChar(next_ch)) + state = OPERATOR; + else { + // a single-character token + switch (next_ch) { + case '#': + state = COMMENT; + case ';': + if (!f.newlines) { + state = START; + } + else { + f.ttype = NEWLINE; + f.tnum = (int)NEWLINE_CH; + state = STOP; + } + default: + f.ttype = PUNCTUATION; + f.tnum = (int)next_ch; + state = STOP; + } + break; + } + break; + + case WHITE: + while (state == WHITE && isWhitespace(next_ch)) { + if (next_ch == NEWLINE_CH && f.newlines) { + f.ttype = NEWLINE; + state = STOP; + } else { + more_chars = f.readChar(next_ch); + } + } + if (state == WHITE) { + if (more_chars) + // decide on new non-white character + state = DECIDE; + else + state = STOP; + } + break; + + case COMMENT: + case QUOTE: + s = ""; + more_chars = f.readChar(next_ch); + while (more_chars && next_ch != NEWLINE_CH) { + s = s + next_ch; + more_chars = f.readChar(next_ch); + } + if (state == COMMENT) { + if (more_chars) + state = START; + else + state = STOP; + } else { + // quoted literal + f.unreadChar(next_ch); // leave \n for the next guy + f.ttype = QUOTE_LIT; + f.tnum = add_non_unique_str(g_vm->Literals, s); + state = STOP; + } + break; + + case LITERAL: + bracket = next_ch; + s = ""; + more_chars = f.readChar(next_ch); // start the loop + while (more_chars && next_ch != NEWLINE_CH && next_ch != bracket) { + if (next_ch == '\\') { + more_chars = f.readChar(next_ch); + switch (next_ch) { + case 't': + next_ch = '\t'; + break; + case 'b': + next_ch = '\x8'; + break; + case 'e': + next_ch = (char)27; + break; + case'n': + s = s + '\r'; + next_ch = '\n'; + break; + } + } + s = s + next_ch; + + more_chars = f.readChar(next_ch); + } + + if (next_ch != bracket) { + f.sourcePos(); + error("Unterminated literal"); + } else { + switch (bracket) { + case '"': + f.ttype = TEXT_LIT; + f.tnum = add_non_unique_str(g_vm->Literals, s); + break; + case '\'': + f.ttype = MESSAGE; + f.tnum = add_unique_str(g_vm->Vocabulary, s); + break; + default: + error("Programmer error: unknown literal type"); + break; + } + + state = STOP; + } + break; + + case IDENTIFIER: + s = ""; + while (isIdChar(next_ch)) { + s = s + next_ch; + more_chars = f.readChar(next_ch); + } + if (!isIdChar(next_ch)) + f.unreadChar(next_ch); + + // Check for reserved words or operators + if (binary_search(Reserved_Wds, NUM_RWORDS, s, f.tnum)) + f.ttype = RESERVED; + else if (binary_search(Operators, NUM_OPERS, s, f.tnum)) + f.ttype = OPER; + else { + f.ttype = IDENT; + f.tnum = add_ident(s); + } + + state = STOP; + break; + + case NUMBER: + s = ""; + while (more_chars && isDigit(next_ch)) { + s = s + next_ch; + more_chars = f.readChar(next_ch); + } + + if (!isDigit(next_ch)) + f.unreadChar(next_ch); + f.ttype = NUMERIC; + + f.tnum = atoi(s.c_str()); + state = STOP; + break; + + case OPERATOR: + s = ""; + + while (more_chars && isLongOper(next_ch) && s != ">>") { + // have to stop short with >> + s = s + next_ch; + more_chars = f.readChar(next_ch); + } + + if (s == ">>") { + f.unreadChar(next_ch); + state = QUOTE; + } else { + if (!isOperChar(next_ch)) + f.unreadChar(next_ch); + else + s = s + next_ch; + + state = STOP; + + if (s == ":") { + f.ttype = PUNCTUATION; + f.tnum = (int)':'; + } else if (!binary_search(Operators, NUM_OPERS, s, f.tnum)) { + f.sourcePos(); + error("Unknown operator %s", s.c_str()); + } else { + f.ttype = OPER; + } + } + break; + + default: + break; + } + } + + return more_chars; +} + +void write_token(AclType the_type, int the_number) { + StringPtr str_ptr; + IdRecPtr the_id_ptr; + void *p; + + switch (the_type) { + case IDENT: + if (the_number < 0) { + g_vm->write("an identifier"); + } + else { + g_vm->write("<identifier %d >: ", the_number); + if (index_ident(the_number, the_id_ptr)) + g_vm->write("\"%s\"", the_id_ptr->id_name); + } + break; + + case RESERVED: + if (the_number < 0) + g_vm->write("a reserved word"); + else + g_vm->write("reserved word \"%s\"", Reserved_Wds[the_number]); + break; + + case OPER: + if (the_number < 0) + g_vm->write("an operator"); + else + g_vm->write("operator \"%s\"", Operators[the_number]); + break; + + case PUNCTUATION: + g_vm->write("%c", (char)the_number); + break; + + case TEXT_LIT: + if (the_number < 0) + g_vm->write("a text literal"); + else if (index_xarray(g_vm->Literals, the_number, p)) { + str_ptr = (StringPtr)p; + g_vm->write("\"%s\"", str_ptr->c_str()); + } + else { + g_vm->write("<text literal %d >: ", the_number); + } + break; + + case MESSAGE: + if (the_number < 0) + g_vm->write("a message"); + else if (index_xarray(g_vm->Vocabulary, the_number, p)) { + str_ptr = (StringPtr)p; + g_vm->write("\'%s\'", str_ptr->c_str()); + } else { + g_vm->write("<message %d>: ", the_number); + } + break; + + case NUMERIC: + g_vm->write("the number %d", the_number); + break; + + default: + g_vm->write("<unknown token>"); + } +} + +} // End of namespace Archetype +} // End of namespace Glk diff --git a/engines/glk/archetype/token.h b/engines/glk/archetype/token.h new file mode 100644 index 0000000000..eda985eaf9 --- /dev/null +++ b/engines/glk/archetype/token.h @@ -0,0 +1,62 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef ARCHETYPE_TOKEN +#define ARCHETYPE_TOKEN + +#include "glk/archetype/misc.h" +#include "common/str.h" + +namespace Glk { +namespace Archetype { + +/* +extern bool isLiteralType(char c); +extern bool isLetter(char c); +extern bool isDigit(char c); +extern bool isStartChar(char c); +extern bool isIdChar(char c); +extern bool isLongOper(char c); +extern bool isOperChar(char c); +*/ + +/** + * State machine which passes out the next token from the file f_in. + * + * A token is a constant (including parse words and literal text), + * a reserved word, or an operator (including the curly braces). + * @param f The input file + * @returns True if there is a token available, false if the file is empty + */ +extern bool get_token(progfile &f); + +/** + * Given a token type andtoken number, writes out the proper string (without terminating the line). + * @param the_type the token type + * @param the_number the token number + */ +extern void write_token(AclType the_type, int the_number); + +} // End of namespace Archetype +} // End of namespace Glk + +#endif diff --git a/engines/glk/archetype/wrap.cpp b/engines/glk/archetype/wrap.cpp new file mode 100644 index 0000000000..6ef961f0d3 --- /dev/null +++ b/engines/glk/archetype/wrap.cpp @@ -0,0 +1,147 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "glk/archetype/wrap.h" +#include "glk/archetype/archetype.h" + +namespace Glk { +namespace Archetype { + +const int + MAXCOLS = 75, // leave room for punctuation + SAFETY_MARGIN = 3, + MAXROWS = 24, + + REVERSE_VID = 3, + BOLDFACE = 8; + +int Rows; +int cursor; + +void wrap_init() { + cursor_reset(); + Rows = 0; +} + +static void wrap_wait() { +#ifdef UNUSED + char ch; + + TextColor(BOLDFACE); TextBackground(REVERSE_VID); + write('Hit any key to continue...'); + ch := ReadKey; + write(chr(13)); + NormVideo; + ClrScr; //or ClrEol if you don't want the whole screen } + Rows : = 0 +#endif +} + +void wrapint(int i, bool terminate) { + String s = String::format("%d", i); + wrapout(s, terminate); +} + +void wrapout(const String &str, bool terminate) { + int thisline, maxchars, startnext; + String s = str; + + // 'thisline' starts out as the maximum number of characters that can be + // written before a newline; it gets trimmed back to being the number of + // characters from the string that are actually written on this line. } + maxchars = MAXCOLS - cursor; + + const char CHARS[7] = { '.', ',', ':', ';', ')', '-', '"' }; + for (int i = 0; i < 7; ++i) { + if (s[0] == CHARS[i]) { + maxchars += SAFETY_MARGIN; + break; + } + } + + thisline = maxchars; + while (thisline < (int)s.size()) { + while (thisline >= 0 && s[thisline] != ' ') + --thisline; + } + + // If we were unable to find a wrapping point then it means one of two + // things : a) the string is too long to fit on one line, andmust be + // split unnaturally; or b) we are near the end of a line andmust wrap + // the entire string; i.e.print nothing, finish the line andgo on + if (thisline == 0 && s.size() > MAXCOLS) + thisline = maxchars + 1; + + g_vm->writeln(s.substring(0, thisline - 1)); + ++Rows; + if (Rows >= MAXROWS) + wrap_wait(); + + startnext = thisline; + while (startnext < (int)s.size() && s[startnext] == ' ') { + ++startnext; + + s = s.substring(startnext, s.size()); + cursor = 1; + thisline = MAXCOLS - cursor; + } + + g_vm->write(s); + cursor += s.size(); + + if (terminate) { + g_vm->writeln(); + ++Rows; + if (Rows >= MAXROWS) + wrap_wait(); + cursor = 1; + } +} + +void wraperr(const String &s) { + if (cursor > 1) + g_vm->writeln(); + g_vm->writeln(s); + + String tmp; + for (int i = 1; i < cursor; ++i) + tmp += ' '; + g_vm->write(tmp); +} + +StringPtr ReadLine(bool full_line) { + String s; + + if (full_line) + g_vm->readln(s); + else + s = g_vm->ReadKey(); + + return NewDynStr(s); +} + +void cursor_reset() { + cursor = 1; +} + +} // End of namespace Archetype +} // End of namespace Glk diff --git a/engines/glk/archetype/wrap.h b/engines/glk/archetype/wrap.h new file mode 100644 index 0000000000..7195303b9f --- /dev/null +++ b/engines/glk/archetype/wrap.h @@ -0,0 +1,65 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef ARCHETYPE_WRAP +#define ARCHETYPE_WRAP + +#include "glk/archetype/misc.h" + +namespace Glk { +namespace Archetype { + +extern int Rows; + +/** + * When we want to wrap a number + */ +extern void wrapint(int i, bool terminate); + +/** + * Given a string, writes it out to screen, making sure that if it exceeds the screen columns, + * it is broken at natural word boundaries (i.e. white space) + */ +extern void wrapout(const String &s, bool terminate); + +/** + * Used for printing run-time errors. It will print the error message on + * a line by itself and pick up the next line at the exact same cursor position. + */ +extern void wraperr(const String &s); + +/** + * Hides the extra stack space necessary for performing a readln() so that + * it won't affect eval_expr + */ +extern StringPtr ReadLine(bool full_line); + +/** + * Used for directly resetting the cursor position by means other than + * physically wrapping it around + */ +extern void cursor_reset(); + +} // End of namespace Archetype +} // End of namespace Glk + +#endif diff --git a/engines/glk/module.mk b/engines/glk/module.mk index 449607cdf0..8915c669f3 100644 --- a/engines/glk/module.mk +++ b/engines/glk/module.mk @@ -146,7 +146,26 @@ endif ifdef ENABLE_GLK_ARCHETYPE MODULE_OBJS += \ archetype/archetype.o \ - archetype/detection.o + archetype/array.o \ + archetype/crypt.o \ + archetype/detection.o \ + archetype/error.o \ + archetype/expression.o \ + archetype/game_stat.o \ + archetype/heap_sort.o \ + archetype/id_table.o \ + archetype/interpreter.o \ + archetype/keywords.o \ + archetype/linked_list.o \ + archetype/misc.o \ + archetype/parser.o \ + archetype/saveload.o \ + archetype/semantic.o \ + archetype/string.o \ + archetype/sys_object.o \ + archetype/timestamp.o \ + archetype/token.o \ + archetype/wrap.o endif ifdef ENABLE_GLK_FROTZ |