diff --git a/.gitignore b/.gitignore index 1a472de..687f053 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,8 @@ Frameworks/wasm-c-api */wapm .idea/ .idea/* +.idea/** +.idea/**/* node_modules/ tmp/ coverage/ diff --git a/docs/wasp.js b/docs/wasp.js index d6780c5..fbdc552 100644 --- a/docs/wasp.js +++ b/docs/wasp.js @@ -51,11 +51,15 @@ function format(object) { function error(msg) { if (typeof results !== 'undefined') - results.value = "⚠️ ERROR: " + msg + "\n"; - if (msg instanceof WebAssembly.CompileError) + results.value += "\n⚠️ ERROR: " + msg + "\n"; + if (msg instanceof WebAssembly.CompileError) { + results.value += "\n⚠️ COMPILE ERROR:\n"; + results.value += msg.stack + "\n"; ; // ignore as it indicates that wasp has invalid syntax - else if (msg instanceof Error) + } else if (msg instanceof Error) { + results.value += msg.stack + "\n"; throw msg + } else throw new Error("⚠️ ERROR: " + msg) } @@ -64,10 +68,13 @@ let nop = x => 0 // careful, some wasi shim needs 0! const fd_write = function (fd, c_io_vector, iovs_count, nwritten) { while (iovs_count-- > 0) { + let text = string(c_io_vector); if (fd === 0) - console.error(string(c_io_vector) || "\n"); - else - console.log(string(c_io_vector) || "\n"); + error(text || "\n"); + else { + results.value += text || "\n"; + console.log(text || "\n"); + } c_io_vector += 8 } return -1; // todo diff --git a/source/Wasp.cpp b/source/Wasp.cpp index 6b85992..add032f 100644 --- a/source/Wasp.cpp +++ b/source/Wasp.cpp @@ -72,13 +72,14 @@ bool use_wasm_arrays = false; // array in wat #endif +#define err(m) err1("%s:%d\n%s"s%__FILE__%__LINE__%m) +#define parserError(m) err1("%s:%d\n%s"s%__FILE__%__LINE__%m) bool isalpha0(codepoint c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); } -#define err(m) err1("%s:%d\n%s"s%__FILE__%__LINE__%m) bool data_mode = true;// todo ! // tread '=' as ':' instead of keeping as expression operator WHY would we keep it again?? @@ -268,7 +269,7 @@ codepoint closingBracket(codepoint bracket) { // List operators; // reuse functions! -// if(is_grapheme_modifier(ch))error("multi codepoint graphemes not"); +// if(is_grapheme_modifier(ch))parseError("multi codepoint graphemes not"); // everything that is not an is_identifier is treated as operator/symbol/identifier? // NEEDs complete codepoint, not just leading char because ☺ == e2 98 ba √ == e2 88 9a bool is_operator(codepoint ch) {// todo is_KNOWN_operator todo Julia @@ -349,6 +350,16 @@ class Wasp { #define INDENT 0x0F // SI ␏ ^O Shift In #define DEDENT 0x0E // SO ␎ ^N Shift Out +// void parserError(String message) { +// String msg = message; +// msg += position(); +// auto error = new SyntaxError(msg); +// error->at = at; +// error->lineNumber = lineNumber; +// error->columnNumber = at - columnStart; +// error->file = file.data; +// err(msg); +// } // indent 􀋵 (increase.indent) ☞ 𒋰 𒐂 ˆ ˃ int indentation() { if (is_grouper(lastNonWhite)) { @@ -369,21 +380,22 @@ class Wasp { if (text[offset] == '\n' or text[offset] == '\r')offset++; while (text[offset] == '\t') { if (indentation_level > 0 and not indentation_by_tabs) - proceed() ? err("mixing tabs and spaces for indentation") : ""; + if (proceed()) + parserError("mixing tabs and spaces for indentation"); indentation_by_tabs = true; tabs++; offset++; } if (tabs > 0 and text[offset] == ' ') - err("ambiguous indentation, mixing tabs and spaces"); + parserError("ambiguous indentation, mixing tabs and spaces"); while (text[offset] == ' ') { if (indentation_level > 0 and indentation_by_tabs) - proceed() ? err("mixing tabs and spaces for indentation") : ""; + if (proceed()) parserError("mixing tabs and spaces for indentation"); indentation_by_tabs = false; tabs = tabs + 1. / spaces_per_tab; offset++; } - if (tabs > 0 and text[offset] == '\t')err("ambiguous indentation, mixing tabs and spaces"); + if (tabs > 0 and text[offset] == '\t')parserError("ambiguous indentation, mixing tabs and spaces"); // while(next==' ' or next=='\t')proceed();// but keep last ch as INDENT! if (text[offset] == '\n') return indentation_level; // careful empty lines if next indentation == last one : just hangover spacer! @@ -479,7 +491,7 @@ class Wasp { if (ch and ch != -1 and ch != DEDENT) { printf("UNEXPECTED CHAR %c", ch); print(position()); - error("Expect end of input"); + parserError("Expect end of input"); result = ERROR; } // Mark does not support the legacy JSON reviver function todo ?? @@ -528,9 +540,9 @@ class Wasp { String position() { auto columnNumber = at - columnStart; String msg; - msg = msg + " in line " + lineNumber + " column " + columnNumber + " (char#" + at + ")"; - msg = msg + line + ""; - msg = msg + (s(" ").times(columnNumber - 1)) + "^"; + msg = msg + " in line " + lineNumber + " column " + columnNumber + " (char#" + at + ")\n"; + msg = msg + line + "\n"; + msg = msg + (s(" ").times(columnNumber - 1)) + "^^^"; if (not file.empty()) msg = msg + "" + file + ":" + lineNumber; // print(msg); return msg; @@ -569,7 +581,7 @@ class Wasp { // one char at a time, NO JUMPING OVER ' ' here! char proceed(char c = 0) { if (not ch and at >= 0) { - warn("end of code"); + parserError("end of code"); return ch; } // If a c parameter is provided, verify that it matches the current character. @@ -615,7 +627,7 @@ class Wasp { String identifier() { // identifiers must start with a letter, _ or $. if (!is_identifier(ch)) - err("Unexpected identifier character "s + renderChar(ch)); + parserError("Unexpected identifier character "s + renderChar(ch)); int start = at; // subsequent characters can contain ANYTHING except operators while ((proceed() and is_identifier(ch)) or isDigit(ch) or isKebabBridge()) @@ -670,7 +682,7 @@ class Wasp { // const Node &ok = word(); // bool ok = word(); -// if (!ok) { error('expected word to be NaN'); } +// if (!ok) { parseError('expected word to be NaN'); } // ignore sign as -NaN also is NaN return Nan; } @@ -710,7 +722,7 @@ class Wasp { number0 = +parseLong(string.data); } // if (!isFinite(number)) { -// error("Bad number"); +// parseError("Bad number"); // } else { if (base != 10) todo("base "s + base); if(is_identifier(ch)) @@ -728,17 +740,6 @@ class Wasp { return String((char) (uffff));// itoa0(uffff); } - void parserError(String message) { - String msg = message; - msg += position(); - auto error = new SyntaxError(msg); - error->at = at; - error->lineNumber = lineNumber; - error->columnNumber = at - columnStart; - error->file = file.data; - err(msg); - } - bool end_of_text() { return at >= text.length; } @@ -819,7 +820,7 @@ class Wasp { // } // } // } -// err("Bad string"); +// parseError()("Bad string"); // return NIL; // }; @@ -829,7 +830,7 @@ class Wasp { // be the second / character in the // pair that begins this inline comment. // To finish the inline comment, we look for a newline or the end of the text. if (ch != '/' and ch != '#') { - err("Not an inline comment"); + parserError("Not an inline comment"); } do { proceed(); @@ -847,7 +848,7 @@ class Wasp { // To finish the block comment, we look for an ending */ pair of characters, // but we also watch for the end of text before the comment is terminated. // if (ch != '*') { -// err("Not a block comment"); +// parseError()("Not a block comment"); // } do { proceed(); @@ -859,7 +860,7 @@ class Wasp { } } } while (ch); - err("Unterminated block comment"); + parserError("Unterminated block comment"); }; // Parse a comment @@ -887,7 +888,7 @@ class Wasp { previous = lastNonWhite = preserveLast; return true; } - if (ch != '/') err("Not a comment"); + if (ch != '/') parserError("Not a comment"); if (next == '/') { proceed('/'); inlineComment(); @@ -901,7 +902,7 @@ class Wasp { } else { return false; // not a comment // division handled elsewhere -// error("Unrecognized comment"); +// parseError("Unrecognized comment"); } }; @@ -1035,7 +1036,7 @@ class Wasp { return operatorr(); if (is_identifier(ch)) return *resolve(Node(identifier(), true)).clone();// or op - error("Unexpected symbol character "s + String((char) text[at]) + String((char) text[at + 1]) + + parserError("Unexpected symbol character "s + String((char) text[at]) + String((char) text[at + 1]) + String((char) text[at + 2])); return (Node &) NIL; } @@ -1130,7 +1131,7 @@ class Wasp { } if (token("one")) { return True; } if (token("two")) { return Node(2); } - error("Unexpected character "s + renderChar(text.charAt(at - 1)));// throws, but WASM still needs: + parserError("Unexpected character "s + renderChar(text.charAt(at - 1)));// throws, but WASM still needs: return ERROR; }; @@ -1150,7 +1151,7 @@ class Wasp { // proceed(); // } // -// error(UNEXPECT_END); +// parseError(UNEXPECT_END); // }; // value, // Place holder for the value function. @@ -1172,7 +1173,7 @@ class Wasp { } // ES5 and Mark allow omitted elements in arrays, e.g. [,] and [,null]. JSON don't allow this. // if (ch == ',') { -// error("Missing array element"); +// parseError("Missing array element"); // } else { Node val = valueNode();// copy by value! @@ -1187,7 +1188,7 @@ class Wasp { white(); } } - err("Expecting ]"); + parserError("Expecting ]"); return ERROR; }; @@ -1222,14 +1223,14 @@ class Wasp { // code based on https://github.com/noseglid/base85/blob/master/lib/base85.js auto end = text.indexOf('}', at + 1); // scan binary end - if (end < 0) { err("Missing ascii85 end delimiter"); } + if (end < 0) { parserError("Missing ascii85 end delimiter"); } // first run decodes into base85 int values, and skip the spaces auto p = 0; byte base[end - at + 3]; // 3 extra bytes of padding while (at < end) { auto code = lookup85[(short) text.charCodeAt(at)]; // console.put('bin: ', next, code); - if (code > 85) { err("Invalid ascii85 character"); } + if (code > 85) { parserError("Invalid ascii85 character"); } if (code < 85) { base[p++] = code; } // else skip spaces at++; @@ -1237,7 +1238,7 @@ class Wasp { at = end + 2; proceed(); // skip '~}' // check length - if (p % 5 == 1) { err("Invalid ascii85 stream length"); } + if (p % 5 == 1) { parserError("Invalid ascii85 stream length"); } // second run decodes into actual binary data auto dataLength = p, padding = (dataLength % 5 == 0) ? 0 : 5 - dataLength % 5; @@ -1276,7 +1277,7 @@ class Wasp { Node decodeBase64(String text) { auto end = text.indexOf('}', at), bufEnd = end, pad = 0; // scan binary end - if (end < 0) { err("Missing base64 end delimiter"); } + if (end < 0) { parserError("Missing base64 end delimiter"); } // Use a lookup table to find the index. byte lookup64[128]; @@ -1310,7 +1311,7 @@ class Wasp { auto p = 0; while (at < bufEnd) { auto code = lookup64[(short) text.charCodeAt(at)]; // console.put('bin: ', next, code); - if (code > 64) { err("Invalid base64 character"); } + if (code > 64) { parserError("Invalid base64 character"); } if (code < 64) { base[p++] = code; } // else skip spaces at++; @@ -1319,7 +1320,7 @@ class Wasp { proceed(); // skip '}' // check length if ((pad and (p + pad) % 4 != 0) or (!pad and p % 4 == 1)) { - err("Invalid base64 stream length"); + parserError("Invalid base64 stream length"); } // second run decodes into actual binary data @@ -1439,7 +1440,7 @@ class Wasp { case '<': return generics; } - err("unknown bracket type "s + bracket); + parserError("unknown bracket type "s + bracket); return errors; } @@ -1503,9 +1504,11 @@ class Wasp { // keep ';' ',' ' ' for further analysis (?) else // drop brackets proceed(); // what else?? + close = 0; // ok, we are done break; }// todo: merge <> if (closing(ch, close)) { // 1,2,3; «;» closes «,» list + close = 0; // ok, we are done break; }// inner match ok @@ -1645,7 +1648,7 @@ class Wasp { case ')': case ']':// .. // break loop;// not in c++ - err("wrong closing bracket"); + parserError("wrong closing bracket"); // case '+': // todo WHO writes +1 ? case '-': if (parserOptions.arrow and next == '>') { @@ -1663,7 +1666,7 @@ class Wasp { continue; } if (isKebabBridge()) - error("kebab case should be handled in identifier"); + parserError("kebab case should be handled in identifier"); if (next == '>') {// -> => ⇨ next = u'⇨'; proceed(); @@ -1699,7 +1702,9 @@ class Wasp { else actual.add(quote(closer).clone()); break; - } + } else { + close = 0; // ok, we are done + } Node id = Node(text.substring(start, at)); id.setType(Kind::strings);// todo "3" could have be resolved as number? DONT do js magifuckery actual.add(id); @@ -1873,6 +1878,9 @@ class Wasp { } } } + if (close and close != ' ' and close != '\n' and close != ';' and close != ',') { + parserError("unclosed pair "s + close); + } return actual.flat(); }; @@ -1899,7 +1907,7 @@ float group_precedence(char group) { if (group == ',')return 4; if (group == ' ')return 5; if (group == '_')return 6; -// error("unknown precedence for symbol: "s+group); +// parseError("unknown precedence for symbol: "s+group); return 999; } diff --git a/source/tests.cpp b/source/tests.cpp index 6854a98..cd4563b 100644 --- a/source/tests.cpp +++ b/source/tests.cpp @@ -26,6 +26,8 @@ //void testSourceMap(); void testExceptions() { +// assert_emit("(unclosed bracket",123); + assert_throws("x:int=1;x='ok'"); assert_throws("x:int=1;x=1.1"); // assert_emit("x:int=1;x=1.0",1); // might be cast by compiler @@ -3438,7 +3440,9 @@ void testCurrent() { // exit(1); // testOldRandomBugs(); -// assert_emit("3^2", 9); +// assert_emit("3^2", 9); +// assert_emit("3**2", 9); +// exit(1); // assert_emit("n=3;2ⁿ", 8); // assert_emit("k=(1,2,3);i=1;k#i=4;k#i", 4) // assert_emit("'αβγδε'#3", U'γ'); diff --git a/source/wasp.js b/source/wasp.js index d6780c5..fbdc552 100644 --- a/source/wasp.js +++ b/source/wasp.js @@ -51,11 +51,15 @@ function format(object) { function error(msg) { if (typeof results !== 'undefined') - results.value = "⚠️ ERROR: " + msg + "\n"; - if (msg instanceof WebAssembly.CompileError) + results.value += "\n⚠️ ERROR: " + msg + "\n"; + if (msg instanceof WebAssembly.CompileError) { + results.value += "\n⚠️ COMPILE ERROR:\n"; + results.value += msg.stack + "\n"; ; // ignore as it indicates that wasp has invalid syntax - else if (msg instanceof Error) + } else if (msg instanceof Error) { + results.value += msg.stack + "\n"; throw msg + } else throw new Error("⚠️ ERROR: " + msg) } @@ -64,10 +68,13 @@ let nop = x => 0 // careful, some wasi shim needs 0! const fd_write = function (fd, c_io_vector, iovs_count, nwritten) { while (iovs_count-- > 0) { + let text = string(c_io_vector); if (fd === 0) - console.error(string(c_io_vector) || "\n"); - else - console.log(string(c_io_vector) || "\n"); + error(text || "\n"); + else { + results.value += text || "\n"; + console.log(text || "\n"); + } c_io_vector += 8 } return -1; // todo