Commit 7ce6b2c6 authored by twanvl's avatar twanvl

warn about functions ending with a statement instead of a return value

parent 7104e9a7
...@@ -373,13 +373,23 @@ enum Precedence ...@@ -373,13 +373,23 @@ enum Precedence
, PREC_NONE , PREC_NONE
}; };
enum ExprType
{ EXPR_VAR // A single variable, which could be converted to the left hand side of an assignment
, EXPR_STATEMENT // A 'statement', i.e. an expression that shouldn't be the last one
, EXPR_OTHER
, EXPR_FAILED
};
/// Parse an expression /// Parse an expression
/** @param input Read tokens from the input /** @param input Read tokens from the input
* @param scrip Add resulting instructions to the script * @param scrip Add resulting instructions to the script
* @param min_prec Minimum precedence level for operators * @param min_prec Minimum precedence level for operators
*
* @returns the type of expression
*
* NOTE: The net stack effect of an expression should be +1 * NOTE: The net stack effect of an expression should be +1
*/ */
void parseExpr(TokenIterator& input, Script& script, Precedence min_prec); ExprType parseExpr(TokenIterator& input, Script& script, Precedence min_prec);
/// Parse an expression, possibly with operators applied. Optionally adds an instruction at the end. /// Parse an expression, possibly with operators applied. Optionally adds an instruction at the end.
/** @param input Read tokens from the input /** @param input Read tokens from the input
...@@ -389,7 +399,7 @@ void parseExpr(TokenIterator& input, Script& script, Precedence min_prec); ...@@ -389,7 +399,7 @@ void parseExpr(TokenIterator& input, Script& script, Precedence min_prec);
* @param close_with_data Data for the instruction at the end * @param close_with_data Data for the instruction at the end
* NOTE: The net stack effect of an expression should be +1 * NOTE: The net stack effect of an expression should be +1
*/ */
void parseOper(TokenIterator& input, Script& script, Precedence min_prec, InstructionType close_with = I_NOP, int close_with_data = 0); ExprType parseOper(TokenIterator& input, Script& script, Precedence min_prec, InstructionType close_with = I_NOP, int close_with_data = 0);
/// Parse call arguments, "(...)" /// Parse call arguments, "(...)"
void parseCallArguments(TokenIterator& input, Script& script, vector<Variable>& arguments); void parseCallArguments(TokenIterator& input, Script& script, vector<Variable>& arguments);
...@@ -434,217 +444,220 @@ bool expectToken(TokenIterator& input, const Char* expect, const Token* opening ...@@ -434,217 +444,220 @@ bool expectToken(TokenIterator& input, const Char* expect, const Token* opening
} }
} }
void parseExpr(TokenIterator& input, Script& script, Precedence minPrec) { ExprType parseExpr(TokenIterator& input, Script& script, Precedence minPrec) {
// usually loop only once, unless we encounter newlines Token token = input.read();
while (true) { if (token == _("(")) {
Token token = input.read(); // Parentheses = grouping for precedence of expressions
if (token == _("(")) { parseOper(input, script, PREC_ALL);
// Parentheses = grouping for precedence of expressions expectToken(input, _(")"), &token);
parseOper(input, script, PREC_ALL); } else if (token == _("{")) {
expectToken(input, _(")"), &token); // {} = function block. Parse a new Script
} else if (token == _("{")) { intrusive_ptr<Script> subScript(new Script);
// {} = function block. Parse a new Script ExprType t = parseOper(input, *subScript, PREC_ALL);
intrusive_ptr<Script> subScript(new Script); if (t == EXPR_STATEMENT) {
parseOper(input, *subScript, PREC_ALL); input.add_error(_("Warning: last statement of a function should be an expression, i.e. it should return a result in all cases."));
expectToken(input, _("}"), &token); }
script.addInstruction(I_PUSH_CONST, subScript); expectToken(input, _("}"), &token);
} else if (token == _("[")) { script.addInstruction(I_PUSH_CONST, subScript);
// [] = list or map literal } else if (token == _("[")) {
unsigned int count = 0; // [] = list or map literal
Token t = input.peek(); unsigned int count = 0;
while (t != _("]") && t != TOK_EOF) { Token t = input.peek();
if (input.peek(2) == _(":") && (t.type == TOK_NAME || t.type == TOK_INT || t.type == TOK_STRING)) { while (t != _("]") && t != TOK_EOF) {
// name: ... if (input.peek(2) == _(":") && (t.type == TOK_NAME || t.type == TOK_INT || t.type == TOK_STRING)) {
script.addInstruction(I_PUSH_CONST, to_script(t.value)); // name: ...
input.read(); // skip the name script.addInstruction(I_PUSH_CONST, to_script(t.value));
input.read(); // and the : input.read(); // skip the name
} else { input.read(); // and the :
// implicit numbered element } else {
script.addInstruction(I_PUSH_CONST, script_nil); // implicit numbered element
} script.addInstruction(I_PUSH_CONST, script_nil);
parseOper(input, script, PREC_AND); }
++count; parseOper(input, script, PREC_AND);
++count;
t = input.peek();
if (t == _(",")) {
// Comma separating the elements
input.read();
t = input.peek(); t = input.peek();
if (t == _(",")) {
// Comma separating the elements
input.read();
t = input.peek();
}
} }
expectToken(input, _("]"), &token); }
script.addInstruction(I_MAKE_OBJECT, count); expectToken(input, _("]"), &token);
} else if (minPrec <= PREC_UNARY && token == _("-")) { script.addInstruction(I_MAKE_OBJECT, count);
parseOper(input, script, PREC_UNARY, I_UNARY, I_NEGATE); // unary negation } else if (minPrec <= PREC_UNARY && token == _("-")) {
} else if (token == TOK_NAME) { parseOper(input, script, PREC_UNARY, I_UNARY, I_NEGATE); // unary negation
if (minPrec <= PREC_UNARY && token == _("not")) { } else if (token == TOK_NAME) {
parseOper(input, script, PREC_UNARY, I_UNARY, I_NOT); // unary not if (minPrec <= PREC_UNARY && token == _("not")) {
} else if (token == _("true")) { parseOper(input, script, PREC_UNARY, I_UNARY, I_NOT); // unary not
script.addInstruction(I_PUSH_CONST, script_true); // boolean constant : true } else if (token == _("true")) {
} else if (token == _("false")) { script.addInstruction(I_PUSH_CONST, script_true); // boolean constant : true
script.addInstruction(I_PUSH_CONST, script_false); // boolean constant : false } else if (token == _("false")) {
} else if (token == _("nil")) { script.addInstruction(I_PUSH_CONST, script_false); // boolean constant : false
script.addInstruction(I_PUSH_CONST, script_nil); // universal constant : nil } else if (token == _("nil")) {
} else if (token == _("if")) { script.addInstruction(I_PUSH_CONST, script_nil); // universal constant : nil
// if AAA then BBB else CCC } else if (token == _("if")) {
parseOper(input, script, PREC_AND); // AAA // if AAA then BBB else CCC
unsigned jmpElse = script.addInstruction(I_JUMP_IF_NOT); // jnz lbl_else parseOper(input, script, PREC_AND); // AAA
expectToken(input, _("then")); // then unsigned jmpElse = script.addInstruction(I_JUMP_IF_NOT); // jnz lbl_else
parseOper(input, script, PREC_SET); // BBB expectToken(input, _("then")); // then
unsigned jmpEnd = script.addInstruction(I_JUMP); // jump lbl_end parseOper(input, script, PREC_SET); // BBB
script.comeFrom(jmpElse); // lbl_else: unsigned jmpEnd = script.addInstruction(I_JUMP); // jump lbl_end
if (input.peek() == _("else")) { //else script.comeFrom(jmpElse); // lbl_else:
input.read(); bool has_else = input.peek() == _("else"); //else
parseOper(input, script, PREC_SET); // CCC if (has_else) {
} else { input.read();
script.addInstruction(I_PUSH_CONST, script_nil); parseOper(input, script, PREC_SET); // CCC
} } else {
script.comeFrom(jmpEnd); // lbl_end: script.addInstruction(I_PUSH_CONST, script_nil);
} else if (token == _("for")) { }
// the loop body should have a net stack effect of 0, but the entire expression of +1 script.comeFrom(jmpEnd); // lbl_end:
// solution: add all results from the body, start with nil return has_else ? EXPR_OTHER : EXPR_STATEMENT;
bool is_each = input.peek() == _("each"); } else if (token == _("for")) {
if (is_each) { // the loop body should have a net stack effect of 0, but the entire expression of +1
// for each AAA(:BBB) in CCC do EEE // solution: add all results from the body, start with nil
input.read(); // each? bool is_each = input.peek() == _("each");
} else { if (is_each) {
// for AAA(:BBB) from CCC to DDD do EEE // for each AAA(:BBB) in CCC do EEE
} input.read(); // each?
// name } else {
Token name = input.read(); // AAA // for AAA(:BBB) from CCC to DDD do EEE
}
// name
Token name = input.read(); // AAA
if (name != TOK_NAME) {
input.expected(_("name"));
}
Variable var = string_to_variable(name.value);
// key:value?
bool with_key = input.peek() == _(":");
Variable key = (Variable)-1;
if (with_key) {
input.read(); // :
name = input.read(); // BBB
if (name != TOK_NAME) { if (name != TOK_NAME) {
input.expected(_("name")); input.expected(_("name"));
} }
Variable var = string_to_variable(name.value); key = string_to_variable(name.value);
// key:value? swap(var,key);
bool with_key = input.peek() == _(":"); }
Variable key = (Variable)-1; // iterator
if (with_key) { if (is_each) {
input.read(); // : expectToken(input, _("in")); // in
name = input.read(); // BBB parseOper(input, script, PREC_AND); // CCC
if (name != TOK_NAME) { script.addInstruction(I_UNARY, I_ITERATOR_C); // iterator_collection
input.expected(_("name")); } else {
} expectToken(input, _("from")); // from
key = string_to_variable(name.value); parseOper(input, script, PREC_AND); // CCC
swap(var,key); expectToken(input, _("to")); // to
} parseOper(input, script, PREC_AND); // DDD
// iterator script.addInstruction(I_BINARY, I_ITERATOR_R); // iterator_range
if (is_each) { }
expectToken(input, _("in")); // in script.addInstruction(I_PUSH_CONST, script_nil); // push nil
parseOper(input, script, PREC_AND); // CCC unsigned lblStart = script.addInstruction(with_key
script.addInstruction(I_UNARY, I_ITERATOR_C); // iterator_collection ? I_LOOP_WITH_KEY // lbl_start: loop_with_key lbl_end
} else { : I_LOOP); // lbl_start: loop lbl_end
expectToken(input, _("from")); // from expectToken(input, _("do")); // do
parseOper(input, script, PREC_AND); // CCC if (with_key) {
expectToken(input, _("to")); // to script.addInstruction(I_SET_VAR, key); // set key_name
parseOper(input, script, PREC_AND); // DDD script.addInstruction(I_POP); // pop
script.addInstruction(I_BINARY, I_ITERATOR_R); // iterator_range }
} script.addInstruction(I_SET_VAR, var); // set name
script.addInstruction(I_PUSH_CONST, script_nil); // push nil script.addInstruction(I_POP); // pop
unsigned lblStart = script.addInstruction(with_key parseOper(input, script, PREC_SET, I_BINARY, I_ADD); // EEE; add
? I_LOOP_WITH_KEY // lbl_start: loop_with_key lbl_end script.addInstruction(I_JUMP, lblStart); // jump lbl_start
: I_LOOP); // lbl_start: loop lbl_end script.comeFrom(lblStart); // lbl_end:
expectToken(input, _("do")); // do } else if (token == _("rgb")) {
if (with_key) { // rgb(r, g, b)
script.addInstruction(I_SET_VAR, key); // set key_name expectToken(input, _("("));
script.addInstruction(I_POP); // pop parseOper(input, script, PREC_ALL); // r
} expectToken(input, _(","));
script.addInstruction(I_SET_VAR, var); // set name parseOper(input, script, PREC_ALL); // g
script.addInstruction(I_POP); // pop expectToken(input, _(","));
parseOper(input, script, PREC_SET, I_BINARY, I_ADD); // EEE; add parseOper(input, script, PREC_ALL); // b
script.addInstruction(I_JUMP, lblStart); // jump lbl_start expectToken(input, _(")"));
script.comeFrom(lblStart); // lbl_end: script.addInstruction(I_TERNARY, I_RGB);
} else if (token == _("rgb")) { } else if (token == _("rgba")) {
// rgb(r, g, b) // rgba(r, g, b, a)
expectToken(input, _("(")); expectToken(input, _("("));
parseOper(input, script, PREC_ALL); // r parseOper(input, script, PREC_ALL); // r
expectToken(input, _(",")); expectToken(input, _(","));
parseOper(input, script, PREC_ALL); // g parseOper(input, script, PREC_ALL); // g
expectToken(input, _(",")); expectToken(input, _(","));
parseOper(input, script, PREC_ALL); // b parseOper(input, script, PREC_ALL); // b
expectToken(input, _(")")); expectToken(input, _(","));
script.addInstruction(I_TERNARY, I_RGB); parseOper(input, script, PREC_ALL); // a
} else if (token == _("rgba")) { expectToken(input, _(")"));
// rgba(r, g, b, a) script.addInstruction(I_QUATERNARY, I_RGBA);
expectToken(input, _("(")); } else if (token == _("min") || token == _("max")) {
parseOper(input, script, PREC_ALL); // r // min(x,y,z,...)
expectToken(input, _(",")); unsigned int op = token == _("min") ? I_MIN : I_MAX;
parseOper(input, script, PREC_ALL); // g expectToken(input, _("("));
expectToken(input, _(",")); parseOper(input, script, PREC_ALL); // first
parseOper(input, script, PREC_ALL); // b while(input.peek() == _(",")) {
expectToken(input, _(",")); expectToken(input, _(","));
parseOper(input, script, PREC_ALL); // a parseOper(input, script, PREC_ALL); // second, third, etc.
expectToken(input, _(")")); script.addInstruction(I_BINARY, op);
script.addInstruction(I_QUATERNARY, I_RGBA); }
} else if (token == _("min") || token == _("max")) { expectToken(input, _(")"), &token);
// min(x,y,z,...) } else if (token == _("assert")) {
unsigned int op = token == _("min") ? I_MIN : I_MAX; // assert(condition)
expectToken(input, _("(")); expectToken(input, _("("));
parseOper(input, script, PREC_ALL); // first size_t start = input.peek().pos;
while(input.peek() == _(",")) { int line = input.getLineNumber();
expectToken(input, _(",")); size_t function_pos = script.getConstants().size();
parseOper(input, script, PREC_ALL); // second, third, etc. script.addInstruction(I_PUSH_CONST, script_warning);
script.addInstruction(I_BINARY, op); parseOper(input, script, PREC_ALL); // condition
} size_t end = input.peek().pos;
expectToken(input, _(")"), &token); String message = String::Format(_("Assertion failure on line %d:\n expected: "), line) + input.getSourceCode(start,end);
} else if (token == _("assert")) { expectToken(input, _(")"), &token);
// assert(condition) if (script.getInstructions().back().instr == I_BINARY && script.getInstructions().back().instr2 == I_EQ) {
expectToken(input, _("(")); // compile "assert(x == y)" into
size_t start = input.peek().pos; // warning_if_neq("condition", _1: x, _2: y)
int line = input.getLineNumber(); message += _("\n found: ");
size_t function_pos = script.getConstants().size(); script.getConstants()[function_pos] = script_warning_if_neq;
script.addInstruction(I_PUSH_CONST, script_warning); script.getInstructions().pop_back(); // POP == instruction
parseOper(input, script, PREC_ALL); // condition script.addInstruction(I_PUSH_CONST, message); // push "condition"
size_t end = input.peek().pos; script.addInstruction(I_CALL, 3); // call
String message = String::Format(_("Assertion failure on line %d:\n expected: "), line) + input.getSourceCode(start,end); script.addInstruction(I_NOP, SCRIPT_VAR__1); // (_1:)
expectToken(input, _(")"), &token); script.addInstruction(I_NOP, SCRIPT_VAR__2); // (_2:)
if (script.getInstructions().back().instr == I_BINARY && script.getInstructions().back().instr2 == I_EQ) { script.addInstruction(I_NOP, SCRIPT_VAR_input); // (input:)
// compile "assert(x == y)" into
// warning_if_neq("condition", _1: x, _2: y)
message += _("\n found: ");
script.getConstants()[function_pos] = script_warning_if_neq;
script.getInstructions().pop_back(); // POP == instruction
script.addInstruction(I_PUSH_CONST, message); // push "condition"
script.addInstruction(I_CALL, 3); // call
script.addInstruction(I_NOP, SCRIPT_VAR__1); // (_1:)
script.addInstruction(I_NOP, SCRIPT_VAR__2); // (_2:)
script.addInstruction(I_NOP, SCRIPT_VAR_input); // (input:)
} else {
// compile into: warning("condition", condition: not condition)
script.addInstruction(I_UNARY, I_NOT); // not
script.addInstruction(I_PUSH_CONST, message); // push "condition"
script.addInstruction(I_CALL, 2); // call
script.addInstruction(I_NOP, SCRIPT_VAR_condition); // (condition:)
script.addInstruction(I_NOP, SCRIPT_VAR_input); // (input:)
}
} else { } else {
// variable // compile into: warning("condition", condition: not condition)
Variable var = string_to_variable(token.value); script.addInstruction(I_UNARY, I_NOT); // not
script.addInstruction(I_GET_VAR, var); script.addInstruction(I_PUSH_CONST, message); // push "condition"
script.addInstruction(I_CALL, 2); // call
script.addInstruction(I_NOP, SCRIPT_VAR_condition); // (condition:)
script.addInstruction(I_NOP, SCRIPT_VAR_input); // (input:)
} }
} else if (token == TOK_INT) { return EXPR_STATEMENT;
long l = 0;
//l = lexical_cast<long>(token.value);
token.value.ToLong(&l);
script.addInstruction(I_PUSH_CONST, to_script(l));
} else if (token == TOK_DOUBLE) {
double d = 0;
//d = lexical_cast<double>(token.value);
token.value.ToDouble(&d);
script.addInstruction(I_PUSH_CONST, to_script(d));
} else if (token == TOK_STRING) {
script.addInstruction(I_PUSH_CONST, to_script(token.value));
} else { } else {
input.expected(_("expression")); // variable
return; Variable var = string_to_variable(token.value);
script.addInstruction(I_GET_VAR, var);
return EXPR_VAR;
} }
break; } else if (token == TOK_INT) {
long l = 0;
//l = lexical_cast<long>(token.value);
token.value.ToLong(&l);
script.addInstruction(I_PUSH_CONST, to_script(l));
} else if (token == TOK_DOUBLE) {
double d = 0;
//d = lexical_cast<double>(token.value);
token.value.ToDouble(&d);
script.addInstruction(I_PUSH_CONST, to_script(d));
} else if (token == TOK_STRING) {
script.addInstruction(I_PUSH_CONST, to_script(token.value));
} else {
script.addInstruction(I_PUSH_CONST, script_nil);
input.expected(_("expression"));
return EXPR_FAILED;
} }
return EXPR_OTHER;
} }
void parseOper(TokenIterator& input, Script& script, Precedence minPrec, InstructionType closeWith, int closeWithData) { ExprType parseOper(TokenIterator& input, Script& script, Precedence minPrec, InstructionType closeWith, int closeWithData) {
size_t added = script.getInstructions().size(); // number of instructions added ExprType type = parseExpr(input, script, minPrec); // first argument
parseExpr(input, script, minPrec); // first argument
added = script.getInstructions().size() - added;
// read any operators after an expression // read any operators after an expression
// EBNF: expr = expr | expr oper expr // EBNF: expr = expr | expr oper expr
// without left recursion: expr = expr (oper expr)* // without left recursion: expr = expr (oper expr)*
...@@ -656,6 +669,7 @@ void parseOper(TokenIterator& input, Script& script, Precedence minPrec, Instruc ...@@ -656,6 +669,7 @@ void parseOper(TokenIterator& input, Script& script, Precedence minPrec, Instruc
input.putBack(); input.putBack();
break; break;
} }
if (minPrec <= PREC_SEQ && token==_(";")) { if (minPrec <= PREC_SEQ && token==_(";")) {
Token next = input.peek(1); Token next = input.peek(1);
if (next == TOK_RPAREN || next == TOK_EOF) { if (next == TOK_RPAREN || next == TOK_EOF) {
...@@ -663,12 +677,12 @@ void parseOper(TokenIterator& input, Script& script, Precedence minPrec, Instruc ...@@ -663,12 +677,12 @@ void parseOper(TokenIterator& input, Script& script, Precedence minPrec, Instruc
break; break;
} }
script.addInstruction(I_POP); // discard result of first expression script.addInstruction(I_POP); // discard result of first expression
parseOper(input, script, PREC_SET); type = parseOper(input, script, PREC_SET);
} else if (minPrec <= PREC_SET && token==_(":=")) { } else if (minPrec <= PREC_SET && token==_(":=")) {
// We made a mistake, the part before the := should be a variable name, // We made a mistake, the part before the := should be a variable name,
// not an expression. Remove that instruction. // not an expression. Remove that instruction.
Instruction& instr = script.getInstructions().back(); Instruction& instr = script.getInstructions().back();
if (added != 1 || instr.instr != I_GET_VAR) { if (type != EXPR_VAR || instr.instr != I_GET_VAR) {
input.add_error(_("Can only assign to variables")); input.add_error(_("Can only assign to variables"));
} }
script.getInstructions().pop_back(); script.getInstructions().pop_back();
...@@ -790,16 +804,19 @@ void parseOper(TokenIterator& input, Script& script, Precedence minPrec, Instruc ...@@ -790,16 +804,19 @@ void parseOper(TokenIterator& input, Script& script, Precedence minPrec, Instruc
// only if we don't match another token! // only if we don't match another token!
input.putBack(); input.putBack();
script.addInstruction(I_POP); script.addInstruction(I_POP);
parseOper(input, script, PREC_SET); type = parseOper(input, script, PREC_SET);
} else { } else {
input.putBack(); input.putBack();
break; break;
} }
if (type == EXPR_VAR) type = EXPR_OTHER; // var only applies to single variables, not to things with operators
} }
// add closing instruction // add closing instruction
if (closeWith != I_NOP) { if (closeWith != I_NOP) {
script.addInstruction(closeWith, closeWithData); script.addInstruction(closeWith, closeWithData);
} }
return type;
} }
void parseCallArguments(TokenIterator& input, Script& script, vector<Variable>& arguments) { void parseCallArguments(TokenIterator& input, Script& script, vector<Variable>& arguments) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment