diff --git a/CMakeLists.txt b/CMakeLists.txt index acdeadf..22ab199 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -42,16 +42,13 @@ if(WIN32) target_link_libraries(quantum_stub advapi32) endif() -# ── qpm — standalone npm-compatible package manager ────────────────────────── -file(GLOB QPM_SOURCES CONFIGURE_DEPENDS "src/qpm/*.cpp") -add_executable(qpm ${QPM_SOURCES}) +# ── qpm — standalone npm-compatible package manager (Windows only) ─────────── if(WIN32) - # z: zlib (statically linked via the -static link flag above, same as the - # other 3 targets already get a DLL-free libstdc++/libgcc). - # winhttp/crypt32/ws2_32: Windows system DLLs, present on every install — - # linked the same way `advapi32` already is above. + file(GLOB QPM_SOURCES CONFIGURE_DEPENDS "src/qpm/*.cpp") + add_executable(qpm ${QPM_SOURCES}) target_link_libraries(qpm z winhttp crypt32 ws2_32) endif() + message(STATUS "Quantum v2.0.0 — Bytecode VM (static linking enabled)") message(STATUS " Binaries : quantum qrun quantum_stub qpm") \ No newline at end of file diff --git a/Website b/Website index ca9c422..26b38ae 160000 --- a/Website +++ b/Website @@ -1 +1 @@ -Subproject commit ca9c4224a6c1238476d40f0c8e6a544b9a6063f1 +Subproject commit 26b38ae37b0e293c9ca8e735625d9e36e4b8f8dc diff --git a/examples b/examples index 1db3db3..c5b799a 160000 --- a/examples +++ b/examples @@ -1 +1 @@ -Subproject commit 1db3db3bf7024ab000b86c755985a70b4a40e597 +Subproject commit c5b799abf820cf61630a00550f083c1a37ce4beb diff --git a/src/compiler/CompilerCore.cpp b/src/compiler/CompilerCore.cpp index 0226918..1794e92 100644 --- a/src/compiler/CompilerCore.cpp +++ b/src/compiler/CompilerCore.cpp @@ -270,6 +270,8 @@ void Compiler::compileExpr(ASTNode &node) { compileArrow(n, ln); else if constexpr (std::is_same_v) compileReturn(n, ln); + else if constexpr (std::is_same_v) + compileRaise(n, ln); else throw std::runtime_error("Compiler: unhandled expression node"); }, diff --git a/src/dialect/RubyDialect.cpp b/src/dialect/RubyDialect.cpp index f5a4e94..354e188 100644 --- a/src/dialect/RubyDialect.cpp +++ b/src/dialect/RubyDialect.cpp @@ -76,7 +76,8 @@ static const std::set &rbZeroArgMethods(bool strict) { "resume", "alive", "kill", "shutdown"}; static const std::set mixedNames = { "reverse", "chomp", "downcase", "upcase", "dup", - "clone", "to_i", "to_f", "to_s", "chars"}; + "clone", "to_i", "to_f", "to_s", "chars", + "shift", "pop", "empty", "keys", "first", "last"}; return strict ? strictNames : mixedNames; } @@ -519,7 +520,7 @@ static std::string rbQualifySelf(const std::string &expr, if (afterSuffix < s.size() && (s[afterSuffix] == '?' || s[afterSuffix] == '!')) afterSuffix++; - bool hadDotBefore = !out.empty() && out.back() == '.'; + bool hadDotBefore = !out.empty() && (out.back() == '.' || out.back() == '>' || (out.size() >= 2 && out.substr(out.size() - 2) == "->")); bool followedByParen = (afterSuffix < s.size() && s[afterSuffix] == '('); if (!hadDotBefore && !followedByParen && st.fields.count(ident)) out += "self." + ident; @@ -698,6 +699,23 @@ static std::string rbConvertRanges(std::string line, bool strict = true) { "([A-Z][A-Za-z0-9_]*)(?:\\.|::)new\\b(?!\\()"); line = std::regex_replace(line, mixedNewBareRe, "$1()"); } + // Ruby's namespaced float constants -> Quantum's globals. + static const std::regex infinityRe("Float::INFINITY"); + line = std::regex_replace(line, infinityRe, "INF"); + static const std::regex nanRe("Float::NAN"); + line = std::regex_replace(line, nanRe, "NaN"); + // Ruby's match operator, `str =~ /re/` (and `str !~ /re/`), plus the + // capture globals `$1`..`$9`. `=~` becomes a call to the __rx_match native, + // which runs the regex and stores each capture group in a `__rx_N` global; + // `$N` then reads those. (The `/re/` regex literal is already lexed as a + // string, so it passes straight through as the second argument.) + static const std::regex notMatchRe("\\b([A-Za-z_@][A-Za-z0-9_$.()\\[\\]>-]*)\\s*!~\\s*(/(?:[^/\\\\]|\\\\.)*/[a-z]*|\"(?:[^\"]|\\\\.)*\")"); + line = std::regex_replace(line, notMatchRe, "!__rx_match($1, $2)"); + static const std::regex matchRe("\\b([A-Za-z_@][A-Za-z0-9_$.()\\[\\]>-]*)\\s*=~\\s*(/(?:[^/\\\\]|\\\\.)*/[a-z]*|\"(?:[^\"]|\\\\.)*\")"); + line = std::regex_replace(line, matchRe, "__rx_match($1, $2)"); + static const std::regex captureRe("\\$([1-9])"); + line = std::regex_replace(line, captureRe, "__rx_$1"); + if (!strict) return line; // Ruby's sized-array constructors. These must precede the generic @@ -738,6 +756,10 @@ static std::string rbConvertRanges(std::string line, bool strict = true) { line = std::regex_replace(line, lambdaParamsRe, "fn($1) {"); static const std::regex lambdaNoParamsRe("->\\s*\\{"); line = std::regex_replace(line, lambdaNoParamsRe, "fn() {"); + static const std::regex lambdaPipeRe("\\blambda\\s*\\|([^|]*)\\|\\s*\\{"); + line = std::regex_replace(line, lambdaPipeRe, "fn($1) {"); + static const std::regex inlineBraceBlockRe("\\{\\s*\\|([^|]*)\\|"); + line = std::regex_replace(line, inlineBraceBlockRe, "fn($1) {"); // Ruby's old-style hash-rocket literal (`{ key => value }`) -> Quantum's // `key: value` dict syntax. `rescue X => e` is handled by its own // dedicated whole-line match before this ever runs, so it never reaches @@ -840,22 +862,6 @@ static std::string rbConvertRanges(std::string line, bool strict = true) { "((?:@|self\\.)?[A-Za-z_][A-Za-z0-9_]*(?:\\.[A-Za-z_][A-Za-z0-9_]*)*" "(?:\\[[^\\[\\]]*\\])*)\\s*&&=\\s*(.+)$"); line = std::regex_replace(line, andAssignRe, "$1 = $1 && ($2)"); - // Ruby's namespaced float constants -> Quantum's globals. - static const std::regex infinityRe("Float::INFINITY"); - line = std::regex_replace(line, infinityRe, "INF"); - static const std::regex nanRe("Float::NAN"); - line = std::regex_replace(line, nanRe, "NaN"); - // Ruby's match operator, `str =~ /re/` (and `str !~ /re/`), plus the - // capture globals `$1`..`$9`. `=~` becomes a call to the __rx_match native, - // which runs the regex and stores each capture group in a `__rx_N` global; - // `$N` then reads those. (The `/re/` regex literal is already lexed as a - // string, so it passes straight through as the second argument.) - static const std::regex notMatchRe("(\\S+)\\s*!~\\s*(/(?:[^/\\\\]|\\\\.)*/[a-z]*)"); - line = std::regex_replace(line, notMatchRe, "!__rx_match($1, $2)"); - static const std::regex matchRe("(\\S+)\\s*=~\\s*(/(?:[^/\\\\]|\\\\.)*/[a-z]*)"); - line = std::regex_replace(line, matchRe, "__rx_match($1, $2)"); - static const std::regex captureRe("\\$([1-9])"); - line = std::regex_replace(line, captureRe, "__rx_$1"); return line; } @@ -1866,7 +1872,7 @@ std::string applyRubyDialect(const std::string &source, bool strict) { // The loop modifiers stay Ruby-only: in mixed .sa mode `x; while (c) s` // is a legitimate inline C loop, not a `stmt while cond` modifier, so // enabling them here would mis-rewrite C/JS code. - (strict || (m[2].str() != "while" && m[2].str() != "until")) && + (strict || (m[2].str() != "while" && m[2].str() != "until") || (trimCopy(m[1].str()).back() != ';' && !startsWith(trimCopy(m[3].str()), "("))) && rbFindTopLevel(code, " " + m[2].str() + " ") != std::string::npos && rbFindTopLevel(m[3].str(), " else ") == std::string::npos && // `x = if cond` is an if-*expression*, not a modifier-if: @@ -2133,7 +2139,7 @@ std::string applyRubyDialect(const std::string &source, bool strict) { // The loop modifiers stay Ruby-only: in mixed .sa mode `x; while (c) s` // is a legitimate inline C loop, not a `stmt while cond` modifier, so // enabling them here would mis-rewrite C/JS code. - (strict || (m[2].str() != "while" && m[2].str() != "until")) && + (strict || (m[2].str() != "while" && m[2].str() != "until") || (trimCopy(m[1].str()).back() != ';' && !startsWith(trimCopy(m[3].str()), "("))) && rbFindTopLevel(code, " " + m[2].str() + " ") != std::string::npos && // Python inline ternary (`x = a if cond else b`) also matches // this shape — its giveaway is a top-level ` else `, which a @@ -2214,6 +2220,17 @@ std::string applyRubyDialect(const std::string &source, bool strict) { stack.push_back(f); continue; } + if (code == "}" && stack.size() > 1 && + (stack.back().kind == RBFrameKind::Branch || + stack.back().kind == RBFrameKind::Loop || + stack.back().kind == RBFrameKind::Other)) { + RBFrame top = stack.back(); + stack.pop_back(); + outLines.push_back(indentation + "}"); + if (!stack.empty()) + stack.back().last = RBTailInfo{}; + continue; + } if (code == "end" && stack.size() > 1) { { RBFrame top = stack.back(); @@ -2372,6 +2389,15 @@ std::string applyRubyDialect(const std::string &source, bool strict) { stack.push_back(RBFrame{RBFrameKind::Loop, -1, RBTailInfo{}}); continue; } + if (startsWith(code, "for ") && (code.empty() || code.back() != '{') && + (strict || (rbUnambiguousAllowingColon(code) && rbHasMatchingEnd(rawLines, li)))) { + std::string rest = trimCopy(code.substr(4)); + if (!rest.empty() && rest.back() == ':') + rest = trimCopy(rest.substr(0, rest.size() - 1)); + outLines.push_back(indentation + "for " + rest + " {"); + stack.push_back(RBFrame{RBFrameKind::Loop, -1, RBTailInfo{}}); + continue; + } if ((code == "loop" || code == "loop do") && (strict || (rbUnambiguousAllowingColon(code) && rbHasMatchingEnd(rawLines, li)))) { outLines.push_back(indentation + "while (true) {"); @@ -2531,7 +2557,7 @@ std::string applyRubyDialect(const std::string &source, bool strict) { std::string prefix, params; if (rbTryTrailingDoOpener(code, prefix, params) && !prefix.empty() && - (strict || (rbUnambiguousAllowingColon(code) && rbHasMatchingEnd(rawLines, li)))) { + (strict || rbUnambiguousAllowingColon(code))) { // Multi-line `do` openers bypass transformCore, so the // defaulting-hash read rewriting has to be applied here too // (e.g. `adj[u].each do |v|`). @@ -2590,11 +2616,14 @@ std::string applyRubyDialect(const std::string &source, bool strict) { // Same statement-start array-literal ambiguity guard as in // transformCore (`["a","b"].each do |x|`) — mixed mode too. if (!convertedPrefix.empty() && convertedPrefix[0] == '[' && - previousLineCanContinueExpression()) { - size_t close = rbMatchBracket(convertedPrefix, 0); - if (close != std::string::npos && close + 1 < convertedPrefix.size() && - convertedPrefix[close + 1] == '.') - convertedPrefix = ";" + convertedPrefix; + !outLines.empty() && !outLines.back().empty()) { + char lastChar = outLines.back().back(); + if (lastChar != ';' && lastChar != '{' && lastChar != '}') { + size_t close = rbMatchBracket(convertedPrefix, 0); + if (close != std::string::npos && close + 1 < convertedPrefix.size() && + convertedPrefix[close + 1] == '.') + convertedPrefix = ";" + convertedPrefix; + } } outLines.push_back(indentation + rbBuildBlockOpenText(convertedPrefix, params)); diff --git a/src/parser/ParserExpressions.cpp b/src/parser/ParserExpressions.cpp index def4430..93c46ef 100644 --- a/src/parser/ParserExpressions.cpp +++ b/src/parser/ParserExpressions.cpp @@ -432,7 +432,11 @@ ASTNodePtr Parser::parsePostfix() consume(); // eat second : std::string mem; if (!atEnd() && current().type != TokenType::NEWLINE && current().type != TokenType::SEMICOLON) + { mem = consume().value; + while (!atEnd() && (check(TokenType::IDENTIFIER) || check(TokenType::THIS) || isCTypeKeyword(current().type))) + mem += consume().value; + } if (check(TokenType::LPAREN)) { auto args = parseArgList(); diff --git a/src/parser/ParserLiterals.cpp b/src/parser/ParserLiterals.cpp index 505d5ac..25e61b0 100644 --- a/src/parser/ParserLiterals.cpp +++ b/src/parser/ParserLiterals.cpp @@ -207,8 +207,80 @@ ASTNodePtr Parser::parsePrimary() { } return parseArrayLiteral(); } - if (tok.type == TokenType::LBRACE) + if (tok.type == TokenType::LBRACE) { + if (pos + 1 < tokens.size() && + (tokens[pos + 1].type == TokenType::BIT_OR || + (tokens[pos + 1].type == TokenType::IDENTIFIER && + pos + 2 < tokens.size() && + tokens[pos + 2].type == TokenType::BIT_OR))) { + int ln = tok.line; + consume(); // eat '{' + if (check(TokenType::BIT_OR)) + consume(); // eat '|' + std::vector params; + while (!check(TokenType::BIT_OR) && !check(TokenType::RBRACE) && + !atEnd()) { + if (check(TokenType::IDENTIFIER)) { + params.push_back(consume().value); + } else if (!match(TokenType::COMMA)) { + break; + } + } + match(TokenType::BIT_OR); // eat closing '|' + skipNewlines(); + BlockStmt block; + while (!check(TokenType::RBRACE) && !atEnd()) { + block.statements.push_back(parseStatement()); + skipNewlines(); + } + expect(TokenType::RBRACE, "Expected '}'"); + LambdaExpr le; + le.params = std::move(params); + le.body = std::make_unique(std::move(block), ln); + return std::make_unique(std::move(le), ln); + } return parseDictLiteral(); + } + + if (tok.type == TokenType::DECORATOR) { + int ln = tok.line; + consume(); // eat '@' + if (check(TokenType::IDENTIFIER)) { + std::string name = consume().value; + MemberExpr me; + me.object = std::make_unique(Identifier{"self"}, ln); + me.member = name; + return std::make_unique(std::move(me), ln); + } + throw ParseError("Expected identifier after '@'", current().line, + current().col); + } + + if (tok.type == TokenType::ARROW && pos + 1 < tokens.size() && + tokens[pos + 1].type == TokenType::LPAREN) { + int ln = tok.line; + consume(); // eat '->' + std::vector defaultArgs; + std::vector paramTypes; + auto params = parseParamList(nullptr, &defaultArgs, ¶mTypes); + skipNewlines(); + auto body = (check(TokenType::LBRACE) || check(TokenType::INDENT)) + ? parseBlock() + : parseExpr(); + LambdaExpr le; + le.params = std::move(params); + le.paramTypes = std::move(paramTypes); + le.defaultArgs = std::move(defaultArgs); + le.body = std::move(body); + return std::make_unique(std::move(le), ln); + } + + if (tok.type == TokenType::RAISE) { + int ln = tok.line; + consume(); // eat 'raise' + auto expr = parseExpr(); + return std::make_unique(RaiseStmt{std::move(expr)}, ln); + } if (tok.type == TokenType::RETURN) { consume(); // eat 'return' @@ -272,6 +344,7 @@ ASTNodePtr Parser::parsePrimary() { skipNewlines(); } expect(TokenType::RPAREN, "Expected ')'"); + skipNewlines(); return parseArrowFunction(std::move(arrowParams), ln); } @@ -742,7 +815,8 @@ ASTNodePtr Parser::parseDictLiteral() { // Map to dict with value "true" for membership testing val = std::make_unique(BoolLiteral{true}, ln); } else { - expect(TokenType::COLON, "Expected ':'"); + if (!match(TokenType::COLON)) + expect(TokenType::FAT_ARROW, "Expected ':' or '=>'"); skipNewlines(); val = parseExpr(); } @@ -762,16 +836,28 @@ ASTNodePtr Parser::parseDictLiteral() { } ASTNodePtr Parser::parseLambda() { - // Called after consuming fn / function / def keyword (anonymous form) + // Called after consuming fn / function / def keyword (anonymous form or named + // expression) int ln = current().line; + std::string funcName; + if (check(TokenType::IDENTIFIER) && pos + 1 < tokens.size() && + tokens[pos + 1].type == TokenType::LPAREN) { + funcName = consume().value; + } std::vector defaultArgs; std::vector paramTypes; auto params = parseParamList(nullptr, &defaultArgs, ¶mTypes); - match(TokenType::COLON); // Python: def style - if (!match(TokenType::FAT_ARROW)) - match(TokenType::ARROW); // JS => or Quantum -> + if (!match(TokenType::COLON)) { + if (!match(TokenType::FAT_ARROW)) + match(TokenType::ARROW); // JS => or Quantum -> + } skipNewlines(); - auto body = parseBlock(); + ASTNodePtr body; + if (check(TokenType::LBRACE) || check(TokenType::INDENT)) { + body = parseBlock(); + } else { + body = parseStatement(); + } LambdaExpr le; le.params = std::move(params); le.paramTypes = std::move(paramTypes); @@ -840,7 +926,8 @@ std::vector Parser::parseArgList() { continue; } - // keyword argument: name=expr — preserve as AssignExpr so **kwargs detection works + // keyword argument: name=expr — preserve as AssignExpr so **kwargs + // detection works if (check(TokenType::IDENTIFIER)) { size_t la = pos + 1; while (la < tokens.size() && tokens[la].type == TokenType::NEWLINE) @@ -948,9 +1035,14 @@ std::vector Parser::parseParamList(std::vector *outIsRef, std::vector *outDefaultArgs, std::vector *outParamTypes) { - expect(TokenType::LPAREN, "Expected '('"); + TokenType closingTok = TokenType::RPAREN; + if (match(TokenType::BIT_OR)) { + closingTok = TokenType::BIT_OR; + } else { + expect(TokenType::LPAREN, "Expected '('"); + } std::vector params; - while (!check(TokenType::RPAREN) && !atEnd()) { + while (!check(closingTok) && !atEnd()) { // C++ style: "const" modifier before type if (check(TokenType::CONST)) consume(); // eat const @@ -958,12 +1050,30 @@ Parser::parseParamList(std::vector *outIsRef, bool hasCType = false; while ((isCTypeKeyword(current().type) || check(TokenType::CONST)) && pos + 1 < tokens.size() && - (isCTypeKeyword(tokens[pos + 1].type) || tokens[pos + 1].type == TokenType::IDENTIFIER || - tokens[pos + 1].type == TokenType::STAR || tokens[pos + 1].type == TokenType::BIT_AND)) { + (isCTypeKeyword(tokens[pos + 1].type) || + tokens[pos + 1].type == TokenType::IDENTIFIER || + tokens[pos + 1].type == TokenType::STAR || + tokens[pos + 1].type == TokenType::BIT_AND)) { consume(); // eat return/param type keyword or const hasCType = true; } if (hasCType) { + if (check(TokenType::LT)) { + consume(); // eat '<' + int tdepth = 1; + while (!atEnd() && tdepth > 0) { + if (check(TokenType::LT)) + tdepth++; + else if (check(TokenType::GT)) + tdepth--; + else if (check(TokenType::RSHIFT)) { + tdepth -= 2; + consume(); + continue; + } + consume(); + } + } while (check(TokenType::STAR) || check(TokenType::BIT_AND)) { consume(); // eat pointer/ref qualifier on type } @@ -1000,26 +1110,57 @@ Parser::parseParamList(std::vector *outIsRef, } while (la < tokens.size() && (tokens[la].type == TokenType::BIT_AND || tokens[la].type == TokenType::STAR || - tokens[la].type == TokenType::CONST)) + tokens[la].type == TokenType::CONST || + tokens[la].type == TokenType::COLON || + (tokens[la].type == TokenType::IDENTIFIER && + la + 1 < tokens.size() && + (tokens[la + 1].type == TokenType::COLON || + tokens[la + 1].type == TokenType::LT)))) la++; + if (la < tokens.size() && tokens[la].type == TokenType::LT) { + int tdepth = 0, pdepth = 0; + while (la < tokens.size()) { + if (tokens[la].type == TokenType::LT) + tdepth++; + else if (tokens[la].type == TokenType::GT && pdepth == 0) { + tdepth--; + if (tdepth <= 0) { + la++; + break; + } + } else if (tokens[la].type == TokenType::LPAREN) + pdepth++; + else if (tokens[la].type == TokenType::RPAREN && pdepth > 0) + pdepth--; + la++; + } + while (la < tokens.size() && (tokens[la].type == TokenType::BIT_AND || + tokens[la].type == TokenType::STAR || + tokens[la].type == TokenType::CONST)) + la++; + } if (la < tokens.size() && tokens[la].type == TokenType::IDENTIFIER) { std::string tName = - consume().value; // eat type name (e.g. "Entity", "Room", "Cell", - // "string", "unique_ptr") + consume().value; // eat first identifier (e.g. "std") + while (check(TokenType::COLON) || check(TokenType::IDENTIFIER)) + tName += consume().value; // Skip template arguments: unique_ptr, shared_ptr, etc. if (check(TokenType::LT)) { consume(); // eat '<' - int tdepth = 1; + int tdepth = 1, pdepth = 0; while (!atEnd() && tdepth > 0) { if (check(TokenType::LT)) tdepth++; - else if (check(TokenType::GT)) + else if (check(TokenType::GT) && pdepth == 0) tdepth--; - else if (check(TokenType::RSHIFT)) { + else if (check(TokenType::RSHIFT) && pdepth == 0) { tdepth -= 2; consume(); continue; - } + } else if (check(TokenType::LPAREN)) + pdepth++; + else if (check(TokenType::RPAREN) && pdepth > 0) + pdepth--; consume(); } } @@ -1063,12 +1204,14 @@ Parser::parseParamList(std::vector *outIsRef, } else if (check(TokenType::INPUT) || check(TokenType::PRINT) || check(TokenType::COUT) || check(TokenType::CIN) || isCTypeKeyword(current().type)) { - // keyword tokens used as param names: e.g. void foo(int input, int* cout, long n) + // keyword tokens used as param names: e.g. void foo(int input, int* cout, + // long n) params.push_back(consume().value); if (outIsRef) outIsRef->push_back(isRef); - } else if (check(TokenType::COMMA) || check(TokenType::RPAREN)) { - // Unnamed parameter: e.g. void foo(int*, int) — just skip, no name to + } else if (check(TokenType::COMMA) || check(TokenType::RPAREN) || + check(TokenType::GT) || check(TokenType::ASSIGN)) { + // Unnamed parameter: e.g. void foo(int*, int) or void foo(int = 16) — just skip, no name to // bind Generate a placeholder name so param count stays consistent params.push_back("__unnamed_" + std::to_string(params.size())); if (outIsRef) @@ -1127,7 +1270,8 @@ Parser::parseParamList(std::vector *outIsRef, if (!match(TokenType::COMMA)) break; } - expect(TokenType::RPAREN, "Expected ')'"); + expect(closingTok, + closingTok == TokenType::BIT_OR ? "Expected '|'" : "Expected ')'"); return params; } diff --git a/src/parser/ParserStatements.cpp b/src/parser/ParserStatements.cpp index a684ebe..dc4f0a2 100644 --- a/src/parser/ParserStatements.cpp +++ b/src/parser/ParserStatements.cpp @@ -7,29 +7,50 @@ ASTNodePtr Parser::parseStatement() { skipNewlines(); - // Skip Python-style decorators (e.g. @property, @dataclass) + // Skip Python-style decorators (e.g. @property, @dataclass) only when followed by def/class/fn while (check(TokenType::DECORATOR)) { - consume(); // eat @ - if (check(TokenType::IDENTIFIER)) + size_t la = pos + 1; + if (la < tokens.size() && tokens[la].type == TokenType::IDENTIFIER) { - consume(); // eat decorator name - // Optional call parens e.g. @decorator(args) - if (check(TokenType::LPAREN)) + la++; + if (la < tokens.size() && tokens[la].type == TokenType::LPAREN) { - consume(); // eat ( int depth = 1; - while (!atEnd() && depth > 0) + la++; + while (la < tokens.size() && depth > 0) { - if (check(TokenType::LPAREN)) - depth++; - else if (check(TokenType::RPAREN)) - depth--; - consume(); + if (tokens[la].type == TokenType::LPAREN) depth++; + else if (tokens[la].type == TokenType::RPAREN) depth--; + la++; + } + } + while (la < tokens.size() && tokens[la].type == TokenType::NEWLINE) + la++; + if (la < tokens.size() && (tokens[la].type == TokenType::DEF || + tokens[la].type == TokenType::CLASS || + tokens[la].type == TokenType::FN || + tokens[la].type == TokenType::FUNCTION || + tokens[la].type == TokenType::DECORATOR)) + { + consume(); // eat @ + consume(); // eat decorator name + if (check(TokenType::LPAREN)) + { + consume(); // eat ( + int depth = 1; + while (!atEnd() && depth > 0) + { + if (check(TokenType::LPAREN)) depth++; + else if (check(TokenType::RPAREN)) depth--; + consume(); + } } + skipNewlines(); + continue; } } - skipNewlines(); + break; } int ln = current().line; @@ -1143,6 +1164,8 @@ ASTNodePtr Parser::parseFunctionDecl() } match(TokenType::COLON); // optional Python-style colon + if (check(TokenType::IDENTIFIER) && current().value == "async") + consume(); // eat async modifier // C++ constructor initializer list: skip member(val), member2(val2) before body if (check(TokenType::IDENTIFIER)) { diff --git a/tests b/tests index a3790d8..62653b5 160000 --- a/tests +++ b/tests @@ -1 +1 @@ -Subproject commit a3790d8c8c3f5d40b31b6579afc13de10f5b7718 +Subproject commit 62653b50865d542fb96a6a8c6c4eea75e727b80d