mirror of
https://github.com/hanzoai/insights.git
synced 2026-10-30 16:15:31 +00:00
The tree was a debranded snapshot of upstream taken around 2026-02-20, and upstream has moved about five months since. This replaces it with upstream master, mechanically re-derived: bin/debrand does the rename, and the pipeline and squashed migrations are restored from the shipped tree. Not a merge. The previous import replaced the tree wholesale in a single commit, so there is no shared content history to merge against. Reproducing the rename on the new tree is what actually converges. What survives: .hanzo/workflows, the Dockerfiles and bin/docker* entrypoints, compose files, LLM.md, and insights/migrations. The squashed initial migration is load-bearing — the live database records it by name, so upstream's ~918 individual migrations cannot come back. Nine products are dropped as unreferenced. Twenty-one more that we do not run are kept anyway because the core app still imports them; unpicking those is a refactor and does not belong in a rebase. This base does not build yet, and the reasons are not in the rename: upstream now wants Python 3.13.13 against our 3.12.12 and Django 5.2 against our 4.2, and the dependency manifests still name upstream packages that we republish under our own names. Those are decisions, not renames.
269 lines
8.9 KiB
C++
269 lines
8.9 KiB
C++
// parser_wasm.cpp - WebAssembly Bindings for InsightsQL Parser
|
|
// This file provides JavaScript/WASM bindings for the core parser (parser_core.cpp).
|
|
// It exports functions that can be called from JavaScript.
|
|
|
|
#include <emscripten/bind.h>
|
|
#include <string>
|
|
|
|
#include "InsightsQLLexer.h"
|
|
#include "InsightsQLParser.h"
|
|
#include "InsightsQLParserBaseVisitor.h"
|
|
|
|
#include "error.h"
|
|
#include "json.h"
|
|
#include "string.h"
|
|
|
|
using namespace std;
|
|
using namespace emscripten;
|
|
|
|
// Include the core parser implementation
|
|
#include "parser_json.cpp"
|
|
|
|
// ERROR HANDLING FOR WASM
|
|
|
|
class InsightsQLErrorListener : public antlr4::BaseErrorListener {
|
|
public:
|
|
string input;
|
|
|
|
explicit InsightsQLErrorListener(string input) : input(std::move(input)) {}
|
|
|
|
void syntaxError(
|
|
antlr4::Recognizer* recognizer,
|
|
antlr4::Token* /* offendingSymbol */,
|
|
size_t line,
|
|
size_t charPositionInLine,
|
|
const string& msg,
|
|
exception_ptr /* e */
|
|
) override {
|
|
size_t start = getPosition(line, charPositionInLine);
|
|
if (start == string::npos) {
|
|
start = 0;
|
|
}
|
|
// A character outside the InsightsQL token set lexes as UNEXPECTED_CHARACTER
|
|
// and dooms the parse. The default message quotes the raw character,
|
|
// which is unactionable when it is invisible — report the code point
|
|
// instead and point at the character itself.
|
|
if (auto* parser = dynamic_cast<antlr4::Parser*>(recognizer)) {
|
|
if (auto* tokens = dynamic_cast<antlr4::BufferedTokenStream*>(parser->getTokenStream())) {
|
|
tokens->fill();
|
|
for (antlr4::Token* token : tokens->getTokens()) {
|
|
if (token->getType() == InsightsQLLexer::UNEXPECTED_CHARACTER) {
|
|
throw SyntaxError(
|
|
describe_unexpected_character(token->getText()), token->getStartIndex(), input.size()
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
throw SyntaxError(msg, start, input.size());
|
|
}
|
|
|
|
private:
|
|
size_t getPosition(size_t line, size_t column) {
|
|
size_t linePosition = 0;
|
|
for (size_t i = 0; i < line - 1; i++) {
|
|
size_t endOfLine = input.find("\n", linePosition);
|
|
if (endOfLine == string::npos) {
|
|
return string::npos;
|
|
}
|
|
linePosition = endOfLine + 1;
|
|
}
|
|
return linePosition + column;
|
|
}
|
|
};
|
|
|
|
// Helper function to build JSON error
|
|
string buildWASMError(const char* error_type, const string& message, size_t start, size_t end) {
|
|
Json json = Json::object();
|
|
json["error"] = true;
|
|
json["type"] = error_type;
|
|
json["message"] = message;
|
|
|
|
Json start_json = Json::object();
|
|
start_json["line"] = 0;
|
|
start_json["column"] = 0;
|
|
start_json["offset"] = start;
|
|
json["start"] = start_json;
|
|
|
|
Json end_json = Json::object();
|
|
end_json["line"] = 0;
|
|
end_json["column"] = 0;
|
|
end_json["offset"] = end;
|
|
json["end"] = end_json;
|
|
|
|
return json.dump();
|
|
}
|
|
|
|
struct ParserContext {
|
|
// Note: Declaration order matters for destruction - parser must be destroyed before stream, stream before lexer etc.
|
|
unique_ptr<antlr4::ANTLRInputStream> input_stream;
|
|
unique_ptr<InsightsQLLexer> lexer;
|
|
unique_ptr<antlr4::CommonTokenStream> stream;
|
|
unique_ptr<InsightsQLErrorListener> error_listener;
|
|
unique_ptr<InsightsQLParser> parser;
|
|
|
|
explicit ParserContext(const string& input) {
|
|
input_stream = std::make_unique<antlr4::ANTLRInputStream>(input.c_str(), input.length());
|
|
lexer = std::make_unique<InsightsQLLexer>(input_stream.get());
|
|
stream = std::make_unique<antlr4::CommonTokenStream>(lexer.get());
|
|
parser = std::make_unique<InsightsQLParser>(stream.get());
|
|
parser->removeErrorListeners();
|
|
error_listener = std::make_unique<InsightsQLErrorListener>(input);
|
|
parser->addErrorListener(error_listener.get());
|
|
// Reject pathologically deep bracket nesting before ANTLR runs (see guardNestingDepth).
|
|
guardNestingDepth(stream.get());
|
|
}
|
|
|
|
// Prevent copying (would cause double-free)
|
|
ParserContext(const ParserContext&) = delete;
|
|
ParserContext& operator=(const ParserContext&) = delete;
|
|
};
|
|
|
|
// WASM EXPORTED FUNCTIONS
|
|
|
|
/**
|
|
* Parse a InsightsQL expression and return JSON AST.
|
|
*
|
|
* @param input The InsightsQL expression string
|
|
* @param is_internal If true, omit position information
|
|
* @return JSON string representing the AST or error
|
|
*/
|
|
string parse_expr(const string& input, bool is_internal = false) {
|
|
try {
|
|
ParserContext ctx(input);
|
|
|
|
InsightsQLParser::ExprContext* parse_tree;
|
|
try {
|
|
parse_tree = ctx.parser->expr();
|
|
} catch (const antlr4::EmptyStackException& e) {
|
|
return buildWASMError("SyntaxError", "Unmatched curly bracket", 0, input.size());
|
|
}
|
|
|
|
InsightsQLParseTreeJSONConverter converter(is_internal);
|
|
return converter.visitAsJSONFinal(parse_tree);
|
|
} catch (const SyntaxError& e) {
|
|
return buildWASMError("SyntaxError", e.what(), e.start, e.end);
|
|
} catch (const NotImplementedError& e) {
|
|
return buildWASMError("NotImplementedError", e.what(), e.start, e.end);
|
|
} catch (const ParsingError& e) {
|
|
return buildWASMError("ParsingError", e.what(), 0, input.size());
|
|
} catch (...) {
|
|
return buildWASMError("ParsingError", "Unexpected exception in parser", 0, input.size());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Parse an ORDER BY expression and return JSON AST.
|
|
*/
|
|
string parse_order_expr(const string& input, bool is_internal = false) {
|
|
try {
|
|
ParserContext ctx(input);
|
|
|
|
InsightsQLParser::OrderExprContext* parse_tree;
|
|
try {
|
|
parse_tree = ctx.parser->orderExpr();
|
|
} catch (const antlr4::EmptyStackException& e) {
|
|
return buildWASMError("SyntaxError", "Unmatched curly bracket", 0, input.size());
|
|
}
|
|
|
|
InsightsQLParseTreeJSONConverter converter(is_internal);
|
|
return converter.visitAsJSONFinal(parse_tree);
|
|
} catch (const SyntaxError& e) {
|
|
return buildWASMError("SyntaxError", e.what(), e.start, e.end);
|
|
} catch (...) {
|
|
return buildWASMError("ParsingError", "Unexpected exception in parser", 0, input.size());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Parse a SELECT statement and return JSON AST.
|
|
*/
|
|
string parse_select(const string& input, bool is_internal = false) {
|
|
try {
|
|
ParserContext ctx(input);
|
|
|
|
InsightsQLParser::SelectContext* parse_tree;
|
|
try {
|
|
parse_tree = ctx.parser->select();
|
|
} catch (const antlr4::EmptyStackException& /* e */) {
|
|
return buildWASMError("SyntaxError", "Unmatched curly bracket", 0, input.size());
|
|
}
|
|
|
|
InsightsQLParseTreeJSONConverter converter(is_internal);
|
|
return converter.visitAsJSONFinal(parse_tree);
|
|
} catch (const SyntaxError& e) {
|
|
return buildWASMError("SyntaxError", e.what(), e.start, e.end);
|
|
} catch (...) {
|
|
return buildWASMError("ParsingError", "Unexpected exception in parser", 0, input.size());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Parse a Script template string and return JSON AST.
|
|
*/
|
|
string parse_full_template_string(const string& input, bool is_internal = false) {
|
|
try {
|
|
ParserContext ctx(input);
|
|
|
|
InsightsQLParser::FullTemplateStringContext* parse_tree;
|
|
try {
|
|
parse_tree = ctx.parser->fullTemplateString();
|
|
} catch (const antlr4::EmptyStackException& /* e */) {
|
|
return buildWASMError("SyntaxError", "Unmatched curly bracket", 0, input.size());
|
|
}
|
|
|
|
InsightsQLParseTreeJSONConverter converter(is_internal);
|
|
return converter.visitAsJSONFinal(parse_tree);
|
|
} catch (const SyntaxError& e) {
|
|
return buildWASMError("SyntaxError", e.what(), e.start, e.end);
|
|
} catch (...) {
|
|
return buildWASMError("ParsingError", "Unexpected exception in parser", 0, input.size());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Parse a Script program and return JSON AST.
|
|
*/
|
|
string parse_program(const string& input, bool is_internal = false) {
|
|
try {
|
|
ParserContext ctx(input);
|
|
|
|
InsightsQLParser::ProgramContext* parse_tree;
|
|
try {
|
|
parse_tree = ctx.parser->program();
|
|
} catch (const antlr4::EmptyStackException& /* e */) {
|
|
return buildWASMError("SyntaxError", "Unmatched curly bracket", 0, input.size());
|
|
}
|
|
|
|
InsightsQLParseTreeJSONConverter converter(is_internal);
|
|
return converter.visitAsJSONFinal(parse_tree);
|
|
} catch (const SyntaxError& e) {
|
|
return buildWASMError("SyntaxError", e.what(), e.start, e.end);
|
|
} catch (...) {
|
|
return buildWASMError("ParsingError", "Unexpected exception in parser", 0, input.size());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Unquote a string literal and return the text.
|
|
*/
|
|
string parse_string_literal_text_wasm(const string& input) {
|
|
try {
|
|
return parse_string_literal_text(input.c_str());
|
|
} catch (const SyntaxError& e) {
|
|
return buildWASMError("SyntaxError", e.what(), e.start, e.end);
|
|
} catch (...) {
|
|
return buildWASMError("ParsingError", "Failed to parse string literal", 0, input.size());
|
|
}
|
|
}
|
|
|
|
// EMSCRIPTEN BINDINGS
|
|
|
|
EMSCRIPTEN_BINDINGS(insightsql_parser) {
|
|
emscripten::function("parseExpr", &parse_expr);
|
|
emscripten::function("parseOrderExpr", &parse_order_expr);
|
|
emscripten::function("parseSelect", &parse_select);
|
|
emscripten::function("parseFullTemplateString", &parse_full_template_string);
|
|
emscripten::function("parseProgram", &parse_program);
|
|
emscripten::function("parseStringLiteralText", &parse_string_literal_text_wasm);
|
|
} |