Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 4 additions & 7 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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")
2 changes: 1 addition & 1 deletion Website
2 changes: 1 addition & 1 deletion examples
2 changes: 2 additions & 0 deletions src/compiler/CompilerCore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,8 @@ void Compiler::compileExpr(ASTNode &node) {
compileArrow(n, ln);
else if constexpr (std::is_same_v<T, ReturnStmt>)
compileReturn(n, ln);
else if constexpr (std::is_same_v<T, RaiseStmt>)
compileRaise(n, ln);
else
throw std::runtime_error("Compiler: unhandled expression node");
},
Expand Down
81 changes: 55 additions & 26 deletions src/dialect/RubyDialect.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@ static const std::set<std::string> &rbZeroArgMethods(bool strict) {
"resume", "alive", "kill", "shutdown"};
static const std::set<std::string> 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;
}

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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) {");
Expand Down Expand Up @@ -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|`).
Expand Down Expand Up @@ -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));
Expand Down
4 changes: 4 additions & 0 deletions src/parser/ParserExpressions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
Loading