diff --git a/src/compiler.h b/src/compiler.h index bee2c8b9..81b6a917 100644 --- a/src/compiler.h +++ b/src/compiler.h @@ -593,6 +593,7 @@ __LISTCOMP: } consume(TK("@indent")); while (peek() != TK("@dedent")) { + matchNewLines(); (this->*action)(); matchNewLines(); } @@ -624,17 +625,13 @@ __LISTCOMP: void parsePrecedence(Precedence precedence) { lexToken(); GrammarFn prefix = rules[parser->previous.type].prefix; - - if (prefix == nullptr) syntaxError("expected an expression"); - + if (prefix == nullptr) syntaxError(_Str("expected an expression, but got ") + TK_STR(parser->previous.type)); (this->*prefix)(); while (rules[peek()].precedence >= precedence) { lexToken(); _TokenType op = parser->previous.type; GrammarFn infix = rules[op].infix; - if(infix == nullptr) { - throw UnexpectedError("(infix == nullptr) is true"); - } + if(infix == nullptr) throw UnexpectedError("(infix == nullptr) is true"); (this->*infix)(); } } @@ -874,23 +871,18 @@ __LITERAL_EXIT: } } - /**** Error Reporter ***/ + /***** Error Reporter *****/ LineSnapshot getLineSnapshot(){ - LineSnapshot snapshot; - snapshot.filename = path; - snapshot.lineno = parser->previous.line; - snapshot.source = ""; - return snapshot; + const char* line_start = parser->line_starts.at(parser->previous.line-1); + const char* i = line_start; + while(*i != '\n' && *i != '\0') i++; + return LineSnapshot(path, parser->previous.line, _Str(line_start, i-line_start)); } void syntaxError(_Str msg){ throw CompileError("SyntaxError", msg, getLineSnapshot()); } - void unknownSyntaxError(){ - throw CompileError("SyntaxError", "invalid syntax", getLineSnapshot()); - } - void indentationError(_Str msg){ throw CompileError("IndentationError", msg, getLineSnapshot()); } diff --git a/src/error.h b/src/error.h index e141bf1f..b23df1a8 100644 --- a/src/error.h +++ b/src/error.h @@ -25,10 +25,12 @@ public: } }; -struct LineSnapshot { +class LineSnapshot { _Str filename; int lineno; _Str source; +public: + LineSnapshot(_Str filename, int lineno, _Str source="") : filename(filename), lineno(lineno), source(source) {} _Str str() const { _StrStream ss; diff --git a/src/main.cpp b/src/main.cpp index a8f53abc..e898762d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -48,7 +48,7 @@ void REPL(){ while(true){ CompileMode mode = SINGLE_MODE; - vm->printFn(need_more_lines ? "... " : ">>> "); + vm->_stdout(need_more_lines ? "... " : ">>> "); std::string line; std::getline(std::cin, line); @@ -86,8 +86,8 @@ __NOT_ENOUGH_LINES: buffer += '\n'; need_more_lines = ne->isClassDef ? 3 : 2; }else{ - vm->printFn(e.what()); - vm->printFn("\n"); + vm->_stdout(e.what()); + vm->_stdout("\n"); vm->cleanError(); } } diff --git a/src/parser.h b/src/parser.h index b6344441..fd3f190d 100644 --- a/src/parser.h +++ b/src/parser.h @@ -87,10 +87,11 @@ struct Parser { const char* source; //< Currently compiled source. const char* token_start; //< Start of the currently parsed token. const char* current_char; //< Current char position in the source. - const char* line_start; //< Start of the current line. int current_line = 1; + std::vector line_starts; + Token previous, current; std::queue nexts; @@ -159,7 +160,7 @@ struct Parser { current_char++; if (c == '\n'){ current_line++; - line_start = current_char; + line_starts.push_back(current_char); } return c; } @@ -235,8 +236,7 @@ struct Parser { this->source = source; this->token_start = source; this->current_char = source; - this->line_start = source; - + this->line_starts.push_back(source); this->nexts.push(Token{TK("@sof"), token_start, 0, current_line}); this->indents.push(0); } diff --git a/src/pocketpy.h b/src/pocketpy.h index 0c44d9a3..cb7d2fe9 100644 --- a/src/pocketpy.h +++ b/src/pocketpy.h @@ -11,7 +11,7 @@ inline int _round(float f){ #define BIND_NUM_ARITH_OPT(name, op) \ _vm->bindMethodMulti({"int","float"}, #name, [](VM* vm, PyVarList args){ \ if(!vm->isIntOrFloat(args[0], args[1])) \ - vm->_error("TypeError", "unsupported operand type(s) for " #op ); \ + vm->typeError("unsupported operand type(s) for " #op ); \ if(args[0]->isType(vm->_tp_int) && args[1]->isType(vm->_tp_int)){ \ return vm->PyInt(vm->PyInt_AS_C(args[0]) op vm->PyInt_AS_C(args[1])); \ }else{ \ @@ -23,7 +23,7 @@ inline int _round(float f){ _vm->bindMethodMulti({"int","float"}, #name, [](VM* vm, PyVarList args){ \ if(!vm->isIntOrFloat(args[0], args[1])){ \ if constexpr(fallback) return vm->PyBool(args[0] op args[1]); \ - vm->_error("TypeError", "unsupported operand type(s) for " #op ); \ + vm->typeError("unsupported operand type(s) for " #op ); \ } \ return vm->PyBool(vm->numToFloat(args[0]) op vm->numToFloat(args[1])); \ }); @@ -44,14 +44,14 @@ void __initializeBuiltinFunctions(VM* _vm) { #undef BIND_NUM_LOGICAL_OPT _vm->bindBuiltinFunc("print", [](VM* vm, PyVarList args) { - for (auto& arg : args) vm->printFn(vm->PyStr_AS_C(vm->asStr(arg)) + " "); - vm->printFn("\n"); + for (auto& arg : args) vm->_stdout(vm->PyStr_AS_C(vm->asStr(arg)) + " "); + vm->_stdout("\n"); return vm->None; }); _vm->bindBuiltinFunc("eval", [](VM* vm, PyVarList args) { - if (args.size() != 1) vm->_error("TypeError", "eval() takes exactly one argument"); - if (!args[0]->isType(vm->_tp_str)) vm->_error("TypeError", "eval() argument must be a string"); + if (args.size() != 1) vm->typeError("eval() takes exactly one argument"); + if (!args[0]->isType(vm->_tp_str)) vm->typeError("eval() argument must be a string"); const _Str& expr = vm->PyStr_AS_C(args[0]); _Code code = compile(vm, expr, "", EVAL_MODE); return vm->exec(code); // not working in function @@ -67,7 +67,7 @@ void __initializeBuiltinFunctions(VM* _vm) { _vm->bindBuiltinFunc("chr", [](VM* vm, PyVarList args) { int i = vm->PyInt_AS_C(args.at(0)); - if (i < 0 || i > 128) vm->_error("ValueError", "chr() arg not in range(128)"); + if (i < 0 || i > 128) vm->valueError("chr() arg not in range(128)"); return vm->PyStr(_Str(1, (char)i)); }); @@ -77,7 +77,7 @@ void __initializeBuiltinFunctions(VM* _vm) { _vm->bindBuiltinFunc("ord", [](VM* vm, PyVarList args) { _Str s = vm->PyStr_AS_C(args.at(0)); - if (s.size() != 1) vm->_error("TypeError", "ord() expected an ASCII character"); + if (s.size() != 1) vm->typeError("ord() expected an ASCII character"); return vm->PyInt((int)s[0]); }); @@ -104,7 +104,7 @@ void __initializeBuiltinFunctions(VM* _vm) { case 1: r.stop = vm->PyInt_AS_C(args[0]); break; case 2: r.start = vm->PyInt_AS_C(args[0]); r.stop = vm->PyInt_AS_C(args[1]); break; case 3: r.start = vm->PyInt_AS_C(args[0]); r.stop = vm->PyInt_AS_C(args[1]); r.step = vm->PyInt_AS_C(args[2]); break; - default: vm->_error("TypeError", "range expected 1-3 arguments, got " + std::to_string(args.size())); + default: vm->typeError("range expected 1-3 arguments, got " + std::to_string(args.size())); } return vm->PyRange(r); }); @@ -121,13 +121,13 @@ void __initializeBuiltinFunctions(VM* _vm) { _vm->bindMethodMulti({"int", "float"}, "__truediv__", [](VM* vm, PyVarList args) { if(!vm->isIntOrFloat(args[0], args[1])) - vm->_error("TypeError", "unsupported operand type(s) for " "/" ); + vm->typeError("unsupported operand type(s) for " "/" ); return vm->PyFloat(vm->numToFloat(args[0]) / vm->numToFloat(args[1])); }); _vm->bindMethodMulti({"int", "float"}, "__pow__", [](VM* vm, PyVarList args) { if(!vm->isIntOrFloat(args[0], args[1])) - vm->_error("TypeError", "unsupported operand type(s) for " "**" ); + vm->typeError("unsupported operand type(s) for " "**" ); if(args[0]->isType(vm->_tp_int) && args[1]->isType(vm->_tp_int)){ return vm->PyInt(_round(pow(vm->PyInt_AS_C(args[0]), vm->PyInt_AS_C(args[1])))); }else{ @@ -138,19 +138,19 @@ void __initializeBuiltinFunctions(VM* _vm) { /************ PyInt ************/ _vm->bindMethod("int", "__floordiv__", [](VM* vm, PyVarList args) { if(!args[0]->isType(vm->_tp_int) || !args[1]->isType(vm->_tp_int)) - vm->_error("TypeError", "unsupported operand type(s) for " "//" ); + vm->typeError("unsupported operand type(s) for " "//" ); return vm->PyInt(vm->PyInt_AS_C(args[0]) / vm->PyInt_AS_C(args[1])); }); _vm->bindMethod("int", "__mod__", [](VM* vm, PyVarList args) { if(!args[0]->isType(vm->_tp_int) || !args[1]->isType(vm->_tp_int)) - vm->_error("TypeError", "unsupported operand type(s) for " "%" ); + vm->typeError("unsupported operand type(s) for " "%" ); return vm->PyInt(vm->PyInt_AS_C(args[0]) % vm->PyInt_AS_C(args[1])); }); _vm->bindMethod("int", "__neg__", [](VM* vm, PyVarList args) { if(!args[0]->isType(vm->_tp_int)) - vm->_error("TypeError", "unsupported operand type(s) for " "-" ); + vm->typeError("unsupported operand type(s) for " "-" ); return vm->PyInt(-1 * vm->PyInt_AS_C(args[0])); }); @@ -175,7 +175,7 @@ void __initializeBuiltinFunctions(VM* _vm) { _vm->bindMethod("str", "__add__", [](VM* vm, PyVarList args) { if(!args[0]->isType(vm->_tp_str) || !args[1]->isType(vm->_tp_str)) - vm->_error("TypeError", "unsupported operand type(s) for " "+" ); + vm->typeError("unsupported operand type(s) for " "+" ); const _Str& lhs = vm->PyStr_AS_C(args[0]); const _Str& rhs = vm->PyStr_AS_C(args[1]); return vm->PyStr(lhs + rhs); @@ -317,7 +317,7 @@ void __initializeBuiltinFunctions(VM* _vm) { _vm->bindMethod("list", "pop", [](VM* vm, PyVarList args) { PyVarList& _self = vm->PyList_AS_C(args[0]); - if(_self.empty()) vm->_error("IndexError", "pop from empty list"); + if(_self.empty()) vm->indexError("pop from empty list"); PyVar ret = _self.back(); _self.pop_back(); return ret; @@ -428,12 +428,12 @@ void __addModuleRandom(VM* vm){ extern "C" { __EXPORT - VM* createVM(PrintFn printFn){ + VM* createVM(PrintFn _stdout){ VM* vm = new VM(); __initializeBuiltinFunctions(vm); __runCodeBuiltins(vm, __BUILTINS_CODE); __addModuleRandom(vm); - vm->printFn = printFn; + vm->_stdout = _stdout; return vm; } @@ -448,8 +448,8 @@ extern "C" { _Code code = compile(vm, source, "main.py"); vm->exec(code); }catch(std::exception& e){ - vm->printFn(e.what()); - vm->printFn("\n"); + vm->_stdout(e.what()); + vm->_stdout("\n"); vm->cleanError(); } } diff --git a/src/vm.h b/src/vm.h index 02905ea5..cd1ad3f1 100644 --- a/src/vm.h +++ b/src/vm.h @@ -2,6 +2,7 @@ #include "codeobject.h" #include "iter.h" +#include "error.h" #define __DEF_PY_AS_C(type, ctype, ptype) \ inline ctype& Py##type##_AS_C(const PyVar& obj) { \ @@ -18,7 +19,6 @@ __DEF_PY(type, ctype, ptype) \ __DEF_PY_AS_C(type, ctype, ptype) -// TODO: we should split this into stdout and stderr typedef void(*PrintFn)(const char*); #define NUM_POOL_MAX_SIZE 1024 @@ -31,7 +31,8 @@ public: PyVarDict _types; // builtin types PyVar None, True, False; - PrintFn printFn = [](auto s){}; + PrintFn _stdout = [](auto s){}; + PrintFn _stderr = [](auto s){}; PyVar builtins; // builtins module PyVar _main; // __main__ module @@ -41,24 +42,6 @@ public: initializeBuiltinClasses(); } - void cleanError(){ - while(!callstack.empty()) callstack.pop(); - } - - void nameError(const _Str& name){ - _error("NameError", "name '" + name + "' is not defined"); - } - - void attributeError(PyVar obj, const _Str& name){ - _error("AttributeError", "type '" + obj->getTypeName() + "' has no attribute '" + name + "'"); - } - - inline void __checkType(const PyVar& obj, const PyVar& type){ - if(!obj->isType(type)){ - _error("TypeError", "expected '" + type->getName() + "', but got '" + obj->getTypeName() + "'"); - } - } - PyVar asStr(const PyVar& obj){ PyVarOrNull str_fn = getAttr(obj, __str__, false); if(str_fn != nullptr) return call(str_fn, {}); @@ -130,7 +113,7 @@ public: if(i < args.size()) { locals[name] = args[i++]; }else{ - _error("TypeError", "missing positional argument '" + name + "'"); + typeError("missing positional argument '" + name + "'"); } } // handle *args @@ -148,12 +131,12 @@ public: } } - if(i < args.size()) _error("TypeError", "too many arguments"); + if(i < args.size()) typeError("too many arguments"); // TODO: handle **kwargs return exec(fn.code, locals); } - _error("TypeError", "'" + callable->getTypeName() + "' object is not callable"); + typeError("'" + callable->getTypeName() + "' object is not callable"); return None; } @@ -264,8 +247,8 @@ public: { const PyVar& expr = frame->topValue(this); if(expr == None) break; - printFn(PyStr_AS_C(asRepr(expr))); - printFn("\n"); + _stdout(PyStr_AS_C(asRepr(expr))); + _stdout("\n"); } break; case OP_POP_TOP: frame->popValue(this); break; case OP_BINARY_OP: @@ -318,7 +301,7 @@ public: case OP_ASSERT: { PyVar expr = frame->popValue(this); - if(!PyBool_AS_C(expr)) _error("AssertionError", "assertion failed"); + _assert(PyBool_AS_C(expr), "assertion failed"); } break; case OP_RAISE_ERROR: { @@ -357,7 +340,7 @@ public: PyIter_AS_C(tmp)->var = PyPointer_AS_C(frame->__pop()); frame->push(tmp); }else{ - _error("TypeError", "'" + obj->getTypeName() + "' object is not iterable"); + typeError("'" + obj->getTypeName() + "' object is not iterable"); } } break; case OP_FOR_ITER: @@ -404,19 +387,19 @@ public: } } break; default: - _error("SystemError", _Str("opcode ") + OP_NAMES[byte.op] + " is not implemented"); + systemError(_Str("opcode ") + OP_NAMES[byte.op] + " is not implemented"); break; } } if(frame->code->mode == EVAL_MODE) { if(frame->stackSize() != 1) { - _error("SystemError", "stack size is not 1 in EVAL_MODE"); + systemError("stack size is not 1 in EVAL_MODE"); } return frame->popValue(this); } - if(frame->stackSize() != 0) _error("SystemError", "stack not empty in EXEC_MODE"); + if(frame->stackSize() != 0) systemError("stack not empty in EXEC_MODE"); callstack.pop(); return None; } @@ -431,19 +414,6 @@ public: return runFrame(frame); } - void _assert(bool val, const _Str& msg){ - if (!val) _error("AssertionError", msg); - } - - void _error(const _Str& name, const _Str& msg){ - _StrStream ss; - auto frame = callstack.top(); - ss << "Traceback (most recent call last):" << std::endl; - ss << " File '" << frame->code->co_filename << "', line "; - ss << frame->currentLine() << '\n' << name << ": " << msg; - throw std::runtime_error(ss.str()); - } - PyVar newUserClassType(_Str name, PyVar base){ PyVar obj = newClassType(name, base); setAttr(obj, "__name__", PyStr(name)); @@ -469,7 +439,7 @@ public: PyVar newNumber(PyVar type, _Value _native) { if(type != _tp_int && type != _tp_float) - _error("SystemError", "type is not a number type"); + systemError("type is not a number type"); PyObject* _raw = nullptr; if(numPool.size() > 0) { _raw = numPool.back(); @@ -567,7 +537,7 @@ public: int normalizedIndex(int index, int size){ if(index < 0) index += size; if(index < 0 || index >= size){ - _error("IndexError", "index out of range, " + std::to_string(index) + " not in [0, " + std::to_string(size) + ")"); + indexError("index out of range, " + std::to_string(index) + " not in [0, " + std::to_string(size) + ")"); } return index; } @@ -651,7 +621,7 @@ public: } if (obj->isType(_tp_str)) return PyStr_AS_C(obj).hash(); if (obj->isType(_tp_type)) return (int64_t)obj.get(); - _error("TypeError", "unhashable type: " + obj->getTypeName()); + typeError("unhashable type: " + obj->getTypeName()); return 0; } @@ -660,9 +630,61 @@ public: exec(code, {}, _m); _modules[name] = _m; } + + /***** Error Reporter *****/ +private: + void _error(const _Str& name, const _Str& msg){ + std::stack snapshots; + while (!callstack.empty()){ + auto frame = callstack.top(); + snapshots.push(LineSnapshot( + frame->code->co_filename, + frame->currentLine() + )); + callstack.pop(); + } + throw RuntimeError(name, msg, snapshots); + } + +public: + void cleanError(){ + while(!callstack.empty()) callstack.pop(); + } + + void typeError(const _Str& msg){ + typeError(msg); + } + + void systemError(const _Str& msg){ + systemError(msg); + } + + void indexError(const _Str& msg){ + _error("IndexError", msg); + } + + void valueError(const _Str& msg){ + _error("ValueError", msg); + } + + void nameError(const _Str& name){ + _error("NameError", "name '" + name + "' is not defined"); + } + + void attributeError(PyVar obj, const _Str& name){ + _error("AttributeError", "type '" + obj->getTypeName() + "' has no attribute '" + name + "'"); + } + + inline void __checkType(const PyVar& obj, const PyVar& type){ + if(!obj->isType(type)) typeError("expected '" + type->getName() + "', but got '" + obj->getTypeName() + "'"); + } + + void _assert(bool val, const _Str& msg){ + if (!val) _error("AssertionError", msg); + } }; -/**************** Pointers' Impl ****************/ +/***** Pointers' Impl *****/ PyVar NamePointer::get(VM* vm, Frame* frame) const{ auto it = frame->f_locals.find(name); @@ -724,7 +746,7 @@ void AttrPointer::set(VM* vm, Frame* frame, PyVar val) const{ } void AttrPointer::del(VM* vm, Frame* frame) const{ - vm->_error("AttributeError", "can't delete attribute"); + vm->typeError("cannot delete attribute"); } PyVar IndexPointer::get(VM* vm, Frame* frame) const{ @@ -749,11 +771,11 @@ PyVar CompoundPointer::get(VM* vm, Frame* frame) const{ void CompoundPointer::set(VM* vm, Frame* frame, PyVar val) const{ if(!val->isType(vm->_tp_tuple) && !val->isType(vm->_tp_list)){ - vm->_error("TypeError", "only tuple or list can be unpacked"); + vm->typeError("only tuple or list can be unpacked"); } const PyVarList& args = std::get(val->_native); - if(args.size() > pointers.size()) vm->_error("ValueError", "too many values to unpack"); - if(args.size() < pointers.size()) vm->_error("ValueError", "not enough values to unpack"); + if(args.size() > pointers.size()) vm->valueError("too many values to unpack"); + if(args.size() < pointers.size()) vm->valueError("not enough values to unpack"); for (int i = 0; i < pointers.size(); i++) { pointers[i]->set(vm, frame, args[i]); } diff --git a/tests/errors/1.py b/tests/errors/1.py new file mode 100644 index 00000000..d73c0113 --- /dev/null +++ b/tests/errors/1.py @@ -0,0 +1,17 @@ +def is_prime(x): + if x<2: + return False + + for i in range(2,x): + if x%i == 0: + return False1 + return True + +def test(n): + k = 0 + for i in range(n): + if is_prime(i): + k += 1 + return k + +print(test(100)) \ No newline at end of file