diff --git a/doc/function/assert.txt b/doc/function/assert.txt new file mode 100644 index 00000000..0a7cafe4 --- /dev/null +++ b/doc/function/assert.txt @@ -0,0 +1,16 @@ +Function: assert + +--Usage-- +> assert(condition) + +Check that the condition is @true@. Shows a warning message if it is not. + +Note: @assert@ is a special built-in keyword, so that the error message can include the condition. + +--Parameters-- +! Parameter Type Description +| @input@ [[type:boolean]] Condition to check. + +--Examples-- +> assert(1 + 1 == 2) == nil # nothing happens +> assert(1 * 1 == 2) == nil # An error message is shown diff --git a/doc/function/index.txt b/doc/function/index.txt index dafb0ecc..1c81b8f7 100644 --- a/doc/function/index.txt +++ b/doc/function/index.txt @@ -87,3 +87,4 @@ These functions are built into the program, other [[type:function]]s can be defi ! Other functions <<< | [[fun:trace]] Output a message for debugging purposes. +| [[fun:assert]] Check a condition for debugging purposes. diff --git a/src/script/context.cpp b/src/script/context.cpp index bac67cbf..82f3b121 100644 --- a/src/script/context.cpp +++ b/src/script/context.cpp @@ -201,7 +201,14 @@ void Context::setVariable(const String& name, const ScriptValueP& value) { setVariable(string_to_variable(name), value); } +#ifdef _DEBUG + extern vector variable_names; +#endif + void Context::setVariable(Variable name, const ScriptValueP& value) { + #ifdef _DEBUG + assert((size_t)name < variable_names.size()); + #endif VariableValue& var = variables[name]; if (var.level < level) { // keep shadow copy diff --git a/src/script/functions/basic.cpp b/src/script/functions/basic.cpp index 6608a76d..b2763bba 100644 --- a/src/script/functions/basic.cpp +++ b/src/script/functions/basic.cpp @@ -31,6 +31,12 @@ SCRIPT_FUNCTION(trace) { SCRIPT_RETURN(input); } +SCRIPT_FUNCTION(warning) { + SCRIPT_PARAM_C(String, input); + handle_warning(input, true); + return script_nil; +} + // ----------------------------------------------------------------------------- : String stuff // convert a string to upper case diff --git a/src/script/parser.cpp b/src/script/parser.cpp index 65802bde..68fd9c6f 100644 --- a/src/script/parser.cpp +++ b/src/script/parser.cpp @@ -22,6 +22,8 @@ DECLARE_TYPEOF_COLLECTION(Variable); String read_utf8_line(wxInputStream& input, bool eat_bom = true, bool until_eof = false); +extern ScriptValueP script_warning; + // ----------------------------------------------------------------------------- : Tokenizing : class enum TokenType @@ -75,6 +77,11 @@ class TokenIterator { */ void putBack(); + /// Get a section of source code + String getSourceCode(size_t start, size_t end); + /// Get the current line number + int getLineNumber(); + private: String input; size_t pos; @@ -318,6 +325,15 @@ void TokenIterator::expected(const String& expected, const Token* opening) { } +String TokenIterator::getSourceCode(size_t start, size_t end) { + start = min(start, input.size()); + end = min(end, input.size()); + return input.substr(start, end-start); +} +int TokenIterator::getLineNumber() { + return line_number(peek(0).pos, input); +} + // ----------------------------------------------------------------------------- : Parsing @@ -450,24 +466,20 @@ void parseExpr(TokenIterator& input, Script& script, Precedence minPrec) { script.addInstruction(I_PUSH_CONST, script_nil); // universal constant : nil } else if (token == _("if")) { // if AAA then BBB else CCC - unsigned int jmpElse, jmpEnd; - parseOper(input, script, PREC_AND); // AAA - jmpElse = script.getLabel(); // jmp_else: - script.addInstruction(I_JUMP_IF_NOT, INVALID_ADDRESS); // jnz lbl_else - expectToken(input, _("then")); // then - parseOper(input, script, PREC_SET); // BBB - jmpEnd = script.getLabel(); // jmp_end: - script.addInstruction(I_JUMP, INVALID_ADDRESS); // jump lbl_end - script.comeFrom(jmpElse); // lbl_else: - if (input.peek() == _("else")) { // else + parseOper(input, script, PREC_AND); // AAA + unsigned jmpElse = script.addInstruction(I_JUMP_IF_NOT); // jnz lbl_else + expectToken(input, _("then")); // then + parseOper(input, script, PREC_SET); // BBB + unsigned jmpEnd = script.addInstruction(I_JUMP); // jump lbl_end + script.comeFrom(jmpElse); // lbl_else: + if (input.peek() == _("else")) { //else input.read(); - parseOper(input, script, PREC_SET); // CCC + parseOper(input, script, PREC_SET); // CCC } else { script.addInstruction(I_PUSH_CONST, script_nil); } - script.comeFrom(jmpEnd); // lbl_end: + script.comeFrom(jmpEnd); // lbl_end: } else if (token == _("for")) { - unsigned int lblStart; // the loop body should have a net stack effect of 0, but the entire expression of +1 // solution: add all results from the body, start with nil if (input.peek() == _("each")) { @@ -481,8 +493,7 @@ void parseExpr(TokenIterator& input, Script& script, Precedence minPrec) { parseOper(input, script, PREC_AND); // BBB script.addInstruction(I_UNARY, I_ITERATOR_C); // iterator_collection script.addInstruction(I_PUSH_CONST, script_nil); // push nil - lblStart = script.getLabel(); // lbl_start: - script.addInstruction(I_LOOP, INVALID_ADDRESS); // loop + unsigned lblStart = script.addInstruction(I_LOOP); // lbl_start: loop lbl_end expectToken(input, _("do")); // do script.addInstruction(I_SET_VAR, string_to_variable(name.value));// set name @@ -499,8 +510,7 @@ void parseExpr(TokenIterator& input, Script& script, Precedence minPrec) { parseOper(input, script, PREC_AND); // CCC script.addInstruction(I_BINARY, I_ITERATOR_R); // iterator_range script.addInstruction(I_PUSH_CONST, script_nil); // push nil - lblStart = script.getLabel(); // lbl_start: - script.addInstruction(I_LOOP, INVALID_ADDRESS); // loop + unsigned lblStart = script.addInstruction(I_LOOP); // lbl_start: loop lbl_end expectToken(input, _("do")); // do script.addInstruction(I_SET_VAR, string_to_variable(name.value));// set name @@ -542,6 +552,25 @@ void parseExpr(TokenIterator& input, Script& script, Precedence minPrec) { script.addInstruction(I_BINARY, op); } expectToken(input, _(")"), &token); + } else if (token == _("assert")) { + // assert(condition) + expectToken(input, _("(")); + size_t start = input.peek().pos; + int line = input.getLineNumber(); + parseOper(input, script, PREC_ALL); // condition + size_t end = input.peek().pos; + String message = String::Format(_("Assertion failure on line %d: "), line) + input.getSourceCode(start,end); + expectToken(input, _(")"), &token); + // compile into: if condition then nil else warning("condition") + unsigned jmpElse = script.addInstruction(I_JUMP_IF_NOT); // jnz lbl_else + script.addInstruction(I_PUSH_CONST, script_nil); // push nil + unsigned jmpEnd = script.addInstruction(I_JUMP); // jump lbl_end + script.comeFrom(jmpElse); // lbl_else: + script.addInstruction(I_PUSH_CONST, script_warning); // push warning + script.addInstruction(I_PUSH_CONST, message); // push "condition" + script.addInstruction(I_CALL, 1); // call + script.addInstruction(I_NOP, SCRIPT_VAR_input); // (input:) + script.comeFrom(jmpEnd); // lbl_end: } else { // variable Variable var = string_to_variable(token.value); diff --git a/src/script/script.cpp b/src/script/script.cpp index 678d6ee8..3f012d80 100644 --- a/src/script/script.cpp +++ b/src/script/script.cpp @@ -86,10 +86,14 @@ ScriptValueP Script::eval(Context& ctx) const { ScriptValueP Script::dependencies(Context& ctx, const Dependency& dep) const { return ctx.dependencies(dep, *this); } - -void Script::addInstruction(InstructionType t) { - Instruction i = {t, {0}}; + +static const unsigned int INVALID_ADDRESS = 0x03FFFFFF; + +unsigned int Script::addInstruction(InstructionType t) { + assert( t == I_JUMP || t == I_JUMP_IF_NOT || t == I_LOOP); + Instruction i = {t, {INVALID_ADDRESS}}; instructions.push_back(i); + return getLabel() - 1; } void Script::addInstruction(InstructionType t, unsigned int d) { // Don't optimize ...I_PUSH_CONST x; I_MEMBER... to I_MEMBER_C @@ -198,8 +202,7 @@ String Script::dumpInstr(unsigned int pos, Instruction i) const { // arg switch (i.instr) { case I_PUSH_CONST: case I_MEMBER_C: // const - ret += _("\t") + constants[i.data]->toString(); - ret += _("\t(") + constants[i.data]->typeName() + _(")"); + ret += _("\t") + constants[i.data]->typeName(); break; case I_JUMP: case I_JUMP_IF_NOT: case I_LOOP: case I_MAKE_OBJECT: case I_CALL: case I_CLOSURE: // int ret += String::Format(_("\t%d"), i.data); diff --git a/src/script/script.hpp b/src/script/script.hpp index 043aa121..b8c33283 100644 --- a/src/script/script.hpp +++ b/src/script/script.hpp @@ -103,7 +103,6 @@ struct Instruction { QuaternaryInstructionType instr4 : 27; }; }; -static const unsigned int INVALID_ADDRESS = 0x03FFFFFF; // ----------------------------------------------------------------------------- : Variables @@ -157,8 +156,8 @@ class Script : public ScriptValue { virtual ScriptValueP eval(Context& ctx) const; virtual ScriptValueP dependencies(Context& ctx, const Dependency&) const; - /// Add an instruction with no data - void addInstruction(InstructionType t); + /// Add a jump instruction, later comeFrom should be called on the returned value + unsigned int addInstruction(InstructionType t); /// Add an instruction with integer data void addInstruction(InstructionType t, unsigned int d); /// Add an instruction with constant data @@ -169,7 +168,6 @@ class Script : public ScriptValue { /// Update an instruction to point to the current position /** The instruction at pos must be a jumping instruction, it is changed so the current position * 'comes from' pos (in addition to other control flow). - * The position must be a label just before the instruction to be updated. */ void comeFrom(unsigned int pos); /// Get the current instruction position diff --git a/tools/website/drupal/mse-drupal-modules/highlight.inc b/tools/website/drupal/mse-drupal-modules/highlight.inc index 4988e511..511a8455 100644 --- a/tools/website/drupal/mse-drupal-modules/highlight.inc +++ b/tools/website/drupal/mse-drupal-modules/highlight.inc @@ -69,7 +69,9 @@ $built_in_functions = array( 'copy_file' =>'', 'write_text_file' =>'', 'write_image_file' =>'', + // other 'trace' =>'', + 'assert' =>'', );